' +
'
' +
'
{{publicUploadWriteLabel}} ' +
@@ -265,8 +265,8 @@
publicUploadWriteLabel : t('core', 'Download / View / Upload'),
publicUploadWriteDescription : t('core', 'Recipients can view, download and upload contents.'),
- publicUploadWriteValue : OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE,
- publicUploadWriteSelected : this.model.get('permissions') === (OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE),
+ publicUploadWriteValue : OC.PERMISSION_READ | OC.PERMISSION_CREATE,
+ publicUploadWriteSelected : this.model.get('permissions') === (OC.PERMISSION_READ | OC.PERMISSION_CREATE),
publicReadWriteLabel : t('core', 'Download / View / Edit'),
publicReadWriteDescription : t('core', 'Recipients can view, download and edit contents.'),
diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js
index 8dac9550c416..29583d284a5a 100644
--- a/core/js/sharedialogshareelistview.js
+++ b/core/js/sharedialogshareelistview.js
@@ -69,7 +69,7 @@
'
' +
'{{#each shareAttributes}}' +
'' +
- ' ' +
+ ' ' +
'{{label}} ' +
' ' +
'{{/each}}' +
@@ -129,6 +129,9 @@
var cid = this.cid;
var shareWith = model.getShareWith(shareIndex);
+ // Check if reshare, and if so disable the checkboxes
+ var isReshare = model.hasReshare();
+
// Returns OC.Share.Types.ShareAttribute[] which were set for this
// share (and stored in DB)
var attributes = model.getShareAttributes(shareIndex);
@@ -137,19 +140,20 @@
attributes.map(function(attribute) {
// Check if the share attribute set for this file is still in
// registered share attributes and get its label
- var label = model.getRegisteredShareAttributeLabel(
+ var regAttr = model.getRegisteredShareAttribute(
attribute.scope,
attribute.key
);
- if (label) {
+ if (regAttr && regAttr.label) {
list.push({
cid: cid,
+ isReshare: isReshare,
shareWith: shareWith,
enabled: attribute.enabled,
scope: attribute.scope,
name: attribute.key,
- label: label
+ label: regAttr.label
});
} else {
OC.Notification.showTemporary(t('core', 'Share with ' +
@@ -365,7 +369,7 @@
$loading.removeClass('hidden');
this.model.sendNotificationForShare(shareType, shareWith, true).then(function(result) {
- if (result.ocs.meta.status === 'ok') {
+ if (result.ocs.data.status === 'success') {
OC.Notification.showTemporary(t('core', 'Email notification was sent!'));
$target.remove();
} else {
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index 021a8e1cf8de..05bb83d5da56 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -38,7 +38,7 @@
* @typedef {object} OC.Share.Types.ShareInfo
* @property {number} share_type
* @property {number} permissions
- * @property {string} attributes
+ * @property {OC.Share.Types.ShareAttribute[]} attributes
* @property {number} file_source optional
* @property {number} item_source
* @property {string} token
@@ -73,6 +73,7 @@
* @property {string} description
* @property {number[]} shareType
* @property {number[]} incompatiblePermissions
+ * @property {number[]} requiredPermissions
* @property {OC.Share.Types.ShareAttribute[]} incompatibleAttributes
*/
@@ -85,6 +86,13 @@
'storage', 'share_type', 'parent', 'stime'
];
+ /**
+ * Properties which are json and need to be parsed
+ */
+ var SHARE_RESPONSE_JSON_PROPS = [
+ 'attributes'
+ ];
+
/**
* @class OCA.Share.ShareItemModel
* @classdesc
@@ -171,10 +179,11 @@
}
properties.permissions = defaultPermissions & possiblePermissions;
+ // FIXME: simplify the logic by merging and then filtering
// Set default attributes for this share based on registered
// attributes (filtered by their incompatible permissions)
var newShareAttributes = [];
- var filteredRegisteredAttributes = this.filterRegisteredAttributes(properties.permissions);
+ var filteredRegisteredAttributes = this._filterRegisteredAttributes(properties.permissions);
_.map(filteredRegisteredAttributes, function(filteredRegisteredAttribute) {
var isCompatible = true;
// Check if this attribute can be added due to its incompatible attributes
@@ -233,12 +242,13 @@
var self = this;
options = options || {};
+ // FIXME: simplify the logic by merging and then filtering
// Set share attributes for this share based on registered
// attributes (filtered by their incompatible permissions
// and incompatible attributes)
var newShareAttributes = [];
var filteredAttributes = [];
- var filteredRegisteredAttributes = this.filterRegisteredAttributes(properties.permissions);
+ var filteredRegisteredAttributes = this._filterRegisteredAttributes(properties.permissions);
_.map(filteredRegisteredAttributes, function(filteredRegisteredAttribute) {
// Check if this allowed registered attribute
// is on the list of currently set properties,
@@ -785,9 +795,15 @@
// returns integers as string...
var i;
for (i = 0; i < SHARE_RESPONSE_INT_PROPS.length; i++) {
- var prop = SHARE_RESPONSE_INT_PROPS[i];
- if (!_.isUndefined(share[prop])) {
- share[prop] = parseInt(share[prop], 10);
+ var propInt = SHARE_RESPONSE_INT_PROPS[i];
+ if (!_.isUndefined(share[propInt])) {
+ share[propInt] = parseInt(share[propInt], 10);
+ }
+ }
+ for (i = 0; i < SHARE_RESPONSE_JSON_PROPS.length; i++) {
+ var propJson = SHARE_RESPONSE_JSON_PROPS[i];
+ if (!_.isUndefined(share[propJson])) {
+ share[propJson] = JSON.parse(share.attributes);
}
}
return share;
@@ -868,6 +884,25 @@
return _.uniq(result);
},
+ /**
+ * Returns share attributes for given share index
+ *
+ * @param shareIndex
+ * @returns OC.Share.Types.ShareAttribute[]
+ */
+ getShareAttributes: function(shareIndex) {
+ /** @type OC.Share.Types.ShareInfo **/
+ var share = this.get('shares')[shareIndex];
+ if(!_.isObject(share)) {
+ throw "Unknown Share";
+ }
+
+ if (_.isNull(share.attributes) || _.isUndefined(share.attributes)) {
+ return [];
+ }
+ return share.attributes;
+ },
+
/**
* Filter registered attributes by current share permissions
*
@@ -875,7 +910,7 @@
* @returns {OC.Share.Types.RegisteredShareAttribute[]}
* @private
*/
- filterRegisteredAttributes: function(permissions) {
+ _filterRegisteredAttributes: function(permissions) {
var filteredByPermissions = [];
for(var i in this._registeredAttributes) {
var compatible = true;
@@ -885,6 +920,11 @@
compatible = false;
}
}
+ for(var ii in attr.requiredPermissions) {
+ if (!this._hasPermission(permissions, attr.requiredPermissions[ii])) {
+ compatible = false;
+ }
+ }
if (compatible) {
filteredByPermissions.push(attr);
@@ -894,51 +934,30 @@
return filteredByPermissions;
},
- /**
- * Returns share attributes for given share index
- *
- * @param shareIndex
- * @returns OC.Share.Types.ShareAttribute[]
- */
- getShareAttributes: function(shareIndex) {
- /** @type OC.Share.Types.ShareInfo **/
- var share = this.get('shares')[shareIndex];
- if(!_.isObject(share)) {
- throw "Unknown Share";
- }
-
- if (_.isUndefined(share.attributes) || _.isUndefined(share.permissions)) {
- return [];
- }
-
- // Add attributes for this share
- var currentAttributes = JSON.parse(share.attributes);
- if (currentAttributes) {
- return currentAttributes;
- }
- return [];
- },
-
/**
* Returns share attribute label for given attribute scope and name. If
* attribute does not exist, null is returned.
*
* @param scope
* @param key
- * @returns string|null
+ * @returns {OC.Share.Types.RegisteredShareAttribute}
*/
- getRegisteredShareAttributeLabel: function(scope, key) {
+ getRegisteredShareAttribute: function(scope, key) {
for(var i in this._registeredAttributes) {
if (this._registeredAttributes[i].scope === scope
&& this._registeredAttributes[i].key === key) {
- return this._registeredAttributes[i].label;
+ return this._registeredAttributes[i];
}
}
return null;
},
/**
- * Apps can register default share attributes
+ * Apps can register default share attributes. The applications
+ * registering share attributes are required to follow the rules:
+ * attribute enabled -> functionality is added (e.g. can download)
+ * attribute disabled -> functionality is restricted
+ * incompatible attribute -> functionality is ignored
*
* @param {OC.Share.Types.RegisteredShareAttribute} $shareAttribute
*/
diff --git a/core/js/tests/specs/sharedialoglinkshareviewSpec.js b/core/js/tests/specs/sharedialoglinkshareviewSpec.js
index 08e536a230f3..42ccf9fed4e0 100644
--- a/core/js/tests/specs/sharedialoglinkshareviewSpec.js
+++ b/core/js/tests/specs/sharedialoglinkshareviewSpec.js
@@ -209,7 +209,54 @@ describe('OC.Share.ShareDialogLinkShareView', function() {
});
view.render();
expect(view.$('.publicPermissions').length).toEqual(4);
+ expect(view.$('.publicPermissions').context.innerHTML).toContain('Download / View');
+ expect(view.$('.publicPermissions').context.innerHTML).toContain('Download / View / Edit');
+ expect(view.$('.publicPermissions').context.innerHTML).toContain('Download / View / Upload');
+ expect(view.$('.publicPermissions').context.innerHTML).toContain('Upload only (File Drop)');
});
+ it('renders listing radio buttons for file when public upload is allowed globally', function () {
+ fileInfoModel = new OCA.Files.FileInfoModel({
+ id: '123',
+ name: 'file.txt',
+ path: '/subdir',
+ size: 100,
+ mimetype: 'text/plain',
+ permissions: 31,
+ sharePermissions: 31
+ });
+ itemModel = new OC.Share.ShareItemModel({
+ itemType: 'file',
+ itemSource: 123,
+ permissions: 31
+ }, {
+ configModel: configModel,
+ fileInfoModel: fileInfoModel
+ });
+
+ model = new OC.Share.ShareModel({
+ id: 1,
+ name: 'first link',
+ token: 'tehtokenz',
+ shareType: OC.Share.SHARE_TYPE_LINK,
+ itemType: 'file',
+ stime: 1489657516,
+ permissions: OC.PERMISSION_READ
+ });
+
+ view = new OC.Share.ShareDialogLinkShareView({
+ model: model,
+ itemModel: itemModel
+ });
+
+ model.set({
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_DELETE
+ });
+
+ view.render();
+ expect(view.$('.publicPermissions').length).toEqual(1);
+ expect(view.$('.publicPermissions').context.innerHTML).toContain('Download / View');
+ });
+
it('renders checkbox disabled when public upload is disallowed by user', function() {
publicUploadConfigStub.returns(true);
model.set({
diff --git a/core/js/tests/specs/sharedialogshareelistview.js b/core/js/tests/specs/sharedialogshareelistview.js
index 910dc93269c4..05ce4405dbb7 100644
--- a/core/js/tests/specs/sharedialogshareelistview.js
+++ b/core/js/tests/specs/sharedialogshareelistview.js
@@ -45,14 +45,14 @@ describe('OC.Share.ShareDialogShareeListView', function () {
sharePermissions: 31
});
- var attributes = {
+ var properties = {
itemType: fileInfoModel.isDirectory() ? 'folder' : 'file',
itemSource: fileInfoModel.get('id'),
possiblePermissions: 31,
permissions: 31
};
- shareModel = new OC.Share.ShareItemModel(attributes, {
+ shareModel = new OC.Share.ShareItemModel(properties, {
configModel: configModel,
fileInfoModel: fileInfoModel
});
@@ -91,7 +91,8 @@ describe('OC.Share.ShareDialogShareeListView', function () {
describe('rendering', function() {
it('Renders shares', function() {
- shareModel.set('shares', [{
+ shareModel.set('shares', [
+ {
id: 100,
item_source: 123,
permissions: 1,
@@ -99,10 +100,12 @@ describe('OC.Share.ShareDialogShareeListView', function () {
share_with: 'user1',
share_with_displayname: 'User One',
share_with_additional_info: 'user1@example.com'
- }, {
+ },
+ {
id: 101,
item_source: 123,
permissions: 1,
+ attributes: [],
share_type: OC.Share.SHARE_TYPE_GROUP,
share_with: 'group1',
share_with_displayname: 'Group One'
@@ -122,9 +125,39 @@ describe('OC.Share.ShareDialogShareeListView', function () {
expect($li.find('.username').text()).toEqual('Group One (group)');
expect($li.find('.user-additional-info').length).toEqual(0);
});
+
+ it('renders share attribute correctly', function () {
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute",
+ default: true,
+ label: "test attribute",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [],
+ incompatibleAttributes: []
+ });
+
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'test-attribute', enabled: true }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+
+ listView.render();
+ var $li = listView.$('li').eq(0);
+ var input = $li.find("input[name='test-attribute']");
+ expect(input.is(':checked')).toEqual(true);
+ expect($("label[for='" + input.attr('id') + "']").text()).toEqual('test attribute');
+ });
});
describe('Manages checkbox events correctly', function () {
+
it('Checks cruds boxes when edit box checked', function () {
shareModel.set('shares', [{
id: 100,
@@ -188,7 +221,7 @@ describe('OC.Share.ShareDialogShareeListView', function () {
expect(notificationStub.called).toEqual(true);
notificationStub.restore();
- deferred.resolve({ ocs: { meta: {status: 'ok' }}});
+ deferred.resolve({ ocs: { data : { status: 'success'}, meta: {message: null }}});
expect(notifStub.calledOnce).toEqual(true);
notifStub.restore();
@@ -196,6 +229,7 @@ describe('OC.Share.ShareDialogShareeListView', function () {
expect(listView.$el.find("input[name='mailNotification']").length).toEqual(0);
});
+
it('displays error if email notification not sent', function () {
var notifStub = sinon.stub(OC.dialogs, 'alert');
shareModel.set('shares', [{
@@ -213,7 +247,7 @@ describe('OC.Share.ShareDialogShareeListView', function () {
expect(notificationStub.called).toEqual(true);
notificationStub.restore();
- deferred.resolve({ ocs: { meta: {status: 'error', message: 'message'}}});
+ deferred.resolve({ ocs: { data: {status: 'error'}, meta: {message: 'message'}}});
expect(notifStub.calledOnce).toEqual(true);
notifStub.restore();
@@ -222,6 +256,197 @@ describe('OC.Share.ShareDialogShareeListView', function () {
expect(listView.$el.find("input[name='mailNotification']").hasClass('hidden')).toEqual(false);
});
+ it('unchecks share attribute when clicked on checked', function () {
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute",
+ default: true,
+ label: "test attribute",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [],
+ incompatibleAttributes: []
+ });
+
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'test-attribute', enabled: true }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+
+ listView.render();
+ listView.$el.find("input[name='test-attribute']").click();
+ expect(listView.$el.find("input[name='test-attribute']").is(':checked')).toEqual(false);
+ expect(updateShareStub.calledOnce).toEqual(true);
+ });
+
+ it('shows share attribute checkbox when checking required permission', function () {
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute",
+ default: true,
+ label: "test attribute",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [ OC.PERMISSION_UPDATE ],
+ incompatibleAttributes: []
+ });
+
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+
+ listView.render();
+
+ expect(listView.$el.find("input[name='test-attribute']").length > 0).toEqual(false);
+
+ updateShareStub.callsFake(function() {
+ // Updated share permission should now enable the permission
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'test-attribute', enabled: true }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ });
+
+ // Click on update should cause attribute test-attribute
+ // to appear as requiredPermissions got satisfied
+ listView.$el.find("input[name='update']").click();
+ expect(listView.$el.find("input[name='test-attribute']").is(':checked')).toEqual(true);
+ expect(updateShareStub.calledOnce).toEqual(true);
+ });
+
+ it('shows share attribute checkbox when unchecking incompatible attribute', function () {
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "incompatible-attribute",
+ default: true,
+ label: "incompatible attribute",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [ ],
+ incompatibleAttributes: []
+ });
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute",
+ default: true,
+ label: "test attribute",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [ ],
+ incompatibleAttributes: [{ scope: 'test', key: 'incompatible-attribute', enabled: true }]
+ });
+
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'incompatible-attribute', enabled: true }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+
+ listView.render();
+
+ expect(listView.$el.find("input[name='incompatible-attribute']").is(':checked')).toEqual(true);
+ expect(listView.$el.find("input[name='test-attribute']").length > 0).toEqual(false);
+
+ updateShareStub.callsFake(function() {
+ // Updated share permission should now enable the permission
+ shareModel.set('shares', [{
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [
+ { scope: 'test', key: 'incompatible-attribute', enabled: false },
+ { scope: 'test', key: 'test-attribute', enabled: true }
+ ],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }]);
+ });
+
+ listView.$el.find("input[name='incompatible-attribute']").click();
+
+ expect(listView.$el.find("input[name='incompatible-attribute']").is(':checked')).toEqual(false);
+ expect(listView.$el.find("input[name='test-attribute']").is(':checked')).toEqual(true);
+ expect(updateShareStub.calledOnce).toEqual(true);
+ });
+
+ it('prevents checking/unchecking attribute when reshared', function () {
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute-checked",
+ default: true,
+ label: "test attribute checked",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [],
+ incompatibleAttributes: []
+ });
+ shareModel.registerShareAttribute({
+ scope: "test",
+ key: "test-attribute-unchecked",
+ default: false,
+ label: "test attribute unchecked",
+ shareType : [],
+ incompatiblePermissions: [],
+ requiredPermissions: [],
+ incompatibleAttributes: []
+ });
+
+ shareModel.set('reshare', {
+ uid_owner: 100
+ });
+ shareModel.set('shares', [
+ {
+ id: 100,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'test-attribute-checked', enabled: true }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ },
+ {
+ id: 101,
+ item_source: 123,
+ permissions: 1,
+ attributes: [{ scope: 'test', key: 'test-attribute-unchecked', enabled: false }],
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user2',
+ share_with_displayname: 'User Two'
+ }
+ ]);
+
+ listView.render();
+
+ // Click should have no action
+ listView.$el.find("input[name='test-attribute-checked']").click();
+ expect(listView.$el.find("input[name='test-attribute-checked']").is(':checked')).toEqual(true);
+ listView.$el.find("input[name='test-attribute-unchecked']").click();
+ expect(listView.$el.find("input[name='test-attribute-unchecked']").is(':checked')).toEqual(false);
+
+ // Update never called when clicked
+ expect(updateShareStub.called).toEqual(false);
+ });
});
});
diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js
index c71fec21700a..261408d42f11 100644
--- a/core/js/tests/specs/shareitemmodelSpec.js
+++ b/core/js/tests/specs/shareitemmodelSpec.js
@@ -47,13 +47,13 @@ describe('OC.Share.ShareItemModel', function() {
sharePermissions: 31
});
- var attributes = {
+ var properties = {
itemType: fileInfoModel.isDirectory() ? 'folder' : 'file',
itemSource: fileInfoModel.get('id'),
possiblePermissions: fileInfoModel.get('sharePermissions')
};
configModel = new OC.Share.ShareConfigModel();
- model = new OC.Share.ShareItemModel(attributes, {
+ model = new OC.Share.ShareItemModel(properties, {
configModel: configModel,
fileInfoModel: fileInfoModel
});
@@ -118,7 +118,7 @@ describe('OC.Share.ShareItemModel', function() {
fetchReshareStub = null;
});
- it('populates attributes with parsed response', function() {
+ it('populates properties with parsed response', function() {
/* jshint camelcase: false */
fetchReshareDeferred.resolve(makeOcsResponse([
{
@@ -133,6 +133,7 @@ describe('OC.Share.ShareItemModel', function() {
id: 100,
item_source: 123,
permissions: 31,
+ attributes: '[{"scope":"test","key":"test","enabled":false}]',
share_type: OC.Share.SHARE_TYPE_USER,
share_with: 'user1',
share_with_displayname: 'User One'
@@ -140,6 +141,7 @@ describe('OC.Share.ShareItemModel', function() {
id: 101,
item_source: 123,
permissions: 31,
+ attributes: '[]',
share_type: OC.Share.SHARE_TYPE_GROUP,
share_with: 'group',
share_with_displayname: 'group'
@@ -147,6 +149,7 @@ describe('OC.Share.ShareItemModel', function() {
id: 102,
item_source: 123,
permissions: 31,
+ attributes: '[]',
share_type: OC.Share.SHARE_TYPE_REMOTE,
share_with: 'foo@bar.com/baz',
share_with_displayname: 'foo@bar.com/baz'
@@ -200,6 +203,7 @@ describe('OC.Share.ShareItemModel', function() {
expect(shares.length).toEqual(3);
expect(shares[0].id).toEqual(100);
expect(shares[0].permissions).toEqual(31);
+ expect(shares[0].attributes).toEqual([{ scope: 'test', key: 'test', enabled: false }]);
expect(shares[0].share_type).toEqual(OC.Share.SHARE_TYPE_USER);
expect(shares[0].share_with).toEqual('user1');
expect(shares[0].share_with_displayname).toEqual('User One');
@@ -587,6 +591,328 @@ describe('OC.Share.ShareItemModel', function() {
});
});
+ describe('share attributes', function() {
+ beforeEach(function() {
+ model.set({
+ reshare: {},
+ shares: [],
+ _registeredAttributes: []
+ });
+ });
+
+ /**
+ * Creates dummy attribute
+ *
+ * @return {OC.Share.Types.RegisteredShareAttribute} registered attribute
+ */
+ function createRegisteredAttribute() {
+ return {
+ scope: "test",
+ key: "test",
+ default: true,
+ label: "test",
+ shareType : [
+ OC.Share.SHARE_TYPE_GROUP,
+ OC.Share.SHARE_TYPE_USER
+ ],
+ incompatiblePermissions: [],
+ requiredPermissions: [],
+ incompatibleAttributes: []
+ };
+ }
+
+ /**
+ * Parses attributes of share creation
+ * request (request send to the server)
+ *
+ * @return {OC.Share.Types.ShareAttribute[]}
+ */
+ function parseLastRequestAttributes() {
+ var requestBody = OC.parseQueryString(fakeServer.requests[0].requestBody);
+
+ var i = -1;
+ var attributes = [];
+ _.map(Object.keys(requestBody), function(key) {
+ if (key.indexOf('attributes') !== -1) {
+ if (key.indexOf('scope') !== -1) {
+ i = i + 1;
+ attributes.push({});
+ attributes[i].scope = requestBody[key];
+ }
+ if (key.indexOf('key') !== -1) {
+ attributes[i].key = requestBody[key];
+ }
+ if (key.indexOf('enabled') !== -1) {
+ attributes[i].enabled = JSON.parse(requestBody[key]);
+ }
+ }
+ });
+
+ //console.log(requestBody);
+ return attributes;
+ }
+
+ /**
+ * Tests sharing with the given possible attributes
+ *
+ * @param {int} permissionsToSet
+ * @param {OC.Share.Types.RegisteredShareAttribute[]} attributesToRegister
+ * @param {Object} sharePropertiesToUpdate
+ * @return {OC.Share.Types.ShareAttribute[]}
+ */
+ function testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate) {
+ model.set({
+ permissions: permissionsToSet,
+ possiblePermissions: permissionsToSet
+ });
+
+ _.map(attributesToRegister, function (attributeToRegister) {
+ model.registerShareAttribute(attributeToRegister);
+ });
+
+ if (Object.keys(sharePropertiesToUpdate).length > 0) {
+ // if there are properties to update, update share
+ model.updateShare(123, sharePropertiesToUpdate, {});
+ } {
+ // no share properties to update, so new share
+ model.addShare({
+ shareType: OC.Share.SHARE_TYPE_USER,
+ shareWith: 'user2'
+ });
+ }
+
+ return parseLastRequestAttributes();
+ }
+
+ describe('new share', function() {
+
+ it('returns no attributes when no registered attributes', function () {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE;
+ var attributesToRegister = [];
+ var sharePropertiesToUpdate = {};
+
+ // define expected result and test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual([]);
+ });
+
+ it('uses registered attributes as default attributes', function () {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE;
+ var attr1 = createRegisteredAttribute();
+ var attributesToRegister = [
+ attr1
+ ];
+ var sharePropertiesToUpdate = {};
+
+ // define expected result
+ var attributesToExpect = [
+ {scope: "test", key: "test", enabled: true}
+ ];
+
+ // test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual(attributesToExpect);
+ });
+
+ it('properly filters registered attributes', function () {
+ var attr1 = createRegisteredAttribute();
+ attr1.key = "requires-create";
+ attr1.incompatiblePermissions = [];
+ attr1.requiredPermissions = [OC.PERMISSION_CREATE];
+ attr1.incompatibleAttributes = [];
+
+ var attr2 = createRegisteredAttribute();
+ attr2.key = "requires-update";
+ attr2.incompatiblePermissions = [];
+ attr2.requiredPermissions = [OC.PERMISSION_UPDATE];
+ attr2.incompatibleAttributes = [];
+
+ var attr3 = createRegisteredAttribute();
+ attr3.key = "incompatible-create";
+ attr3.incompatiblePermissions = [OC.PERMISSION_CREATE];
+ attr3.requiredPermissions = [];
+ attr3.incompatibleAttributes = [];
+
+ var attr4 = createRegisteredAttribute();
+ attr4.key = "incompatible-update";
+ attr4.incompatiblePermissions = [OC.PERMISSION_UPDATE];
+ attr4.requiredPermissions = [];
+ attr4.incompatibleAttributes = [];
+
+ var attr5 = createRegisteredAttribute();
+ attr5.key = "incompatible-attribute-requires-update-true";
+ attr5.incompatiblePermissions = [];
+ attr5.requiredPermissions = [];
+ attr5.incompatibleAttributes = [{
+ scope: "test",
+ key: "requires-update",
+ enabled: true
+ }];
+
+ var attr6 = createRegisteredAttribute();
+ attr6.key = "incompatible-attribute-requires-update-false";
+ attr6.incompatiblePermissions = [];
+ attr6.requiredPermissions = [];
+ attr6.incompatibleAttributes = [{
+ scope: "test",
+ key: "requires-update",
+ enabled: false
+ }];
+
+ // register attribute
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE;
+ var attributesToRegister = [
+ attr1, attr2, attr3, attr4, attr5, attr6
+ ];
+
+ // this test does not update existing share
+ var sharePropertiesToUpdate = {};
+
+ // expect that new created share will have following attributes
+ var attributesToExpect = [
+ {scope: "test", key: "requires-update", enabled: true},
+ {scope: "test", key: "incompatible-create", enabled: true},
+ {
+ scope: "test",
+ key: "incompatible-attribute-requires-update-false",
+ enabled: true
+ }
+ ];
+
+ // test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual(attributesToExpect);
+ });
+ });
+
+ describe('update share', function() {
+ it('returns no attributes when update with new attribute but none registered (error handling scenario)', function() {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE;
+ var attributesToRegister = [];
+ var sharePropertiesToUpdate = {
+ attributes: [
+ { scope: "test", key: "test", enabled: false }
+ ],
+ permissions: permissionsToSet
+ };
+
+ // define expected result and test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual([]);
+ });
+
+ it('updates attribute with new enabled value correctly', function() {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE;
+
+ var attr1 = createRegisteredAttribute();
+ attr1.scope = "test";
+ attr1.key = "test";
+ attr1.default = true;
+ var attributesToRegister = [attr1];
+
+ var sharePropertiesToUpdate = {
+ attributes: [
+ { scope: "test", key: "test", enabled: false }
+ ],
+ permissions: permissionsToSet
+ };
+
+ // define expected result
+ var attributesToExpect = [
+ { scope: "test", key: "test", enabled: false }
+ ];
+
+ // test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual(attributesToExpect);
+ });
+
+ it('uses/hides attributes with permission filters of registered attributes', function() {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ;
+
+ var attr1 = createRegisteredAttribute();
+ attr1.key = "incompatible-create";
+ attr1.incompatiblePermissions = [ OC.PERMISSION_CREATE ];
+ attr1.requiredPermissions = [];
+ attr1.incompatibleAttributes = [];
+ var attr2 = createRegisteredAttribute();
+ attr2.key = "required-create";
+ attr2.incompatiblePermissions = [];
+ attr2.requiredPermissions = [ OC.PERMISSION_CREATE ];
+ attr2.incompatibleAttributes = [];
+
+ var attributesToRegister = [attr1, attr2];
+
+ var sharePropertiesToUpdate = {
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_CREATE
+ };
+
+ // define expected result
+ var attributesToExpect = [
+ { scope: "test", key: "required-create", enabled: true }
+ ];
+
+ // test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual(attributesToExpect);
+ });
+
+ it('uses/hides attributes with attribute filter of registered attributes when permission changes', function() {
+ // define test
+ var permissionsToSet = OC.PERMISSION_READ | OC.PERMISSION_UPDATE;
+
+ // test attribute which is enabled only without update permission
+ var attr1 = createRegisteredAttribute();
+ attr1.key = "incompatible-update";
+ attr1.incompatiblePermissions = [ OC.PERMISSION_UPDATE ];
+ attr1.requiredPermissions = [ ];
+ attr1.incompatibleAttributes = [];
+
+ // test attribute which is not available when review attr is enabled
+ var attr2 = createRegisteredAttribute();
+ attr2.key = "incompatible-with-attribute";
+ attr2.incompatiblePermissions = [];
+ attr2.requiredPermissions = [];
+ attr2.incompatibleAttributes = [{ scope: "test", key: "incompatible-update", enabled: true }];
+
+ // test attribute that can always be registered
+ var attr3 = createRegisteredAttribute();
+ attr3.key = "test-attribute";
+ attr3.incompatiblePermissions = [];
+ attr3.requiredPermissions = [];
+ attr3.incompatibleAttributes = [];
+
+ var attributesToRegister = [attr1, attr2, attr3];
+
+ var sharePropertiesToUpdate = {
+ permissions: OC.PERMISSION_READ
+ };
+
+ // define expected result - restricted-options should not appear
+ var attributesToExpect = [
+ { scope: "test", key: "incompatible-update", enabled: true },
+ { scope: "test", key: "test-attribute", enabled: true }
+ ];
+
+ // test
+ expect(
+ testShareWithAttributes(permissionsToSet, attributesToRegister, sharePropertiesToUpdate)
+ ).toEqual(attributesToExpect);
+ });
+ });
+ });
+
describe('creating shares', function() {
it('sends POST method to endpoint with passed values', function() {
model.addShare({
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index a0c7daae293a..164c8bdf041d 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -222,16 +222,18 @@ public static function updateApp($info= [], $isShipped=false) {
$info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
$currentDir = OC_App::getAppPath($info['id']);
+ if (\is_dir("$currentDir/.git")) {
+ throw new AppAlreadyInstalledException("App <{$info['id']}> is a git clone - it will not be updated.");
+ }
+
$basedir = OC_App::getInstallPath();
$basedir .= '/';
$basedir .= $info['id'];
- if ($currentDir !== false && \is_writable($currentDir)) {
+ if ($currentDir !== false && OC_App::isAppDirWritable($info['id'])) {
$basedir = $currentDir;
}
- if (\is_dir("$basedir/.git")) {
- throw new AppAlreadyInstalledException("App <{$info['id']}> is a git clone - it will not be updated.");
- }
+
if (\is_dir($basedir)) {
OC_Helper::rmdirr($basedir);
}
diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php
index 5cd34e9e2228..1a12c117622c 100644
--- a/lib/private/legacy/app.php
+++ b/lib/private/legacy/app.php
@@ -568,6 +568,20 @@ public static function getAppWebPath($appId) {
*/
public static function isAppDirWritable($appId) {
$path = self::getAppPath($appId);
+ // Check if the parent directory is marked as writable in config.php
+ if ($path !== false) {
+ $appDir = \substr($path, 0, -\strlen("/$appId"));
+ foreach (OC::$APPSROOTS as $dir) {
+ if ($dir['path'] !== $appDir) {
+ continue;
+ }
+ if (!isset($dir['writable'])
+ || $dir['writable'] !== true
+ ) {
+ return false;
+ }
+ }
+ }
return ($path !== false) ? \is_writable($path) : false;
}
@@ -942,6 +956,7 @@ public static function getAppVersions() {
* @return bool
*/
public static function updateApp($appId) {
+ \OC::$server->getAppManager()->clearAppsCache();
$appPath = self::getAppPath($appId);
if ($appPath === false) {
return false;
@@ -958,7 +973,6 @@ public static function updateApp($appId) {
}
self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
- \OC::$server->getAppManager()->clearAppsCache();
// run upgrade code
if (\file_exists($appPath . '/appinfo/update.php')) {
self::loadApp($appId, false);
diff --git a/tests/Core/Command/Background/Queue/StatusTest.php b/tests/Core/Command/Background/Queue/StatusTest.php
index 00e455b242bc..61a2c5dad5bf 100644
--- a/tests/Core/Command/Background/Queue/StatusTest.php
+++ b/tests/Core/Command/Background/Queue/StatusTest.php
@@ -44,6 +44,12 @@ public function setUp() {
parent::setUp();
$this->jobList = $this->createMock(IJobList::class);
+ $command = new Status($this->jobList);
+ $command->setApplication(new Application());
+ $this->commandTester = new CommandTester($command);
+ }
+
+ public function testCommandInput() {
$this->jobList->expects($this->any())->method('listJobs')
->willReturnCallback(function (\Closure $callBack) {
$job = new RegularJob();
@@ -51,12 +57,6 @@ public function setUp() {
$callBack($job);
});
- $command = new Status($this->jobList);
- $command->setApplication(new Application());
- $this->commandTester = new CommandTester($command);
- }
-
- public function testCommandInput() {
$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
$expected = <<assertContains($expected, $output);
+ }
+
+ public function testJobWithArray() {
+ $this->jobList->expects($this->any())->method('listJobs')
+ ->willReturnCallback(function (\Closure $callBack) {
+ $job = new RegularJob();
+ $job->setId(666);
+ $job->setArgument(['k'=> 'v','test2']);
+ $callBack($job);
+ });
+ $this->commandTester->execute([]);
+ $output = $this->commandTester->getDisplay();
+ $expected = <<assertContains($expected, $output);
diff --git a/tests/TestHelpers/SharingHelper.php b/tests/TestHelpers/SharingHelper.php
index 1b65b90bbc84..e2a3a5bce04f 100644
--- a/tests/TestHelpers/SharingHelper.php
+++ b/tests/TestHelpers/SharingHelper.php
@@ -50,7 +50,7 @@ class SharingHelper {
* 1 = read; 2 = update; 4 = create;
* 8 = delete; 16 = share; 31 = all
* 15 = change
- * 7 = uploadwriteonly
+ * 5 = uploadwriteonly
* (default: 31, for public shares: 1)
* Pass either the (total) number,
* or the keyword,
@@ -171,7 +171,7 @@ public static function getPermissionSum($permissions) {
'read' => 1,
'update' => 2,
'create' => 4,
- 'uploadwriteonly' => 7,
+ 'uploadwriteonly' => 5,
'delete' => 8,
'change' => 15,
'share' => 16,
diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml
index 37672a51fa6e..c139a7faeaf1 100644
--- a/tests/acceptance/config/behat.yml
+++ b/tests/acceptance/config/behat.yml
@@ -389,6 +389,7 @@ default:
- WebUIGeneralContext:
- WebUILoginContext:
- WebUISharingContext:
+ - EmailContext:
- WebUIAdminSharingSettingsContext:
webUISharingInternalUsers:
@@ -400,6 +401,7 @@ default:
- WebUIGeneralContext:
- WebUILoginContext:
- WebUISharingContext:
+ - EmailContext:
- WebUIAdminSharingSettingsContext:
webUISharingNotifications:
diff --git a/tests/acceptance/features/apiShareManagement/createShare.feature b/tests/acceptance/features/apiShareManagement/createShare.feature
index 6381817fc7cc..e76b9a22cdc0 100644
--- a/tests/acceptance/features/apiShareManagement/createShare.feature
+++ b/tests/acceptance/features/apiShareManagement/createShare.feature
@@ -203,13 +203,13 @@ Feature: sharing
And user "user0" has created folder "/afolder"
When user "user0" creates a public link share using the sharing API with settings
| path | /afolder |
- | permissions | 7 |
+ | permissions | 5 |
Then the OCS status code should be ""
And the HTTP status code should be "200"
And the share fields of the last share should include
| id | A_NUMBER |
| share_type | 3 |
- | permissions | 7 |
+ | permissions | 5 |
Examples:
| ocs_api_version | ocs_status_code |
| 1 | 100 |
diff --git a/tests/acceptance/features/apiShareManagement/updateShare.feature b/tests/acceptance/features/apiShareManagement/updateShare.feature
index 5a6afe829a29..39c301f04ec2 100644
--- a/tests/acceptance/features/apiShareManagement/updateShare.feature
+++ b/tests/acceptance/features/apiShareManagement/updateShare.feature
@@ -149,7 +149,7 @@ Feature: sharing
When the user creates a public link share using the sharing API with settings
| path | FOLDER |
And the user updates the last share using the sharing API with
- | permissions | 7 |
+ | permissions | 15 |
And the user gets the info of the last share using the sharing API
Then the OCS status code should be ""
And the HTTP status code should be "200"
@@ -175,6 +175,39 @@ Feature: sharing
| 1 | 100 |
| 2 | 200 |
+ @public_link_share-feature-required
+ Scenario Outline: Creating a new public link share, updating its permissions to view download and upload and getting its info
+ Given using OCS API version ""
+ And as user "user0"
+ When the user creates a public link share using the sharing API with settings
+ | path | FOLDER |
+ And the user updates the last share using the sharing API with
+ | permissions | 7 |
+ And the user gets the info of the last share using the sharing API
+ Then the OCS status code should be ""
+ And the HTTP status code should be "200"
+ And the fields of the last response should include
+ | id | A_NUMBER |
+ | item_type | folder |
+ | item_source | A_NUMBER |
+ | share_type | 3 |
+ | file_source | A_NUMBER |
+ | file_target | /FOLDER |
+ | permissions | 7 |
+ | stime | A_NUMBER |
+ | token | A_TOKEN |
+ | storage | A_NUMBER |
+ | mail_send | 0 |
+ | uid_owner | user0 |
+ | file_parent | A_NUMBER |
+ | displayname_owner | User Zero |
+ | url | AN_URL |
+ | mimetype | httpd/unix-directory |
+ Examples:
+ | ocs_api_version | ocs_status_code |
+ | 1 | 100 |
+ | 2 | 200 |
+
@public_link_share-feature-required
Scenario Outline: Creating a new public link share, updating publicUpload option and getting its info
Given using OCS API version ""
@@ -406,4 +439,4 @@ Feature: sharing
Examples:
| ocs_api_version | ocs_status_code |
| 1 | 100 |
- | 2 | 200 |
\ No newline at end of file
+ | 2 | 200 |
diff --git a/tests/acceptance/features/apiShareOperations/uploadToShare.feature b/tests/acceptance/features/apiShareOperations/uploadToShare.feature
index 1781c21a9f77..6a0f8902931c 100644
--- a/tests/acceptance/features/apiShareOperations/uploadToShare.feature
+++ b/tests/acceptance/features/apiShareOperations/uploadToShare.feature
@@ -328,8 +328,5 @@ Feature: sharing
| path | FOLDER |
| permissions | uploadwriteonly |
When the public uploads file "test.txt" with content "test" using the public WebDAV API
- And the public uploads file "test.txt" with content "test2" with autorename mode using the public WebDAV API
- And the public uploads file "test.txt" with content "test3" with autorename mode using the public WebDAV API
+ And the public uploads file "test.txt" with content "test2" using the public WebDAV API
Then the content of file "/FOLDER/test.txt" for user "user0" should be "test"
- And the content of file "/FOLDER/test (2).txt" for user "user0" should be "test2"
- And the content of file "/FOLDER/test (3).txt" for user "user0" should be "test3"
diff --git a/tests/acceptance/features/bootstrap/WebUIFilesContext.php b/tests/acceptance/features/bootstrap/WebUIFilesContext.php
index c17eacbebf1e..e123752a342d 100644
--- a/tests/acceptance/features/bootstrap/WebUIFilesContext.php
+++ b/tests/acceptance/features/bootstrap/WebUIFilesContext.php
@@ -1002,6 +1002,21 @@ public function theUserChoosesToInTheUploadDialog($label) {
$pageObject->waitForUploadProgressbarToFinish();
}
+ /**
+ * @When the user uploads file :name :number_of times using webUI
+ *
+ * @param string $name
+ * @param int $number_of
+ *
+ * @return void
+ */
+ public function theUserClicksUploadAndCancelMultipleTimes($name, $number_of) {
+ for ($i = 0; $i < $number_of; $i++) {
+ $this->theUserUploadsFileUsingTheWebUI($name);
+ \usleep(STANDARD_SLEEP_TIME_MICROSEC);
+ }
+ }
+
/**
* @Then /^the (?:deleted|moved) elements should (not|)\s?be listed on the webUI$/
*
diff --git a/tests/acceptance/features/bootstrap/WebUISharingContext.php b/tests/acceptance/features/bootstrap/WebUISharingContext.php
index 630ce59ae421..43d5b7b535a0 100644
--- a/tests/acceptance/features/bootstrap/WebUISharingContext.php
+++ b/tests/acceptance/features/bootstrap/WebUISharingContext.php
@@ -675,7 +675,7 @@ public function userReactsToShareOfferedByUsingWebUI(
$fileRows = $this->sharedWithYouPage->findAllFileRowsByName(
$share, $this->getSession()
);
-
+
$found = false;
foreach ($fileRows as $fileRow) {
if ($offeredBy === $fileRow->getSharer()) {
@@ -776,6 +776,40 @@ public function removesPublicLinkAtCertainPosition($number, $entryName) {
$this->sharingDialog->removePublicLink($session, $number);
}
+ /**
+ * @When the user sends the share notification by email using the webUI
+ *
+ * @return void
+ */
+ public function theUserSendsTheShareNotificationByEmailUsingTheWebui() {
+ PHPUnit\Framework\Assert::assertNotNull(
+ $this->sharingDialog, "Sharing Dialog is not open"
+ );
+ $this->sharingDialog->sendShareNotificationByEmail($this->getSession());
+ }
+
+ /**
+ * @Then the user should not be able to send the share notification by email using the webUI
+ *
+ * @return void
+ */
+ public function theUserShouldNotBeAbleToSendTheShareNotificationByEmailUsingTheWebui() {
+ $errorMessage = "";
+ PHPUnit\Framework\Assert::assertNotNull(
+ $this->sharingDialog, "Sharing Dialog is not open"
+ );
+ try {
+ $this->sharingDialog->sendShareNotificationByEmail($this->getSession());
+ } catch (Exception $e) {
+ $errorMessage = $e->getMessage();
+ }
+ PHPUnit\Framework\Assert::assertContains(
+ "could not find notify by email button",
+ $errorMessage,
+ "User was not expected to be able to send the share notification by email but was"
+ );
+ }
+
/**
* @Then the public should not get access to the publicly shared file
*
@@ -956,7 +990,7 @@ public function fileFolderShouldBeMarkedAsSharedBy(
$this->filesPage->closeDetailsDialog();
} catch (Exception $e) {
}
-
+
$row = $this->filesPage->findFileRowByName($itemName, $this->getSession());
$sharingBtn = $row->findSharingButton();
PHPUnit\Framework\Assert::assertSame(
diff --git a/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php b/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php
index 9250cb5c5181..f869dc600d31 100644
--- a/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php
+++ b/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php
@@ -58,6 +58,7 @@ class SharingDialog extends OwncloudPage {
private $noSharingMessageXpath = "//div[@class='noSharingPlaceholder']";
private $publicLinkRemoveBtnXpath = "//div[contains(@class, 'removeLink')]";
private $publicLinkTitleXpath = "//span[@class='link-entry--title']";
+ private $notifyByEmailBtnXpath = "//input[@name='mailNotification']";
private $shareWithListXpath = "//ul[@id='shareWithList']/li";
private $userNameSpanXpath = "//span[contains(@class,'username')]";
@@ -209,7 +210,7 @@ private function shareWithUserOrGroup(
$userElements = $autocompleteNodeElement->findAll(
"xpath", $this->autocompleteItemsTextXpath
);
-
+
$userFound = false;
foreach ($userElements as $user) {
if ($this->getTrimmedText($user) === $nameToMatch) {
@@ -620,6 +621,25 @@ public function isUserPresentInShareWithList($username) {
return false;
}
+ /**
+ * send share notification by email to the sharee
+ *
+ * @param Session $session
+ *
+ * @return void
+ */
+ public function sendShareNotificationByEmail($session) {
+ $notifyByEmailBtn = $this->find("xpath", $this->notifyByEmailBtnXpath);
+ $this->assertElementNotNull(
+ $notifyByEmailBtn,
+ __METHOD__ .
+ " xpath $this->notifyByEmailBtnXpath " .
+ "could not find notify by email button"
+ );
+ $notifyByEmailBtn->click();
+ $this->waitForAjaxCallsToStartAndFinish($session);
+ }
+
/**
* waits for the dialog to appear
*
diff --git a/tests/acceptance/features/lib/LoginPage.php b/tests/acceptance/features/lib/LoginPage.php
index 916d47b8105d..3f839084844d 100644
--- a/tests/acceptance/features/lib/LoginPage.php
+++ b/tests/acceptance/features/lib/LoginPage.php
@@ -44,6 +44,7 @@ class LoginPage extends OwncloudPage {
protected $lostPasswordId = "lost-password";
protected $setPasswordErrorMessageId = "error-message";
+ protected $setPasswordFormXpath = "//form[@id='reset-password' or @id='set-password']";
protected $lostPasswordResetErrorXpath = "//li[contains(@class,'error')]";
protected $imprintUrlXpath = "//a[contains(text(),'Imprint')]";
protected $privacyPolicyXpath = "//a[contains(text(),'Privacy Policy')]";
@@ -204,6 +205,14 @@ public function getLostPasswordResetErrorMessage() {
* @return void
*/
public function resetThePassword($newPassword, $confirmNewPassword, Session $session) {
+ $form = $this->waitTillElementIsNotNull($this->setPasswordFormXpath);
+ $this->assertElementNotNull(
+ $form,
+ __METHOD__ .
+ " xpath $this->setPasswordFormXpath " .
+ 'could not find set password form.'
+ );
+
$this->fillField($this->passwordInputId, $newPassword);
$this->fillField($this->confirmPasswordInputId, $confirmNewPassword);
$this->findById($this->submitLoginId)->click();
diff --git a/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature b/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature
index 2c24dd7e7264..016b8b89cbcd 100644
--- a/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature
+++ b/tests/acceptance/features/webUISharingInternalGroups/shareWithGroups.feature
@@ -130,3 +130,76 @@ Feature: Sharing files and folders with internal groups
And the user types "system-group" in the share-with-field
Then a tooltip with the text "No users or groups found for system-group" should be shown near the share-with-field on the webUI
And the autocomplete list should not be displayed on the webUI
+
+ @mailhog
+ Scenario: user should be able to send notification by email when allow share mail notification has been enabled
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And user "user3" has logged in using the webUI
+ And user "user3" has shared file "lorem.txt" with group "grp1"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then a notification should be displayed on the webUI with the text "Email notification was sent!"
+ And the email address "user1@example.org" should have received an email with the body containing
+ """
+ just letting you know that User Three shared lorem.txt with you.
+ """
+ And the email address "user2@example.org" should have received an email with the body containing
+ """
+ just letting you know that User Three shared lorem.txt with you.
+ """
+
+ @mailhog
+ Scenario: user should not be able to send notification by email more than once
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And user "user3" has logged in using the webUI
+ And user "user3" has shared file "lorem.txt" with group "grp1"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then the user should not be able to send the share notification by email using the webUI
+ When the user reloads the current page of the webUI
+ And the user opens the share dialog for file "lorem.txt"
+ Then the user should not be able to send the share notification by email using the webUI
+
+ Scenario: user should not be able to send notification by email when allow share mail notification has been disabled
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "no"
+ And user "user3" has logged in using the webUI
+ And user "user3" has shared file "lorem.txt" with group "grp1"
+ When the user opens the share dialog for file "lorem.txt"
+ Then the user should not be able to send the share notification by email using the webUI
+
+ @mailhog
+ Scenario: user should not get an email notification if the user is added to the group after the mail notification was sent
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And user "user0" has been created with default attributes
+ And user "user3" has logged in using the webUI
+ And user "user3" has shared file "lorem.txt" with group "grp1"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then a notification should be displayed on the webUI with the text "Email notification was sent!"
+ When the administrator adds user "user0" to group "grp1" using the provisioning API
+ Then the email address "user0@example.org" should not have received an email
+
+ @mailhog
+ Scenario: user should get an error message when trying to send notification by email to the group where some user have set up their email and others haven't
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And these users have been created:
+ | username |
+ | brand-new-user |
+ | off-brand-new-user |
+ And user "brand-new-user" has been added to group "grp1"
+ And user "off-brand-new-user" has been added to group "grp1"
+ And user "user3" has logged in using the webUI
+ And user "user3" has shared file "lorem.txt" with group "grp1"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then dialog should be displayed on the webUI
+ | title | content |
+ | Email notification not sent | Couldn't send mail to following recipient(s): brand-new-user, off-brand-new-user |
+ And the email address "user1@example.org" should have received an email with the body containing
+ """
+ just letting you know that User Three shared lorem.txt with you.
+ """
+ And the email address "user2@example.org" should have received an email with the body containing
+ """
+ just letting you know that User Three shared lorem.txt with you.
+ """
diff --git a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature
index 43993a7d3f13..453806f52018 100644
--- a/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature
+++ b/tests/acceptance/features/webUISharingInternalUsers/shareWithUsers.feature
@@ -232,4 +232,50 @@ Feature: Sharing files and folders with internal users
And the user deletes share with user "User Two" for the current file
Then the user "User Two" should not be in share with user list
And file "lorem.txt" should not be listed in shared-with-others page on the webUI
- And as "user2" file "lorem (2).txt" should not exist
\ No newline at end of file
+ And as "user2" file "lorem (2).txt" should not exist
+
+ @mailhog
+ Scenario: user should be able to send notification by email when allow share mail notification has been enabled
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And user "user1" has logged in using the webUI
+ And user "user1" has shared file "lorem.txt" with user "user2"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then a notification should be displayed on the webUI with the text "Email notification was sent!"
+ And the email address "user2@example.org" should have received an email with the body containing
+ """
+ just letting you know that User One shared lorem.txt with you.
+ """
+
+ @mailhog
+ Scenario: user should get and error message when trying to send notification by email to a user who has not setup their email
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And these users have been created:
+ |username|password|
+ |user0 |1234 |
+ And user "user1" has logged in using the webUI
+ And user "user1" has shared file "lorem.txt" with user "user0"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then dialog should be displayed on the webUI
+ | title | content |
+ | Email notification not sent | Couldn't send mail to following recipient(s): user0 |
+
+ @mailhog
+ Scenario: user should not be able to send notification by email more than once
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "yes"
+ And user "user1" has logged in using the webUI
+ And user "user1" has shared file "lorem.txt" with user "user2"
+ And the user has opened the share dialog for file "lorem.txt"
+ When the user sends the share notification by email using the webUI
+ Then the user should not be able to send the share notification by email using the webUI
+ When the user reloads the current page of the webUI
+ And the user opens the share dialog for file "lorem.txt"
+ Then the user should not be able to send the share notification by email using the webUI
+
+ Scenario: user should not be able to send notification by email when allow share mail notification has been disabled
+ Given parameter "shareapi_allow_mail_notification" of app "core" has been set to "no"
+ And user "user1" has logged in using the webUI
+ And user "user1" has shared file "lorem.txt" with user "user2"
+ When the user opens the share dialog for file "lorem.txt"
+ Then the user should not be able to send the share notification by email using the webUI
diff --git a/tests/acceptance/features/webUISharingPublic/shareByPublicLink.feature b/tests/acceptance/features/webUISharingPublic/shareByPublicLink.feature
index e1f13132c35b..767cf08179e2 100644
--- a/tests/acceptance/features/webUISharingPublic/shareByPublicLink.feature
+++ b/tests/acceptance/features/webUISharingPublic/shareByPublicLink.feature
@@ -371,12 +371,19 @@ Feature: Share by public link
When the user creates a new public link for folder "simple-folder" using the webUI with
| permission | upload-write-without-modify |
And the public accesses the last created public link using the webUI
+ Then it should not be possible to delete file "lorem.txt" using the webUI
- Scenario: user edits the permission of an already existing public link from read-write to upload-write-without-overwrite
+ Scenario: user creates public link with view download and upload feature and uploads same file name and verifies no auto-renamed file seen in UI
Given the user has created a new public link for folder "simple-folder" using the webUI with
- | permission | read-write |
- When the user changes the permission of the public link named "Public link" to "upload-write-without-modify"
+ | permission | upload-write-without-modify |
And the public accesses the last created public link using the webUI
- When the user uploads file "lorem.txt" keeping both new and existing files using the webUI
- Then file "lorem.txt" should be listed on the webUI
- And file "lorem (2).txt" should be listed on the webUI
+ When the user uploads file "lorem.txt" 5 times using webUI
+ Then notifications should be displayed on the webUI with the text
+ | The file lorem.txt already exists |
+ | The file lorem.txt already exists |
+ | The file lorem.txt already exists |
+ | The file lorem.txt already exists |
+ | The file lorem.txt already exists |
+ And file "lorem.txt" should be listed on the webUI
+ And the content of "lorem.txt" should not have changed
+ And file "lorem (2).txt" should not be listed on the webUI
diff --git a/tests/lib/InstallerTest.php b/tests/lib/InstallerTest.php
index 4c8eac34afa7..ebaa4ec9e1fe 100644
--- a/tests/lib/InstallerTest.php
+++ b/tests/lib/InstallerTest.php
@@ -17,11 +17,12 @@ protected function setUp() {
parent::setUp();
Installer::removeApp(self::$appid);
+ \OC::$server->getConfig()->deleteAppValues(self::$appid);
}
protected function tearDown() {
Installer::removeApp(self::$appid);
-
+ \OC::$server->getConfig()->deleteAppValues(self::$appid);
parent::tearDown();
}
@@ -89,4 +90,69 @@ public function testUpdateApp() {
$this->assertNotEquals($oldVersionNumber, $newVersionNumber);
}
+
+ /**
+ * Tests that update is installed into writable app dir if the original app dir is not writable
+ */
+ public function testUpdateIntoWritableAppDir() {
+ $oldAppRoots = \OC::$APPSROOTS;
+ $relativePath = "/anotherdir";
+
+ // Install old version
+ $pathOfOldTestApp = __DIR__ . '/../data/testapp.zip';
+ $oldTmp = \OC::$server->getTempManager()->getTemporaryFile('.zip');
+ \OC_Helper::copyr($pathOfOldTestApp, $oldTmp);
+ $oldData = [
+ 'path' => $oldTmp,
+ 'source' => 'path',
+ 'appdata' => [
+ 'id' => 'testapp',
+ 'level' => 100,
+ ]
+ ];
+ $installResult = Installer::installApp($oldData);
+ $this->assertEquals('testapp', $installResult);
+ $oldAppPath = \OC_App::getAppPath(self::$appid);
+
+ // Mark the first app as dir non-writable and create the second as writable
+ $firstAppDir = \array_shift(\OC::$APPSROOTS);
+ $firstAppDir['writable'] = false;
+
+ $path = \dirname($firstAppDir['path']) . $relativePath;
+ \mkdir($path);
+ \clearstatcache();
+ \OC::$APPSROOTS = [
+ $firstAppDir,
+ [
+ 'path' => $path,
+ 'url' => $relativePath,
+ 'writable' => true
+ ]
+ ];
+ \OC::$server->getAppManager()->clearAppsCache();
+
+ // Update app
+ $pathOfNewTestApp = __DIR__ . '/../data/testapp2.zip';
+ $newTmp = \OC::$server->getTempManager()->getTemporaryFile('.zip');
+ \OC_Helper::copyr($pathOfNewTestApp, $newTmp);
+
+ $newData = [
+ 'path' => $newTmp,
+ 'source' => 'path',
+ 'appdata' => [
+ 'id' => 'testapp',
+ 'level' => 100,
+ ]
+ ];
+ $updateResult = Installer::updateApp($newData);
+ $this->assertTrue($updateResult);
+ $newAppPath = \OC_App::getAppPath(self::$appid);
+
+ $this->assertNotEquals($oldAppPath, $newAppPath);
+ $this->assertStringStartsWith($path, $newAppPath);
+
+ \OC_Helper::rmdirr($path);
+ \OC::$APPSROOTS = $oldAppRoots;
+ \OC::$server->getAppManager()->clearAppsCache();
+ }
}
diff --git a/version.php b/version.php
index 35d810c0bdab..e5d4cdac9645 100644
--- a/version.php
+++ b/version.php
@@ -25,10 +25,10 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = [10, 2, 0, 0];
+$OC_Version = [10, 2, 0, 5];
// The human readable string
-$OC_VersionString = '10.2.0 prealpha';
+$OC_VersionString = '10.2.0';
$OC_VersionCanBeUpgradedFrom = [[8, 2, 11],[9, 0, 9],[9, 1]];