diff options
Diffstat (limited to 'apps')
21 files changed, 470 insertions, 93 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 287dedc23f2..4a8bd5bb30f 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -152,16 +152,20 @@ table th .columntitle.name { padding-right: 80px; margin-left: 50px; } -/* hover effect on sortable column */ -table th a.columntitle:hover { - color: #000; -} + +.sort-indicator.hidden { visibility: hidden; } table th .sort-indicator { width: 10px; height: 8px; margin-left: 10px; display: inline-block; } +table th:hover .sort-indicator.hidden { + width: 10px; + height: 8px; + margin-left: 10px; + visibility: visible; +} table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; } table td { padding: 0 15px; @@ -367,7 +371,6 @@ table td.filename .uploadtext { left: 18px; } - #fileList tr td.filename { position: relative; width: 100%; @@ -432,7 +435,6 @@ a.action>img { margin-bottom: -1px; } - #fileList a.action { display: inline; padding: 18px 8px; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 61e73b7bebc..4fa8ca65e39 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -18,8 +18,8 @@ this.initialize($el, options); }; FileList.prototype = { - SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', - SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', + SORT_INDICATOR_ASC_CLASS: 'icon-triangle-n', + SORT_INDICATOR_DESC_CLASS: 'icon-triangle-s', id: 'files', appName: t('files', 'Files'), @@ -371,7 +371,12 @@ this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); } else { - this.setSort(sort, 'asc'); + if ( sort === 'name' ) { //default sorting of name is opposite to size and mtime + this.setSort(sort, 'asc'); + } + else { + this.setSort(sort, 'desc'); + } } this.reload(); } @@ -707,6 +712,7 @@ * @param options map of attributes: * - "updateSummary": true to update the summary after adding (default), false otherwise * - "silent": true to prevent firing events like "fileActionsReady" + * - "animate": true to animate preview loading (defaults to true here) * @return new tr element (not appended to the table) */ add: function(fileData, options) { @@ -714,7 +720,7 @@ var $tr; var $rows; var $insertionPoint; - options = options || {}; + options = _.extend({animate: true}, options || {}); // there are three situations to cover: // 1) insertion point is visible on the current page @@ -772,6 +778,7 @@ * @param options map of attributes: * - "index" optional index at which to insert the element * - "updateSummary" true to update the summary after adding (default), false otherwise + * - "animate" true to animate the preview rendering * @return new tr element (not appended to the table) */ _renderRow: function(fileData, options) { @@ -813,7 +820,7 @@ if (fileData.isPreviewAvailable) { // lazy load / newly inserted td ? - if (!fileData.icon) { + if (options.animate) { this.lazyLoadPreview({ path: path + '/' + fileData.name, mime: mime, @@ -914,16 +921,25 @@ this._sort = sort; this._sortDirection = (direction === 'desc')?'desc':'asc'; this._sortComparator = comparator; + if (direction === 'desc') { this._sortComparator = function(fileInfo1, fileInfo2) { return -comparator(fileInfo1, fileInfo2); }; } this.$el.find('thead th .sort-indicator') - .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); + .removeClass(this.SORT_INDICATOR_ASC_CLASS) + .removeClass(this.SORT_INDICATOR_DESC_CLASS) + .toggleClass('hidden', true) + .addClass(this.SORT_INDICATOR_DESC_CLASS); + this.$el.find('thead th.column-' + sort + ' .sort-indicator') + .removeClass(this.SORT_INDICATOR_ASC_CLASS) + .removeClass(this.SORT_INDICATOR_DESC_CLASS) + .toggleClass('hidden', false) .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); }, + /** * Reloads the file list using ajax call * diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index ae22ae0123e..0580177c5ff 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1696,7 +1696,7 @@ describe('OCA.Files.FileList tests', function() { var url = fakeServer.requests[0].url; var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); expect(query.sort).toEqual('size'); - expect(query.sortdirection).toEqual('asc'); + expect(query.sortdirection).toEqual('desc'); }); it('Toggles sort direction when clicking on already sorted column', function() { fileList.$el.find('.column-name .columntitle').click(); @@ -1710,37 +1710,51 @@ describe('OCA.Files.FileList tests', function() { var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS; var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS; fileList.$el.find('.column-size .columntitle').click(); - // moves triangle to size column + // moves triangle to size column, check indicator on name is hidden expect( - fileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + fileList.$el.find('.column-name .sort-indicator').hasClass('hidden') + ).toEqual(true); + // check indicator on size is visible and defaults to descending + expect( + fileList.$el.find('.column-size .sort-indicator').hasClass('hidden') ).toEqual(false); expect( - fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) ).toEqual(true); // click again on size column, reverses direction fileList.$el.find('.column-size .columntitle').click(); expect( - fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass('hidden') + ).toEqual(false); + expect( + fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) ).toEqual(true); // click again on size column, reverses direction fileList.$el.find('.column-size .columntitle').click(); expect( - fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass('hidden') + ).toEqual(false); + expect( + fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) ).toEqual(true); // click on mtime column, moves indicator there fileList.$el.find('.column-mtime .columntitle').click(); expect( - fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass('hidden') + ).toEqual(true); + expect( + fileList.$el.find('.column-mtime .sort-indicator').hasClass('hidden') ).toEqual(false); expect( - fileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-mtime .sort-indicator').hasClass(DESC_CLASS) ).toEqual(true); }); it('Uses correct sort comparator when inserting files', function() { testFiles.sort(OCA.Files.FileList.Comparators.size); + testFiles.reverse(); //default is descending // this will make it reload the testFiles with the correct sorting fileList.$el.find('.column-size .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); @@ -1764,17 +1778,16 @@ describe('OCA.Files.FileList tests', function() { etag: '999' }; fileList.add(newFileData); + expect(fileList.findFileEl('Three.pdf').index()).toEqual(0); + expect(fileList.findFileEl('new file.txt').index()).toEqual(1); + expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(fileList.findFileEl('somedir').index()).toEqual(3); + expect(fileList.findFileEl('One.txt').index()).toEqual(4); expect(fileList.files.length).toEqual(5); expect(fileList.$fileList.find('tr').length).toEqual(5); - expect(fileList.findFileEl('One.txt').index()).toEqual(0); - expect(fileList.findFileEl('somedir').index()).toEqual(1); - expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); - expect(fileList.findFileEl('new file.txt').index()).toEqual(3); - expect(fileList.findFileEl('Three.pdf').index()).toEqual(4); }); it('Uses correct reversed sort comparator when inserting files', function() { testFiles.sort(OCA.Files.FileList.Comparators.size); - testFiles.reverse(); // this will make it reload the testFiles with the correct sorting fileList.$el.find('.column-size .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); @@ -1811,13 +1824,13 @@ describe('OCA.Files.FileList tests', function() { etag: '999' }; fileList.add(newFileData); + expect(fileList.findFileEl('One.txt').index()).toEqual(0); + expect(fileList.findFileEl('somedir').index()).toEqual(1); + expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(fileList.findFileEl('new file.txt').index()).toEqual(3); + expect(fileList.findFileEl('Three.pdf').index()).toEqual(4); expect(fileList.files.length).toEqual(5); expect(fileList.$fileList.find('tr').length).toEqual(5); - expect(fileList.findFileEl('One.txt').index()).toEqual(4); - expect(fileList.findFileEl('somedir').index()).toEqual(3); - expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); - expect(fileList.findFileEl('new file.txt').index()).toEqual(1); - expect(fileList.findFileEl('Three.pdf').index()).toEqual(0); }); }); /** diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index c40fc43f7d5..4a257f2ad33 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -419,21 +419,51 @@ class Hooks { $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
+ $type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
+
if ($mp1 === $mp2) {
self::$renamedFiles[$params['oldpath']] = array(
'uid' => $ownerOld,
- 'path' => $pathOld);
+ 'path' => $pathOld,
+ 'type' => $type,
+ 'operation' => 'rename',
+ );
+
}
}
/**
- * after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
- * @param array $params array with oldpath and newpath
+ * mark file as renamed so that we know the original source after the file was renamed
+ * @param array $params with the old path and the new path
+ */
+ public static function preCopy($params) {
+ $user = \OCP\User::getUser();
+ $view = new \OC\Files\View('/');
+ $util = new Util($view, $user);
+ list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
+
+ // we only need to rename the keys if the rename happens on the same mountpoint
+ // otherwise we perform a stream copy, so we get a new set of keys
+ $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
+ $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
+
+ $type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
+
+ if ($mp1 === $mp2) {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'uid' => $ownerOld,
+ 'path' => $pathOld,
+ 'type' => $type,
+ 'operation' => 'copy');
+ }
+ }
+
+ /**
+ * after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
*
- * This function is connected to the rename signal of OC_Filesystem and adjust the name and location
- * of the stored versions along the actual file
+ * @param array $params array with oldpath and newpath
*/
- public static function postRename($params) {
+ public static function postRenameOrCopy($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
@@ -451,6 +481,8 @@ class Hooks { isset(self::$renamedFiles[$params['oldpath']]['path'])) {
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
+ $type = self::$renamedFiles[$params['oldpath']]['type'];
+ $operation = self::$renamedFiles[$params['oldpath']]['operation'];
unset(self::$renamedFiles[$params['oldpath']]);
} else {
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
@@ -485,8 +517,7 @@ class Hooks { }
// handle share keys
- if (!$view->is_dir($oldKeyfilePath)) {
- $type = 'file';
+ if ($type === 'file') {
$oldKeyfilePath .= '.key';
$newKeyfilePath .= '.key';
@@ -494,18 +525,17 @@ class Hooks { $matches = Helper::findShareKeys($oldShareKeyPath, $view);
foreach ($matches as $src) {
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
- $view->rename($src, $dst);
+ $view->$operation($src, $dst);
}
} else {
- $type = "folder";
// handle share-keys folders
- $view->rename($oldShareKeyPath, $newShareKeyPath);
+ $view->$operation($oldShareKeyPath, $newShareKeyPath);
}
// Rename keyfile so it isn't orphaned
if ($view->file_exists($oldKeyfilePath)) {
- $view->rename($oldKeyfilePath, $newKeyfilePath);
+ $view->$operation($oldKeyfilePath, $newKeyfilePath);
}
diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index fed0788028f..ed42cec326a 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -62,7 +62,9 @@ class Helper { public static function registerFilesystemHooks() { \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename'); + \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy'); + \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy'); + \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy'); \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete'); \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete'); \OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount'); diff --git a/apps/files_encryption/settings-admin.php b/apps/files_encryption/settings-admin.php index 88e06613997..496a7cffb50 100644 --- a/apps/files_encryption/settings-admin.php +++ b/apps/files_encryption/settings-admin.php @@ -12,8 +12,11 @@ $tmpl = new OCP\Template('files_encryption', 'settings-admin'); // Check if an adminRecovery account is enabled for recovering files after lost pwd $recoveryAdminEnabled = \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryAdminEnabled', '0'); +$session = new \OCA\Encryption\Session(new \OC\Files\View('/')); +$initStatus = $session->getInitialized(); $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); +$tmpl->assign('initStatus', $initStatus); \OCP\Util::addscript('files_encryption', 'settings-admin'); \OCP\Util::addscript('core', 'multiselect'); diff --git a/apps/files_encryption/templates/settings-admin.php b/apps/files_encryption/templates/settings-admin.php index d4e6abf004a..a97261dc1c9 100644 --- a/apps/files_encryption/templates/settings-admin.php +++ b/apps/files_encryption/templates/settings-admin.php @@ -1,6 +1,9 @@ <form id="encryption" class="section"> <h2><?php p($l->t('Encryption')); ?></h2> + <?php if($_["initStatus"] === \OCA\Encryption\Session::NOT_INITIALIZED): ?> + <?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?> + <?php else: ?> <p> <?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?> <br/> @@ -57,4 +60,5 @@ </button> <span class="msg"></span> </p> + <?php endif; ?> </form> diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index e9988df3275..3d44b9fa9a5 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -1,7 +1,11 @@ <form id="encryption" class="section">
<h2><?php p( $l->t( 'Encryption' ) ); ?></h2>
- <?php if ( $_["initialized"] === '1' ): ?>
+ <?php if ( $_["initialized"] === \OCA\Encryption\Session::NOT_INITIALIZED ): ?>
+
+ <?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?>
+
+ <?php elseif ( $_["initialized"] === \OCA\Encryption\Session::INIT_EXECUTED ): ?>
<p>
<a name="changePKPasswd" />
<label for="changePrivateKeyPasswd">
@@ -33,9 +37,8 @@ </button>
<span class="msg"></span>
</p>
- <?php endif; ?>
- <?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?>
+ <?php elseif ( $_["recoveryEnabled"] && $_["privateKeySet"] && $_["initialized"] === \OCA\Encryption\Session::INIT_SUCCESSFUL ): ?>
<br />
<p>
<label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label>
diff --git a/apps/files_encryption/tests/hooks.php b/apps/files_encryption/tests/hooks.php index 5eda8df01b9..cc5b6d5b6f6 100644 --- a/apps/files_encryption/tests/hooks.php +++ b/apps/files_encryption/tests/hooks.php @@ -336,6 +336,58 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { } /** + * test rename operation + */ + function testCopyHook() { + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename, $this->data); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // check if keys exists + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' + . $this->filename . '.key')); + + // make subfolder and sub-subfolder + $this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder); + $this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder); + + $this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder)); + + // copy the file to the sub-subfolder + \OC\Files\Filesystem::copy($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename); + + $this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename)); + $this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $this->filename)); + + // keys should be copied too + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' + . $this->filename . '.key')); + + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/' + . $this->filename . '.key')); + + // cleanup + $this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder); + $this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename); + } + + /** * @brief replacing encryption keys during password change should be allowed * until the user logged in for the first time */ diff --git a/apps/files_sharing/ajax/publicpreview.php b/apps/files_sharing/ajax/publicpreview.php index 0b2af7a6e59..f5343a7ef26 100644 --- a/apps/files_sharing/ajax/publicpreview.php +++ b/apps/files_sharing/ajax/publicpreview.php @@ -70,10 +70,6 @@ if(substr($path, 0, 1) === '/') { $path = substr($path, 1); } -if ($keepAspect === true) { - $maxY = $maxX; -} - if($maxX === 0 || $maxY === 0) { \OC_Response::setStatus(\OC_Response::STATUS_BAD_REQUEST); \OC_Log::write('core-preview', 'x and/or y set to 0', \OC_Log::DEBUG); diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php index fc547ba349d..72acdbac736 100644 --- a/apps/files_sharing/appinfo/update.php +++ b/apps/files_sharing/appinfo/update.php @@ -32,6 +32,7 @@ function updateFilePermissions($chunkSize = 99) { } } + $connection = \OC_DB::getConnection(); $chunkedPermissionList = array_chunk($updatedRows, $chunkSize, true); foreach ($chunkedPermissionList as $subList) { @@ -39,7 +40,7 @@ function updateFilePermissions($chunkSize = 99) { //update share table $ids = implode(',', array_keys($subList)); foreach ($subList as $id => $permission) { - $statement .= "WHEN " . $id . " THEN " . $permission . " "; + $statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $permission . " "; } $statement .= ' END WHERE `id` IN (' . $ids . ')'; @@ -95,6 +96,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) { } $chunkedShareList = array_chunk($shares, $chunkSize, true); + $connection = \OC_DB::getConnection(); foreach ($chunkedShareList as $subList) { @@ -102,7 +104,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) { //update share table $ids = implode(',', array_keys($subList)); foreach ($subList as $id => $target) { - $statement .= "WHEN " . $id . " THEN '/Shared" . $target . "' "; + $statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $connection->quote('/Shared' . $target, \PDO::PARAM_STR); } $statement .= ' END WHERE `id` IN (' . $ids . ')'; diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index d5c65a6c681..7a43185a2d7 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -59,6 +59,9 @@ $tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(',')); if (this._sharedWithUser) { $tr.attr('data-share-owner', fileData.shareOwner); + $tr.attr('data-mounttype', 'shared-root'); + var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE; + $tr.attr('data-permissions', permission); } return $tr; }, @@ -162,7 +165,6 @@ else { file.type = 'file'; if (share.isPreviewAvailable) { - file.icon = true; file.isPreviewAvailable = true; } } diff --git a/apps/files_sharing/lib/api.php b/apps/files_sharing/lib/api.php index 50ba74f5beb..faf141db25f 100644 --- a/apps/files_sharing/lib/api.php +++ b/apps/files_sharing/lib/api.php @@ -339,6 +339,8 @@ class Api { return self::updatePassword($share, $params); } elseif (isset($params['_put']['publicUpload'])) { return self::updatePublicUpload($share, $params); + } elseif (isset($params['_put']['expireDate'])) { + return self::updateExpireDate($share, $params); } } catch (\Exception $e) { @@ -409,7 +411,7 @@ class Api { if ($share['item_type'] !== 'folder' || (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) { - return new \OC_OCS_Result(null, 404, "public upload is only possible for public shared folders"); + return new \OC_OCS_Result(null, 400, "public upload is only possible for public shared folders"); } // read, create, update (7) if public upload is enabled or @@ -421,6 +423,29 @@ class Api { } /** + * set expire date for public link share + * @param array $share information about the share + * @param array $params contains 'expireDate' which needs to be a well formated date string, e.g DD-MM-YYYY + * @return \OC_OCS_Result + */ + private static function updateExpireDate($share, $params) { + // only public links can have a expire date + if ((int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) { + return new \OC_OCS_Result(null, 400, "expire date only exists for public link shares"); + } + + try { + $expireDateSet = \OCP\Share::setExpirationDate($share['item_type'], $share['item_source'], $params['_put']['expireDate'], (int)$share['stime']); + $result = ($expireDateSet) ? new \OC_OCS_Result() : new \OC_OCS_Result(null, 404, "couldn't set expire date"); + } catch (\Exception $e) { + $result = new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + return $result; + + } + + /** * update password for public link share * @param array $share information about the share * @param array $params 'password' @@ -555,7 +580,7 @@ class Api { * @return array with: item_source, share_type, share_with, item_type, permissions */ private static function getShareFromId($shareID) { - $sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?'; + $sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions`, `stime` FROM `*PREFIX*share` WHERE `id` = ?'; $args = array($shareID); $query = \OCP\DB::prepare($sql); $result = $query->execute($args); diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index 3a0de51192e..855be2872b5 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -49,7 +49,12 @@ class Storage extends DAV implements ISharedStorage { $this->remote = $options['remote']; $this->remoteUser = $options['owner']; list($protocol, $remote) = explode('://', $this->remote); - list($host, $root) = explode('/', $remote, 2); + if (strpos($remote, '/')) { + list($host, $root) = explode('/', $remote, 2); + } else { + $host = $remote; + $root = ''; + } $secure = $protocol === 'https'; $root = rtrim($root, '/') . '/public.php/webdav'; $this->mountPoint = $options['mountpoint']; @@ -148,7 +153,7 @@ class Storage extends DAV implements ISharedStorage { // ownCloud instance is gone, likely to be a temporary server configuration error throw $e; } - } catch(\Exception $shareException) { + } catch (\Exception $shareException) { // todo, maybe handle 403 better and ask the user for a new password throw $e; } diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 386fa7e17cd..8406b79cf1a 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -42,7 +42,7 @@ </div> <?php elseif (substr($_['mimetype'], 0, strpos($_['mimetype'], '/')) == 'video'): ?> <div id="imgframe"> - <video tabindex="0" controls="" autoplay=""> + <video tabindex="0" controls="" preload="none"> <source src="<?php p($_['downloadURL']); ?>" type="<?php p($_['mimetype']); ?>" /> </video> </div> diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php index 72dd5816ea0..49571d4c3c2 100644 --- a/apps/files_sharing/tests/api.php +++ b/apps/files_sharing/tests/api.php @@ -940,6 +940,78 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { /** * @medium + */ + function testUpdateShareExpireDate() { + + $fileInfo = $this->view->getFileInfo($this->folder); + + // enforce expire date, by default 7 days after the file was shared + \OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'yes'); + \OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'yes'); + + $dateWithinRange = new \DateTime(); + $dateWithinRange->add(new \DateInterval('P5D')); + $dateOutOfRange = new \DateTime(); + $dateOutOfRange->add(new \DateInterval('P8D')); + + $result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $items = \OCP\Share::getItemShared('file', null); + + // make sure that we found a link share + $this->assertEquals(1, count($items)); + + $linkShare = reset($items); + + // update expire date to a valid value + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['expireDate'] = $dateWithinRange->format('Y-m-d'); + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded()); + + $items = \OCP\Share::getItemShared('file', $linkShare['file_source']); + + $updatedLinkShare = reset($items); + + // date should be changed + $this->assertTrue(is_array($updatedLinkShare)); + $this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']); + + // update expire date to a value out of range + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['expireDate'] = $dateOutOfRange->format('Y-m-d'); + + $result = Share\Api::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 + \OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'no'); + \OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'no'); + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium * @depends testCreateShare */ function testDeleteShare() { @@ -1158,7 +1230,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $result = \OCP\Share::shareItem('file', $info->getId(), \OCP\Share::SHARE_TYPE_USER, \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); $this->assertTrue($result); - $result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate); + $result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate, $now); $this->assertTrue($result); //manipulate stime so that both shares are older then the default expire date diff --git a/apps/files_sharing/tests/update.php b/apps/files_sharing/tests/update.php index 86b92b69616..d3555cc2ee3 100644 --- a/apps/files_sharing/tests/update.php +++ b/apps/files_sharing/tests/update.php @@ -176,6 +176,7 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base { array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user2', 'admin', '/foo2'), array(\OCP\Share::SHARE_TYPE_USER, 'file', 'user3', 'admin', '/foo3'), array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', '/foo4'), + array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', "/foo'4"), array(\OCP\Share::SHARE_TYPE_LINK, 'file', 'user1', 'admin', '/ShouldNotChange'), array(\OCP\Share::SHARE_TYPE_CONTACT, 'contact', 'admin', 'user1', '/ShouldNotChange'), diff --git a/apps/files_versions/appinfo/app.php b/apps/files_versions/appinfo/app.php index 371162cd16f..8c517d4d0ff 100644 --- a/apps/files_versions/appinfo/app.php +++ b/apps/files_versions/appinfo/app.php @@ -8,9 +8,4 @@ OC::$CLASSPATH['OCA\Files_Versions\Capabilities'] = 'files_versions/lib/capabili OCP\Util::addscript('files_versions', 'versions'); OCP\Util::addStyle('files_versions', 'versions'); -// Listen to write signals -OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook"); -// Listen to delete and rename signals -OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook"); -OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook"); -OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook"); +\OCA\Files_Versions\Hooks::connectHooks(); diff --git a/apps/files_versions/lib/hooks.php b/apps/files_versions/lib/hooks.php index 990f1403e8d..1a584232ba7 100644 --- a/apps/files_versions/lib/hooks.php +++ b/apps/files_versions/lib/hooks.php @@ -14,6 +14,16 @@ namespace OCA\Files_Versions; class Hooks { + public static function connectHooks() { + // Listen to write signals + \OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook"); + // Listen to delete and rename signals + \OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook"); + \OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook"); + \OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook"); + \OCP\Util::connectHook('OC_Filesystem', 'copy', "OCA\Files_Versions\Hooks", "copy_hook"); + } + /** * listen to write event. */ @@ -69,7 +79,25 @@ class Hooks { $oldpath = $params['oldpath']; $newpath = $params['newpath']; if($oldpath<>'' && $newpath<>'') { - Storage::rename( $oldpath, $newpath ); + Storage::renameOrCopy($oldpath, $newpath, 'rename'); + } + } + } + + /** + * copy versions of copied files + * @param array $params array with oldpath and newpath + * + * This function is connected to the copy signal of OC_Filesystem and copies the + * the stored versions to the new location + */ + public static function copy_hook($params) { + + if (\OCP\App::isEnabled('files_versions')) { + $oldpath = $params['oldpath']; + $newpath = $params['newpath']; + if($oldpath<>'' && $newpath<>'') { + Storage::renameOrCopy($oldpath, $newpath, 'copy'); } } } diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 2e048416c11..a9d51b2c58b 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -174,9 +174,12 @@ class Storage { } /** - * rename versions of a file + * rename or copy versions of a file + * @param string $old_path + * @param string $new_path + * @param string $operation can be 'copy' or 'rename' */ - public static function rename($old_path, $new_path) { + public static function renameOrCopy($old_path, $new_path, $operation) { list($uid, $oldpath) = self::getUidAndFilename($old_path); list($uidn, $newpath) = self::getUidAndFilename($new_path); $versions_view = new \OC\Files\View('/'.$uid .'/files_versions'); @@ -188,18 +191,21 @@ class Storage { return self::store($new_path); } - self::expire($newpath); - if ( $files_view->is_dir($oldpath) && $versions_view->is_dir($oldpath) ) { - $versions_view->rename($oldpath, $newpath); + $versions_view->$operation($oldpath, $newpath); } else if ( ($versions = Storage::getVersions($uid, $oldpath)) ) { // create missing dirs if necessary self::createMissingDirectories($newpath, new \OC\Files\View('/'. $uidn)); foreach ($versions as $v) { - $versions_view->rename($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']); + $versions_view->$operation($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']); } } + + if (!$files_view->is_dir($newpath)) { + self::expire($newpath); + } + } /** @@ -254,34 +260,46 @@ class Storage { public static function getVersions($uid, $filename, $userFullPath = '') { $versions = array(); // fetch for old versions - $view = new \OC\Files\View('/' . $uid . '/' . self::VERSIONS_ROOT); + $view = new \OC\Files\View('/' . $uid . '/'); $pathinfo = pathinfo($filename); + $versionedFile = $pathinfo['basename']; - $files = $view->getDirectoryContent($pathinfo['dirname']); + $dir = self::VERSIONS_ROOT . '/' . $pathinfo['dirname']; - $versionedFile = $pathinfo['basename']; + $dirContent = false; + if ($view->is_dir($dir)) { + $dirContent = $view->opendir($dir); + } - foreach ($files as $file) { - if ($file['type'] === 'file') { - $pos = strrpos($file['path'], '.v'); - $currentFile = substr($file['name'], 0, strrpos($file['name'], '.v')); - if ($currentFile === $versionedFile) { - $version = substr($file['path'], $pos + 2); - $key = $version . '#' . $filename; - $versions[$key]['cur'] = 0; - $versions[$key]['version'] = $version; - $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($version); - if (empty($userFullPath)) { - $versions[$key]['preview'] = ''; - } else { - $versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $version)); + if ($dirContent === false) { + return $versions; + } + + if (is_resource($dirContent)) { + while (($entryName = readdir($dirContent)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) { + $pathparts = pathinfo($entryName); + $filename = $pathparts['filename']; + if ($filename === $versionedFile) { + $pathparts = pathinfo($entryName); + $timestamp = substr($pathparts['extension'], 1); + $filename = $pathparts['filename']; + $key = $timestamp . '#' . $filename; + $versions[$key]['version'] = $timestamp; + $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp); + if (empty($userFullPath)) { + $versions[$key]['preview'] = ''; + } else { + $versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $timestamp)); + } + $versions[$key]['path'] = $filename; + $versions[$key]['name'] = $versionedFile; + $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName); } - $versions[$key]['path'] = $filename; - $versions[$key]['name'] = $versionedFile; - $versions[$key]['size'] = $file['size']; } } + closedir($dirContent); } // sort with newest version first diff --git a/apps/files_versions/tests/versions.php b/apps/files_versions/tests/versions.php index aa66faffcbf..03432276358 100644 --- a/apps/files_versions/tests/versions.php +++ b/apps/files_versions/tests/versions.php @@ -20,6 +20,7 @@ * */ +require_once __DIR__ . '/../appinfo/app.php'; require_once __DIR__ . '/../lib/versions.php'; /** @@ -28,6 +29,32 @@ require_once __DIR__ . '/../lib/versions.php'; */ class Test_Files_Versioning extends \PHPUnit_Framework_TestCase { + const TEST_VERSIONS_USER = 'test-versions-user'; + const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions'; + + private $rootView; + + public static function setUpBeforeClass() { + // create test user + self::loginHelper(self::TEST_VERSIONS_USER, true); + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(self::TEST_VERSIONS_USER); + } + + function setUp() { + self::loginHelper(self::TEST_VERSIONS_USER); + $this->rootView = new \OC\Files\View(); + if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) { + $this->rootView->mkdir(self::USERS_VERSIONS_ROOT); + } + } + + function tearDown() { + $this->rootView->deleteAll(self::USERS_VERSIONS_ROOT); + } /** * @medium @@ -176,6 +203,87 @@ class Test_Files_Versioning extends \PHPUnit_Framework_TestCase { ); } + function testRename() { + + \OC\Files\Filesystem::file_put_contents("test.txt", "test file"); + + $t1 = time(); + // second version is two weeks older, this way we make sure that no + // version will be expired + $t2 = $t1 - 60 * 60 * 24 * 14; + + // create some versions + $v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1; + $v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2; + $v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1; + $v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2; + + $this->rootView->file_put_contents($v1, 'version1'); + $this->rootView->file_put_contents($v2, 'version2'); + + // execute rename hook of versions app + \OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'rename'); + + $this->assertFalse($this->rootView->file_exists($v1)); + $this->assertFalse($this->rootView->file_exists($v2)); + + $this->assertTrue($this->rootView->file_exists($v1Renamed)); + $this->assertTrue($this->rootView->file_exists($v2Renamed)); + + //cleanup + \OC\Files\Filesystem::unlink('test2.txt'); + } + + function testCopy() { + + \OC\Files\Filesystem::file_put_contents("test.txt", "test file"); + + $t1 = time(); + // second version is two weeks older, this way we make sure that no + // version will be expired + $t2 = $t1 - 60 * 60 * 24 * 14; + + // create some versions + $v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1; + $v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2; + $v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1; + $v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2; + + $this->rootView->file_put_contents($v1, 'version1'); + $this->rootView->file_put_contents($v2, 'version2'); + + // execute copy hook of versions app + \OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'copy'); + + $this->assertTrue($this->rootView->file_exists($v1)); + $this->assertTrue($this->rootView->file_exists($v2)); + + $this->assertTrue($this->rootView->file_exists($v1Copied)); + $this->assertTrue($this->rootView->file_exists($v2Copied)); + + //cleanup + \OC\Files\Filesystem::unlink('test.txt'); + \OC\Files\Filesystem::unlink('test2.txt'); + } + + /** + * @param string $user + * @param bool $create + * @param bool $password + */ + public static function loginHelper($user, $create = false) { + + if ($create) { + \OC_User::createUser($user, $user); + } + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_User::setUserId($user); + \OC_Util::setupFS($user); + } + } // extend the original class to make it possible to test protected methods |