Compare commits
433 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8e6800cd4 | |||
| 5785378951 | |||
| 57789e15a9 | |||
| 8a1cfa4229 | |||
| 747cc45e9c | |||
| adf1b2b5f3 | |||
| eb76504df6 | |||
| 698274491a | |||
| 8538aabfb5 | |||
| 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 | |||
| 0f7f5bdb34 | |||
| dead1acba8 | |||
| 6ea7e89649 | |||
| af4e04602a | |||
| 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.5
|
||||
<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
|
||||
|
||||
+1
-1
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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);";
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+10
-2
@@ -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 {
|
||||
|
||||
+8
-3
@@ -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
|
||||
|
||||
@@ -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.1
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
+87
-30
@@ -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() : [];
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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,
|
||||
|
||||
@@ -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']); ?>" />
|
||||
|
||||
@@ -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
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
@@ -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();
|
||||
|
||||
@@ -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.' (' .
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -253,12 +253,15 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
$config = $this->getMock('\OCP\IConfig');
|
||||
$config->expects($this->exactly(2))
|
||||
->method('getUserValue')
|
||||
->will($this->returnValue(1));
|
||||
->will($this->onConsecutiveCalls('1', '/var/vhome/jdings/'));
|
||||
|
||||
$backend = new UserLDAP($access, $config);
|
||||
|
||||
$result = $backend->deleteUser('jeremy');
|
||||
$this->assertTrue($result);
|
||||
|
||||
$home = $backend->getHome('jeremy');
|
||||
$this->assertSame($home, '/var/vhome/jdings/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,21 +414,53 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for existing user
|
||||
$result = $backend->userExists('gunslinger');
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testUserExistsForDeleted() {
|
||||
$access = $this->getAccessMock();
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for deleted user
|
||||
$result = $backend->userExists('formerUser');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testUserExistsForNeverExisting() {
|
||||
$access = $this->getAccessMock();
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for never-existing user
|
||||
$result = $backend->userExists('mallory');
|
||||
@@ -439,21 +474,55 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
\OC_User::useBackend($backend);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for existing user
|
||||
$result = \OCP\User::userExists('gunslinger');
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testUserExistsPublicAPIForDeleted() {
|
||||
$access = $this->getAccessMock();
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
\OC_User::useBackend($backend);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for deleted user
|
||||
$result = \OCP\User::userExists('formerUser');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testUserExistsPublicAPIForNeverExisting() {
|
||||
$access = $this->getAccessMock();
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
\OC_User::useBackend($backend);
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn) {
|
||||
if($dn === 'dnOfRoland,dc=test') {
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
|
||||
//test for never-existing user
|
||||
$result = \OCP\User::userExists('mallory');
|
||||
@@ -469,54 +538,105 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testGetHome() {
|
||||
public function testGetHomeAbsolutePath() {
|
||||
$access = $this->getAccessMock();
|
||||
$config = $this->getMock('\OCP\IConfig');
|
||||
$backend = new UserLDAP($access, $config);
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->connection->expects($this->any())
|
||||
->method('__get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if($name === 'homeFolderNamingRule') {
|
||||
return 'attr:testAttribute';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
->method('__get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if($name === 'homeFolderNamingRule') {
|
||||
return 'attr:testAttribute';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn, $attr) {
|
||||
switch ($dn) {
|
||||
case 'dnOfRoland,dc=test':
|
||||
if($attr === 'testAttribute') {
|
||||
return array('/tmp/rolandshome/');
|
||||
}
|
||||
return array();
|
||||
break;
|
||||
case 'dnOfLadyOfShadows,dc=test':
|
||||
if($attr === 'testAttribute') {
|
||||
return array('susannah/');
|
||||
}
|
||||
return array();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn, $attr) {
|
||||
switch ($dn) {
|
||||
case 'dnOfRoland,dc=test':
|
||||
if($attr === 'testAttribute') {
|
||||
return array('/tmp/rolandshome/');
|
||||
}
|
||||
return array();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
//absolut path
|
||||
$result = $backend->getHome('gunslinger');
|
||||
$this->assertEquals('/tmp/rolandshome/', $result);
|
||||
}
|
||||
|
||||
public function testGetHomeRelative() {
|
||||
$access = $this->getAccessMock();
|
||||
$config = $this->getMock('\OCP\IConfig');
|
||||
$backend = new UserLDAP($access, $config);
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->connection->expects($this->any())
|
||||
->method('__get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if($name === 'homeFolderNamingRule') {
|
||||
return 'attr:testAttribute';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn, $attr) {
|
||||
switch ($dn) {
|
||||
case 'dnOfLadyOfShadows,dc=test':
|
||||
if($attr === 'testAttribute') {
|
||||
return array('susannah/');
|
||||
}
|
||||
return array();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
//datadir-relativ path
|
||||
$datadir = '/my/data/dir';
|
||||
$config->expects($this->once())
|
||||
->method('getSystemValue')
|
||||
->will($this->returnValue($datadir));
|
||||
|
||||
//absolut path
|
||||
$result = $backend->getHome('gunslinger');
|
||||
$this->assertEquals('/tmp/rolandshome/', $result);
|
||||
|
||||
//datadir-relativ path
|
||||
$result = $backend->getHome('ladyofshadows');
|
||||
$this->assertEquals($datadir.'/susannah/', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testGetHomeNoPath() {
|
||||
$access = $this->getAccessMock();
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->connection->expects($this->any())
|
||||
->method('__get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if($name === 'homeFolderNamingRule') {
|
||||
return 'attr:testAttribute';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($dn, $attr) {
|
||||
switch ($dn) {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
//no path at all – triggers OC default behaviour
|
||||
$result = $backend->getHome('newyorker');
|
||||
@@ -556,6 +676,12 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->connection->expects($this->any())
|
||||
->method('getConnectionResource')
|
||||
->will($this->returnCallback(function() {
|
||||
return true;
|
||||
}));
|
||||
|
||||
//with displayName
|
||||
$result = $backend->getDisplayName('gunslinger');
|
||||
$this->assertEquals('Roland Deschain', $result);
|
||||
@@ -567,9 +693,36 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
|
||||
|
||||
public function testGetDisplayNamePublicAPI() {
|
||||
$access = $this->getAccessMock();
|
||||
$access->expects($this->any())
|
||||
->method('username2dn')
|
||||
->will($this->returnCallback(function($uid) {
|
||||
switch ($uid) {
|
||||
case 'gunslinger':
|
||||
return 'dnOfRoland,dc=test';
|
||||
break;
|
||||
case 'formerUser':
|
||||
return 'dnOfFormerUser,dc=test';
|
||||
break;
|
||||
case 'newyorker':
|
||||
return 'dnOfNewYorker,dc=test';
|
||||
break;
|
||||
case 'ladyofshadows':
|
||||
return 'dnOfLadyOfShadows,dc=test';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
$this->prepareAccessForGetDisplayName($access);
|
||||
$backend = new UserLDAP($access, $this->getMock('\OCP\IConfig'));
|
||||
$this->prepareMockForUserExists($access);
|
||||
|
||||
$access->connection->expects($this->any())
|
||||
->method('getConnectionResource')
|
||||
->will($this->returnCallback(function() {
|
||||
return true;
|
||||
}));
|
||||
|
||||
\OC_User::useBackend($backend);
|
||||
|
||||
//with displayName
|
||||
|
||||
@@ -188,6 +188,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
|
||||
* check if a user exists
|
||||
* @param string $uid the username
|
||||
* @return boolean
|
||||
* @throws \Exception when connection could not be established
|
||||
*/
|
||||
public function userExists($uid) {
|
||||
if($this->access->connection->isCached('userExists'.$uid)) {
|
||||
@@ -195,6 +196,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
|
||||
}
|
||||
//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
|
||||
$user = $this->access->userManager->get($uid);
|
||||
|
||||
if(is_null($user)) {
|
||||
\OCP\Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
|
||||
$this->access->connection->ldapHost, \OCP\Util::DEBUG);
|
||||
@@ -206,17 +208,12 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->userExistsOnLDAP($user);
|
||||
$this->access->connection->writeToCache('userExists'.$uid, $result);
|
||||
if($result === true) {
|
||||
$user->update();
|
||||
}
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
\OCP\Util::writeLog('user_ldap', $e->getMessage(), \OCP\Util::WARN);
|
||||
return false;
|
||||
$result = $this->userExistsOnLDAP($user);
|
||||
$this->access->connection->writeToCache('userExists'.$uid, $result);
|
||||
if($result === true) {
|
||||
$user->update();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,16 +248,16 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getHome($uid) {
|
||||
// user Exists check required as it is not done in user proxy!
|
||||
if(!$this->userExists($uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
|
||||
//a deleted user who needs some clean up
|
||||
return $this->homesToKill[$uid];
|
||||
}
|
||||
|
||||
// user Exists check required as it is not done in user proxy!
|
||||
if(!$this->userExists($uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cacheKey = 'getHome'.$uid;
|
||||
if($this->access->connection->isCached($cacheKey)) {
|
||||
return $this->access->connection->getFromCache($cacheKey);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"handlebars": "~1.3.0",
|
||||
"jcrop": "~0.9.12",
|
||||
"jquery": "~1.10.0",
|
||||
"jquery-migrate": "~1.2.1",
|
||||
"jquery-ui": "1.10.0",
|
||||
"jsTimezoneDetect": "~1.0.5",
|
||||
"moment": "~2.8.3",
|
||||
|
||||
@@ -100,7 +100,6 @@ $CONFIG = array(
|
||||
* - mysql (MySQL/MariaDB)
|
||||
* - pgsql (PostgreSQL)
|
||||
* - oci (Oracle - Enterprise Edition Only)
|
||||
* - mssql (Microsoft SQL Server - Enterprise Edition Only)
|
||||
*/
|
||||
'dbtype' => 'sqlite',
|
||||
|
||||
@@ -226,12 +225,12 @@ $CONFIG = array(
|
||||
* copied to the data directory of new users. Leave empty to not copy any
|
||||
* skeleton files.
|
||||
*/
|
||||
'skeletondirectory' => '',
|
||||
'skeletondirectory' => '/path/to/owncloud/core/skeleton',
|
||||
|
||||
/**
|
||||
* The ``user_backends`` app allows you to configure alternate authentication
|
||||
* backends. Supported backends are IMAP (OC_User_IMAP), SMB (OC_User_SMB), and
|
||||
* FTP (OC_User_FTP).
|
||||
* The ``user_backends`` app (which needs to be enabled first) allows you to
|
||||
* configure alternate authentication backends. Supported backends are:
|
||||
* IMAP (OC_User_IMAP), SMB (OC_User_SMB), and FTP (OC_User_FTP).
|
||||
*/
|
||||
'user_backends' => array(
|
||||
array(
|
||||
@@ -420,8 +419,9 @@ $CONFIG = array(
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check 3rd party apps to make sure they are using the private API and not the
|
||||
* public API. If the app uses the private API it cannot be installed.
|
||||
* Checks an app before install whether it uses private APIs instead of the
|
||||
* proper public APIs. If this is set to true it will only allow to install or
|
||||
* enable apps that pass this check.
|
||||
*/
|
||||
'appcodechecker' => true,
|
||||
|
||||
@@ -669,6 +669,10 @@ $CONFIG = array(
|
||||
* - OC\Preview\StarOffice
|
||||
* - OC\Preview\SVG
|
||||
* - OC\Preview\TIFF
|
||||
*
|
||||
* .. note:: Troubleshooting steps for the MS Word previews are available
|
||||
* at the :doc:`../configuration_files/collaborative_documents_configuration`
|
||||
* section of the Administrators Manual.
|
||||
*
|
||||
* The following providers are not available in Microsoft Windows:
|
||||
*
|
||||
|
||||
@@ -11,6 +11,9 @@ use Symfony\Component\Console\Application;
|
||||
try {
|
||||
require_once 'lib/base.php';
|
||||
|
||||
// set to run indefinitely if needed
|
||||
set_time_limit(0);
|
||||
|
||||
// Don't do anything if ownCloud has not been installed yet
|
||||
if (!\OC::$server->getConfig()->getSystemValue('installed', false)) {
|
||||
echo "Console can only be used once ownCloud has been installed" . PHP_EOL;
|
||||
@@ -48,7 +51,11 @@ try {
|
||||
$application = new Application($defaults->getName(), \OC_Util::getVersionString());
|
||||
require_once 'core/register_command.php';
|
||||
if (!\OCP\Util::needUpgrade()) {
|
||||
$appManager = \OC::$server->getAppManager();
|
||||
foreach(OC_App::getAllApps() as $app) {
|
||||
if(!$appManager->isInstalled($app)) {
|
||||
continue;
|
||||
}
|
||||
$file = OC_App::getAppPath($app).'/appinfo/register_command.php';
|
||||
if(file_exists($file)) {
|
||||
require $file;
|
||||
|
||||
+2
-2
@@ -105,7 +105,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo
|
||||
// don't send a mail to the user who shared the file
|
||||
$recipientList = array_diff($recipientList, array(\OCP\User::getUser()));
|
||||
|
||||
$mailNotification = new OC\Share\MailNotifications();
|
||||
$mailNotification = new OC\Share\MailNotifications(\OCP\User::getUser());
|
||||
$result = $mailNotification->sendInternalShareMail($recipientList, $itemSource, $itemType);
|
||||
|
||||
\OCP\Share::setSendMailStatus($itemType, $itemSource, $shareType, $recipient, true);
|
||||
@@ -137,7 +137,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo
|
||||
$file = $_POST['file'];
|
||||
$to_address = $_POST['toaddress'];
|
||||
|
||||
$mailNotification = new \OC\Share\MailNotifications();
|
||||
$mailNotification = new \OC\Share\MailNotifications(\OCP\User::getUser());
|
||||
|
||||
$expiration = null;
|
||||
if (isset($_POST['expiration']) && $_POST['expiration'] !== '') {
|
||||
|
||||
+26
-8
@@ -2,6 +2,8 @@
|
||||
set_time_limit(0);
|
||||
require_once '../../lib/base.php';
|
||||
|
||||
\OCP\JSON::callCheck();
|
||||
|
||||
if (OC::checkUpgrade(false)) {
|
||||
// if a user is currently logged in, their session must be ignored to
|
||||
// avoid side effects
|
||||
@@ -11,9 +13,12 @@ if (OC::checkUpgrade(false)) {
|
||||
$eventSource = \OC::$server->createEventSource();
|
||||
$updater = new \OC\Updater(
|
||||
\OC::$server->getHTTPHelper(),
|
||||
\OC::$server->getAppConfig(),
|
||||
\OC::$server->getConfig(),
|
||||
\OC_Log::$object
|
||||
);
|
||||
$incompatibleApps = [];
|
||||
$disabledThirdPartyApps = [];
|
||||
|
||||
$updater->listen('\OC\Updater', 'maintenanceStart', function () use ($eventSource, $l) {
|
||||
$eventSource->send('success', (string)$l->t('Turned on maintenance mode'));
|
||||
});
|
||||
@@ -32,13 +37,17 @@ if (OC::checkUpgrade(false)) {
|
||||
$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($eventSource, $l) {
|
||||
$eventSource->send('success', (string)$l->t('Updated "%s" to %s', array($app, $version)));
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'disabledApps', function ($appList) use ($eventSource, $l) {
|
||||
$list = array();
|
||||
foreach ($appList as $appId) {
|
||||
$info = OC_App::getAppInfo($appId);
|
||||
$list[] = $info['name'] . ' (' . $info['id'] . ')';
|
||||
}
|
||||
$eventSource->send('success', (string)$l->t('Disabled incompatible apps: %s', implode(', ', $list)));
|
||||
$updater->listen('\OC\Updater', 'repairWarning', function ($description) use ($eventSource, $l) {
|
||||
$eventSource->send('notice', (string)$l->t('Repair warning: ') . $description);
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'repairError', function ($description) use ($eventSource, $l) {
|
||||
$eventSource->send('notice', (string)$l->t('Repair error: ') . $description);
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use (&$incompatibleApps) {
|
||||
$incompatibleApps[]= $app;
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use (&$disabledThirdPartyApps) {
|
||||
$disabledThirdPartyApps[]= $app;
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource) {
|
||||
$eventSource->send('failure', $message);
|
||||
@@ -48,6 +57,15 @@ if (OC::checkUpgrade(false)) {
|
||||
|
||||
$updater->upgrade();
|
||||
|
||||
if (!empty($incompatibleApps)) {
|
||||
$eventSource->send('notice',
|
||||
(string)$l->t('Following incompatible apps have been disabled: %s', implode(', ', $incompatibleApps)));
|
||||
}
|
||||
if (!empty($disabledThirdPartyApps)) {
|
||||
$eventSource->send('notice',
|
||||
(string)$l->t('Following 3rd party apps have been disabled: %s', implode(', ', $disabledThirdPartyApps)));
|
||||
}
|
||||
|
||||
$eventSource->send('done', '');
|
||||
$eventSource->close();
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ class Repair extends Command {
|
||||
$this->repair->listen('\OC\Repair', 'info', function ($description) use ($output) {
|
||||
$output->writeln(' - ' . $description);
|
||||
});
|
||||
$this->repair->listen('\OC\Repair', 'warning', function ($description) use ($output) {
|
||||
$output->writeln(' - WARNING: ' . $description);
|
||||
});
|
||||
$this->repair->listen('\OC\Repair', 'error', function ($description) use ($output) {
|
||||
$output->writeln(' - ERROR: ' . $description);
|
||||
});
|
||||
|
||||
@@ -53,6 +53,12 @@ class Upgrade extends Command {
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'only runs the database schema migration simulation, do not actually update'
|
||||
)
|
||||
->addOption(
|
||||
'--no-app-disable',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'skips the disable of third party apps'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +72,7 @@ class Upgrade extends Command {
|
||||
|
||||
$simulateStepEnabled = true;
|
||||
$updateStepEnabled = true;
|
||||
$skip3rdPartyAppsDisable = false;
|
||||
|
||||
if ($input->getOption('skip-migration-test')) {
|
||||
$simulateStepEnabled = false;
|
||||
@@ -73,6 +80,9 @@ class Upgrade extends Command {
|
||||
if ($input->getOption('dry-run')) {
|
||||
$updateStepEnabled = false;
|
||||
}
|
||||
if ($input->getOption('no-app-disable')) {
|
||||
$skip3rdPartyAppsDisable = true;
|
||||
}
|
||||
|
||||
if (!$simulateStepEnabled && !$updateStepEnabled) {
|
||||
$output->writeln(
|
||||
@@ -84,10 +94,12 @@ class Upgrade extends Command {
|
||||
|
||||
if(\OC::checkUpgrade(false)) {
|
||||
$self = $this;
|
||||
$updater = new Updater(\OC::$server->getHTTPHelper(), \OC::$server->getAppConfig());
|
||||
$updater = new Updater(\OC::$server->getHTTPHelper(),
|
||||
\OC::$server->getConfig());
|
||||
|
||||
$updater->setSimulateStepEnabled($simulateStepEnabled);
|
||||
$updater->setUpdateStepEnabled($updateStepEnabled);
|
||||
$updater->setSkip3rdPartyAppsDisable($skip3rdPartyAppsDisable);
|
||||
|
||||
$updater->listen('\OC\Updater', 'maintenanceStart', function () use($output) {
|
||||
$output->writeln('<info>Turned on maintenance mode</info>');
|
||||
@@ -106,10 +118,24 @@ class Upgrade extends Command {
|
||||
$updater->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($output) {
|
||||
$output->writeln('<info>Checked database schema update</info>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'disabledApps', function ($appList) use($output) {
|
||||
$output->writeln('<info>Disabled incompatible apps: ' . implode(', ', $appList) . '</info>');
|
||||
$updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($output) {
|
||||
$output->writeln('<info>Disabled incompatible app: ' . $app . '</info>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use ($output) {
|
||||
$output->writeln('<info>Disabled 3rd-party app: ' . $app . '</info>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'repairWarning', function ($app) use($output) {
|
||||
$output->writeln('<error>Repair warning: ' . $app . '</error>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'repairError', function ($app) use($output) {
|
||||
$output->writeln('<error>Repair error: ' . $app . '</error>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'appUpgradeCheck', function () use ($output) {
|
||||
$output->writeln('<info>Checked database schema update for apps</info>');
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output) {
|
||||
$output->writeln("<info>Updated <$app> to $version</info>");
|
||||
});
|
||||
|
||||
$updater->listen('\OC\Updater', 'failure', function ($message) use($output, $self) {
|
||||
$output->writeln("<error>$message</error>");
|
||||
$self->upgradeFailed = true;
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
line-height: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: border-box; box-sizing: border-box;
|
||||
|
||||
@@ -62,6 +62,7 @@ select {
|
||||
|
||||
/* fix installation screen rendering issue for IE8+9 */
|
||||
.lte9 #body-login {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* do not show update notification on mobile */
|
||||
#update-notification {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* position share dropdown */
|
||||
#dropdown {
|
||||
margin-right: 10% !important;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"vendor": [
|
||||
"jquery/jquery.min.js",
|
||||
"jquery/jquery-migrate.min.js",
|
||||
"jquery-migrate/jquery-migrate.min.js",
|
||||
"jquery-ui/ui/jquery-ui.custom.js",
|
||||
"underscore/underscore.js",
|
||||
"moment/min/moment-with-locales.js",
|
||||
|
||||
+56
-1
@@ -1191,6 +1191,33 @@ function initCore() {
|
||||
// initial call
|
||||
toggleSnapperOnSize();
|
||||
|
||||
// adjust controls bar width
|
||||
var adjustControlsWidth = function() {
|
||||
if($('#controls').length) {
|
||||
var controlsWidth;
|
||||
// if there is a scrollbar …
|
||||
if($('#app-content').get(0).scrollHeight > $('#app-content').height()) {
|
||||
if($(window).width() > 768) {
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth();
|
||||
} else {
|
||||
controlsWidth = $('#content').width() - getScrollBarWidth();
|
||||
}
|
||||
} else { // if there is none
|
||||
if($(window).width() > 768) {
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width();
|
||||
} else {
|
||||
controlsWidth = $('#content').width();
|
||||
}
|
||||
}
|
||||
$('#controls').css('width', controlsWidth);
|
||||
$('#controls').css('min-width', controlsWidth);
|
||||
}
|
||||
};
|
||||
|
||||
$(window).resize(_.debounce(adjustControlsWidth, 250));
|
||||
|
||||
$('body').delegate('#app-content', 'apprendered', adjustControlsWidth);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1213,7 +1240,7 @@ $.fn.filterAttr = function(attr_name, attr_value) {
|
||||
function humanFileSize(size, skipSmallSizes) {
|
||||
var humanList = ['B', 'kB', 'MB', 'GB', 'TB'];
|
||||
// Calculate Log with base 1024: size = 1024 ** order
|
||||
var order = size?Math.floor(Math.log(size) / Math.log(1024)):0;
|
||||
var order = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
|
||||
// Stay in range of the byte sizes that are defined
|
||||
order = Math.min(humanList.length - 1, order);
|
||||
var readableFormat = humanList[order];
|
||||
@@ -1615,3 +1642,31 @@ jQuery.fn.selectRange = function(start, end) {
|
||||
jQuery.fn.exists = function(){
|
||||
return this.length > 0;
|
||||
};
|
||||
|
||||
function getScrollBarWidth() {
|
||||
var inner = document.createElement('p');
|
||||
inner.style.width = "100%";
|
||||
inner.style.height = "200px";
|
||||
|
||||
var outer = document.createElement('div');
|
||||
outer.style.position = "absolute";
|
||||
outer.style.top = "0px";
|
||||
outer.style.left = "0px";
|
||||
outer.style.visibility = "hidden";
|
||||
outer.style.width = "200px";
|
||||
outer.style.height = "150px";
|
||||
outer.style.overflow = "hidden";
|
||||
outer.appendChild (inner);
|
||||
|
||||
document.body.appendChild (outer);
|
||||
var w1 = inner.offsetWidth;
|
||||
outer.style.overflow = 'scroll';
|
||||
var w2 = inner.offsetWidth;
|
||||
if(w1 === w2) {
|
||||
w2 = outer.clientWidth;
|
||||
}
|
||||
|
||||
document.body.removeChild (outer);
|
||||
|
||||
return (w1 - w2);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ var OCdialogs = {
|
||||
* @param title dialog title
|
||||
* @param callback which will be triggered when user presses Choose
|
||||
* @param multiselect whether it should be possible to select multiple files
|
||||
* @param mimetypeFilter mimetype to filter by
|
||||
* @param mimetypeFilter mimetype to filter by - directories will always be included
|
||||
* @param modal make the dialog modal
|
||||
*/
|
||||
filepicker:function(title, callback, multiselect, mimetypeFilter, modal) {
|
||||
|
||||
@@ -59,6 +59,16 @@
|
||||
t('core', 'Your data directory and your files are probably accessible from the internet. The .htaccess file is not working. We strongly suggest that you configure your webserver in a way that the data directory is no longer accessible or you move the data directory outside the webserver document root.')
|
||||
);
|
||||
}
|
||||
if(!data.hasCurlInstalled) {
|
||||
messages.push(
|
||||
t('core', 'cURL is not installed, some functionality might not work. Please install the PHP cURL extension. Future versions will require installed cURL.')
|
||||
);
|
||||
}
|
||||
if(!data.isUrandomAvailable) {
|
||||
messages.push(
|
||||
t('core', '/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a href="{docLink}">documentation</a>.', {docLink: data.securityDocs})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
messages.push(t('core', 'Error occurred while checking server setup'));
|
||||
}
|
||||
|
||||
+30
-10
@@ -473,6 +473,10 @@ OC.Share={
|
||||
} else {
|
||||
response();
|
||||
}
|
||||
}).fail(function(){
|
||||
$('#dropdown').find('.shareWithLoading').addClass('hidden');
|
||||
OC.Notification.show(t('core', 'An error occured. Please try again'));
|
||||
window.setTimeout(OC.Notification.hide, 5000);
|
||||
});
|
||||
},
|
||||
focus: function(event, focused) {
|
||||
@@ -798,6 +802,24 @@ OC.Share={
|
||||
$('#defaultExpireMessage').show('blind');
|
||||
}
|
||||
$.datepicker.setDefaults(datePickerOptions);
|
||||
},
|
||||
/**
|
||||
* Get the default Expire date
|
||||
*
|
||||
* @return {String} The expire date
|
||||
*/
|
||||
getDefaultExpirationDate:function() {
|
||||
var expireDateString = '';
|
||||
if (oc_appconfig.core.defaultExpireDateEnabled) {
|
||||
var date = new Date().getTime();
|
||||
var expireAfterMs = oc_appconfig.core.defaultExpireDate * 24 * 60 * 60 * 1000;
|
||||
var expireDate = new Date(date + expireAfterMs);
|
||||
var month = expireDate.getMonth() + 1;
|
||||
var year = expireDate.getFullYear();
|
||||
var day = expireDate.getDate();
|
||||
expireDateString = year + "-" + month + '-' + day + ' 00:00:00';
|
||||
}
|
||||
return expireDateString;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -942,17 +964,9 @@ $(document).ready(function() {
|
||||
|
||||
if (this.checked) {
|
||||
var expireDateString = '';
|
||||
if (oc_appconfig.core.defaultExpireDateEnabled) {
|
||||
var date = new Date().getTime();
|
||||
var expireAfterMs = oc_appconfig.core.defaultExpireDate * 24 * 60 * 60 * 1000;
|
||||
var expireDate = new Date(date + expireAfterMs);
|
||||
var month = expireDate.getMonth() + 1;
|
||||
var year = expireDate.getFullYear();
|
||||
var day = expireDate.getDate();
|
||||
expireDateString = year + "-" + month + '-' + day + ' 00:00:00';
|
||||
}
|
||||
// Create a link
|
||||
if (oc_appconfig.core.enforcePasswordForPublicLink === false) {
|
||||
expireDateString = OC.Share.getDefaultExpirationDate();
|
||||
$loading.removeClass('hidden');
|
||||
$button.addClass('hidden');
|
||||
$button.prop('disabled', true);
|
||||
@@ -1086,8 +1100,10 @@ $(document).ready(function() {
|
||||
permissions = OC.PERMISSION_READ;
|
||||
}
|
||||
|
||||
var expireDateString = OC.Share.getDefaultExpirationDate();
|
||||
|
||||
$loading.removeClass('hidden');
|
||||
OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, $('#linkPassText').val(), permissions, itemSourceName, function(data) {
|
||||
OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, $('#linkPassText').val(), permissions, itemSourceName, expireDateString, function(data) {
|
||||
$loading.addClass('hidden');
|
||||
linkPassText.val('');
|
||||
linkPassText.attr('placeholder', t('core', 'Password protected'));
|
||||
@@ -1096,8 +1112,12 @@ $(document).ready(function() {
|
||||
OC.Share.showLink(data.token, "password set", itemSource);
|
||||
OC.Share.updateIcon(itemType, itemSource);
|
||||
}
|
||||
$('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares}));
|
||||
});
|
||||
|
||||
if (expireDateString !== '') {
|
||||
OC.Share.showExpirationDate(expireDateString);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -465,6 +465,8 @@ describe('Core base tests', function() {
|
||||
it('renders file sizes with the correct unit', function() {
|
||||
var data = [
|
||||
[0, '0 B'],
|
||||
["0", '0 B'],
|
||||
["A", 'NaN B'],
|
||||
[125, '125 B'],
|
||||
[128000, '125 kB'],
|
||||
[128000000, '122.1 MB'],
|
||||
|
||||
@@ -309,6 +309,7 @@ describe('OC.Share tests', function() {
|
||||
};
|
||||
loadItemStub.returns(shareData);
|
||||
oc_appconfig.core.defaultExpireDate = 7;
|
||||
oc_appconfig.core.enforcePasswordForPublicLink = false;
|
||||
oc_appconfig.core.defaultExpireDateEnabled = false;
|
||||
oc_appconfig.core.defaultExpireDateEnforced = false;
|
||||
});
|
||||
@@ -364,6 +365,32 @@ describe('OC.Share tests', function() {
|
||||
$('#dropdown [name=expirationCheckbox]').click();
|
||||
expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true);
|
||||
});
|
||||
it('enforces default date when enforced date setting is enabled and password is enforced', function() {
|
||||
/* jshint camelcase:false */
|
||||
oc_appconfig.core.enforcePasswordForPublicLink = true;
|
||||
oc_appconfig.core.defaultExpireDateEnabled = true;
|
||||
oc_appconfig.core.defaultExpireDateEnforced = true;
|
||||
showDropDown();
|
||||
$('#dropdown [name=linkCheckbox]').click();
|
||||
|
||||
//Enter password
|
||||
$('#dropdown #linkPassText').val('foo');
|
||||
$('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13}));
|
||||
fakeServer.requests[0].respond(
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({data: {token: 'xyz'}, status: 'success'})
|
||||
);
|
||||
|
||||
expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true);
|
||||
// TODO: those zeros must go...
|
||||
expect($('#dropdown #expirationDate').val()).toEqual('2014-1-27 00:00:00');
|
||||
|
||||
// disabling is not allowed
|
||||
expect($('#dropdown [name=expirationCheckbox]').prop('disabled')).toEqual(true);
|
||||
$('#dropdown [name=expirationCheckbox]').click();
|
||||
expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true);
|
||||
});
|
||||
it('displayes email form when sending emails is enabled', function() {
|
||||
$('input[name=mailPublicNotificationEnabled]').val('yes');
|
||||
showDropDown();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user