diff options
author | Frank Karlitschek <frank@owncloud.org> | 2014-07-30 17:40:05 -0400 |
---|---|---|
committer | Frank Karlitschek <frank@owncloud.org> | 2014-07-30 17:40:05 -0400 |
commit | c0f0e79b5e62e229cc3e3a0fa597239b5eebccaf (patch) | |
tree | 702174b82cfd2adc4d38f032003810a4f5447c8e | |
parent | 727374a6f3e693b3237d6766d3bce86d73c00833 (diff) | |
parent | a0f024fec22854509de19a1aa735a1567c3b534c (diff) | |
download | nextcloud-server-7.0.1RC1.tar.gz nextcloud-server-7.0.1RC1.zip |
Merge branch 'stable7' of https://github.com/owncloud/core into stable7v7.0.1RC1
65 files changed, 1308 insertions, 200 deletions
diff --git a/3rdparty b/3rdparty -Subproject 6ece897f4435c246730db947576ad6f8a94dbdb +Subproject b958a1e609b6bd09dd0fa29943b48eb024a31b7 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 diff --git a/core/ajax/preview.php b/core/ajax/preview.php index d38043707ac..edbd41d2db4 100644 --- a/core/ajax/preview.php +++ b/core/ajax/preview.php @@ -21,10 +21,6 @@ if ($file === '') { exit; } -if ($keepAspect === true) { - $maxY = $maxX; -} - if ($maxX === 0 || $maxY === 0) { //400 Bad Request \OC_Response::setStatus(400); diff --git a/core/ajax/share.php b/core/ajax/share.php index be72e36541a..4b5a6cd664f 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -80,18 +80,12 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo break; case 'setExpirationDate': if (isset($_POST['date'])) { - $l = OC_L10N::get('core'); - $date = new \DateTime($_POST['date']); - $today = new \DateTime('now'); - - - - if ($date < $today) { - OC_JSON::error(array('data' => array('message' => $l->t('Expiration date is in the past.')))); - return; + try { + $return = OCP\Share::setExpirationDate($_POST['itemType'], $_POST['itemSource'], $_POST['date']); + ($return) ? OC_JSON::success() : OC_JSON::error(); + } catch (\Exception $e) { + OC_JSON::error(array('data' => array('message' => $e->getMessage()))); } - $return = OCP\Share::setExpirationDate($_POST['itemType'], $_POST['itemSource'], $_POST['date']); - ($return) ? OC_JSON::success() : OC_JSON::error(); } break; case 'informRecipients': diff --git a/core/css/header.css b/core/css/header.css index 86db48a3f08..2df8cdd3aa3 100644 --- a/core/css/header.css +++ b/core/css/header.css @@ -190,6 +190,8 @@ #navigation .app-icon { margin: 0 auto; padding: 0; + max-height: 32px; + max-width: 32px; } /* Apps management */ diff --git a/core/css/icons.css b/core/css/icons.css index 60f0b1b8c60..534c1c66514 100644 --- a/core/css/icons.css +++ b/core/css/icons.css @@ -136,6 +136,9 @@ .icon-search { background-image: url('../img/actions/search.svg'); } +.icon-search-white { + background-image: url('../img/actions/search-white.svg'); +} .icon-settings { background-image: url('../img/actions/settings.svg'); diff --git a/core/css/mobile.css b/core/css/mobile.css index 6e2172ddbb7..21090d08cb7 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -31,16 +31,22 @@ /* compress search box on mobile, expand when focused */ .searchbox input[type="search"] { - width: 15%; - -webkit-transition: width 100ms; - -moz-transition: width 100ms; - -o-transition: width 100ms; - transition: width 100ms; + width: 0; + cursor: pointer; + background-color: transparent; + background-image: url('../img/actions/search-white.svg'); + -webkit-transition: all 100ms; + -moz-transition: all 100ms; + -o-transition: all 100ms; + transition: all 100ms; } .searchbox input[type="search"]:focus, .searchbox input[type="search"]:active { width: 155px; max-width: 50%; + cursor: text; + background-color: #fff; + background-image: url('../img/actions/search.svg'); } /* do not show display name on mobile when profile picture is present */ diff --git a/core/css/styles.css b/core/css/styles.css index 40c1622ca26..3390b3000e2 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -444,13 +444,15 @@ input[name='password-clone'] { .groupbottom { position: relative; } -#body-login .grouptop input { +#body-login .grouptop input, +.grouptop input { margin-bottom: 0; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -#body-login .groupmiddle input { +#body-login .groupmiddle input, +.groupmiddle input { margin-top: 0; margin-bottom: 0; border-top: 0; @@ -458,7 +460,8 @@ input[name='password-clone'] { border-radius: 0; box-shadow: 0 1px 0 rgba(0,0,0,.1) inset !important; } -#body-login .groupbottom input { +#body-login .groupbottom input, +.groupbottom input { margin-top: 0; border-top: 0; border-top-right-radius: 0; @@ -677,19 +680,33 @@ label.infield { .center { text-align:center; } .inlineblock { display: inline-block; } -#notification-container { position: fixed; top: 0px; width: 100%; text-align: center; z-index: 101; line-height: 1.2;} +#notification-container { + position: absolute; + top: 0; + width: 100%; + text-align: center; +} #notification, #update-notification { + margin: 0 auto; + max-width: 60%; z-index: 101; background-color: #fc4; border: 0; - padding: 0 .7em .3em; + padding: 1px 8px; display: none; position: relative; top: 0; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; + filter:alpha(opacity=90); + opacity: .9; +} +#notification span, #update-notification span { + cursor: pointer; + font-weight: bold; + margin-left: 1em; } -#notification span, #update-notification span { cursor:pointer; font-weight:bold; margin-left:1em; } tr .action:not(.permanent), .selectedActions a { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; } tr:hover .action, tr .action.permanent, .selectedActions a { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter:alpha(opacity=50); opacity:.5; } diff --git a/core/img/actions/public.png b/core/img/actions/public.png Binary files differindex 077bb7504de..772838ad205 100644 --- a/core/img/actions/public.png +++ b/core/img/actions/public.png diff --git a/core/img/actions/public.svg b/core/img/actions/public.svg index c70a7627788..99a71c6cb5b 100644 --- a/core/img/actions/public.svg +++ b/core/img/actions/public.svg @@ -1,4 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"> - <path d="m8 1c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zm0.80208 0.89323c1.2011 0.026708 2.2625 0.74821 3.3359 1.2214l1.732 2.3971-0.274 1.03 0.529 0.3281-0.009 1.2213c-0.0121 0.34937 0.005 0.69921-0.0091 1.0482-0.16635 0.66235-0.55063 1.2666-0.875 1.8685-0.21989 0.10841 0.02005-0.7185-0.11849-0.97526 0.032-0.5934-0.471-0.566-0.811-0.2364-0.421 0.2454-1.346 0.3194-1.376-0.3464-0.239-0.8001-0.035-1.6526 0.291-2.3971l-0.537-0.6563 0.191-1.6862-0.857-0.8658 0.201-0.948-1.0028-0.5651c-0.1977-0.1552-0.5738-0.2166-0.6563-0.4284 0.0814-0.0046 0.166-0.0109 0.2461-0.0091zm-2.4609 0.00912c0.031442 0.00459 0.069992 0.026431 0.1276 0.072917 0.338 0.1857-0.0825 0.3964-0.1823 0.5925-0.5398 0.3651 0.166 0.6641 0.401 0.957 0.3767-0.1082 0.7535-0.6467 1.3034-0.483 0.7034-0.2195 0.5913 0.5891 0.9935 0.9479 0.0522 0.1689 0.88 0.7185 0.3828 0.5377-0.4095-0.3174-0.8649-0.2935-1.1576 0.1641-0.7909 0.4286-0.3228-0.8252-0.7018-1.1302-0.5729-0.6392-0.3328 0.4775-0.401 0.8112-0.3725-0.0081-1.0681-0.2866-1.4492 0.1641l0.3736 0.6106 0.4467-0.6836c0.1085-0.2474 0.2447 0.1923 0.3645 0.2735 0.1431 0.2759 0.823 0.7434 0.3099 0.875-0.7606 0.4219-1.3589 1.0618-2.0052 1.6315-0.218 0.46-0.663 0.4074-0.9388 0.0273-0.6672-0.4105-0.6177 0.6566-0.5833 1.0573l0.58333-0.36458v0.60156c-0.0165 0.1138-0.0024 0.2322-0.0091 0.3464-0.4087 0.427-0.8207-0.5995-1.1758-0.8295l-0.0273-1.5039c0.0129-0.4225-0.0763-0.8551 0.0091-1.2669 0.8038-0.8625 1.6202-1.7561 2.0964-2.8529h0.78385c0.5478 0.2654 0.2357-0.5881 0.4557-0.556zm-1.1576 7.8204c0.095099-0.010145 0.20328 0.011573 0.31901 0.072921 0.73794 0.10562 1.2897 0.6409 1.8776 1.0482 0.46872 0.46452 1.4828 0.31578 1.5951 1.1029-0.17061 0.85375-1.0105 1.3122-1.75 1.6133-0.1846 0.103-0.383 0.185-0.5925 0.219-0.6856 0.171-0.982-0.532-1.1211-1.058-0.3104-0.65-1.0862-1.142-0.9752-1.941 0.0182-0.397 0.235-1.0134 0.6471-1.0573z"/> +<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> + <g transform="matrix(.67042 -.67042 .67042 .67042 .62542 93.143)"> + <path style="block-progression:tb;text-indent:0;color:#000000;text-transform:none" d="m69.5-61.5c-1.9217 0-3.5 1.5783-3.5 3.5 0 0.17425 0.0062 0.33232 0.03125 0.5h2.0625c-0.053-0.156-0.094-0.323-0.094-0.5 0-0.8483 0.6517-1.5 1.5-1.5h5c0.8483 0 1.5 0.6517 1.5 1.5s-0.6517 1.5-1.5 1.5h-1.6875c-0.28733 0.79501-0.78612 1.4793-1.4375 2h3.125c1.9217 0 3.5-1.5783 3.5-3.5s-1.5783-3.5-3.5-3.5h-5z"/> + <path style="block-progression:tb;text-indent:0;color:#000000;text-transform:none" d="m68.5-54.5c1.9217 0 3.5-1.5783 3.5-3.5 0-0.17425-0.0062-0.33232-0.03125-0.5h-2.0625c0.053 0.156 0.094 0.323 0.094 0.5 0 0.8483-0.6517 1.5-1.5 1.5h-5c-0.8483 0-1.5-0.6517-1.5-1.5s0.6517-1.5 1.5-1.5h1.6875c0.28733-0.79501 0.78612-1.4793 1.4375-2h-3.125c-1.9217 0-3.5 1.5783-3.5 3.5s1.5783 3.5 3.5 3.5h5z"/> + </g> </svg> diff --git a/core/img/actions/search-white.png b/core/img/actions/search-white.png Binary files differnew file mode 100644 index 00000000000..9812c44a3d6 --- /dev/null +++ b/core/img/actions/search-white.png diff --git a/core/img/actions/search-white.svg b/core/img/actions/search-white.svg new file mode 100644 index 00000000000..9b312d0460d --- /dev/null +++ b/core/img/actions/search-white.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> + <rect style="color:#000000" fill-opacity="0" height="97.986" width="163.31" y="-32.993" x="-62.897"/> + <path style="color:#000000" d="m6 1c-2.7614 0-5 2.2386-5 5s2.2386 5 5 5c0.98478 0 1.8823-0.28967 2.6562-0.78125l4.4688 4.625c0.09558 0.10527 0.22619 0.16452 0.375 0.15625 0.14882-0.0083 0.3031-0.07119 0.40625-0.1875l0.9375-1.0625c0.19194-0.22089 0.19549-0.53592 0-0.71875l-4.594-4.406c0.478-0.7663 0.75-1.6555 0.75-2.625 0-2.7614-2.2386-5-5-5zm0 2c1.6569 0 3 1.3431 3 3s-1.3431 3-3 3-3-1.3431-3-3 1.3431-3 3-3z" fill="#fff"/> +</svg> diff --git a/core/img/filetypes/folder-public.png b/core/img/filetypes/folder-public.png Binary files differindex 40d8cd067bb..7f8f8e3411c 100644 --- a/core/img/filetypes/folder-public.png +++ b/core/img/filetypes/folder-public.png diff --git a/core/img/filetypes/folder-public.svg b/core/img/filetypes/folder-public.svg index f1f9321fd35..9516a7de59d 100644 --- a/core/img/filetypes/folder-public.svg +++ b/core/img/filetypes/folder-public.svg @@ -1,59 +1,62 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="32px" width="32px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> <defs> - <linearGradient id="c" x1="27.557" gradientUnits="userSpaceOnUse" y1="7.1627" gradientTransform="matrix(.89186 0 0 1.0539 3.1208 5.4125)" x2="27.557" y2="21.387"> + <linearGradient id="p" y2="21.387" gradientUnits="userSpaceOnUse" x2="27.557" gradientTransform="matrix(.89186 0 0 1.0539 3.1208 5.4125)" y1="7.1627" x1="27.557"> <stop stop-color="#fff" offset="0"/> <stop stop-color="#fff" stop-opacity=".23529" offset=".0097359"/> <stop stop-color="#fff" stop-opacity=".15686" offset=".99001"/> <stop stop-color="#fff" stop-opacity=".39216" offset="1"/> </linearGradient> - <linearGradient id="d" x1="22.935" gradientUnits="userSpaceOnUse" y1="49.629" gradientTransform="matrix(.74675 0 0 .65549 -1.9219 3.1676)" x2="22.809" y2="36.658"> + <linearGradient id="o" y2="36.658" gradientUnits="userSpaceOnUse" x2="22.809" gradientTransform="matrix(.74675 0 0 .65549 -1.9219 3.1676)" y1="49.629" x1="22.935"> <stop stop-color="#0a0a0a" stop-opacity=".498" offset="0"/> <stop stop-color="#0a0a0a" stop-opacity="0" offset="1"/> </linearGradient> - <linearGradient id="e" x1="35.793" gradientUnits="userSpaceOnUse" y1="17.118" gradientTransform="matrix(.64444 0 0 .64286 .53352 .89286)" x2="35.793" y2="43.761"> + <linearGradient id="n" y2="43.761" gradientUnits="userSpaceOnUse" x2="35.793" gradientTransform="matrix(.64444 0 0 .64286 .53352 .89286)" y1="17.118" x1="35.793"> <stop stop-color="#b4cee1" offset="0"/> <stop stop-color="#5d9fcd" offset="1"/> </linearGradient> - <linearGradient id="f" x1="302.86" gradientUnits="userSpaceOnUse" y1="366.65" gradientTransform="matrix(.051143 0 0 .015916 -2.49 22.299)" x2="302.86" y2="609.51"> + <linearGradient id="m" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(.051143 0 0 .015916 -2.49 22.299)" y1="366.65" x1="302.86"> <stop stop-opacity="0" offset="0"/> <stop offset=".5"/> <stop stop-opacity="0" offset="1"/> </linearGradient> - <radialGradient id="b" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(.019836 0 0 .015916 16.388 22.299)" r="117.14"> + <radialGradient id="q" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(.019836 0 0 .015916 16.388 22.299)" r="117.14"> <stop offset="0"/> <stop stop-opacity="0" offset="1"/> </radialGradient> - <radialGradient id="a" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-.019836 0 0 .015916 15.601 22.299)" r="117.14"> + <radialGradient id="r" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-.019836 0 0 .015916 15.601 22.299)" r="117.14"> <stop offset="0"/> <stop stop-opacity="0" offset="1"/> </radialGradient> - <linearGradient id="g" x1="21.37" gradientUnits="userSpaceOnUse" y1="4.7324" gradientTransform="matrix(.54384 0 0 .61466 3.2689 5.0911)" x2="21.37" y2="34.143"> + <linearGradient id="l" y2="34.143" gradientUnits="userSpaceOnUse" x2="21.37" gradientTransform="matrix(.54384 0 0 .61466 3.2689 5.0911)" y1="4.7324" x1="21.37"> <stop stop-color="#fff" offset="0"/> <stop stop-color="#fff" stop-opacity=".23529" offset=".11063"/> <stop stop-color="#fff" stop-opacity=".15686" offset=".99001"/> <stop stop-color="#fff" stop-opacity=".39216" offset="1"/> </linearGradient> - <linearGradient id="h" x1="62.989" gradientUnits="userSpaceOnUse" y1="13" gradientTransform="matrix(.61905 0 0 .61905 -30.392 1.4286)" x2="62.989" y2="16"> + <linearGradient id="k" y2="16" gradientUnits="userSpaceOnUse" x2="62.989" gradientTransform="matrix(.61905 0 0 .61905 -30.392 1.4286)" y1="13" x1="62.989"> <stop stop-color="#f9f9f9" offset="0"/> <stop stop-color="#d8d8d8" offset="1"/> </linearGradient> - <linearGradient id="i" x1="-51.786" gradientUnits="userSpaceOnUse" x2="-51.786" gradientTransform="matrix(.50703 0 0 .503 68.029 1.3298)" y1="53.514" y2="3.6337"> + <linearGradient id="j" y2="3.6337" gradientUnits="userSpaceOnUse" y1="53.514" gradientTransform="matrix(.50703 0 0 .503 68.029 1.3298)" x2="-51.786" x1="-51.786"> <stop stop-opacity=".32174" offset="0"/> <stop stop-opacity=".27826" offset="1"/> </linearGradient> </defs> - <path opacity=".8" style="color:#000000" d="m4.0002 6.5001c-0.43342 0.005-0.5 0.21723-0.5 0.6349v1.365c-1.2457 0-1-0.002-1 0.54389 0.0216 6.5331 0 6.9014 0 7.4561 0.90135 0 27-2.349 27-3.36v-4.0961c0-0.41767-0.34799-0.54876-0.78141-0.54389h-14.219v-1.365c0-0.41767-0.26424-0.63977-0.69767-0.6349h-9.8023z" stroke="url(#i)" fill="none"/> - <path style="color:#000000" d="m4.0002 7v2h-1v4h26v-4h-15v-2h-10z" fill="url(#h)"/> - <path style="color:#000000" stroke-linecap="round" d="m4.5002 7.5v2h-1v4h25v-4h-15v-2h-9z" stroke="url(#g)" fill="none"/> + <path opacity=".8" style="color:#000000" d="m4.0002 6.5001c-0.43342 0.005-0.5 0.21723-0.5 0.6349v1.365c-1.2457 0-1-0.002-1 0.54389 0.0216 6.5331 0 6.9014 0 7.4561 0.90135 0 27-2.349 27-3.36v-4.0961c0-0.41767-0.34799-0.54876-0.78141-0.54389h-14.219v-1.365c0-0.41767-0.26424-0.63977-0.69767-0.6349h-9.8023z" stroke="url(#j)" fill="none"/> + <path style="color:#000000" d="m4.0002 7v2h-1v4h26v-4h-15v-2h-10z" fill="url(#k)"/> + <path style="color:#000000" d="m4.5002 7.5v2h-1v4h25v-4h-15v-2h-9z" stroke="url(#l)" stroke-linecap="round" fill="none"/> <g transform="translate(.00017936 -1)"> - <rect opacity=".3" height="3.8653" width="24.695" y="28.135" x="3.6472" fill="url(#f)"/> - <path opacity=".3" d="m28.342 28.135v3.865c1.0215 0.0073 2.4695-0.86596 2.4695-1.9328s-1.1399-1.9323-2.4695-1.9323z" fill="url(#b)"/> - <path opacity=".3" d="m3.6472 28.135v3.865c-1.0215 0.0073-2.4695-0.86596-2.4695-1.9328s1.1399-1.9323 2.4695-1.9323z" fill="url(#a)"/> + <rect opacity=".3" height="3.8653" width="24.695" y="28.135" x="3.6472" fill="url(#m)"/> + <path opacity=".3" d="m28.342 28.135v3.865c1.0215 0.0073 2.4695-0.86596 2.4695-1.9328s-1.1399-1.9323-2.4695-1.9323z" fill="url(#q)"/> + <path opacity=".3" d="m3.6472 28.135v3.865c-1.0215 0.0073-2.4695-0.86596-2.4695-1.9328s1.1399-1.9323 2.4695-1.9323z" fill="url(#r)"/> </g> - <path style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" fill="url(#e)"/> - <path opacity=".4" d="m1.682 13 28.636 0.00027c0.4137 0 0.68181 0.29209 0.68181 0.65523l-0.6735 17.712c0.01 0.45948-0.1364 0.64166-0.61707 0.63203l-27.256-0.0115c-0.4137 0-0.83086-0.27118-0.83086-0.63432l-0.62244-17.698c0-0.36314 0.26812-0.65549 0.68182-0.65549z" fill="url(#d)"/> - <path opacity=".5" style="color:#000000" d="m2.5002 12.5 0.62498 16h25.749l0.62498-16z" stroke="url(#c)" stroke-linecap="round" fill="none"/> + <path style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" fill="url(#n)"/> + <path opacity=".4" d="m1.682 13 28.636 0.00027c0.4137 0 0.68181 0.29209 0.68181 0.65523l-0.6735 17.712c0.01 0.45948-0.1364 0.64166-0.61707 0.63203l-27.256-0.0115c-0.4137 0-0.83086-0.27118-0.83086-0.63432l-0.62244-17.698c0-0.36314 0.26812-0.65549 0.68182-0.65549z" fill="url(#o)"/> + <path opacity=".5" style="color:#000000" d="m2.5002 12.5 0.62498 16h25.749l0.62498-16z" stroke="url(#p)" stroke-linecap="round" fill="none"/> <path opacity=".3" stroke-linejoin="round" style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" stroke="#000" stroke-linecap="round" fill="none"/> - <path opacity=".7" d="m16 13c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zm0.80208 0.89323c1.2011 0.02671 2.2625 0.74821 3.3359 1.2214l1.732 2.3971-0.274 1.03 0.529 0.3281-0.009 1.2213c-0.0121 0.34937 0.005 0.69921-0.0091 1.0482-0.16635 0.66235-0.55063 1.2666-0.875 1.8685-0.21989 0.10841 0.02005-0.7185-0.11849-0.97526 0.032-0.5934-0.471-0.566-0.811-0.2364-0.421 0.2454-1.346 0.3194-1.376-0.3464-0.239-0.8001-0.035-1.6526 0.291-2.3971l-0.537-0.6563 0.191-1.6862-0.857-0.8658 0.201-0.948-1.0028-0.5651c-0.1977-0.1552-0.5738-0.2166-0.6563-0.4284 0.0814-0.0046 0.166-0.0109 0.2461-0.0091zm-2.4609 0.0091c0.03144 0.0046 0.06999 0.02643 0.1276 0.07292 0.338 0.1857-0.0825 0.3964-0.1823 0.5925-0.5398 0.3651 0.166 0.6641 0.401 0.957 0.3767-0.1082 0.7535-0.6467 1.3034-0.483 0.7034-0.2195 0.5913 0.5891 0.9935 0.9479 0.0522 0.1689 0.88 0.7185 0.3828 0.5377-0.4095-0.3174-0.8649-0.2935-1.1576 0.1641-0.7909 0.4286-0.3228-0.8252-0.7018-1.1302-0.5729-0.6392-0.3328 0.4775-0.401 0.8112-0.3725-0.0081-1.0681-0.2866-1.4492 0.1641l0.3736 0.6106 0.4467-0.6836c0.1085-0.2474 0.2447 0.1923 0.3645 0.2735 0.1431 0.2759 0.823 0.7434 0.3099 0.875-0.7606 0.4219-1.3589 1.0618-2.0052 1.6315-0.218 0.46-0.663 0.4074-0.9388 0.0273-0.6672-0.4105-0.6177 0.6566-0.5833 1.0573l0.58333-0.36458v0.60156c-0.0165 0.1138-0.0024 0.2322-0.0091 0.3464-0.4087 0.427-0.8207-0.5995-1.1758-0.8295l-0.0273-1.5039c0.0129-0.4225-0.0763-0.8551 0.0091-1.2669 0.8038-0.8625 1.6202-1.7561 2.0964-2.8529h0.78385c0.5478 0.2654 0.2357-0.5881 0.4557-0.556zm-1.1576 7.8204c0.0951-0.01014 0.20328 0.01157 0.31901 0.07292 0.73794 0.10562 1.2897 0.6409 1.8776 1.0482 0.46872 0.46452 1.4828 0.31578 1.5951 1.1029-0.17061 0.85375-1.0105 1.3122-1.75 1.6133-0.1846 0.103-0.383 0.185-0.5925 0.219-0.6856 0.171-0.982-0.532-1.1211-1.058-0.3104-0.65-1.0862-1.142-0.9752-1.941 0.0182-0.397 0.235-1.0134 0.6471-1.0573z"/> + <g opacity=".7" transform="matrix(.67042 -.67042 .67042 .67042 8.6253 106.14)"> + <path style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m69.5-61.5c-1.9217 0-3.5 1.5783-3.5 3.5 0 0.17425 0.0062 0.33232 0.03125 0.5h2.0625c-0.053-0.156-0.094-0.323-0.094-0.5 0-0.8483 0.6517-1.5 1.5-1.5h5c0.8483 0 1.5 0.6517 1.5 1.5s-0.6517 1.5-1.5 1.5h-1.6875c-0.28733 0.79501-0.78612 1.4793-1.4375 2h3.125c1.9217 0 3.5-1.5783 3.5-3.5s-1.5783-3.5-3.5-3.5h-5z"/> + <path style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m68.5-54.5c1.9217 0 3.5-1.5783 3.5-3.5 0-0.17425-0.0062-0.33232-0.03125-0.5h-2.0625c0.053 0.156 0.094 0.323 0.094 0.5 0 0.8483-0.6517 1.5-1.5 1.5h-5c-0.8483 0-1.5-0.6517-1.5-1.5s0.6517-1.5 1.5-1.5h1.6875c0.28733-0.79501 0.78612-1.4793 1.4375-2h-3.125c-1.9217 0-3.5 1.5783-3.5 3.5s1.5783 3.5 3.5 3.5h5z"/> + </g> </svg> diff --git a/core/js/js.js b/core/js/js.js index 4a9a5ce82ff..60f9cc11a58 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -696,7 +696,7 @@ OC.Notification={ var notification = $('#notification'); if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){ notification.html(html); - notification.fadeIn().css("display","inline"); + notification.fadeIn().css('display','inline-block'); }else{ OC.Notification.queuedNotifications.push(html); } @@ -710,7 +710,7 @@ OC.Notification={ var notification = $('#notification'); if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){ notification.text(text); - notification.fadeIn().css("display","inline"); + notification.fadeIn().css('display','inline-block'); }else{ OC.Notification.queuedNotifications.push($('<div/>').text(text).html()); } @@ -1353,6 +1353,18 @@ OC.Util = { } }); }); + }, + + /** + * Remove the time component from a given date + * + * @param {Date} date date + * @return {Date} date with stripped time + */ + stripTime: function(date) { + // FIXME: likely to break when crossing DST + // would be better to use a library like momentJS + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); } }; diff --git a/core/js/share.js b/core/js/share.js index e8d486055b0..14abdf18ade 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -436,7 +436,7 @@ OC.Share={ } } if (share.expiration != null) { - OC.Share.showExpirationDate(share.expiration); + OC.Share.showExpirationDate(share.expiration, share.stime); } }); } @@ -716,7 +716,24 @@ OC.Share={ dirname:function(path) { return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, ''); }, - showExpirationDate:function(date) { + /** + * Displays the expiration date field + * + * @param {Date} date current expiration date + * @param {int} [shareTime] share timestamp in seconds, defaults to now + */ + showExpirationDate:function(date, shareTime) { + var now = new Date(); + var datePickerOptions = { + minDate: now, + maxDate: null + }; + if (_.isNumber(shareTime)) { + shareTime = new Date(shareTime * 1000); + } + if (!shareTime) { + shareTime = now; + } $('#expirationCheckbox').attr('checked', true); $('#expirationDate').val(date); $('#expirationDate').show('blind'); @@ -726,13 +743,14 @@ OC.Share={ }); if (oc_appconfig.core.defaultExpireDateEnforced) { $('#expirationCheckbox').attr('disabled', true); - $.datepicker.setDefaults({ - maxDate : new Date(date.replace(' 00:00:00', '')) - }); + shareTime = OC.Util.stripTime(shareTime).getTime(); + // max date is share date + X days + datePickerOptions.maxDate = new Date(shareTime + oc_appconfig.core.defaultExpireDate * 24 * 3600 * 1000); } if(oc_appconfig.core.defaultExpireDateEnabled) { $('#defaultExpireMessage').show('blind'); } + $.datepicker.setDefaults(datePickerOptions); } }; diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index dd9d4a79277..166210d0312 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -466,6 +466,12 @@ describe('Core base tests', function() { } }); }); + describe('stripTime', function() { + it('strips time from dates', function() { + expect(OC.Util.stripTime(new Date(2014, 2, 24, 15, 4, 45, 24))) + .toEqual(new Date(2014, 2, 24, 0, 0, 0, 0)); + }); + }); }); }); diff --git a/core/js/tests/specs/shareSpec.js b/core/js/tests/specs/shareSpec.js index 00a88ba36ef..32fecf82b65 100644 --- a/core/js/tests/specs/shareSpec.js +++ b/core/js/tests/specs/shareSpec.js @@ -83,16 +83,6 @@ describe('OC.Share tests', function() { expect($el.attr('data-item-source')).toEqual('123'); // TODO: expect that other parts are rendered correctly }); - it('shows default expiration date when set', function() { - oc_appconfig.core.defaultExpireDateEnabled = "yes"; - oc_appconfig.core.defaultExpireDate = ''; - // TODO: expect that default date was set - }); - it('shows default expiration date is set but disabled', function() { - oc_appconfig.core.defaultExpireDateEnabled = "no"; - oc_appconfig.core.defaultExpireDate = ''; - // TODO: expect that default date was NOT set - }); describe('Share with link', function() { // TODO: test ajax calls // TODO: test password field visibility (whenever enforced or not) @@ -265,6 +255,135 @@ describe('OC.Share tests', function() { OC.linkTo('', 'public.php')+'?service=files&t=anothertoken'; expect($('#dropdown #linkText').val()).toEqual(link); }); + describe('expiration date', function() { + var shareData; + var shareItem; + var clock; + + function showDropDown() { + OC.Share.showDropDown( + 'file', + 123, + $container, + 'http://localhost/dummylink', + 31, + 'folder' + ); + } + + beforeEach(function() { + // pick a fake date + clock = sinon.useFakeTimers(new Date(2014, 0, 20, 14, 0, 0).getTime()); + shareItem = { + displayname_owner: 'root', + expiration: null, + file_source: 123, + file_target: '/folder', + id: 20, + item_source: '123', + item_type: 'folder', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }; + shareData = { + reshare: [], + shares: [] + }; + loadItemStub.returns(shareData); + oc_appconfig.core.defaultExpireDate = 7; + oc_appconfig.core.defaultExpireDateEnabled = false; + oc_appconfig.core.defaultExpireDateEnforced = false; + }); + afterEach(function() { + clock.restore(); + }); + + it('does not check expiration date checkbox when no date was set', function() { + shareItem.expiration = null; + shareData.shares.push(shareItem); + showDropDown(); + expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); + expect($('#dropdown #expirationDate').val()).toEqual(''); + }); + it('does not check expiration date checkbox for new share', function() { + showDropDown(); + expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); + expect($('#dropdown #expirationDate').val()).toEqual(''); + }); + it('checks expiration date checkbox and populates field when expiration date was set', function() { + shareItem.expiration = 1234; + shareData.shares.push(shareItem); + showDropDown(); + expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); + expect($('#dropdown #expirationDate').val()).toEqual('1234'); + }); + it('sets default date when default date setting is enabled', function() { + /* jshint camelcase:false */ + oc_appconfig.core.defaultExpireDateEnabled = true; + showDropDown(); + $('#dropdown [name=linkCheckbox]').click(); + // enabled by default + 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 allowed + $('#dropdown [name=expirationCheckbox]').click(); + expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); + }); + it('enforces default date when enforced date setting is enabled', function() { + /* jshint camelcase:false */ + oc_appconfig.core.defaultExpireDateEnabled = true; + oc_appconfig.core.defaultExpireDateEnforced = true; + showDropDown(); + $('#dropdown [name=linkCheckbox]').click(); + 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('sets picker minDate to today and no maxDate by default', function() { + showDropDown(); + $('#dropdown [name=linkCheckbox]').click(); + $('#dropdown [name=expirationCheckbox]').click(); + expect($.datepicker._defaults.minDate).toEqual(new Date()); + expect($.datepicker._defaults.maxDate).toEqual(null); + }); + it('limits the date range to X days after share time when enforced', function() { + /* jshint camelcase:false */ + oc_appconfig.core.defaultExpireDateEnabled = true; + oc_appconfig.core.defaultExpireDateEnforced = true; + showDropDown(); + $('#dropdown [name=linkCheckbox]').click(); + expect($.datepicker._defaults.minDate).toEqual(new Date()); + expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); + }); + it('limits the date range to X days after share time when enforced, even when redisplayed the next days', function() { + // item exists, was created two days ago + shareItem.expiration = '2014-1-27'; + // share time has time component but must be stripped later + shareItem.stime = new Date(2014, 0, 20, 11, 0, 25).getTime() / 1000; + shareData.shares.push(shareItem); + /* jshint camelcase:false */ + oc_appconfig.core.defaultExpireDateEnabled = true; + oc_appconfig.core.defaultExpireDateEnforced = true; + showDropDown(); + expect($.datepicker._defaults.minDate).toEqual(new Date()); + expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); + }); + }); }); describe('"sharesChanged" event', function() { var autocompleteOptions; diff --git a/core/lostpassword/controller/lostcontroller.php b/core/lostpassword/controller/lostcontroller.php index b1be65b4f01..e4d51fde077 100644 --- a/core/lostpassword/controller/lostcontroller.php +++ b/core/lostpassword/controller/lostcontroller.php @@ -20,13 +20,36 @@ use \OC\Core\LostPassword\EncryptedDataException; class LostController extends Controller { + /** + * @var \OCP\IURLGenerator + */ protected $urlGenerator; + + /** + * @var \OCP\IUserManager + */ protected $userManager; + + /** + * @var \OC_Defaults + */ protected $defaults; + + /** + * @var IL10N + */ protected $l10n; protected $from; protected $isDataEncrypted; + + /** + * @var IConfig + */ protected $config; + + /** + * @var IUserSession + */ protected $userSession; public function __construct($appName, @@ -110,7 +133,7 @@ class LostController extends Controller { throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid')); } - if (!$user->setPassword($userId, $password)) { + if (!$user->setPassword($password)) { throw new \Exception(); } @@ -48,6 +48,11 @@ try { require_once 'lib/base.php'; + if (\OCP\Util::needUpgrade()) { + \OCP\Util::writeLog('cron', 'Update required, skipping cron', \OCP\Util::DEBUG); + exit(); + } + // load all apps to get all api routes properly setup OC_App::loadApps(); diff --git a/lib/base.php b/lib/base.php index 730cee5231d..341859dc5f3 100644 --- a/lib/base.php +++ b/lib/base.php @@ -564,6 +564,7 @@ class OC { self::registerPreviewHooks(); self::registerShareHooks(); self::registerLogRotate(); + self::registerLocalAddressBook(); //make sure temporary files are cleaned up register_shutdown_function(array('OC_Helper', 'cleanTmp')); @@ -575,6 +576,14 @@ class OC { } } + private static function registerLocalAddressBook() { + self::$server->getContactsManager()->register(function() { + $userManager = \OC::$server->getUserManager(); + \OC::$server->getContactsManager()->registerAddressBook( + new \OC\Contacts\LocalAddressBook($userManager)); + }); + } + /** * register hooks for the cache */ diff --git a/lib/private/backgroundjob/joblist.php b/lib/private/backgroundjob/joblist.php index 211d7e9abfc..9d15cd1663a 100644 --- a/lib/private/backgroundjob/joblist.php +++ b/lib/private/backgroundjob/joblist.php @@ -96,7 +96,10 @@ class JobList implements IJobList { $query->execute(); $jobs = array(); while ($row = $query->fetch()) { - $jobs[] = $this->buildJob($row); + $job = $this->buildJob($row); + if ($job) { + $jobs[] = $job; + } } return $jobs; } diff --git a/lib/private/contacts/localaddressbook.php b/lib/private/contacts/localaddressbook.php new file mode 100644 index 00000000000..483bbee83f8 --- /dev/null +++ b/lib/private/contacts/localaddressbook.php @@ -0,0 +1,104 @@ +<?php + /** + * ownCloud + * + * @author Thomas Müller + * @copyright 2014 Thomas Müller >deepdiver@owncloud.com> + * + */ + +namespace OC\Contacts; + +class LocalAddressBook implements \OCP\IAddressBook { + + /** + * @var \OCP\IUserManager + */ + private $userManager; + + /** + * @param $userManager + */ + public function __construct($userManager) { + $this->userManager = $userManager; + } + + /** + * @return string defining the technical unique key + */ + public function getKey() { + return 'local'; + } + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * + * @return mixed + */ + public function getDisplayName() { + return "Local users"; + } + + /** + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - for future use. One should always have options! + * @return array an array of contacts which are arrays of key-value-pairs + */ + public function search($pattern, $searchProperties, $options) { + $users = array(); + if($pattern == '') { + // Fetch all contacts + $users = $this->userManager->search(''); + } else { + foreach($searchProperties as $property) { + $result = array(); + if($property === 'FN') { + $result = $this->userManager->searchDisplayName($pattern); + } else if ($property === 'id') { + $result = $this->userManager->search($pattern); + } + if (is_array($result)) { + $users = array_merge($users, $result); + } + } + } + + $contacts = array(); + foreach($users as $user){ + $contact = array( + "id" => $user->getUID(), + "FN" => $user->getDisplayname(), + "EMAIL" => array(), + "IMPP" => array( + "x-owncloud-handle:" . $user->getUID() + ) + ); + $contacts[] = $contact; + } + return $contacts; + } + + /** + * @param array $properties this array if key-value-pairs defines a contact + * @return array an array representing the contact just created or updated + */ + public function createOrUpdate($properties) { + return array(); + } + + /** + * @return int + */ + public function getPermissions() { + return \OCP\PERMISSION_READ; + } + + /** + * @param object $id the unique identifier to a contact + * @return bool successful or not + */ + public function delete($id) { + return false; + } +} diff --git a/lib/private/db/mdb2schemamanager.php b/lib/private/db/mdb2schemamanager.php index d3e379c9417..91e590a901a 100644 --- a/lib/private/db/mdb2schemamanager.php +++ b/lib/private/db/mdb2schemamanager.php @@ -59,7 +59,8 @@ class MDB2SchemaManager { public function getMigrator() { $platform = $this->conn->getDatabasePlatform(); if ($platform instanceof SqlitePlatform) { - return new SQLiteMigrator($this->conn); + $config = \OC::$server->getConfig(); + return new SQLiteMigrator($this->conn, $config); } else if ($platform instanceof OraclePlatform) { return new OracleMigrator($this->conn); } else if ($platform instanceof MySqlPlatform) { diff --git a/lib/private/db/migrator.php b/lib/private/db/migrator.php index 6443cf4ed48..d05f8455551 100644 --- a/lib/private/db/migrator.php +++ b/lib/private/db/migrator.php @@ -110,7 +110,9 @@ class Migrator { $this->dropTable($tmpName); } catch (DBALException $e) { // pgsql needs to commit it's failed transaction before doing anything else - $this->connection->commit(); + if ($this->connection->isTransactionActive()) { + $this->connection->commit(); + } $this->dropTable($tmpName); throw new MigrationException($table->getName(), $e->getMessage()); } diff --git a/lib/private/db/mysqlmigrator.php b/lib/private/db/mysqlmigrator.php index 97495f52032..c0adcdf5df3 100644 --- a/lib/private/db/mysqlmigrator.php +++ b/lib/private/db/mysqlmigrator.php @@ -17,6 +17,10 @@ class MySQLMigrator extends Migrator { * @return \Doctrine\DBAL\Schema\SchemaDiff */ protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $platform = $connection->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('enum', 'string'); + $platform->registerDoctrineTypeMapping('bit', 'string'); + $schemaDiff = parent::getDiff($targetSchema, $connection); // identifiers need to be quoted for mysql diff --git a/lib/private/db/sqlitemigrator.php b/lib/private/db/sqlitemigrator.php index f5f78986771..94b421c5562 100644 --- a/lib/private/db/sqlitemigrator.php +++ b/lib/private/db/sqlitemigrator.php @@ -9,8 +9,24 @@ namespace OC\DB; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Schema; class SQLiteMigrator extends Migrator { + + /** + * @var \OCP\IConfig + */ + private $config; + + /** + * @param \Doctrine\DBAL\Connection $connection + * @param \OCP\IConfig $config + */ + public function __construct(\Doctrine\DBAL\Connection $connection, \OCP\IConfig $config) { + parent::__construct($connection); + $this->config = $config; + } + /** * @param \Doctrine\DBAL\Schema\Schema $targetSchema * @throws \OC\DB\MigrationException @@ -19,7 +35,7 @@ class SQLiteMigrator extends Migrator { */ public function checkMigrate(\Doctrine\DBAL\Schema\Schema $targetSchema) { $dbFile = $this->connection->getDatabase(); - $tmpFile = \OC_Helper::tmpFile('.db'); + $tmpFile = $this->buildTempDatabase(); copy($dbFile, $tmpFile); $connectionParams = array( @@ -37,4 +53,25 @@ class SQLiteMigrator extends Migrator { throw new MigrationException('', $e->getMessage()); } } + + /** + * @return string + */ + private function buildTempDatabase() { + $dataDir = $this->config->getSystemValue("datadirectory", \OC::$SERVERROOT . '/data'); + $tmpFile = uniqid("oc_"); + return "$dataDir/$tmpFile.db"; + } + + /** + * @param Schema $targetSchema + * @param \Doctrine\DBAL\Connection $connection + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) { + $platform = $connection->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('tinyint unsigned', 'integer'); + + return parent::getDiff($targetSchema, $connection); + } } diff --git a/lib/private/image.php b/lib/private/image.php index 5331c399159..0dff8c5a9da 100644 --- a/lib/private/image.php +++ b/lib/private/image.php @@ -870,6 +870,14 @@ class OC_Image { imagedestroy($process); return false; } + + // preserve transparency + if($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) { + imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127)); + imagealphablending($process, false); + imagesavealpha($process, true); + } + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); if ($process == false) { OC_Log::write('core', __METHOD__.'(): Error resampling process image '.$w.'x'.$h, OC_Log::ERROR); diff --git a/lib/private/preview.php b/lib/private/preview.php index 8089379bde5..6172519c7d1 100755 --- a/lib/private/preview.php +++ b/lib/private/preview.php @@ -561,9 +561,15 @@ class Preview { $realX = (int)$image->width(); $realY = (int)$image->height(); - // compute $maxY using the aspect of the generated preview + // compute $maxY and $maxX using the aspect of the generated preview if ($this->keepAspect) { - $y = $x / ($realX / $realY); + $ratio = $realX / $realY; + if($x / $ratio < $y) { + // width restricted + $y = $x / $ratio; + } else { + $x = $y * $ratio; + } } if ($x === $realX && $y === $realY) { diff --git a/lib/private/preview/mp3.php b/lib/private/preview/mp3.php index 21f160fd50f..bb4d3dfce86 100644 --- a/lib/private/preview/mp3.php +++ b/lib/private/preview/mp3.php @@ -14,8 +14,6 @@ class MP3 extends Provider { } public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) { - require_once('getid3/getid3.php'); - $getID3 = new \getID3(); $tmpPath = $fileview->toTmpFile($path); diff --git a/lib/private/server.php b/lib/private/server.php index da705863078..3299792e20d 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -255,7 +255,11 @@ class Server extends SimpleContainer implements IServerContainer { * @return \OCP\Files\Folder */ function getUserFolder() { - $dir = '/' . \OCP\User::getUser(); + $user = $this->getUserSession()->getUser(); + if (!$user) { + return null; + } + $dir = '/' . $user->getUID(); $root = $this->getRootFolder(); $folder = null; diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 673c0dc383a..7fd5cd70e1d 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -595,6 +595,7 @@ class Share extends \OC\Share\Constants { $shareWith['group'] = $group; $shareWith['users'] = array_diff(\OC_Group::usersInGroup($group), array($uidOwner)); } else if ($shareType === self::SHARE_TYPE_LINK) { + $updateExistingShare = false; if (\OC_Appconfig::getValue('core', 'shareapi_allow_links', 'yes') == 'yes') { // when updating a link share @@ -629,7 +630,7 @@ class Share extends \OC\Share\Constants { throw new \Exception($message_t); } - if (!empty($updateExistingShare) && + if ($updateExistingShare === false && self::isDefaultExpireDateEnabled() && empty($expirationDate)) { $expirationDate = Helper::calcExpireDate(); @@ -925,19 +926,69 @@ class Share extends \OC\Share\Constants { } /** + * validate expire date if it meets all constraints + * + * @param string $expireDate well formate date string, e.g. "DD-MM-YYYY" + * @param string $shareTime timestamp when the file was shared + * @param string $itemType + * @param string $itemSource + * @return DateTime validated date + * @throws \Exception + */ + private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) { + $l = \OC_L10N::get('lib'); + $date = new \DateTime($expireDate); + $today = new \DateTime('now'); + + // if the user doesn't provide a share time we need to get it from the database + // fall-back mode to keep API stable, because the $shareTime parameter was added later + $defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced(); + if ($defaultExpireDateEnforced && $shareTime === null) { + $items = self::getItemShared($itemType, $itemSource); + $firstItem = reset($items); + $shareTime = (int)$firstItem['stime']; + } + + if ($defaultExpireDateEnforced) { + // initialize max date with share time + $maxDate = new \DateTime(); + $maxDate->setTimestamp($shareTime); + $maxDays = \OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7'); + $maxDate->add(new \DateInterval('P' . $maxDays . 'D')); + if ($date > $maxDate) { + $warning = 'Can not set expire date. Shares can not expire later then ' . $maxDays . ' after they where shared'; + $warning_t = $l->t('Can not set expire date. Shares can not expire later then %s after they where shared', array($maxDays)); + \OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN); + throw new \Exception($warning_t); + } + } + + if ($date < $today) { + $message = 'Can not set expire date. Expire date is in the past'; + $message_t = $l->t('Can not set expire date. Expire date is in the past'); + \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN); + throw new \Exception($message_t); + } + + return $date; + } + + /** * Set expiration date for a share * @param string $itemType * @param string $itemSource * @param string $date expiration date + * @param int $shareTime timestamp from when the file was shared + * @throws \Exception * @return boolean */ - public static function setExpirationDate($itemType, $itemSource, $date) { + public static function setExpirationDate($itemType, $itemSource, $date, $shareTime = null) { $user = \OC_User::getUser(); if ($date == '') { $date = null; } else { - $date = new \DateTime($date); + $date = self::validateExpireDate($date, $shareTime, $itemType, $itemSource); } $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ? AND `uid_owner` = ? AND `share_type` = ?'); $query->bindValue(1, $date, 'datetime'); @@ -954,11 +1005,10 @@ class Share extends \OC\Share\Constants { 'date' => $date, 'uidOwner' => $user )); - + return true; + } - } - /** * Checks whether a share has expired, calls unshareItem() if yes. * @param array $item Share data (usually database row) @@ -1348,7 +1398,7 @@ class Share extends \OC\Share\Constants { } } // Check if resharing is allowed, if not remove share permission - if (isset($row['permissions']) && !self::isResharingAllowed()) { + if (isset($row['permissions']) && (!self::isResharingAllowed() | \OC_Util::isSharingDisabledForUser())) { $row['permissions'] &= ~\OCP\PERMISSION_SHARE; } // Add display names to result diff --git a/lib/private/updater.php b/lib/private/updater.php index d50c2554c75..7acd6446ec4 100644 --- a/lib/private/updater.php +++ b/lib/private/updater.php @@ -212,8 +212,6 @@ class Updater extends BasicEmitter { \OC_DB::updateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml'); $this->emit('\OC\Updater', 'dbUpgrade'); - // TODO: why not do this at the end ? - \OC_Config::setValue('version', implode('.', \OC_Util::getVersion())); $disabledApps = \OC_App::checkAppsRequirements(); if (!empty($disabledApps)) { $this->emit('\OC\Updater', 'disabledApps', array($disabledApps)); @@ -227,6 +225,9 @@ class Updater extends BasicEmitter { //Invalidate update feed \OC_Appconfig::setValue('core', 'lastupdatedat', 0); + + // only set the final version if everything went well + \OC_Config::setValue('version', implode('.', \OC_Util::getVersion())); } } } diff --git a/lib/private/user/user.php b/lib/private/user/user.php index f9c2cb4d130..993fb4c0c64 100644 --- a/lib/private/user/user.php +++ b/lib/private/user/user.php @@ -156,7 +156,7 @@ class User implements IUser { * @param string $recoveryPassword for the encryption app to reset encryption keys * @return bool */ - public function setPassword($password, $recoveryPassword) { + public function setPassword($password, $recoveryPassword = null) { if ($this->emitter) { $this->emitter->emit('\OC\User', 'preSetPassword', array($this, $password, $recoveryPassword)); } diff --git a/lib/private/util.php b/lib/private/util.php index eea194288f9..896b076afa6 100755 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -22,7 +22,7 @@ class OC_Util { self::$rootMounted = true; } } - + /** * mounting an object storage as the root fs will in essence remove the * necessity of a data folder being present. @@ -50,7 +50,7 @@ class OC_Util { self::$rootMounted = true; } } - + /** * Can be set up * @param string $user @@ -171,6 +171,21 @@ class OC_Util { } /** + * check if share API enforces a default expire date + * @return boolean + */ + public static function isDefaultExpireDateEnforced() { + $isDefaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no'); + $enforceDefaultExpireDate = false; + if ($isDefaultExpireDateEnabled === 'yes') { + $value = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no'); + $enforceDefaultExpireDate = ($value === 'yes') ? true : false; + } + + return $enforceDefaultExpireDate; + } + + /** * Get the quota of a user * @param string $user * @return int Quota bytes @@ -1217,11 +1232,16 @@ class OC_Util { /** * @Brief Get file content via curl. * @param string $url Url to get content + * @throws Exception If the URL does not start with http:// or https:// * @return string of the response or false on error * This function get the content of a page via curl, if curl is enabled. * If not, file_get_contents is used. */ public static function getUrlContent($url) { + if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { + throw new Exception('$url must start with https:// or http://', 1); + } + if (function_exists('curl_init')) { $curl = curl_init(); $max_redirects = 10; diff --git a/lib/public/appframework/http/templateresponse.php b/lib/public/appframework/http/templateresponse.php index 02589f4e2a4..c74d3b60254 100644 --- a/lib/public/appframework/http/templateresponse.php +++ b/lib/public/appframework/http/templateresponse.php @@ -134,8 +134,10 @@ class TemplateResponse extends Response { * @return string the rendered html */ public function render(){ + // \OCP\Template needs an empty string instead of 'blank' for an unwrapped response + $renderAs = $this->renderAs === 'blank' ? '' : $this->renderAs; - $template = new \OCP\Template($this->appName, $this->templateName, $this->renderAs); + $template = new \OCP\Template($this->appName, $this->templateName, $renderAs); foreach($this->params as $key => $value){ $template->assign($key, $value); diff --git a/lib/public/iuser.php b/lib/public/iuser.php index dc4acc7658f..c15edcd14dd 100644 --- a/lib/public/iuser.php +++ b/lib/public/iuser.php @@ -18,14 +18,14 @@ interface IUser { public function getUID(); /** - * get the displayname for the user, if no specific displayname is set it will fallback to the user id + * get the display name for the user, if no specific display name is set it will fallback to the user id * * @return string */ public function getDisplayName(); /** - * set the displayname for the user + * set the display name for the user * * @param string $displayName * @return bool @@ -59,7 +59,7 @@ interface IUser { * @param string $recoveryPassword for the encryption app to reset encryption keys * @return bool */ - public function setPassword($password, $recoveryPassword); + public function setPassword($password, $recoveryPassword = null); /** * get the users home folder to mount diff --git a/lib/public/share.php b/lib/public/share.php index 8566a38c61e..c0939dce53f 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -298,10 +298,11 @@ class Share extends \OC\Share\Constants { * @param string $itemType * @param string $itemSource * @param string $date expiration date + * @param int $shareTime timestamp from when the file was shared * @return boolean */ - public static function setExpirationDate($itemType, $itemSource, $date) { - return \OC\Share\Share::setExpirationDate($itemType, $itemSource, $date); + public static function setExpirationDate($itemType, $itemSource, $date, $shareTime = null) { + return \OC\Share\Share::setExpirationDate($itemType, $itemSource, $date, $shareTime); } /** diff --git a/lib/public/util.php b/lib/public/util.php index 8f4691eeade..2a6e977f085 100644 --- a/lib/public/util.php +++ b/lib/public/util.php @@ -518,6 +518,15 @@ class Util { } /** + * check if share API enforces a default expire date + * @return boolean + */ + public static function isDefaultExpireDateEnforced() { + return \OC_Util::isDefaultExpireDateEnforced(); + } + + + /** * Checks whether the current version needs upgrade. * * @return bool true if upgrade is needed, false otherwise diff --git a/settings/admin.php b/settings/admin.php index 704f4519ff6..dd5f969fa1a 100755 --- a/settings/admin.php +++ b/settings/admin.php @@ -101,9 +101,11 @@ $tmpl->printPage(); * @return null|string */ function findBinaryPath($program) { - exec('command -v ' . escapeshellarg($program) . ' 2> /dev/null', $output, $returnCode); - if ($returnCode === 0 && count($output) > 0) { - return escapeshellcmd($output[0]); + if (OC_Helper::is_function_enabled('exec')) { + exec('command -v ' . escapeshellarg($program) . ' 2> /dev/null', $output, $returnCode); + if ($returnCode === 0 && count($output) > 0) { + return escapeshellcmd($output[0]); + } } return null; } diff --git a/settings/js/apps.js b/settings/js/apps.js index 3f9a9eab17a..9061b43c7be 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -398,7 +398,7 @@ $(document).ready(function(){ if(item) { item.trigger('click'); item.addClass('active'); - $('#app-navigation').animate({scrollTop: $(item).offset().top-70}, 'slow','swing'); + $('#app-navigation').animate({scrollTop: item.offset().top-70}, 'slow','swing'); } } diff --git a/tests/lib/contacts/localadressbook.php b/tests/lib/contacts/localadressbook.php new file mode 100644 index 00000000000..bb69910820f --- /dev/null +++ b/tests/lib/contacts/localadressbook.php @@ -0,0 +1,95 @@ +<?php +use OC\Contacts\LocalAddressBook; + +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2014 Thomas Müller thomas.mueller@tmit.eu + * + * 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/>. + */ + +class Test_LocalAddressBook extends PHPUnit_Framework_TestCase +{ + + public function testSearchFN() { + $stub = $this->getMockForAbstractClass('\OCP\IUserManager', array('searchDisplayName')); + + $stub->expects($this->any())->method('searchDisplayName')->will($this->returnValue(array( + new SimpleUserForTesting('tom', 'Thomas'), + new SimpleUserForTesting('tomtom', 'Thomas T.'), + ))); + + $localAddressBook = new LocalAddressBook($stub); + + $result = $localAddressBook->search('tom', array('FN'), array()); + $this->assertEquals(2, count($result)); + } + + public function testSearchId() { + $stub = $this->getMockForAbstractClass('\OCP\IUserManager', array('searchDisplayName')); + + $stub->expects($this->any())->method('search')->will($this->returnValue(array( + new SimpleUserForTesting('tom', 'Thomas'), + new SimpleUserForTesting('tomtom', 'Thomas T.'), + ))); + + $localAddressBook = new LocalAddressBook($stub); + + $result = $localAddressBook->search('tom', array('id'), array()); + $this->assertEquals(2, count($result)); + } +} + + +class SimpleUserForTesting implements \OCP\IUser { + + public function __construct($uid, $displayName) { + + $this->uid = $uid; + $this->displayName = $displayName; + } + + public function getUID() { + return $this->uid; + } + + public function getDisplayName() { + return $this->displayName; + } + + public function setDisplayName($displayName) { + } + + public function getLastLogin() { + } + + public function updateLastLoginTimestamp() { + } + + public function delete() { + } + + public function setPassword($password, $recoveryPassword = null) { + } + + public function getHome() { + } + + public function canChangeAvatar() { + } + + public function canChangePassword() { + } + + public function canChangeDisplayName() { + } + + public function isEnabled() { + } + + public function setEnabled($enabled) { + } +} diff --git a/tests/lib/db/mysqlmigration.php b/tests/lib/db/mysqlmigration.php new file mode 100644 index 00000000000..584df1d4465 --- /dev/null +++ b/tests/lib/db/mysqlmigration.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright (c) 2014 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class TestMySqlMigration extends \PHPUnit_Framework_TestCase { + + /** @var \Doctrine\DBAL\Connection */ + private $connection; + + /** @var string */ + private $tableName; + + public function setUp() { + $this->connection = \OC_DB::getConnection(); + if (!$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform) { + $this->markTestSkipped("Test only relevant on MySql"); + } + + $dbPrefix = \OC::$server->getConfig()->getSystemValue("dbtableprefix"); + $this->tableName = uniqid($dbPrefix . "_enum_bit_test"); + $this->connection->exec("CREATE TABLE $this->tableName(b BIT, e ENUM('1','2','3','4'))"); + } + + public function tearDown() { + $this->connection->getSchemaManager()->dropTable($this->tableName); + } + + public function testNonOCTables() { + $manager = new \OC\DB\MDB2SchemaManager($this->connection); + $manager->updateDbFromStructure(__DIR__ . '/testschema.xml'); + + $this->assertTrue(true); + } + +} diff --git a/tests/lib/db/sqlitemigration.php b/tests/lib/db/sqlitemigration.php new file mode 100644 index 00000000000..adfc03a2ca7 --- /dev/null +++ b/tests/lib/db/sqlitemigration.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright (c) 2014 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class TestSqliteMigration extends \PHPUnit_Framework_TestCase { + + /** @var \Doctrine\DBAL\Connection */ + private $connection; + + /** @var string */ + private $tableName; + + public function setUp() { + $this->connection = \OC_DB::getConnection(); + if (!$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) { + $this->markTestSkipped("Test only relevant on Sqlite"); + } + + $dbPrefix = \OC::$server->getConfig()->getSystemValue("dbtableprefix"); + $this->tableName = uniqid($dbPrefix . "_enum_bit_test"); + $this->connection->exec("CREATE TABLE $this->tableName(t0 tinyint unsigned, t1 tinyint)"); + } + + public function tearDown() { + $this->connection->getSchemaManager()->dropTable($this->tableName); + } + + public function testNonOCTables() { + $manager = new \OC\DB\MDB2SchemaManager($this->connection); + $manager->updateDbFromStructure(__DIR__ . '/testschema.xml'); + + $this->assertTrue(true); + } + +} diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php index 5920b44a8e0..bb827eece73 100644 --- a/tests/lib/share/share.php +++ b/tests/lib/share/share.php @@ -326,10 +326,14 @@ class Test_Share extends PHPUnit_Framework_TestCase { $this->shareUserOneTestFileWithUserTwo(); $this->shareUserTestFileAsLink(); - $this->assertTrue( - OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInPast), - 'Failed asserting that user 1 successfully set an expiration date for the test.txt share.' - ); + // manipulate share table and set expire date to the past + $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ? AND `uid_owner` = ? AND `share_type` = ?'); + $query->bindValue(1, new \DateTime($this->dateInPast), 'datetime'); + $query->bindValue(2, 'test'); + $query->bindValue(3, 'test.txt'); + $query->bindValue(4, $this->user1); + $query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK); + $query->execute(); $shares = OCP\Share::getItemsShared('test'); $this->assertSame(1, count($shares)); @@ -337,6 +341,24 @@ class Test_Share extends PHPUnit_Framework_TestCase { $this->assertSame(\OCP\Share::SHARE_TYPE_USER, $share['share_type']); } + public function testSetExpireDateInPast() { + OC_User::setUserId($this->user1); + $this->shareUserOneTestFileWithUserTwo(); + $this->shareUserTestFileAsLink(); + + $setExpireDateFailed = false; + try { + $this->assertTrue( + OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInPast, ''), + 'Failed asserting that user 1 successfully set an expiration date for the test.txt share.' + ); + } catch (\Exception $e) { + $setExpireDateFailed = true; + } + + $this->assertTrue($setExpireDateFailed); + } + public function testShareWithUserExpirationValid() { OC_User::setUserId($this->user1); $this->shareUserOneTestFileWithUserTwo(); @@ -344,7 +366,7 @@ class Test_Share extends PHPUnit_Framework_TestCase { $this->assertTrue( - OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInFuture), + OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInFuture, ''), 'Failed asserting that user 1 successfully set an expiration date for the test.txt share.' ); @@ -353,6 +375,39 @@ class Test_Share extends PHPUnit_Framework_TestCase { } + /* + * if user is in a group excluded from resharing, then the share permission should + * be removed + */ + public function testShareWithUserAndUserIsExcludedFromResharing() { + + OC_User::setUserId($this->user1); + $this->assertTrue( + OCP\Share::shareItem('test', 'test.txt', OCP\Share::SHARE_TYPE_USER, $this->user4, OCP\PERMISSION_ALL), + 'Failed asserting that user 1 successfully shared text.txt with user 4.' + ); + $this->assertContains( + 'test.txt', + OCP\Share::getItemShared('test', 'test.txt', Test_Share_Backend::FORMAT_SOURCE), + 'Failed asserting that test.txt is a shared file of user 1.' + ); + + // exclude group2 from sharing + \OC_Appconfig::setValue('core', 'shareapi_exclude_groups_list', $this->group2); + \OC_Appconfig::setValue('core', 'shareapi_exclude_groups', "yes"); + + OC_User::setUserId($this->user4); + + $share = OCP\Share::getItemSharedWith('test', 'test.txt'); + + $this->assertSame(\OCP\PERMISSION_ALL & ~OCP\PERMISSION_SHARE, $share['permissions'], + 'Failed asserting that user 4 is excluded from re-sharing'); + + \OC_Appconfig::deleteKey('core', 'shareapi_exclude_groups_list'); + \OC_Appconfig::deleteKey('core', 'shareapi_exclude_groups'); + + } + protected function shareUserOneTestFileWithGroupOne() { OC_User::setUserId($this->user1); $this->assertTrue( @@ -552,7 +607,7 @@ class Test_Share extends PHPUnit_Framework_TestCase { // testGetShareByTokenExpirationValid $this->assertTrue( - OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInFuture), + OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInFuture, ''), 'Failed asserting that user 1 successfully set a future expiration date for the test.txt share.' ); $row = $this->getShareByValidToken($token); @@ -561,17 +616,47 @@ class Test_Share extends PHPUnit_Framework_TestCase { 'Failed asserting that the returned row has an expiration date.' ); - // testGetShareByTokenExpirationExpired - $this->assertTrue( - OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInPast), - 'Failed asserting that user 1 successfully set a past expiration date for the test.txt share.' - ); + // manipulate share table and set expire date to the past + $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ? AND `uid_owner` = ? AND `share_type` = ?'); + $query->bindValue(1, new \DateTime($this->dateInPast), 'datetime'); + $query->bindValue(2, 'test'); + $query->bindValue(3, 'test.txt'); + $query->bindValue(4, $this->user1); + $query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK); + $query->execute(); + $this->assertFalse( OCP\Share::getShareByToken($token), 'Failed asserting that an expired share could not be found.' ); } + public function testShareItemWithLinkAndDefaultExpireDate() { + OC_User::setUserId($this->user1); + + \OC_Appconfig::setValue('core', 'shareapi_default_expire_date', 'yes'); + \OC_Appconfig::setValue('core', 'shareapi_expire_after_n_days', '2'); + + $token = OCP\Share::shareItem('test', 'test.txt', OCP\Share::SHARE_TYPE_LINK, null, OCP\PERMISSION_READ); + $this->assertInternalType( + 'string', + $token, + 'Failed asserting that user 1 successfully shared text.txt as link with token.' + ); + + // share should have default expire date + + $row = $this->getShareByValidToken($token); + $this->assertNotEmpty( + $row['expiration'], + 'Failed asserting that the returned row has an default expiration date.' + ); + + \OC_Appconfig::deleteKey('core', 'shareapi_default_expire_date'); + \OC_Appconfig::deleteKey('core', 'shareapi_expire_after_n_days'); + + } + public function testUnshareAll() { $this->shareUserTestFileWithUser($this->user1, $this->user2); $this->shareUserTestFileWithUser($this->user2, $this->user3); |