aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml16
-rw-r--r--apps/files/js/filelist.js22
-rw-r--r--apps/files/js/mainfileinfodetailview.js4
-rw-r--r--apps/files/tests/js/filelistSpec.js47
-rw-r--r--apps/files/tests/js/mainfileinfodetailviewSpec.js14
-rw-r--r--apps/files_external/controller/storagescontroller.php16
-rw-r--r--apps/files_external/js/settings.js39
-rw-r--r--apps/files_external/lib/config.php1
-rw-r--r--apps/files_external/lib/storageconfig.php25
-rw-r--r--apps/files_external/lib/swift.php5
-rw-r--r--apps/files_sharing/js/share.js7
-rw-r--r--apps/provisioning_api/appinfo/app.php20
-rw-r--r--core/ajax/share.php8
-rw-r--r--core/command/maintenance/repair.php17
-rw-r--r--core/js/mimetype.js2
-rw-r--r--core/js/share.js17
-rw-r--r--core/js/tests/specs/shareSpec.js7
-rw-r--r--core/js/tests/specs/sharedialogshareelistview.js158
-rw-r--r--core/templates/internalaltmail.php13
-rw-r--r--core/templates/internalmail.php39
-rw-r--r--lib/private/files/cache/cache.php18
-rw-r--r--lib/private/files/cache/updater.php14
-rw-r--r--lib/private/files/storage/wrapper/availability.php3
-rw-r--r--lib/private/repair.php13
-rw-r--r--lib/private/share/mailnotifications.php10
-rw-r--r--lib/repair/oldgroupmembershipshares.php117
-rw-r--r--lib/repair/repairinvalidshares.php32
-rw-r--r--settings/templates/users/main.php10
-rw-r--r--tests/lib/files/cache/updater.php41
-rw-r--r--tests/lib/files/view.php12
-rw-r--r--tests/lib/repair/oldgroupmembershipsharestest.php138
-rw-r--r--tests/lib/repair/repairinvalidsharestest.php86
-rwxr-xr-xtests/travis/changed_app.sh18
-rwxr-xr-xtests/travis/test_for_app.sh17
35 files changed, 910 insertions, 97 deletions
diff --git a/.gitignore b/.gitignore
index a6baffc6a59..531e372e607 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,3 +115,4 @@ Vagrantfile
/tests/data/testimage-copy.png
/config/config-autotest-backup.php
/config/autoconfig.php
+clover.xml
diff --git a/.travis.yml b/.travis.yml
index 74dbfe00f01..95e8a2f7de4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ php:
env:
global:
- - APP=dav
+ - TEST_DAV=$(tests/travis/changed_app.sh dav)
- TC=litmus-v2
matrix:
- DB=sqlite
@@ -21,20 +21,22 @@ addons:
- realpath
before_install:
- - bash tests/travis/test_for_app.sh $APP
- - bash tests/travis/before_install.sh $DB
+ - sh -c "if [ '$TEST_DAV' = '1' ]; then bash tests/travis/before_install.sh $DB; fi"
install:
- - bash tests/travis/install.sh $DB
+ - sh -c "if [ '$TEST_DAV' = '1' ]; then bash tests/travis/install.sh $DB; fi"
script:
- - bash apps/$APP/tests/travis/$TC.sh
+ - sh -c "if [ '$TEST_DAV' != '1' ]; then echo \"Not testing DAV\"; fi"
+ - sh -c "if [ '$TEST_DAV' = '1' ]; then echo \"Testing DAV\"; fi"
+
+ - sh -c "if [ '$TEST_DAV' = '1' ]; then bash apps/dav/tests/travis/$TC.sh; fi"
matrix:
include:
- php: 5.4
- env: DB=pgsql;TC=litmus-v1;APP=dav
+ env: DB=pgsql;TC=litmus-v1
# - php: 5.4
-# env: DB=mysql;TC=caldavtester;APP=dav
+# env: DB=mysql;TC=caldavtester
fast_finish: true
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index c84d6c3c47d..99804b1b9be 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -767,7 +767,7 @@
*/
elementToFile: function($el){
$el = $($el);
- return {
+ var data = {
id: parseInt($el.attr('data-id'), 10),
name: $el.attr('data-file'),
mimetype: $el.attr('data-mime'),
@@ -777,6 +777,15 @@
etag: $el.attr('data-etag'),
permissions: parseInt($el.attr('data-permissions'), 10)
};
+ var icon = $el.attr('data-icon');
+ if (icon) {
+ data.icon = icon;
+ }
+ var mountType = $el.attr('data-mounttype');
+ if (mountType) {
+ data.mountType = mountType;
+ }
+ return data;
},
/**
@@ -899,11 +908,12 @@
mtime = parseInt(fileData.mtime, 10),
mime = fileData.mimetype,
path = fileData.path,
+ dataIcon = null,
linkUrl;
options = options || {};
if (isNaN(mtime)) {
- mtime = new Date().getTime()
+ mtime = new Date().getTime();
}
if (type === 'dir') {
@@ -911,6 +921,7 @@
if (fileData.mountType && fileData.mountType.indexOf('external') === 0) {
icon = OC.MimeType.getIconUrl('dir-external');
+ dataIcon = icon;
}
}
@@ -926,6 +937,11 @@
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
});
+ if (dataIcon) {
+ // icon override
+ tr.attr('data-icon', dataIcon);
+ }
+
if (fileData.mountType) {
tr.attr('data-mounttype', fileData.mountType);
}
@@ -1177,7 +1193,7 @@
// display actions
this.fileActions.display(filenameTd, !options.silent, this);
- if (fileData.isPreviewAvailable) {
+ if (fileData.isPreviewAvailable && mime !== 'httpd/unix-directory') {
var iconDiv = filenameTd.find('.thumbnail');
// lazy load / newly inserted td ?
// the typeof check ensures that the default value of animate is true
diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js
index abf7da52ff4..69c796e492f 100644
--- a/apps/files/js/mainfileinfodetailview.js
+++ b/apps/files/js/mainfileinfodetailview.js
@@ -128,8 +128,8 @@
$iconDiv.addClass('icon-loading icon-32');
this.loadPreview(this.model.getFullPath(), this.model.get('mimetype'), this.model.get('etag'), $iconDiv, $container, this.model.isImage());
} else {
- // TODO: special icons / shared / external
- $iconDiv.css('background-image', 'url("' + OC.MimeType.getIconUrl('dir') + '")');
+ var iconUrl = this.model.get('icon') || OC.MimeType.getIconUrl('dir');
+ $iconDiv.css('background-image', 'url("' + iconUrl + '")');
OC.Util.scaleFixForIE8($iconDiv);
}
this.$el.find('[title]').tooltip({placement: 'bottom'});
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 994e1d32844..05e6fcc6122 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -1166,7 +1166,7 @@ describe('OCA.Files.FileList tests', function() {
it('renders provided icon for file when provided', function() {
var fileData = {
type: 'file',
- name: 'test dir',
+ name: 'test file',
icon: OC.webroot + '/core/img/filetypes/application-pdf.svg',
mimetype: 'application/pdf'
};
@@ -1178,7 +1178,7 @@ describe('OCA.Files.FileList tests', function() {
it('renders preview when no icon was provided and preview is available', function() {
var fileData = {
type: 'file',
- name: 'test dir',
+ name: 'test file',
isPreviewAvailable: true
};
var $tr = fileList.add(fileData);
@@ -1192,7 +1192,7 @@ describe('OCA.Files.FileList tests', function() {
it('renders default file type icon when no icon was provided and no preview is available', function() {
var fileData = {
type: 'file',
- name: 'test dir',
+ name: 'test file',
isPreviewAvailable: false
};
var $tr = fileList.add(fileData);
@@ -1200,6 +1200,47 @@ describe('OCA.Files.FileList tests', function() {
expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(previewLoadStub.notCalled).toEqual(true);
});
+ it('does not render preview for directories', function() {
+ var fileData = {
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ name: 'test dir',
+ isPreviewAvailable: true
+ };
+ var $tr = fileList.add(fileData);
+ var $td = $tr.find('td.filename');
+ expect(OC.TestUtil.getImageUrl($td.find('.thumbnail'))).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
+ expect(previewLoadStub.notCalled).toEqual(true);
+ });
+ it('render external storage icon for external storage root', function() {
+ var fileData = {
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ name: 'test dir',
+ isPreviewAvailable: true,
+ mountType: 'external-root'
+ };
+ var $tr = fileList.add(fileData);
+ var $td = $tr.find('td.filename');
+ expect(OC.TestUtil.getImageUrl($td.find('.thumbnail'))).toEqual(OC.webroot + '/core/img/filetypes/folder-external.svg');
+ expect(previewLoadStub.notCalled).toEqual(true);
+ });
+ it('render external storage icon for external storage subdir', function() {
+ var fileData = {
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ name: 'test dir',
+ isPreviewAvailable: true,
+ mountType: 'external'
+ };
+ var $tr = fileList.add(fileData);
+ var $td = $tr.find('td.filename');
+ expect(OC.TestUtil.getImageUrl($td.find('.thumbnail'))).toEqual(OC.webroot + '/core/img/filetypes/folder-external.svg');
+ expect(previewLoadStub.notCalled).toEqual(true);
+ // default icon override
+ expect($tr.attr('data-icon')).toEqual(OC.webroot + '/core/img/filetypes/folder-external.svg');
+ });
+
});
describe('viewer mode', function() {
it('enabling viewer mode hides files table and action buttons', function() {
diff --git a/apps/files/tests/js/mainfileinfodetailviewSpec.js b/apps/files/tests/js/mainfileinfodetailviewSpec.js
index f4403196f2e..460629806c8 100644
--- a/apps/files/tests/js/mainfileinfodetailviewSpec.js
+++ b/apps/files/tests/js/mainfileinfodetailviewSpec.js
@@ -112,6 +112,20 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() {
lazyLoadPreviewStub.restore();
});
+ it('uses icon from model if present in model', function() {
+ var lazyLoadPreviewStub = sinon.stub(fileList, 'lazyLoadPreview');
+ testFileInfo.set('mimetype', 'httpd/unix-directory');
+ testFileInfo.set('icon', OC.MimeType.getIconUrl('dir-external'));
+ view.setFileInfo(testFileInfo);
+
+ expect(lazyLoadPreviewStub.notCalled).toEqual(true);
+
+ expect(view.$el.find('.thumbnail').hasClass('icon-loading')).toEqual(false);
+ expect(view.$el.find('.thumbnail').css('background-image'))
+ .toContain('filetypes/folder-external.svg');
+
+ lazyLoadPreviewStub.restore();
+ });
it('displays thumbnail', function() {
var lazyLoadPreviewStub = sinon.stub(fileList, 'lazyLoadPreview');
diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php
index f754565f628..048f3588ed7 100644
--- a/apps/files_external/controller/storagescontroller.php
+++ b/apps/files_external/controller/storagescontroller.php
@@ -237,9 +237,21 @@ abstract class StoragesController extends Controller {
)
);
} catch (InsufficientDataForMeaningfulAnswerException $e) {
- $storage->setStatus(\OC_Mount_Config::STATUS_INDETERMINATE);
+ $storage->setStatus(
+ \OC_Mount_Config::STATUS_INDETERMINATE,
+ $this->l10n->t('Insufficient data: %s', [$e->getMessage()])
+ );
} catch (StorageNotAvailableException $e) {
- $storage->setStatus(\OC_Mount_Config::STATUS_ERROR);
+ $storage->setStatus(
+ \OC_Mount_Config::STATUS_ERROR,
+ $e->getMessage()
+ );
+ } catch (\Exception $e) {
+ // FIXME: convert storage exceptions to StorageNotAvailableException
+ $storage->setStatus(
+ \OC_Mount_Config::STATUS_ERROR,
+ get_class($e).': '.$e->getMessage()
+ );
}
}
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js
index 5da34c52193..a839f396b9b 100644
--- a/apps/files_external/js/settings.js
+++ b/apps/files_external/js/settings.js
@@ -643,6 +643,10 @@ MountConfigListView.prototype = _.extend({
});
addSelect2(this.$el.find('tr:not(#addMountPoint) .applicableUsers'), this._userListLimit);
+ this.$el.tooltip({
+ selector: '.status span',
+ container: 'body'
+ });
this._initEvents();
@@ -709,11 +713,12 @@ MountConfigListView.prototype = _.extend({
}
highlightInput($target);
var $tr = $target.closest('tr');
+ this.updateStatus($tr, null);
var timer = $tr.data('save-timer');
clearTimeout(timer);
timer = setTimeout(function() {
- self.saveStorageConfig($tr);
+ self.saveStorageConfig($tr, null, timer);
}, 2000);
$tr.data('save-timer', timer);
},
@@ -931,8 +936,9 @@ MountConfigListView.prototype = _.extend({
*
* @param $tr storage row
* @param Function callback callback to call after save
+ * @param concurrentTimer only update if the timer matches this
*/
- saveStorageConfig:function($tr, callback) {
+ saveStorageConfig:function($tr, callback, concurrentTimer) {
var self = this;
var storage = this.getStorageConfig($tr);
if (!storage.validate()) {
@@ -942,15 +948,23 @@ MountConfigListView.prototype = _.extend({
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
storage.save({
success: function(result) {
- self.updateStatus($tr, result.status);
- $tr.attr('data-id', result.id);
-
- if (_.isFunction(callback)) {
- callback(storage);
+ if (concurrentTimer === undefined
+ || $tr.data('save-timer') === concurrentTimer
+ ) {
+ self.updateStatus($tr, result.status, result.statusMessage);
+ $tr.attr('data-id', result.id);
+
+ if (_.isFunction(callback)) {
+ callback(storage);
+ }
}
},
error: function() {
- self.updateStatus($tr, StorageConfig.Status.ERROR);
+ if (concurrentTimer === undefined
+ || $tr.data('save-timer') === concurrentTimer
+ ) {
+ self.updateStatus($tr, StorageConfig.Status.ERROR);
+ }
}
});
},
@@ -971,7 +985,7 @@ MountConfigListView.prototype = _.extend({
this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
storage.recheck({
success: function(result) {
- self.updateStatus($tr, result.status);
+ self.updateStatus($tr, result.status, result.statusMessage);
},
error: function() {
self.updateStatus($tr, StorageConfig.Status.ERROR);
@@ -984,11 +998,15 @@ MountConfigListView.prototype = _.extend({
*
* @param {jQuery} $tr
* @param {int} status
+ * @param {string} message
*/
- updateStatus: function($tr, status) {
+ updateStatus: function($tr, status, message) {
var $statusSpan = $tr.find('.status span');
$statusSpan.removeClass('loading-small success indeterminate error');
switch (status) {
+ case null:
+ // remove status
+ break;
case StorageConfig.Status.IN_PROGRESS:
$statusSpan.addClass('loading-small');
break;
@@ -1001,6 +1019,7 @@ MountConfigListView.prototype = _.extend({
default:
$statusSpan.addClass('error');
}
+ $statusSpan.attr('data-original-title', (typeof message === 'string') ? message : '');
},
/**
diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php
index d9fdb748fcd..56a7e547ec6 100644
--- a/apps/files_external/lib/config.php
+++ b/apps/files_external/lib/config.php
@@ -269,6 +269,7 @@ class OC_Mount_Config {
}
} catch (Exception $exception) {
\OCP\Util::logException('files_external', $exception);
+ throw $e;
}
}
return self::STATUS_ERROR;
diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php
index 70aaa186783..86a7e6ffa12 100644
--- a/apps/files_external/lib/storageconfig.php
+++ b/apps/files_external/lib/storageconfig.php
@@ -73,6 +73,13 @@ class StorageConfig implements \JsonSerializable {
private $status;
/**
+ * Status message
+ *
+ * @var string
+ */
+ private $statusMessage;
+
+ /**
* Priority
*
* @var int
@@ -295,7 +302,7 @@ class StorageConfig implements \JsonSerializable {
}
/**
- * Sets the storage status, whether the config worked last time
+ * Gets the storage status, whether the config worked last time
*
* @return int $status status
*/
@@ -304,12 +311,23 @@ class StorageConfig implements \JsonSerializable {
}
/**
+ * Gets the message describing the storage status
+ *
+ * @return string|null
+ */
+ public function getStatusMessage() {
+ return $this->statusMessage;
+ }
+
+ /**
* Sets the storage status, whether the config worked last time
*
* @param int $status status
+ * @param string|null $message optional message
*/
- public function setStatus($status) {
+ public function setStatus($status, $message = null) {
$this->status = $status;
+ $this->statusMessage = $message;
}
/**
@@ -341,6 +359,9 @@ class StorageConfig implements \JsonSerializable {
if (!is_null($this->status)) {
$result['status'] = $this->status;
}
+ if (!is_null($this->statusMessage)) {
+ $result['statusMessage'] = $this->statusMessage;
+ }
return $result;
}
}
diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php
index beb47ce0bce..e946e7feb77 100644
--- a/apps/files_external/lib/swift.php
+++ b/apps/files_external/lib/swift.php
@@ -106,7 +106,10 @@ class Swift extends \OC\Files\Storage\Common {
$this->getContainer()->getPartialObject($path);
return true;
} catch (ClientErrorResponseException $e) {
- \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
+ // Expected response is "404 Not Found", so only log if it isn't
+ if ($e->getResponse()->getStatusCode() !== 404) {
+ \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
+ }
return false;
}
}
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 30a803f3207..63225a0d8ec 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -130,6 +130,13 @@
// remove icon, if applicable
OC.Share.markFileAsShared($tr, false, false);
}
+ var newIcon = $tr.attr('data-icon');
+ // in case markFileAsShared decided to change the icon,
+ // we need to modify the model
+ // (FIXME: yes, this is hacky)
+ if (fileInfoModel.get('icon') !== newIcon) {
+ fileInfoModel.set('icon', newIcon);
+ }
});
fileList.registerTabView(shareTab);
},
diff --git a/apps/provisioning_api/appinfo/app.php b/apps/provisioning_api/appinfo/app.php
deleted file mode 100644
index 40d8d5d04d3..00000000000
--- a/apps/provisioning_api/appinfo/app.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-/**
-
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
diff --git a/core/ajax/share.php b/core/ajax/share.php
index 68afd7b4e84..fd42a94de6e 100644
--- a/core/ajax/share.php
+++ b/core/ajax/share.php
@@ -360,8 +360,8 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo
}
if ((!isset($_GET['itemShares'])
- || !is_array((string)$_GET['itemShares'][OCP\Share::SHARE_TYPE_USER])
- || !in_array($uid, (string)$_GET['itemShares'][OCP\Share::SHARE_TYPE_USER]))
+ || !is_array($_GET['itemShares'][OCP\Share::SHARE_TYPE_USER])
+ || !in_array($uid, $_GET['itemShares'][OCP\Share::SHARE_TYPE_USER]))
&& $uid != OC_User::getUser()) {
$shareWith[] = array(
'label' => $displayName,
@@ -386,8 +386,8 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo
if ($count < $request_limit) {
if (!isset($_GET['itemShares'])
|| !isset($_GET['itemShares'][OCP\Share::SHARE_TYPE_GROUP])
- || !is_array((string)$_GET['itemShares'][OCP\Share::SHARE_TYPE_GROUP])
- || !in_array($group, (string)$_GET['itemShares'][OCP\Share::SHARE_TYPE_GROUP])) {
+ || !is_array($_GET['itemShares'][OCP\Share::SHARE_TYPE_GROUP])
+ || !in_array($group, $_GET['itemShares'][OCP\Share::SHARE_TYPE_GROUP])) {
$shareWith[] = array(
'label' => $group,
'value' => array(
diff --git a/core/command/maintenance/repair.php b/core/command/maintenance/repair.php
index 5df362f8111..f7c0cc46048 100644
--- a/core/command/maintenance/repair.php
+++ b/core/command/maintenance/repair.php
@@ -26,6 +26,7 @@ namespace OC\Core\Command\Maintenance;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Repair extends Command {
@@ -49,10 +50,24 @@ class Repair extends Command {
protected function configure() {
$this
->setName('maintenance:repair')
- ->setDescription('repair this installation');
+ ->setDescription('repair this installation')
+ ->addOption(
+ 'include-expensive',
+ null,
+ InputOption::VALUE_NONE,
+ 'Use this option when you want to include resource and load expensive tasks'
+ )
+ ;
}
protected function execute(InputInterface $input, OutputInterface $output) {
+ $includeExpensive = $input->getOption('include-expensive');
+ if ($includeExpensive) {
+ foreach ($this->repair->getExpensiveRepairSteps() as $step) {
+ $this->repair->addStep($step);
+ }
+ }
+
$maintenanceMode = $this->config->getSystemValue('maintenance', false);
$this->config->setSystemValue('maintenance', true);
diff --git a/core/js/mimetype.js b/core/js/mimetype.js
index b0de8eb8411..3cc33ce2830 100644
--- a/core/js/mimetype.js
+++ b/core/js/mimetype.js
@@ -46,6 +46,8 @@ OC.MimeType = {
return 'folder';
} else if (mimeType === 'dir-shared' && $.inArray('folder-shared', files) !== -1) {
return 'folder-shared';
+ } else if (mimeType === 'dir-public' && $.inArray('folder-public', files) !== -1) {
+ return 'folder-public';
} else if (mimeType === 'dir-external' && $.inArray('folder-external', files) !== -1) {
return 'folder-external';
} else if ($.inArray(icon, files) !== -1) {
diff --git a/core/js/share.js b/core/js/share.js
index 1131ae8f112..e14e19a2543 100644
--- a/core/js/share.js
+++ b/core/js/share.js
@@ -253,14 +253,25 @@ OC.Share = _.extend(OC.Share || {}, {
// update folder icon
if (type === 'dir' && (hasShares || hasLink || owner)) {
if (hasLink) {
- shareFolderIcon = OC.imagePath('core', 'filetypes/folder-public');
+ shareFolderIcon = OC.MimeType.getIconUrl('dir-public');
}
else {
- shareFolderIcon = OC.imagePath('core', 'filetypes/folder-shared');
+ shareFolderIcon = OC.MimeType.getIconUrl('dir-shared');
}
$tr.find('.filename .thumbnail').css('background-image', 'url(' + shareFolderIcon + ')');
+ $tr.attr('data-icon', shareFolderIcon);
} else if (type === 'dir') {
- shareFolderIcon = OC.imagePath('core', 'filetypes/folder');
+ var mountType = $tr.attr('data-mounttype');
+ // FIXME: duplicate of FileList._createRow logic for external folder,
+ // need to refactor the icon logic into a single code path eventually
+ if (mountType && mountType.indexOf('external') === 0) {
+ shareFolderIcon = OC.MimeType.getIconUrl('dir-external');
+ $tr.attr('data-icon', shareFolderIcon);
+ } else {
+ shareFolderIcon = OC.MimeType.getIconUrl('dir');
+ // back to default
+ $tr.removeAttr('data-icon');
+ }
$tr.find('.filename .thumbnail').css('background-image', 'url(' + shareFolderIcon + ')');
}
// update share action text / icon
diff --git a/core/js/tests/specs/shareSpec.js b/core/js/tests/specs/shareSpec.js
index 9e80f4fe19d..5c76ea600b8 100644
--- a/core/js/tests/specs/shareSpec.js
+++ b/core/js/tests/specs/shareSpec.js
@@ -142,6 +142,13 @@ describe('OC.Share tests', function() {
checkIcon('filetypes/folder-public');
});
+ it('shows external storage icon if external mount point', function() {
+ $file.attr('data-type', 'dir');
+ $file.attr('data-mountType', 'external');
+ OC.Share.markFileAsShared($file, false, false);
+
+ checkIcon('filetypes/folder-external');
+ });
});
describe('displaying the recipoients', function() {
diff --git a/core/js/tests/specs/sharedialogshareelistview.js b/core/js/tests/specs/sharedialogshareelistview.js
new file mode 100644
index 00000000000..d468ce790dc
--- /dev/null
+++ b/core/js/tests/specs/sharedialogshareelistview.js
@@ -0,0 +1,158 @@
+/**
+ * ownCloud
+ *
+ * @author Tom Needham
+ * @copyright 2015 Tom Needham <tom@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* global oc_appconfig */
+describe('OC.Share.ShareDialogShareeListView', function () {
+
+ var oldCurrentUser;
+ var fileInfoModel;
+ var configModel;
+ var shareModel;
+ var listView;
+ var setPermissionsStub;
+
+ beforeEach(function () {
+ /* jshint camelcase:false */
+ oldAppConfig = _.extend({}, oc_appconfig.core);
+ oc_appconfig.core.enforcePasswordForPublicLink = false;
+
+ $('#testArea').append('<input id="mailPublicNotificationEnabled" name="mailPublicNotificationEnabled" type="hidden" value="yes">');
+
+ fileInfoModel = new OCA.Files.FileInfoModel({
+ id: 123,
+ name: 'shared_file_name.txt',
+ path: '/subdir',
+ size: 100,
+ mimetype: 'text/plain',
+ permissions: 31,
+ sharePermissions: 31
+ });
+
+ var attributes = {
+ itemType: fileInfoModel.isDirectory() ? 'folder' : 'file',
+ itemSource: fileInfoModel.get('id'),
+ possiblePermissions: 31,
+ permissions: 31
+ };
+
+ shareModel = new OC.Share.ShareItemModel(attributes, {
+ configModel: configModel,
+ fileInfoModel: fileInfoModel
+ });
+
+ configModel = new OC.Share.ShareConfigModel({
+ enforcePasswordForPublicLink: false,
+ isResharingAllowed: true,
+ enforcePasswordForPublicLink: false,
+ isDefaultExpireDateEnabled: false,
+ isDefaultExpireDateEnforced: false,
+ defaultExpireDate: 7
+ });
+
+ listView = new OC.Share.ShareDialogShareeListView({
+ configModel: configModel,
+ model: shareModel
+ });
+
+ // required for proper event propagation when simulating clicks in some cases (jquery bugs)
+ $('#testArea').append(listView.$el);
+
+ shareModel.set({
+ linkShare: {isLinkShare: false}
+ });
+
+ oldCurrentUser = OC.currentUser;
+ OC.currentUser = 'user0';
+ setPermissionsStub = sinon.stub(listView.model, 'setPermissions');
+ });
+
+ afterEach(function () {
+ OC.currentUser = oldCurrentUser;
+ /* jshint camelcase:false */
+ oc_appconfig.core = oldAppConfig;
+ listView.remove();
+ setPermissionsStub.restore();
+ });
+
+ describe('Manages checkbox events correctly', function () {
+ it('Checks cruds boxes when edit box checked', function () {
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ listView.render();
+ listView.$el.find("input[name='edit']").click();
+ expect(listView.$el.find("input[name='update']").is(':checked')).toEqual(true);
+ expect(setPermissionsStub.called).toEqual(true);
+ });
+
+ it('Checks edit box when create/update/delete are checked', function () {
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ listView.render();
+ listView.$el.find("input[name='update']").click();
+ expect(listView.$el.find("input[name='edit']").is(':checked')).toEqual(true);
+ expect(setPermissionsStub.called).toEqual(true);
+ });
+
+ it('shows cruds checkboxes when toggled', function () {
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ listView.render();
+ listView.$el.find('a.showCruds').click();
+ expect(listView.$el.find('li.cruds').hasClass('hidden')).toEqual(false);
+ });
+
+ it('sends notification to user when checkbox clicked', function () {
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ listView.render();
+ var notificationStub = sinon.stub(listView.model, 'sendNotificationForShare');
+ listView.$el.find("input[name='mailNotification']").click();
+ expect(notificationStub.called).toEqual(true);
+ notificationStub.restore();
+ });
+
+ });
+
+});
diff --git a/core/templates/internalaltmail.php b/core/templates/internalaltmail.php
new file mode 100644
index 00000000000..38531d109b7
--- /dev/null
+++ b/core/templates/internalaltmail.php
@@ -0,0 +1,13 @@
+<?php
+print_unescaped($l->t("Hey there,\n\njust letting you know that %s shared %s with you.\nView it: %s\n\n", array($_['user_displayname'], $_['filename'], $_['link'])));
+if ( isset($_['expiration']) ) {
+ print_unescaped($l->t("The share will expire on %s.", array($_['expiration'])));
+ print_unescaped("\n\n");
+}
+// TRANSLATORS term at the end of a mail
+p($l->t("Cheers!"));
+?>
+
+--
+<?php p($theme->getName() . ' - ' . $theme->getSlogan()); ?>
+<?php print_unescaped("\n".$theme->getBaseUrl());
diff --git a/core/templates/internalmail.php b/core/templates/internalmail.php
new file mode 100644
index 00000000000..0e73a601857
--- /dev/null
+++ b/core/templates/internalmail.php
@@ -0,0 +1,39 @@
+<table cellspacing="0" cellpadding="0" border="0" width="100%">
+<tr><td>
+<table cellspacing="0" cellpadding="0" border="0" width="600px">
+<tr>
+<td bgcolor="<?php p($theme->getMailHeaderColor());?>" width="20px">&nbsp;</td>
+<td bgcolor="<?php p($theme->getMailHeaderColor());?>">
+<img src="<?php p(OC_Helper::makeURLAbsolute(image_path('', 'logo-mail.gif'))); ?>" alt="<?php p($theme->getName()); ?>"/>
+</td>
+</tr>
+<tr><td colspan="2">&nbsp;</td></tr>
+<tr>
+<td width="20px">&nbsp;</td>
+<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">
+<?php
+print_unescaped($l->t('Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href="%s">View it!</a><br><br>', array($_['user_displayname'], $_['filename'], $_['link'])));
+if ( isset($_['expiration']) ) {
+ p($l->t("The share will expire on %s.", array($_['expiration'])));
+ print_unescaped('<br><br>');
+}
+// TRANSLATORS term at the end of a mail
+p($l->t('Cheers!'));
+?>
+</td>
+</tr>
+<tr><td colspan="2">&nbsp;</td></tr>
+<tr>
+<td width="20px">&nbsp;</td>
+<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">--<br>
+<?php p($theme->getName()); ?> -
+<?php p($theme->getSlogan()); ?>
+<br><a href="<?php p($theme->getBaseUrl()); ?>"><?php p($theme->getBaseUrl());?></a>
+</td>
+</tr>
+<tr>
+<td colspan="2">&nbsp;</td>
+</tr>
+</table>
+</td></tr>
+</table>
diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php
index f3e22701f40..71720ac58bf 100644
--- a/lib/private/files/cache/cache.php
+++ b/lib/private/files/cache/cache.php
@@ -313,6 +313,14 @@ class Cache {
$fields = array(
'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
'etag', 'permissions');
+
+ $doNotCopyStorageMTime = false;
+ if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
+ // this horrific magic tells it to not copy storage_mtime to mtime
+ unset($data['mtime']);
+ $doNotCopyStorageMTime = true;
+ }
+
$params = array();
$queryParts = array();
foreach ($data as $name => $value) {
@@ -325,7 +333,7 @@ class Cache {
$queryParts[] = '`mimepart`';
$value = $this->mimetypeLoader->getId($value);
} elseif ($name === 'storage_mtime') {
- if (!isset($data['mtime'])) {
+ if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
$params[] = $value;
$queryParts[] = '`mtime`';
}
@@ -477,6 +485,9 @@ class Cache {
list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
+ // sql for final update
+ $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
+
if ($sourceData['mimetype'] === 'httpd/unix-directory') {
//find all child entries
$sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
@@ -490,11 +501,12 @@ class Cache {
$newTargetPath = $targetPath . substr($child['path'], $sourceLength);
\OC_DB::executeAudited($query, [$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
}
+ \OC_DB::executeAudited($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
\OC_DB::commit();
+ } else {
+ \OC_DB::executeAudited($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
}
- $sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
- \OC_DB::executeAudited($sql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
}
/**
diff --git a/lib/private/files/cache/updater.php b/lib/private/files/cache/updater.php
index 4cc5f511565..85dcc8589de 100644
--- a/lib/private/files/cache/updater.php
+++ b/lib/private/files/cache/updater.php
@@ -194,12 +194,26 @@ class Updater {
$targetCache->correctFolderSize($targetInternalPath);
$this->correctParentStorageMtime($sourceStorage, $sourceInternalPath);
$this->correctParentStorageMtime($targetStorage, $targetInternalPath);
+ $this->updateStorageMTimeOnly($targetStorage, $targetInternalPath);
$this->propagator->addChange($source);
$this->propagator->addChange($target);
$this->propagator->propagateChanges();
}
}
+ private function updateStorageMTimeOnly($storage, $internalPath) {
+ $cache = $storage->getCache();
+ $fileId = $cache->getId($internalPath);
+ if ($fileId !== -1) {
+ $cache->update(
+ $fileId, [
+ 'mtime' => null, // this magic tells it to not overwrite mtime
+ 'storage_mtime' => $storage->filemtime($internalPath)
+ ]
+ );
+ }
+ }
+
/**
* update the storage_mtime of the direct parent in the cache to the mtime from the storage
*
diff --git a/lib/private/files/storage/wrapper/availability.php b/lib/private/files/storage/wrapper/availability.php
index 37319a8f7d1..d6ce78f6e44 100644
--- a/lib/private/files/storage/wrapper/availability.php
+++ b/lib/private/files/storage/wrapper/availability.php
@@ -220,6 +220,9 @@ class Availability extends Wrapper {
/** {@inheritdoc} */
public function file_exists($path) {
+ if ($path === '') {
+ return true;
+ }
$this->checkAvailability();
try {
return parent::file_exists($path);
diff --git a/lib/private/repair.php b/lib/private/repair.php
index 20219e313fd..f6ac7ebe65b 100644
--- a/lib/private/repair.php
+++ b/lib/private/repair.php
@@ -34,6 +34,7 @@ use OC\Repair\AssetCache;
use OC\Repair\CleanTags;
use OC\Repair\Collation;
use OC\Repair\DropOldJobs;
+use OC\Repair\OldGroupMembershipShares;
use OC\Repair\RemoveGetETagEntries;
use OC\Repair\SqliteAutoincrement;
use OC\Repair\DropOldTables;
@@ -119,6 +120,18 @@ class Repair extends BasicEmitter {
}
/**
+ * Returns expensive repair steps to be run on the
+ * command line with a special option.
+ *
+ * @return array of RepairStep instances
+ */
+ public static function getExpensiveRepairSteps() {
+ return [
+ new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
+ ];
+ }
+
+ /**
* Returns the repair steps to be run before an
* upgrade.
*
diff --git a/lib/private/share/mailnotifications.php b/lib/private/share/mailnotifications.php
index 8056260bf17..2797e5ed99b 100644
--- a/lib/private/share/mailnotifications.php
+++ b/lib/private/share/mailnotifications.php
@@ -136,7 +136,7 @@ class MailNotifications {
$link = \OCP\Util::linkToAbsolute('files', 'index.php', $args);
- list($htmlBody, $textBody) = $this->createMailBody($filename, $link, $expiration);
+ list($htmlBody, $textBody) = $this->createMailBody($filename, $link, $expiration, 'internal');
// send it out now
try {
@@ -210,20 +210,20 @@ class MailNotifications {
* @param string $filename the shared file
* @param string $link link to the shared file
* @param int $expiration expiration date (timestamp)
+ * @param bool $prefix prefix of mail template files
* @return array an array of the html mail body and the plain text mail body
*/
- private function createMailBody($filename, $link, $expiration) {
-
+ private function createMailBody($filename, $link, $expiration, $prefix = '') {
$formattedDate = $expiration ? $this->l->l('date', $expiration) : null;
- $html = new \OC_Template("core", "mail", "");
+ $html = new \OC_Template('core', $prefix . 'mail', '');
$html->assign ('link', $link);
$html->assign ('user_displayname', $this->senderDisplayName);
$html->assign ('filename', $filename);
$html->assign('expiration', $formattedDate);
$htmlMail = $html->fetchPage();
- $plainText = new \OC_Template("core", "altmail", "");
+ $plainText = new \OC_Template('core', $prefix . 'altmail', '');
$plainText->assign ('link', $link);
$plainText->assign ('user_displayname', $this->senderDisplayName);
$plainText->assign ('filename', $filename);
diff --git a/lib/repair/oldgroupmembershipshares.php b/lib/repair/oldgroupmembershipshares.php
new file mode 100644
index 00000000000..2d701ac9fb7
--- /dev/null
+++ b/lib/repair/oldgroupmembershipshares.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\Share;
+
+class OldGroupMembershipShares extends BasicEmitter implements RepairStep {
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ /** @var \OCP\IGroupManager */
+ protected $groupManager;
+
+ /**
+ * @var array [gid => [uid => (bool)]]
+ */
+ protected $memberships;
+
+ /**
+ * @param IDBConnection $connection
+ * @param IGroupManager $groupManager
+ */
+ public function __construct(IDBConnection $connection, IGroupManager $groupManager) {
+ $this->connection = $connection;
+ $this->groupManager = $groupManager;
+ }
+
+ /**
+ * Returns the step's name
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'Remove shares of old group memberships';
+ }
+
+ /**
+ * Run repair step.
+ * Must throw exception on error.
+ *
+ * @throws \Exception in case of failure
+ */
+ public function run() {
+ $deletedEntries = 0;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select(['s1.id', $query->createFunction('s1.`share_with` AS `user`'), $query->createFunction('s2.`share_with` AS `group`')])
+ ->from('share', 's1')
+ ->where($query->expr()->isNotNull('s1.parent'))
+ // \OC\Share\Constant::$shareTypeGroupUserUnique === 2
+ ->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2)))
+ ->andWhere($query->expr()->isNotNull('s2.id'))
+ ->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(Share::SHARE_TYPE_GROUP)))
+ ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'));
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('share')
+ ->where($query->expr()->eq('id', $deleteQuery->createParameter('share')));
+
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ if (!$this->isMember($row['group'], $row['user'])) {
+ $deletedEntries += $deleteQuery->setParameter('share', (int) $row['id'])
+ ->execute();
+ }
+ }
+ $result->closeCursor();
+
+ if ($deletedEntries) {
+ $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore'));
+ }
+ }
+
+ /**
+ * @param string $gid
+ * @param string $uid
+ * @return bool
+ */
+ protected function isMember($gid, $uid) {
+ if (isset($this->memberships[$gid][$uid])) {
+ return $this->memberships[$gid][$uid];
+ }
+
+ $isMember = $this->groupManager->isInGroup($uid, $gid);
+ if (!isset($this->memberships[$gid])) {
+ $this->memberships[$gid] = [];
+ }
+ $this->memberships[$gid][$uid] = $isMember;
+
+ return $isMember;
+ }
+}
diff --git a/lib/repair/repairinvalidshares.php b/lib/repair/repairinvalidshares.php
index 4b0aeb70c12..5a4cb445ce9 100644
--- a/lib/repair/repairinvalidshares.php
+++ b/lib/repair/repairinvalidshares.php
@@ -70,11 +70,43 @@ class RepairInvalidShares extends BasicEmitter implements \OC\RepairStep {
}
}
+ /**
+ * Remove shares where the parent share does not exist anymore
+ */
+ private function removeSharesNonExistingParent() {
+ $deletedEntries = 0;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('s1.parent')
+ ->from('share', 's1')
+ ->where($query->expr()->isNotNull('s1.parent'))
+ ->andWhere($query->expr()->isNull('s2.id'))
+ ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'))
+ ->groupBy('s1.parent');
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('share')
+ ->where($query->expr()->eq('parent', $deleteQuery->createParameter('parent')));
+
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
+ ->execute();
+ }
+ $result->closeCursor();
+
+ if ($deletedEntries) {
+ $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where the parent did not exist'));
+ }
+ }
+
public function run() {
$ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.7', '<')) {
// this situation was only possible before 8.2
$this->removeExpirationDateFromNonLinkShares();
}
+
+ $this->removeSharesNonExistingParent();
}
}
diff --git a/settings/templates/users/main.php b/settings/templates/users/main.php
index 0abe31f4a59..f50f83b38b3 100644
--- a/settings/templates/users/main.php
+++ b/settings/templates/users/main.php
@@ -46,35 +46,35 @@ translation('settings');
<div id="userlistoptions">
<p>
<input type="checkbox" name="StorageLocation" value="StorageLocation" id="CheckboxStorageLocation"
- <?php if ($_['show_storage_location'] === 'true') print_unescaped('checked="checked"'); ?> />
+ class="checkbox" <?php if ($_['show_storage_location'] === 'true') print_unescaped('checked="checked"'); ?> />
<label for="CheckboxStorageLocation">
<?php p($l->t('Show storage location')) ?>
</label>
</p>
<p>
<input type="checkbox" name="LastLogin" value="LastLogin" id="CheckboxLastLogin"
- <?php if ($_['show_last_login'] === 'true') print_unescaped('checked="checked"'); ?> />
+ class="checkbox" <?php if ($_['show_last_login'] === 'true') print_unescaped('checked="checked"'); ?> />
<label for="CheckboxLastLogin">
<?php p($l->t('Show last log in')) ?>
</label>
</p>
<p>
<input type="checkbox" name="UserBackend" value="UserBackend" id="CheckboxUserBackend"
- <?php if ($_['show_backend'] === 'true') print_unescaped('checked="checked"'); ?> />
+ class="checkbox" <?php if ($_['show_backend'] === 'true') print_unescaped('checked="checked"'); ?> />
<label for="CheckboxUserBackend">
<?php p($l->t('Show user backend')) ?>
</label>
</p>
<p>
<input type="checkbox" name="MailOnUserCreate" value="MailOnUserCreate" id="CheckboxMailOnUserCreate"
- <?php if ($_['send_email'] === 'true') print_unescaped('checked="checked"'); ?> />
+ class="checkbox" <?php if ($_['send_email'] === 'true') print_unescaped('checked="checked"'); ?> />
<label for="CheckboxMailOnUserCreate">
<?php p($l->t('Send email to new user')) ?>
</label>
</p>
<p>
<input type="checkbox" name="EmailAddress" value="EmailAddress" id="CheckboxEmailAddress"
- <?php if ($_['show_email'] === 'true') print_unescaped('checked="checked"'); ?> />
+ class="checkbox" <?php if ($_['show_email'] === 'true') print_unescaped('checked="checked"'); ?> />
<label for="CheckboxEmailAddress">
<?php p($l->t('Show email address')) ?>
</label>
diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php
index e3fa26829b4..b7e76aeace4 100644
--- a/tests/lib/files/cache/updater.php
+++ b/tests/lib/files/cache/updater.php
@@ -161,6 +161,47 @@ class Updater extends \Test\TestCase {
$this->assertEquals($cached['fileid'], $cachedTarget['fileid']);
}
+ public function testUpdateStorageMTime() {
+ $this->storage->mkdir('sub');
+ $this->storage->mkdir('sub2');
+ $this->storage->file_put_contents('sub/foo.txt', 'qwerty');
+
+ $this->updater->update('sub');
+ $this->updater->update('sub/foo.txt');
+ $this->updater->update('sub2');
+
+ $cachedSourceParent = $this->cache->get('sub');
+ $cachedSource = $this->cache->get('sub/foo.txt');
+
+ $this->storage->rename('sub/foo.txt', 'sub2/bar.txt');
+
+ // simulate storage having a different mtime
+ $testmtime = 1433323578;
+
+ // source storage mtime change
+ $this->storage->touch('sub', $testmtime);
+
+ // target storage mtime change
+ $this->storage->touch('sub2', $testmtime);
+ // some storages (like Dropbox) change storage mtime on rename
+ $this->storage->touch('sub2/bar.txt', $testmtime);
+
+ $this->updater->rename('sub/foo.txt', 'sub2/bar.txt');
+
+ $cachedTargetParent = $this->cache->get('sub2');
+ $cachedTarget = $this->cache->get('sub2/bar.txt');
+
+ $this->assertEquals($cachedSource['mtime'], $cachedTarget['mtime'], 'file mtime preserved');
+
+ $this->assertNotEquals($cachedTarget['storage_mtime'], $cachedTarget['mtime'], 'mtime is not storage_mtime for moved file');
+
+ $this->assertEquals($testmtime, $cachedTarget['storage_mtime'], 'target file storage_mtime propagated');
+ $this->assertNotEquals($testmtime, $cachedTarget['mtime'], 'target file mtime changed, not from storage');
+
+ $this->assertEquals($testmtime, $cachedTargetParent['storage_mtime'], 'target parent storage_mtime propagated');
+ $this->assertNotEquals($testmtime, $cachedTargetParent['mtime'], 'target folder mtime changed, not from storage');
+ }
+
public function testNewFileDisabled() {
$this->storage->file_put_contents('foo.txt', 'bar');
$this->assertFalse($this->cache->inCache('foo.txt'));
diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php
index a84b8badd5a..a7979146b85 100644
--- a/tests/lib/files/view.php
+++ b/tests/lib/files/view.php
@@ -1976,9 +1976,13 @@ class View extends \Test\TestCase {
$view = new \OC\Files\View('/' . $this->user . '/files/');
$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
- ->setMethods([$operation])
+ ->setMethods([$operation, 'filemtime'])
->getMock();
+ $storage->expects($this->any())
+ ->method('filemtime')
+ ->will($this->returnValue(123456789));
+
$sourcePath = 'original.txt';
$targetPath = 'target.txt';
@@ -2117,9 +2121,13 @@ class View extends \Test\TestCase {
->setMethods([$storageOperation])
->getMock();
$storage2 = $this->getMockBuilder('\OC\Files\Storage\Temporary')
- ->setMethods([$storageOperation])
+ ->setMethods([$storageOperation, 'filemtime'])
->getMock();
+ $storage2->expects($this->any())
+ ->method('filemtime')
+ ->will($this->returnValue(123456789));
+
$sourcePath = 'original.txt';
$targetPath = 'substorage/target.txt';
diff --git a/tests/lib/repair/oldgroupmembershipsharestest.php b/tests/lib/repair/oldgroupmembershipsharestest.php
new file mode 100644
index 00000000000..74f68bd7899
--- /dev/null
+++ b/tests/lib/repair/oldgroupmembershipsharestest.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\OldGroupMembershipShares;
+use OC\Share\Constants;
+
+class OldGroupMembershipSharesTest extends \Test\TestCase {
+
+ /** @var OldGroupMembershipShares */
+ protected $repair;
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ /** @var \OCP\IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $groupManager;
+
+ protected function setUp() {
+ parent::setUp();
+
+ /** \OCP\IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
+ $this->groupManager = $this->getMockBuilder('OCP\IGroupManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->connection = \OC::$server->getDatabaseConnection();
+
+ $this->deleteAllShares();
+ }
+
+ protected function tearDown() {
+ $this->deleteAllShares();
+
+ parent::tearDown();
+ }
+
+ protected function deleteAllShares() {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('share')->execute();
+ }
+
+ public function testRun() {
+ $repair = new OldGroupMembershipShares(
+ $this->connection,
+ $this->groupManager
+ );
+
+ $this->groupManager->expects($this->exactly(2))
+ ->method('isInGroup')
+ ->willReturnMap([
+ ['member', 'group', true],
+ ['not-a-member', 'group', false],
+ ]);
+
+ $parent = $this->createShare(Constants::SHARE_TYPE_GROUP, 'group', null);
+ $group2 = $this->createShare(Constants::SHARE_TYPE_GROUP, 'group2', $parent);
+ $user1 = $this->createShare(Constants::SHARE_TYPE_USER, 'user1', $parent);
+
+ // \OC\Share\Constant::$shareTypeGroupUserUnique === 2
+ $member = $this->createShare(2, 'member', $parent);
+ $notAMember = $this->createShare(2, 'not-a-member', $parent);
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->execute();
+ $rows = $result->fetchAll();
+ $this->assertSame([['id' => $parent], ['id' => $group2], ['id' => $user1], ['id' => $member], ['id' => $notAMember]], $rows);
+ $result->closeCursor();
+
+ $repair->run();
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->execute();
+ $rows = $result->fetchAll();
+ $this->assertSame([['id' => $parent], ['id' => $group2], ['id' => $user1], ['id' => $member]], $rows);
+ $result->closeCursor();
+ }
+
+ /**
+ * @param string $shareType
+ * @param string $shareWith
+ * @param null|int $parent
+ * @return int
+ */
+ protected function createShare($shareType, $shareWith, $parent) {
+ $qb = $this->connection->getQueryBuilder();
+ $shareValues = [
+ 'share_type' => $qb->expr()->literal($shareType),
+ 'share_with' => $qb->expr()->literal($shareWith),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(1),
+ 'stime' => $qb->expr()->literal(time()),
+ 'expiration' => $qb->expr()->literal('2015-09-25 00:00:00'),
+ ];
+
+ if ($parent) {
+ $shareValues['parent'] = $qb->expr()->literal($parent);
+ }
+
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values($shareValues)
+ ->execute();
+
+ return $this->getLastShareId();
+ }
+
+ /**
+ * @return int
+ */
+ protected function getLastShareId() {
+ // select because lastInsertId does not work with OCI
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'DESC')
+ ->execute();
+ $row = $result->fetch();
+ $result->closeCursor();
+ return $row['id'];
+ }
+}
diff --git a/tests/lib/repair/repairinvalidsharestest.php b/tests/lib/repair/repairinvalidsharestest.php
index 89a5ba470e1..005a2db2344 100644
--- a/tests/lib/repair/repairinvalidsharestest.php
+++ b/tests/lib/repair/repairinvalidsharestest.php
@@ -77,13 +77,7 @@ class RepairInvalidSharesTest extends TestCase {
])
->execute();
- // select because lastInsertId does not work with OCI
- $results = $this->connection->getQueryBuilder()
- ->select('id')
- ->from('share')
- ->execute()
- ->fetchAll();
- $bogusShareId = $results[0]['id'];
+ $bogusShareId = $this->getLastShareId();
// link share with expiration date
$qb = $this->connection->getQueryBuilder();
@@ -119,5 +113,83 @@ class RepairInvalidSharesTest extends TestCase {
$this->assertNull($userShare['expiration'], 'bogus expiration date was removed');
$this->assertNotNull($linkShare['expiration'], 'valid link share expiration date still there');
}
+
+ /**
+ * Test remove shares where the parent share does not exist anymore
+ */
+ public function testSharesNonExistingParent() {
+ $qb = $this->connection->getQueryBuilder();
+ $shareValues = [
+ 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_USER),
+ 'share_with' => $qb->expr()->literal('recipientuser1'),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(1),
+ 'stime' => $qb->expr()->literal(time()),
+ 'expiration' => $qb->expr()->literal('2015-09-25 00:00:00')
+ ];
+
+ // valid share
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values($shareValues)
+ ->execute();
+ $parent = $this->getLastShareId();
+
+ // share with existing parent
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values(array_merge($shareValues, [
+ 'parent' => $qb->expr()->literal($parent),
+ ]))->execute();
+ $validChild = $this->getLastShareId();
+
+ // share with non-existing parent
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values(array_merge($shareValues, [
+ 'parent' => $qb->expr()->literal($parent + 100),
+ ]))->execute();
+ $invalidChild = $this->getLastShareId();
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->execute();
+ $rows = $result->fetchAll();
+ $this->assertSame([['id' => $parent], ['id' => $validChild], ['id' => $invalidChild]], $rows);
+ $result->closeCursor();
+
+ $this->repair->run();
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->execute();
+ $rows = $result->fetchAll();
+ $this->assertSame([['id' => $parent], ['id' => $validChild]], $rows);
+ $result->closeCursor();
+ }
+
+ /**
+ * @return int
+ */
+ protected function getLastShareId() {
+ // select because lastInsertId does not work with OCI
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'DESC')
+ ->execute();
+ $row = $result->fetch();
+ $result->closeCursor();
+ return $row['id'];
+ }
}
diff --git a/tests/travis/changed_app.sh b/tests/travis/changed_app.sh
new file mode 100755
index 00000000000..a4be0476573
--- /dev/null
+++ b/tests/travis/changed_app.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# ownCloud
+#
+# @author Joas Schilling
+# @author Thomas Müller
+# @copyright 2015 Thomas Müller thomas.mueller@tmit.eu
+#
+
+APP=$1
+
+FOUND=$(git diff ${TRAVIS_COMMIT_RANGE} | grep -- "^+++ b/apps/$APP/")
+
+if [ "x$FOUND" != 'x' ]; then
+ echo "1"
+else
+ echo "0"
+fi
diff --git a/tests/travis/test_for_app.sh b/tests/travis/test_for_app.sh
deleted file mode 100755
index a97c66dba83..00000000000
--- a/tests/travis/test_for_app.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# ownCloud
-#
-# @author Thomas Müller
-# @copyright 2015 Thomas Müller thomas.mueller@tmit.eu
-#
-
-set -e
-APP=$1
-
-if git diff ${TRAVIS_COMMIT_RANGE} | grep -- "^+++ b/apps/$APP/"; then
- echo "Executing this test config ...."
-else
- echo "Test config is not relevant for this change. terminating"
- exit 1
-fi