diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 194c658e3a18..23834fa44de3 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -367,6 +367,21 @@ return model; }, + /** + * Displays the details view for the given file and + * selects the given tab + * + * @param {string} fileName file name for which to show details + * @param {string} [tabId] optional tab id to select + */ + showDetailsView: function(fileName, tabId) { + this._updateDetailsView(fileName); + if (tabId) { + this._detailsView.selectTab(tabId); + } + OC.Apps.showAppSidebar(); + }, + /** * Update the details view to display the given file * diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 20f1b046d35b..8d919d1466fa 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -54,9 +54,18 @@ \OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); \OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); -\OCP\Util::addScript('files_sharing', 'share'); -\OCP\Util::addScript('files_sharing', 'external'); -\OCP\Util::addStyle('files_sharing', 'sharetabview'); +$eventDispatcher = \OC::$server->getEventDispatcher(); +$eventDispatcher->addListener( + 'OCA\Files::loadAdditionalScripts', + function() { + \OCP\Util::addScript('files_sharing', 'share'); + \OCP\Util::addScript('files_sharing', 'sharetabview'); + \OCP\Util::addScript('files_sharing', 'external'); + \OCP\Util::addStyle('files_sharing', 'sharetabview'); + } +); + +// \OCP\Util::addStyle('files_sharing', 'sharetabview'); \OC::$server->getActivityManager()->registerExtension(function() { return new \OCA\Files_Sharing\Activity( diff --git a/apps/files_sharing/css/sharetabview.css b/apps/files_sharing/css/sharetabview.css index 42c9bee71731..0cc812e917c3 100644 --- a/apps/files_sharing/css/sharetabview.css +++ b/apps/files_sharing/css/sharetabview.css @@ -1,3 +1,75 @@ .app-files .shareTabView { min-height: 100px; } + +.shareTabView .oneline { white-space: nowrap; } + +.shareTabView .shareWithLoading { + padding-left: 10px; + position: relative; + right: 30px; + top: 2px; +} + +.shareTabView .shareWithRemoteInfo { + padding: 11px 0 11px 10px +} + +.shareTabView label { + font-weight:400; + white-space: nowrap; +} + +.shareTabView input[type="checkbox"] { + margin:0 3px 0 8px; + vertical-align: middle; +} + +.shareTabView input[type="text"], .shareTabView input[type="password"] { + width: 91%; + margin-left: 7px; +} + +.shareTabView form { + font-size: 100%; + margin-left: 0; + margin-right: 0; +} + +#shareWithList { + list-style-type:none; + padding:8px; +} + +#shareWithList li { + padding-top: 10px; + padding-bottom: 10px; + font-weight: bold; + line-height: 21px; + white-space: normal; +} + +#shareWithList .unshare img, #shareWithList .showCruds img { + vertical-align:text-bottom; /* properly align icons */ +} + +#shareWithList label input[type=checkbox]{ + margin-left: 0; + position: relative; +} +#shareWithList .username{ + padding-right: 8px; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 254px; + display: inline-block; + overflow: hidden; + vertical-align: middle; +} +#shareWithList li label{ + margin-right: 8px; +} + +.shareTabView .icon-loading-small { + margin-left: -30px; +} diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index c124d390d047..5290dfbb7d19 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -79,7 +79,9 @@ $files = fileList.$fileList.find('tr'); } _.each($files, function(file) { - OCA.Sharing.Util.updateFileActionIcon($(file)); + var $tr = $(file); + var shareStatus = OC.Share.statuses[$tr.data('id')]; + OCA.Sharing.Util._updateFileActionIcon($tr, !!shareStatus, shareStatus && shareStatus.link); }); } @@ -104,71 +106,59 @@ permissions: OC.PERMISSION_SHARE, icon: OC.imagePath('core', 'actions/share'), type: OCA.Files.FileActions.TYPE_INLINE, - actionHandler: function(filename, context) { - var $tr = context.$file; - var itemType = 'file'; - if ($tr.data('type') === 'dir') { - itemType = 'folder'; - } - var possiblePermissions = $tr.data('share-permissions'); - if (_.isUndefined(possiblePermissions)) { - possiblePermissions = $tr.data('permissions'); - } - - var appendTo = $tr.find('td.filename'); - // Check if drop down is already visible for a different file - if (OC.Share.droppedDown) { - if ($tr.attr('data-id') !== $('#dropdown').attr('data-item-source')) { - OC.Share.hideDropDown(function () { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); - }); - } else { - OC.Share.hideDropDown(); - } - } else { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); - } - $('#dropdown').on('sharesChanged', function(ev) { - // files app current cannot show recipients on load, so we don't update the - // icon when changed for consistency - if (context.fileList.$el.closest('#app-content-files').length) { - return; - } - var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname'); - var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname'); - recipients = recipients.concat(groupRecipients); - // note: we only update the data attribute because updateIcon() - // is called automatically after this event - if (recipients.length) { - $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients)); - } - else { - $tr.removeAttr('data-share-recipients'); - } - }); + actionHandler: function(fileName) { + fileList.showDetailsView(fileName, 'shareTabView'); } }); - OC.addScript('files_sharing', 'sharetabview').done(function() { - fileList.registerTabView(new OCA.Sharing.ShareTabView('shareTabView')); + var shareTab = new OCA.Sharing.ShareTabView('shareTabView'); + // detect changes and change the matching list entry + shareTab.on('sharesChanged', function(shareModel) { + var fileInfoModel = shareModel.fileInfoModel; + var $tr = fileList.findFileEl(fileInfoModel.get('name')); + OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel); + if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), shareModel.hasLinkShare())) { + // remove icon, if applicable + OC.Share.markFileAsShared($tr, false, false); + } }); + fileList.registerTabView(shareTab); + }, + + /** + * Update file list data attributes + */ + _updateFileListDataAttributes: function(fileList, $tr, shareModel) { + // files app current cannot show recipients on load, so we don't update the + // icon when changed for consistency + if (fileList.id === 'files') { + return; + } + var recipients = _.pluck(shareModel.get('shares'), 'share_with_displayname'); + // note: we only update the data attribute because updateIcon() + if (recipients.length) { + $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients)); + } + else { + $tr.removeAttr('data-share-recipients'); + } }, /** * Update the file action share icon for the given file * * @param $tr file element of the file to update + * @param {bool} hasUserShares true if a user share exists + * @param {bool} hasLinkShare true if a link share exists + * + * @return {bool} true if the icon was set, false otherwise */ - updateFileActionIcon: function($tr) { + _updateFileActionIcon: function($tr, hasUserShares, hasLinkShare) { // if the statuses are loaded already, use them for the icon // (needed when scrolling to the next page) - var shareStatus = OC.Share.statuses[$tr.data('id')]; - if (shareStatus || $tr.attr('data-share-recipients') || $tr.attr('data-share-owner')) { + if (hasUserShares || hasLinkShare || $tr.attr('data-share-recipients') || $tr.attr('data-share-owner')) { var permissions = $tr.data('permissions'); - var hasLink = !!(shareStatus && shareStatus.link); - OC.Share.markFileAsShared($tr, true, hasLink); + OC.Share.markFileAsShared($tr, true, hasLinkShare); if ((permissions & OC.PERMISSION_SHARE) === 0 && $tr.attr('data-share-owner')) { // if no share action exists because the admin disabled sharing for this user // we create a share notification action to inform the user about files @@ -187,7 +177,9 @@ return $result; }); } + return true; } + return false; }, /** diff --git a/apps/files_sharing/js/sharetabview.js b/apps/files_sharing/js/sharetabview.js index ee572b747ead..e24320604fb1 100644 --- a/apps/files_sharing/js/sharetabview.js +++ b/apps/files_sharing/js/sharetabview.js @@ -10,7 +10,9 @@ (function() { var TEMPLATE = - '
'; + '
' + + '
' + + '
'; /** * @memberof OCA.Sharing @@ -20,7 +22,12 @@ id: 'shareTabView', className: 'tab shareTabView', - _template: null, + template: function(params) { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template(params); + }, getLabel: function() { return t('files_sharing', 'Sharing'); @@ -30,23 +37,40 @@ * Renders this details view */ render: function() { - this.$el.empty(); - - if (!this._template) { - this._template = Handlebars.compile(TEMPLATE); + var self = this; + if (this._dialog) { + // remove/destroy older instance + this._dialog.model.off(); + this._dialog.remove(); + this._dialog = null; } if (this.model) { - console.log(this.model); - var owner = this.model.get('shareOwner'); - if (owner === OC.currentUser) { - owner = null; - } - this.$el.append(this._template({ - owner: owner - })); + this.$el.html(this.template()); + // TODO: the model should read these directly off the passed fileInfoModel + var attributes = { + itemType: this.model.isDirectory() ? 'folder' : 'file', + itemSource: this.model.get('id'), + possiblePermissions: this.model.get('sharePermissions') + }; + var configModel = new OC.Share.ShareConfigModel(); + var shareModel = new OC.Share.ShareItemModel(attributes, { + configModel: configModel, + fileInfoModel: this.model + }); + this._dialog = new OC.Share.ShareDialogView({ + configModel: configModel, + model: shareModel + }); + this.$el.find('.dialogContainer').append(this._dialog.$el); + this._dialog.render(); + this._dialog.model.fetch(); + this._dialog.model.on('change', function() { + self.trigger('sharesChanged', shareModel); + }); } else { + this.$el.empty(); // TODO: render placeholder text? } } diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js index b6368b901eeb..96a96f1b8146 100644 --- a/apps/files_sharing/tests/js/shareSpec.js +++ b/apps/files_sharing/tests/js/shareSpec.js @@ -97,7 +97,6 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(true); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder.svg'); expect($action.find('img').length).toEqual(1); @@ -116,7 +115,6 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('Shared'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder-shared.svg'); @@ -137,7 +135,6 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('Shared'); expect(OC.basename($action.find('img').attr('src'))).toEqual('public.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder-public.svg'); @@ -158,7 +155,6 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('User One'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder-shared.svg'); @@ -178,7 +174,6 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('Shared with User One, User Two'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder-shared.svg'); @@ -200,7 +195,6 @@ describe('OCA.Sharing.Util tests', function() { $tr = fileList.$el.find('tbody tr:first'); expect($tr.find('.action-share').length).toEqual(0); $action = $tr.find('.action-share-notification'); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('User One'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder-shared.svg'); @@ -225,7 +219,7 @@ describe('OCA.Sharing.Util tests', function() { }); }); describe('Share action', function() { - var showDropDownStub; + var shareTab; function makeDummyShareItem(displayName) { return { @@ -234,12 +228,35 @@ describe('OCA.Sharing.Util tests', function() { } beforeEach(function() { - showDropDownStub = sinon.stub(OC.Share, 'showDropDown', function() { - $('#testArea').append($('')); - }); + // make it look like not the "All files" list + fileList.id = 'test'; + shareTab = fileList._detailsView._tabViews[0]; }); afterEach(function() { - showDropDownStub.restore(); + shareTab = null; + }); + it('clicking share action opens sidebar and share tab', function() { + var showDetailsViewStub = sinon.stub(fileList, 'showDetailsView'); + + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + + var $tr = fileList.$el.find('tr:first'); + $tr.find('.action-share').click(); + + expect(showDetailsViewStub.calledOnce).toEqual(true); + expect(showDetailsViewStub.getCall(0).args[0]).toEqual('One.txt'); + expect(showDetailsViewStub.getCall(0).args[1]).toEqual('shareTabView'); + + showDetailsViewStub.restore(); }); it('adds share icon after sharing a non-shared file', function() { var $action, $tr; @@ -257,24 +274,20 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(true); - $tr.find('.action-share').click(); - expect(showDropDownStub.calledOnce).toEqual(true); - - // simulate what the dropdown does - var shares = {}; - OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user1', 'user2']; - OC.Share.itemShares[OC.Share.SHARE_TYPE_GROUP] = ['group1', 'group2']; - shares[OC.Share.SHARE_TYPE_USER] = _.map(['User One', 'User Two'], makeDummyShareItem); - shares[OC.Share.SHARE_TYPE_GROUP] = _.map(['Group One', 'Group Two'], makeDummyShareItem); - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + // simulate updating shares + shareTab._dialog.model.set({ + shares: [ + {share_with_displayname: 'User One'}, + {share_with_displayname: 'User Two'}, + {share_with_displayname: 'Group One'}, + {share_with_displayname: 'Group Two'} + ] + }); expect($tr.attr('data-share-recipients')).toEqual('Group One, Group Two, User One, User Two'); - OC.Share.updateIcon('file', 1); - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('Shared with Group One, Group Two, User One, User Two'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); }); @@ -294,23 +307,19 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(true); - $tr.find('.action-share').click(); - expect(showDropDownStub.calledOnce).toEqual(true); - - // simulate what the dropdown does - var shares = {}; - OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user1', 'user2', 'user3']; - shares[OC.Share.SHARE_TYPE_USER] = _.map(['User One', 'User Two', 'User Three'], makeDummyShareItem); - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + // simulate updating shares + shareTab._dialog.model.set({ + shares: [ + {share_with_displayname: 'User One'}, + {share_with_displayname: 'User Two'}, + {share_with_displayname: 'User Three'} + ] + }); expect($tr.attr('data-share-recipients')).toEqual('User One, User Three, User Two'); - OC.Share.updateIcon('file', 1); - - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('Shared with User One, User Three, User Two'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); }); @@ -331,20 +340,14 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(true); - $tr.find('.action-share').click(); - expect(showDropDownStub.calledOnce).toEqual(true); - - // simulate what the dropdown does - OC.Share.itemShares = {}; - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: {}})); + // simulate updating shares + shareTab._dialog.model.set({ + shares: [] + }); expect($tr.attr('data-share-recipients')).not.toBeDefined(); - - OC.Share.updateIcon('file', 1); - expect($action.hasClass('permanent')).toEqual(true); }); it('keep share text after updating reshare', function() { var $action, $tr; @@ -363,23 +366,15 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(true); - $tr.find('.action-share').click(); - expect(showDropDownStub.calledOnce).toEqual(true); - - // simulate what the dropdown does - var shares = {}; - OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user2']; - shares[OC.Share.SHARE_TYPE_USER] = _.map(['User Two'], makeDummyShareItem); - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + // simulate updating shares + shareTab._dialog.model.set({ + shares: [{share_with_displayname: 'User Two'}] + }); expect($tr.attr('data-share-recipients')).toEqual('User Two'); - OC.Share.updateIcon('file', 1); - - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('User One'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); }); @@ -401,21 +396,15 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(true); - $tr.find('.action-share').click(); - expect(showDropDownStub.calledOnce).toEqual(true); - - // simulate what the dropdown does - OC.Share.itemShares = {}; - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: {}})); + // simulate updating shares + shareTab._dialog.model.set({ + shares: [] + }); expect($tr.attr('data-share-recipients')).not.toBeDefined(); - OC.Share.updateIcon('file', 1); - - expect($action.hasClass('permanent')).toEqual(true); expect($action.find('>span').text().trim()).toEqual('User One'); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); }); diff --git a/core/css/share.css b/core/css/share.css index 0d687cb76dac..6467cc0239ce 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -23,29 +23,29 @@ } } -#dropdown.shareDropDown .unshare.icon-loading-small { +.shareTabView .unshare.icon-loading-small { margin-top: 1px; } -#dropdown.shareDropDown .shareWithLoading, -#dropdown.shareDropDown .linkShare .icon-loading-small { +.shareTabView .shareWithLoading, +.shareTabView .linkShare .icon-loading-small { display: inline-block !important; padding-left: 10px; } -#dropdown.shareDropDown .shareWithLoading { +.shareTabView .shareWithLoading { position: relative; right: 70px; top: 2px; } -#dropdown.shareDropDown .icon-loading-small.hidden { +.shareTabView .icon-loading-small.hidden { display: none !important; } -#dropdown .shareWithRemoteInfo { +.shareTabView .shareWithRemoteInfo { padding: 11px 20px; } -#dropdown .avatar { +.shareTabView .avatar { margin-right: 8px; display: inline-block; overflow: hidden; @@ -87,12 +87,12 @@ #shareWithList li label{ margin-right: 8px; } -#dropdown label { +.shareTabView label { font-weight:400; white-space: nowrap; } -#dropdown input[type="checkbox"] { +.shareTabView input[type="checkbox"] { margin:0 3px 0 8px; vertical-align: middle; } @@ -115,19 +115,29 @@ a.unshare { padding-top:8px; } -#dropdown input[type="text"],#dropdown input[type="password"] { - width: 86%; +.shareTabView input[type="text"], +.shareTabView input[type="password"], +.shareTabView input[type="submit"] { margin-left: 7px; } -#dropdown form { +.shareTabView input[type="text"], +.shareTabView input[type="password"] { + width: 86%; +} + +.shareTabView form { font-size: 100%; margin-left: 0; margin-right: 0; } -#linkText,#linkPass,#expiration { - display:none; +.shareTabView .error { + color: #e9322d; + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; } #link #showPassword img { diff --git a/core/js/core.json b/core/js/core.json index a67491c4a357..a80636e84636 100644 --- a/core/js/core.json +++ b/core/js/core.json @@ -24,6 +24,13 @@ "l10n.js", "apps.js", "share.js", + "shareconfigmodel.js", + "shareitemmodel.js", + "sharedialogview.js", + "sharedialogexpirationview.js", + "sharedialoglinkshareview.js", + "sharedialogresharerinfoview.js", + "sharedialogshareelistview.js", "octemplate.js", "eventsource.js", "config.js", diff --git a/core/js/share.js b/core/js/share.js index cd4a614e9d15..a2e6e6af0fcc 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -3,7 +3,7 @@ /** * @namespace */ -OC.Share={ +OC.Share = _.extend(OC.Share || {}, { SHARE_TYPE_USER:0, SHARE_TYPE_GROUP:1, SHARE_TYPE_LINK:3, @@ -289,21 +289,34 @@ OC.Share={ } img.attr('src', image); }, - loadItem:function(itemType, itemSource) { + /** + * + * @param itemType + * @param itemSource + * @param callback - optional. If a callback is given this method works + * asynchronous and the callback will be provided with data when the request + * is done. + * @returns {OC.Share.Types.ShareInfo} + */ + loadItem:function(itemType, itemSource, callback) { var data = ''; var checkReshare = true; + var async = !_.isUndefined(callback); if (typeof OC.Share.statuses[itemSource] === 'undefined') { // NOTE: Check does not always work and misses some shares, fix later var checkShares = true; } else { var checkShares = true; } - $.ajax({type: 'GET', url: OC.filePath('core', 'ajax', 'share.php'), data: { fetch: 'getItem', itemType: itemType, itemSource: itemSource, checkReshare: checkReshare, checkShares: checkShares }, async: false, success: function(result) { + $.ajax({type: 'GET', url: OC.filePath('core', 'ajax', 'share.php'), data: { fetch: 'getItem', itemType: itemType, itemSource: itemSource, checkReshare: checkReshare, checkShares: checkShares }, async: async, success: function(result) { if (result && result.status === 'success') { data = result.data; } else { data = false; } + if(async) { + callback(data); + } }}); return data; @@ -371,269 +384,27 @@ OC.Share={ }); }, showDropDown:function(itemType, itemSource, appendTo, link, possiblePermissions, filename) { - var data = OC.Share.loadItem(itemType, itemSource); - var dropDownEl; - var html = ''; - dropDownEl = $(html); - dropDownEl.appendTo(appendTo); - } - dropDownEl.attr('data-item-source-name', filename); - $('#dropdown').slideDown(OC.menuSpeed, function() { + }); + dialogView.setShowLink(link); + var $dialog = dialogView.render().$el; + $dialog.appendTo(appendTo); + $dialog.slideDown(OC.menuSpeed, function() { OC.Share.droppedDown = true; }); - if ($('html').hasClass('lte9')){ - $('#dropdown input[placeholder]').placeholder(); - } - $('#shareWith').focus(); + itemModel.fetch(); }, hideDropDown:function(callback) { OC.Share.currentShares = null; @@ -648,256 +419,10 @@ OC.Share={ } }); }, - addShareWith:function(shareType, shareWith, shareWithDisplayName, permissions, possiblePermissions, mailSend, collection) { - var shareItem = { - share_type: shareType, - share_with: shareWith, - share_with_displayname: shareWithDisplayName, - permissions: permissions - }; - if (shareType === OC.Share.SHARE_TYPE_GROUP) { - shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')'; - } - if (shareType === OC.Share.SHARE_TYPE_REMOTE) { - shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')'; - } - if (!OC.Share.itemShares[shareType]) { - OC.Share.itemShares[shareType] = []; - } - OC.Share.itemShares[shareType].push(shareWith); - if (collection) { - if (collection.item_type == 'file' || collection.item_type == 'folder') { - var item = collection.path; - } else { - var item = collection.item_source; - } - var collectionList = $('#shareWithList li').filterAttr('data-collection', item); - if (collectionList.length > 0) { - $(collectionList).append(', '+shareWithDisplayName); - } else { - var html = '
  • '+t('core', 'Shared in {item} with {user}', {'item': item, user: shareWithDisplayName})+'
  • '; - $('#shareWithList').prepend(html); - } - } else { - var editChecked = createChecked = updateChecked = deleteChecked = shareChecked = ''; - if (permissions & OC.PERMISSION_CREATE) { - createChecked = 'checked="checked"'; - editChecked = 'checked="checked"'; - } - if (permissions & OC.PERMISSION_UPDATE) { - updateChecked = 'checked="checked"'; - editChecked = 'checked="checked"'; - } - if (permissions & OC.PERMISSION_DELETE) { - deleteChecked = 'checked="checked"'; - editChecked = 'checked="checked"'; - } - if (permissions & OC.PERMISSION_SHARE) { - shareChecked = 'checked="checked"'; - } - var html = '
  • '; - var showCrudsButton; - html += ''+t('core', 'Unshare')+''; - if (oc_config.enable_avatars === true) { - html += '
    '; - } - html += '' + escapeHTML(shareWithDisplayName) + ''; - var mailNotificationEnabled = $('input:hidden[name=mailNotificationEnabled]').val(); - if (mailNotificationEnabled === 'yes' && shareType !== OC.Share.SHARE_TYPE_REMOTE) { - var checked = ''; - if (mailSend === '1') { - checked = 'checked'; - } - html += ' '; - } - if (oc_appconfig.core.resharingAllowed && (possiblePermissions & OC.PERMISSION_SHARE)) { - html += ''; - } - if (possiblePermissions & OC.PERMISSION_CREATE || possiblePermissions & OC.PERMISSION_UPDATE || possiblePermissions & OC.PERMISSION_DELETE) { - html += ''; - } - if (shareType !== OC.Share.SHARE_TYPE_REMOTE) { - showCrudsButton = ''+t('core', 'access control')+''; - } - html += ''; - html += '
  • '; - html = $(html).appendTo('#shareWithList'); - if (oc_config.enable_avatars === true) { - if (shareType === OC.Share.SHARE_TYPE_USER) { - html.find('.avatar').avatar(escapeHTML(shareWith), 32); - } else { - //Add sharetype to generate different seed if there is a group and use with the same name - html.find('.avatar').imageplaceholder(escapeHTML(shareWith) + ' ' + shareType); - } - } - // insert cruds button into last label element - var lastLabel = html.find('>label:last'); - if (lastLabel.exists()){ - lastLabel.append(showCrudsButton); - } - else{ - html.find('.cruds').before(showCrudsButton); - } - if (!OC.Share.currentShares[shareType]) { - OC.Share.currentShares[shareType] = []; - } - OC.Share.currentShares[shareType].push(shareItem); - } - }, - showLink:function(token, password, itemSource) { - OC.Share.itemShares[OC.Share.SHARE_TYPE_LINK] = true; - $('#linkCheckbox').attr('checked', true); - - //check itemType - var linkSharetype=$('#dropdown').data('item-type'); - - if (! token) { - //fallback to pre token link - var filename = $('tr').filterAttr('data-id', String(itemSource)).data('file'); - var type = $('tr').filterAttr('data-id', String(itemSource)).data('type'); - if ($('#dir').val() == '/') { - var file = $('#dir').val() + filename; - } else { - var file = $('#dir').val() + '/' + filename; - } - file = '/'+OC.currentUser+'/files'+file; - // TODO: use oc webroot ? - var link = parent.location.protocol+'//'+location.host+OC.linkTo('', 'public.php')+'?service=files&'+type+'='+encodeURIComponent(file); - } else { - //TODO add path param when showing a link to file in a subfolder of a public link share - var service=''; - if(linkSharetype === 'folder' || linkSharetype === 'file'){ - service='files'; - }else{ - service=linkSharetype; - } - - // TODO: use oc webroot ? - if (service !== 'files') { - var link = parent.location.protocol+'//'+location.host+OC.linkTo('', 'public.php')+'?service='+service+'&t='+token; - } else { - var link = parent.location.protocol+'//'+location.host+OC.generateUrl('/s/')+token; - } - } - $('#linkText').val(link); - $('#linkText').slideDown(OC.menuSpeed); - $('#linkText').css('display','block'); - if (oc_appconfig.core.enforcePasswordForPublicLink === false || password === null) { - $('#showPassword').show(); - $('#showPassword+label').show(); - } - if (password != null) { - $('#linkPass').slideDown(OC.menuSpeed); - $('#showPassword').attr('checked', true); - $('#linkPassText').attr('placeholder', '**********'); - } - $('#expiration').show(); - $('#emailPrivateLink #email').show(); - $('#emailPrivateLink #emailButton').show(); - $('#allowPublicUploadWrapper').show(); - }, - hideLink:function() { - $('#linkText').slideUp(OC.menuSpeed); - $('#defaultExpireMessage').hide(); - $('#showPassword').hide(); - $('#showPassword+label').hide(); - $('#linkPass').slideUp(OC.menuSpeed); - $('#emailPrivateLink #email').hide(); - $('#emailPrivateLink #emailButton').hide(); - $('#allowPublicUploadWrapper').hide(); - }, dirname:function(path) { return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, ''); - }, - /** - * Parses a string to an valid integer (unix timestamp) - * @param time - * @returns {*} - * @internal Only used to work around a bug in the backend - */ - _parseTime: function(time) { - if (_.isString(time)) { - // skip empty strings and hex values - if (time === '' || (time.length > 1 && time[0] === '0' && time[1] === 'x')) { - return null; - } - time = parseInt(time, 10); - if(isNaN(time)) { - time = null; - } - } - return time; - }, - /** - * Displays the expiration date field - * - * @param {Date} date current expiration date - * @param {int} [shareTime] share timestamp in seconds, defaults to now - */ - showExpirationDate:function(date, shareTime) { - var now = new Date(); - // min date should always be the next day - var minDate = new Date(); - minDate.setDate(minDate.getDate()+1); - var datePickerOptions = { - minDate: minDate, - maxDate: null - }; - // TODO: hack: backend returns string instead of integer - shareTime = OC.Share._parseTime(shareTime); - if (_.isNumber(shareTime)) { - shareTime = new Date(shareTime * 1000); - } - if (!shareTime) { - shareTime = now; - } - $('#expirationCheckbox').attr('checked', true); - $('#expirationDate').val(date); - $('#expirationDate').slideDown(OC.menuSpeed); - $('#expirationDate').css('display','block'); - $('#expirationDate').datepicker({ - dateFormat : 'dd-mm-yy' - }); - if (oc_appconfig.core.defaultExpireDateEnforced) { - $('#expirationCheckbox').attr('disabled', true); - shareTime = OC.Util.stripTime(shareTime).getTime(); - // max date is share date + X days - datePickerOptions.maxDate = new Date(shareTime + oc_appconfig.core.defaultExpireDate * 24 * 3600 * 1000); - } - if(oc_appconfig.core.defaultExpireDateEnabled) { - $('#defaultExpireMessage').slideDown(OC.menuSpeed); - } - $.datepicker.setDefaults(datePickerOptions); - }, - /** - * Get the default Expire date - * - * @return {String} The expire date - */ - getDefaultExpirationDate:function() { - var expireDateString = ''; - if (oc_appconfig.core.defaultExpireDateEnabled) { - var date = new Date().getTime(); - var expireAfterMs = oc_appconfig.core.defaultExpireDate * 24 * 60 * 60 * 1000; - var expireDate = new Date(date + expireAfterMs); - var month = expireDate.getMonth() + 1; - var year = expireDate.getFullYear(); - var day = expireDate.getDate(); - expireDateString = year + "-" + month + '-' + day + ' 00:00:00'; - } - return expireDateString; } -}; +}); $(document).ready(function() { @@ -915,403 +440,16 @@ $(document).ready(function() { minDate : minDate }); } - $(document).on('click', 'a.share', function(event) { - event.stopPropagation(); - if ($(this).data('item-type') !== undefined && $(this).data('item') !== undefined) { - var itemType = $(this).data('item-type'); - var itemSource = $(this).data('item'); - var appendTo = $(this).parent().parent(); - var link = false; - var possiblePermissions = $(this).data('possible-permissions'); - if ($(this).data('link') !== undefined && $(this).data('link') == true) { - link = true; - } - if (OC.Share.droppedDown) { - if (itemSource != $('#dropdown').data('item')) { - OC.Share.hideDropDown(function () { - OC.Share.showDropDown(itemType, itemSource, appendTo, link, possiblePermissions); - }); - } else { - OC.Share.hideDropDown(); - } - } else { - OC.Share.showDropDown(itemType, itemSource, appendTo, link, possiblePermissions); - } - } - }); $(this).click(function(event) { var target = $(event.target); var isMatched = !target.is('.drop, .ui-datepicker-next, .ui-datepicker-prev, .ui-icon') && !target.closest('#ui-datepicker-div').length && !target.closest('.ui-autocomplete').length; - if (OC.Share.droppedDown && isMatched && $('#dropdown').has(event.target).length === 0) { + if (OC.Share && OC.Share.droppedDown && isMatched && $('#dropdown').has(event.target).length === 0) { OC.Share.hideDropDown(); } }); - $(document).on('click', '#dropdown .showCruds', function() { - $(this).closest('li').find('.cruds').toggle(); - return false; - }); - - $(document).on('click', '#dropdown .unshare', function() { - var $li = $(this).closest('li'); - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - var shareType = $li.data('share-type'); - var shareWith = $li.attr('data-share-with'); - var $button = $(this); - - if (!$button.is('a')) { - $button = $button.closest('a'); - } - - if ($button.hasClass('icon-loading-small')) { - // deletion in progress - return false; - } - $button.empty().addClass('icon-loading-small'); - - OC.Share.unshare(itemType, itemSource, shareType, shareWith, function() { - $li.remove(); - var index = OC.Share.itemShares[shareType].indexOf(shareWith); - OC.Share.itemShares[shareType].splice(index, 1); - // updated list of shares - OC.Share.currentShares[shareType].splice(index, 1); - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); - OC.Share.updateIcon(itemType, itemSource); - if (typeof OC.Share.statuses[itemSource] === 'undefined') { - $('#expiration').slideUp(OC.menuSpeed); - } - }); - - return false; - }); - - $(document).on('change', '#dropdown .permissions', function() { - var li = $(this).closest('li'); - if ($(this).attr('name') == 'edit') { - var checkboxes = $('.permissions', li); - var checked = $(this).is(':checked'); - // Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck - $(checkboxes).filter('input[name="create"]').attr('checked', checked); - $(checkboxes).filter('input[name="update"]').attr('checked', checked); - $(checkboxes).filter('input[name="delete"]').attr('checked', checked); - } else { - var checkboxes = $('.permissions', li); - // Uncheck Edit if Create, Update, and Delete are not checked - if (!$(this).is(':checked') - && !$(checkboxes).filter('input[name="create"]').is(':checked') - && !$(checkboxes).filter('input[name="update"]').is(':checked') - && !$(checkboxes).filter('input[name="delete"]').is(':checked')) - { - $(checkboxes).filter('input[name="edit"]').attr('checked', false); - // Check Edit if Create, Update, or Delete is checked - } else if (($(this).attr('name') == 'create' - || $(this).attr('name') == 'update' - || $(this).attr('name') == 'delete')) - { - $(checkboxes).filter('input[name="edit"]').attr('checked', true); - } - } - var permissions = OC.PERMISSION_READ; - $(checkboxes).filter(':not(input[name="edit"])').filter(':checked').each(function(index, checkbox) { - permissions |= $(checkbox).data('permissions'); - }); - OC.Share.setPermissions($('#dropdown').data('item-type'), - $('#dropdown').data('item-source'), - li.data('share-type'), - li.attr('data-share-with'), - permissions); - }); - - $(document).on('change', '#dropdown #linkCheckbox', function() { - var $dropDown = $('#dropdown'); - var itemType = $dropDown.data('item-type'); - var itemSource = $dropDown.data('item-source'); - var itemSourceName = $dropDown.data('item-source-name'); - var $loading = $dropDown.find('#link .icon-loading-small'); - var $button = $(this); - - if (!$loading.hasClass('hidden')) { - // already in progress - return false; - } - - if (this.checked) { - // Reset password placeholder - $('#linkPassText').attr('placeholder', t('core', 'Choose a password for the public link')); - // Reset link - $('#linkText').val(''); - $('#showPassword').prop('checked', false); - $('#linkPass').hide(); - $('#sharingDialogAllowPublicUpload').prop('checked', false); - $('#expirationCheckbox').prop('checked', false); - $('#expirationDate').hide(); - var expireDateString = ''; - // Create a link - if (oc_appconfig.core.enforcePasswordForPublicLink === false) { - expireDateString = OC.Share.getDefaultExpirationDate(); - $loading.removeClass('hidden'); - $button.addClass('hidden'); - $button.prop('disabled', true); - - OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', OC.PERMISSION_READ, itemSourceName, expireDateString, function(data) { - $loading.addClass('hidden'); - $button.removeClass('hidden'); - $button.prop('disabled', false); - OC.Share.showLink(data.token, null, itemSource); - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); - OC.Share.updateIcon(itemType, itemSource); - }); - } else { - $('#linkPass').slideToggle(OC.menuSpeed); - // TODO drop with IE8 drop - if($('html').hasClass('ie8')) { - $('#linkPassText').attr('placeholder', null); - $('#linkPassText').val(''); - } - $('#linkPassText').focus(); - } - if (expireDateString !== '') { - OC.Share.showExpirationDate(expireDateString); - } - } else { - // Delete private link - OC.Share.hideLink(); - $('#expiration').slideUp(OC.menuSpeed); - if ($('#linkText').val() !== '') { - $loading.removeClass('hidden'); - $button.addClass('hidden'); - $button.prop('disabled', true); - OC.Share.unshare(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', function() { - $loading.addClass('hidden'); - $button.removeClass('hidden'); - $button.prop('disabled', false); - OC.Share.itemShares[OC.Share.SHARE_TYPE_LINK] = false; - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); - OC.Share.updateIcon(itemType, itemSource); - if (typeof OC.Share.statuses[itemSource] === 'undefined') { - $('#expiration').slideUp(OC.menuSpeed); - } - }); - } - } - }); - - $(document).on('click', '#dropdown #linkText', function() { - $(this).focus(); - $(this).select(); - }); - - // Handle the Allow Public Upload Checkbox - $(document).on('click', '#sharingDialogAllowPublicUpload', function() { - - // Gather data - var $dropDown = $('#dropdown'); - var allowPublicUpload = $(this).is(':checked'); - var itemType = $dropDown.data('item-type'); - var itemSource = $dropDown.data('item-source'); - var itemSourceName = $dropDown.data('item-source-name'); - var expirationDate = ''; - if ($('#expirationCheckbox').is(':checked') === true) { - expirationDate = $( "#expirationDate" ).val(); - } - var permissions = 0; - var $button = $(this); - var $loading = $dropDown.find('#allowPublicUploadWrapper .icon-loading-small'); - - if (!$loading.hasClass('hidden')) { - // already in progress - return false; - } - - // Calculate permissions - if (allowPublicUpload) { - permissions = OC.PERMISSION_UPDATE + OC.PERMISSION_CREATE + OC.PERMISSION_READ; - } else { - permissions = OC.PERMISSION_READ; - } - - // Update the share information - $button.addClass('hidden'); - $button.prop('disabled', true); - $loading.removeClass('hidden'); - OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', permissions, itemSourceName, expirationDate, function(data) { - $loading.addClass('hidden'); - $button.removeClass('hidden'); - $button.prop('disabled', false); - }); - }); - - $(document).on('click', '#dropdown #showPassword', function() { - $('#linkPass').slideToggle(OC.menuSpeed); - if (!$('#showPassword').is(':checked') ) { - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - var itemSourceName = $('#dropdown').data('item-source-name'); - var allowPublicUpload = $('#sharingDialogAllowPublicUpload').is(':checked'); - var permissions = 0; - var $loading = $('#showPassword .icon-loading-small'); - - // Calculate permissions - if (allowPublicUpload) { - permissions = OC.PERMISSION_UPDATE + OC.PERMISSION_CREATE + OC.PERMISSION_READ; - } else { - permissions = OC.PERMISSION_READ; - } - - $loading.removeClass('hidden'); - OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', permissions, itemSourceName).then(function() { - $loading.addClass('hidden'); - $('#linkPassText').attr('placeholder', t('core', 'Choose a password for the public link')); - }); - } else { - $('#linkPassText').focus(); - } - }); - - $(document).on('focusout keyup', '#dropdown #linkPassText', function(event) { - var linkPassText = $('#linkPassText'); - if ( linkPassText.val() != '' && (event.type == 'focusout' || event.keyCode == 13) ) { - var allowPublicUpload = $('#sharingDialogAllowPublicUpload').is(':checked'); - var dropDown = $('#dropdown'); - var itemType = dropDown.data('item-type'); - var itemSource = dropDown.data('item-source'); - var itemSourceName = $('#dropdown').data('item-source-name'); - var permissions = 0; - var $loading = dropDown.find('#linkPass .icon-loading-small'); - - // Calculate permissions - if (allowPublicUpload) { - permissions = OC.PERMISSION_UPDATE + OC.PERMISSION_CREATE + OC.PERMISSION_READ; - } else { - permissions = OC.PERMISSION_READ; - } - - var expireDateString = OC.Share.getDefaultExpirationDate(); - - $loading.removeClass('hidden'); - OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, $('#linkPassText').val(), permissions, itemSourceName, expireDateString, function(data) { - $loading.addClass('hidden'); - linkPassText.val(''); - linkPassText.attr('placeholder', t('core', 'Password protected')); - - if (oc_appconfig.core.enforcePasswordForPublicLink) { - OC.Share.showLink(data.token, "password set", itemSource); - OC.Share.updateIcon(itemType, itemSource); - } - $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); - }, function(result) { - $loading.addClass('hidden'); - linkPassText.val(''); - linkPassText.attr('placeholder', result.data.message); - }); - - if (expireDateString !== '') { - OC.Share.showExpirationDate(expireDateString); - } - } - }); - - $(document).on('click', '#dropdown #expirationCheckbox', function() { - if (this.checked) { - OC.Share.showExpirationDate(''); - } else { - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - $.post(OC.filePath('core', 'ajax', 'share.php'), { action: 'setExpirationDate', itemType: itemType, itemSource: itemSource, date: '' }, function(result) { - if (!result || result.status !== 'success') { - OC.dialogs.alert(t('core', 'Error unsetting expiration date'), t('core', 'Error')); - } - $('#expirationDate').slideUp(OC.menuSpeed); - if (oc_appconfig.core.defaultExpireDateEnforced === false) { - $('#defaultExpireMessage').slideDown(OC.menuSpeed); - } - }); - } - }); - - $(document).on('change', '#dropdown #expirationDate', function() { - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - - $(this).tipsy('hide'); - $(this).removeClass('error'); - - $.post(OC.filePath('core', 'ajax', 'share.php'), { action: 'setExpirationDate', itemType: itemType, itemSource: itemSource, date: $(this).val() }, function(result) { - if (!result || result.status !== 'success') { - var expirationDateField = $('#dropdown #expirationDate'); - if (!result.data.message) { - expirationDateField.attr('original-title', t('core', 'Error setting expiration date')); - } else { - expirationDateField.attr('original-title', result.data.message); - } - expirationDateField.tipsy({gravity: 'n'}); - expirationDateField.tipsy('show'); - expirationDateField.addClass('error'); - } else { - if (oc_appconfig.core.defaultExpireDateEnforced === 'no') { - $('#defaultExpireMessage').slideUp(OC.menuSpeed); - } - } - }); - }); - - - $(document).on('submit', '#dropdown #emailPrivateLink', function(event) { - event.preventDefault(); - var link = $('#linkText').val(); - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - var file = $('tr').filterAttr('data-id', String(itemSource)).data('file'); - var email = $('#email').val(); - var expirationDate = ''; - if ( $('#expirationCheckbox').is(':checked') === true ) { - expirationDate = $( "#expirationDate" ).val(); - } - if (email != '') { - $('#email').prop('disabled', true); - $('#email').val(t('core', 'Sending ...')); - $('#emailButton').prop('disabled', true); - - $.post(OC.filePath('core', 'ajax', 'share.php'), { action: 'email', toaddress: email, link: link, itemType: itemType, itemSource: itemSource, file: file, expiration: expirationDate}, - function(result) { - $('#email').prop('disabled', false); - $('#emailButton').prop('disabled', false); - if (result && result.status == 'success') { - $('#email').css('font-weight', 'bold').val(t('core','Email sent')); - setTimeout(function() { - $('#email').css('font-weight', 'normal').val(''); - }, 2000); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Error while sharing')); - } - }); - } - }); - - $(document).on('click', '#dropdown input[name=mailNotification]', function() { - var $li = $(this).closest('li'); - var itemType = $('#dropdown').data('item-type'); - var itemSource = $('#dropdown').data('item-source'); - var action = ''; - if (this.checked) { - action = 'informRecipients'; - } else { - action = 'informRecipientsDisabled'; - } - - var shareType = $li.data('share-type'); - var shareWith = $li.attr('data-share-with'); - - $.post(OC.filePath('core', 'ajax', 'share.php'), {action: action, recipient: shareWith, shareType: shareType, itemSource: itemSource, itemType: itemType}, function(result) { - if (result.status !== 'success') { - OC.dialogs.alert(t('core', result.data.message), t('core', 'Warning')); - } - }); - -}); }); diff --git a/core/js/shareconfigmodel.js b/core/js/shareconfigmodel.js new file mode 100644 index 000000000000..8729698d136f --- /dev/null +++ b/core/js/shareconfigmodel.js @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if (!OC.Share) { + OC.Share = {}; + OC.Share.Types = {}; + } + + // FIXME: the config model should populate its own model attributes based on + // the old DOM-based config + var ShareConfigModel = OC.Backbone.Model.extend({ + defaults: { + publicUploadEnabled: false, + enforcePasswordForPublicLink: oc_appconfig.core.enforcePasswordForPublicLink, + isDefaultExpireDateEnforced: oc_appconfig.core.defaultExpireDateEnforced === true, + isDefaultExpireDateEnabled: oc_appconfig.core.defaultExpireDateEnabled === true, + isRemoteShareAllowed: oc_appconfig.core.remoteShareAllowed, + defaultExpireDate: oc_appconfig.core.defaultExpireDate, + isResharingAllowed: oc_appconfig.core.resharingAllowed + }, + + /** + * @returns {boolean} + */ + areAvatarsEnabled: function() { + return oc_config.enable_avatars === true; + }, + + /** + * @returns {boolean} + */ + isPublicUploadEnabled: function() { + var publicUploadEnabled = $('#filestable').data('allow-public-upload'); + return publicUploadEnabled === 'yes'; + }, + + /** + * @returns {boolean} + */ + isMailPublicNotificationEnabled: function() { + return $('input:hidden[name=mailPublicNotificationEnabled]').val() === 'yes'; + }, + + /** + * @returns {boolean} + */ + isShareWithLinkAllowed: function() { + return $('#allowShareWithLink').val() === 'yes'; + }, + + /** + * @returns {string} + */ + getFederatedShareDocLink: function() { + return oc_appconfig.core.federatedCloudShareDoc; + }, + + getDefaultExpirationDateString: function () { + var expireDateString = ''; + if (this.get('isDefaultExpireDateEnabled')) { + var date = new Date().getTime(); + var expireAfterMs = this.get('defaultExpireDate') * 24 * 60 * 60 * 1000; + var expireDate = new Date(date + expireAfterMs); + var month = expireDate.getMonth() + 1; + var year = expireDate.getFullYear(); + var day = expireDate.getDate(); + expireDateString = year + "-" + month + '-' + day + ' 00:00:00'; + } + return expireDateString; + } + }); + + + OC.Share.ShareConfigModel = ShareConfigModel; +})(); diff --git a/core/js/sharedialogexpirationview.js b/core/js/sharedialogexpirationview.js new file mode 100644 index 000000000000..3fba4b135d99 --- /dev/null +++ b/core/js/sharedialogexpirationview.js @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if (!OC.Share) { + OC.Share = {}; + } + + var TEMPLATE = + // currently expiration is only effective for link share. + // this is about to change in future. Therefore this is not included + // in the LinkShareView to ease reusing it in future. Then, + // modifications (getting rid of IDs) are still necessary. + '{{#if isLinkShare}}' + + '' + + '' + + '
    ' + + ' ' + + ' ' + + '
    ' + + ' {{#if isExpirationEnforced}}' + + // originally the expire message was shown when a default date was set, however it never had text + '{{defaultExpireMessage}}' + + ' {{/if}}' + + '{{/if}}' + ; + + /** + * @class OCA.Share.ShareDialogExpirationView + * @member {OC.Share.ShareItemModel} model + * @member {jQuery} $el + * @memberof OCA.Sharing + * @classdesc + * + * Represents the expiration part in the GUI of the share dialogue + * + */ + var ShareDialogExpirationView = OC.Backbone.View.extend({ + /** @type {string} **/ + id: 'shareDialogLinkShare', + + /** @type {OC.Share.ShareConfigModel} **/ + configModel: undefined, + + /** @type {Function} **/ + _template: undefined, + + /** @type {boolean} **/ + showLink: true, + + className: 'hidden', + + events: { + 'change .expirationCheckbox': '_onToggleExpiration', + 'change .datepicker': '_onChangeExpirationDate' + }, + + initialize: function(options) { + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } else { + throw 'missing OC.Share.ShareConfigModel'; + } + + var view = this; + this.configModel.on('change:isDefaultExpireDateEnforced', function() { + view.render(); + }); + + this.model.on('change:itemType', function() { + view.render(); + }); + + this.model.on('change:linkShare', function() { + view.render(); + }); + }, + + _onToggleExpiration: function(event) { + var $checkbox = $(event.target); + var state = $checkbox.prop('checked'); + // TODO: slide animation + this.$el.find('.expirationDateContainer').toggleClass('hidden', !state); + if (!state) { + // discard expiration date + this.model.setExpirationDate(''); + this.model.saveLinkShare(); + } + }, + + _onChangeExpirationDate: function(event) { + var $target = $(event.target); + $target.tooltip('hide'); + $target.removeClass('error'); + + this.model.setExpirationDate($target.val()); + this.model.saveLinkShare(null, { + error: function(model, message) { + if (!message) { + $target.attr('title', t('core', 'Error setting expiration date')); + } else { + $target.attr('title', message); + } + $target.tooltip({gravity: 'n'}); + $target.tooltip('show'); + $target.addClass('error'); + } + }); + }, + + render: function() { + var defaultExpireMessage = ''; + var defaultExpireDays = this.configModel.get('defaultExpireDate'); + var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced'); + + if( (this.model.isFolder() || this.model.isFile()) + && isExpirationEnforced) { + defaultExpireMessage = t( + 'core', + 'The public link will expire no later than {days} days after it is created', + {'days': defaultExpireDays } + ); + } + + var isExpirationSet = !!this.model.get('linkShare').expiration || isExpirationEnforced; + + var expirationTemplate = this.template(); + this.$el.html(expirationTemplate({ + setExpirationLabel: t('core', 'Set expiration date'), + expirationLabel: t('core', 'Expiration'), + expirationDatePlaceholder: t('core', 'Expiration date'), + defaultExpireMessage: defaultExpireMessage, + isLinkShare: this.model.get('linkShare').isLinkShare, + isExpirationSet: isExpirationSet, + isExpirationEnforced: isExpirationEnforced, + disableCheckbox: isExpirationEnforced && isExpirationSet, + expirationValue: this.model.get('linkShare').expiration + })); + + // what if there is another date picker on that page? + var minDate = new Date(); + var maxDate = null; + // min date should always be the next day + minDate.setDate(minDate.getDate()+1); + + if(isExpirationSet) { + if(isExpirationEnforced) { + // TODO: hack: backend returns string instead of integer + var shareTime = this.model.get('linkShare').stime; + if (_.isNumber(shareTime)) { + shareTime = new Date(shareTime * 1000); + } + if (!shareTime) { + shareTime = new Date(); // now + } + shareTime = OC.Util.stripTime(shareTime).getTime(); + maxDate = new Date(shareTime + defaultExpireDays * 24 * 3600 * 1000); + } + } + $.datepicker.setDefaults({ + minDate: minDate, + maxDate: maxDate + }); + + this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'}); + + this.delegateEvents(); + + return this; + }, + + /** + * @returns {Function} from Handlebars + * @private + */ + template: function () { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template; + } + + }); + + OC.Share.ShareDialogExpirationView = ShareDialogExpirationView; + +})(); diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js new file mode 100644 index 000000000000..ab591b9c6e85 --- /dev/null +++ b/core/js/sharedialoglinkshareview.js @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if (!OC.Share) { + OC.Share = {}; + } + + var TEMPLATE = + '{{#if shareAllowed}}' + + '' + + '' + + '
    ' + + '' + + '' + + ' {{#if showPasswordCheckBox}}' + + '' + + ' {{/if}}' + + '
    ' + + ' ' + + ' ' + + ' ' + + '
    ' + + ' {{#if publicUpload}}' + + '
    ' + + ' ' + + ' ' + + '' + + '
    ' + + ' {{/if}}' + + ' {{#if mailPublicNotificationEnabled}}' + + '' + + ' {{/if}}' + + '{{else}}' + + '' + + '{{/if}}' + ; + + /** + * @class OCA.Share.ShareDialogLinkShareView + * @member {OC.Share.ShareItemModel} model + * @member {jQuery} $el + * @memberof OCA.Sharing + * @classdesc + * + * Represents the GUI of the share dialogue + * + */ + var ShareDialogLinkShareView = OC.Backbone.View.extend({ + /** @type {string} **/ + id: 'shareDialogLinkShare', + + /** @type {OC.Share.ShareConfigModel} **/ + configModel: undefined, + + /** @type {Function} **/ + _template: undefined, + + /** @type {boolean} **/ + showLink: true, + + events: { + 'submit .emailPrivateLinkForm': '_onEmailPrivateLink' + }, + + initialize: function(options) { + var view = this; + + this.model.on('change:permissions', function() { + view.render(); + }); + + this.model.on('change:itemType', function() { + view.render(); + }); + + this.model.on('change:allowPublicUploadStatus', function() { + view.render(); + }); + + this.model.on('change:linkShare', function() { + view.render(); + }); + + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } else { + throw 'missing OC.Share.ShareConfigModel'; + } + + _.bindAll(this, 'onLinkCheckBoxChange', 'onPasswordEntered', + 'onShowPasswordClick', 'onAllowPublicUploadChange'); + }, + + onLinkCheckBoxChange: function() { + var $checkBox = this.$el.find('#linkCheckbox'); + var $loading = $checkBox.siblings('.icon-loading-small'); + if(!$loading.hasClass('hidden')) { + return false; + } + + if($checkBox.is(':checked')) { + if(this.configModel.get('enforcePasswordForPublicLink') === false) { + $loading.removeClass('hidden'); + // this will create it + this.model.saveLinkShare(); + } else { + this.$el.find('#linkPass').slideToggle(OC.menuSpeed); + // TODO drop with IE8 drop + if($('html').hasClass('ie8')) { + this.$el.find('#linkPassText').attr('placeholder', null); + this.$el.find('#linkPassText').val(''); + } + this.$el.find('#linkPassText').focus(); + } + } else { + this.model.removeLinkShare(); + } + }, + + onLinkTextClick: function() { + this.focus(); + this.select(); + }, + + onShowPasswordClick: function() { + this.$el.find('#linkPass').slideToggle(OC.menuSpeed); + if(!this.$el.find('#showPassword').is(':checked')) { + this.model.setPassword(''); + this.model.saveLinkShare(); + } else { + this.$el.find('#linkPassText').focus(); + } + }, + + onPasswordEntered: function() { + var password = this.$el.find('#linkPassText').val(); + if(password === '') { + return; + } + + this.$el.find('#linkPass .icon-loading-small') + .removeClass('hidden') + .addClass('inlineblock'); + + this.model.setPassword(password); + this.model.saveLinkShare(); + }, + + onAllowPublicUploadChange: function() { + this.$el.find('#sharingDialogAllowPublicUpload') + .siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); + this.model.setPublicUpload(this.$el.find('#sharingDialogAllowPublicUpload').is(':checked')); + this.model.saveLinkShare(); + }, + + _onEmailPrivateLink: function(event) { + event.preventDefault(); + + var $emailField = this.$el.find('#email'); + var $emailButton = this.$el.find('#emailButton'); + var email = this.$el.find('#email').val(); + if (email !== '') { + $emailField.prop('disabled', true); + $emailButton.prop('disabled', true); + $emailField.val(t('core', 'Sending ...')); + this.model.sendEmailPrivateLink(email).then(function() { + $emailField.css('font-weight', 'bold').val(t('core','Email sent')); + setTimeout(function() { + $emailField.css('font-weight', 'normal').val(''); + $emailField.prop('disabled', false); + $emailButton.prop('disabled', false); + }, 2000); + }); + } + return false; + }, + + render: function() { + var linkShareTemplate = this.template(); + + if( !this.model.sharePermissionPossible() + || !this.showLink + || !this.configModel.isShareWithLinkAllowed()) + { + this.$el.html(linkShareTemplate({ + shareAllowed: false, + noSharingPlaceholder: t('core', 'Resharing is not allowed') + })); + return this; + } + + var publicUpload = + this.model.isFolder() + && this.model.createPermissionPossible() + && this.configModel.isPublicUploadEnabled(); + + var publicUploadChecked = ''; + if(this.model.isPublicUploadAllowed()) { + publicUploadChecked = 'checked="checked"'; + } + + var isLinkShare = this.model.get('linkShare').isLinkShare; + var isPasswordSet = !!this.model.get('linkShare').password; + var showPasswordCheckBox = isLinkShare + && ( !this.configModel.get('enforcePasswordForPublicLink') + || !this.model.get('linkShare').password); + + this.$el.html(linkShareTemplate({ + shareAllowed: true, + isLinkShare: isLinkShare, + shareLinkURL: this.model.get('linkShare').link, + linkShareLabel: t('core', 'Share link'), + urlLabel: t('core', 'Link'), + enablePasswordLabel: t('core', 'Password protect'), + passwordLabel: t('core', 'Password'), + passwordPlaceholder: isPasswordSet ? '**********' : t('core', 'Choose a password for the public link'), + isPasswordSet: isPasswordSet, + showPasswordCheckBox: showPasswordCheckBox, + publicUpload: publicUpload && isLinkShare, + publicUploadChecked: publicUploadChecked, + publicUploadLabel: t('core', 'Allow editing'), + mailPublicNotificationEnabled: isLinkShare && this.configModel.isMailPublicNotificationEnabled(), + mailPrivatePlaceholder: t('core', 'Email link to person'), + mailButtonText: t('core', 'Send') + })); + + // TODO: move this to delegate events instead + this.$el.find('#linkCheckbox').click(this.onLinkCheckBoxChange); + this.$el.find('#sharingDialogAllowPublicUpload').change(this.onAllowPublicUploadChange); + this.$el.find('#linkText').click(this.onLinkTextClick); + this.$el.find('#showPassword').click(this.onShowPasswordClick); + this.$el.find('#linkPassText').focusout(this.onPasswordEntered); + var view = this; + this.$el.find('#linkPassText').keyup(function(event) { + if(event.keyCode == 13) { + view.onPasswordEntered(); + } + }); + + var $emailField = this.$el.find('#email'); + if (isLinkShare && $emailField.length !== 0) { + $emailField.autocomplete({ + minLength: 1, + source: function (search, response) { + $.get( + OC.generateUrl('core/ajax/share.php'), { + fetch: 'getShareWithEmail', + search: search.term + }, function(result) { + if (result.status == 'success' && result.data.length > 0) { + response(result.data); + } + }); + }, + select: function( event, item ) { + $emailField.val(item.item.email); + return false; + } + }) + .data("ui-autocomplete")._renderItem = function( ul, item ) { + return $('
  • ') + .append('' + escapeHTML(item.displayname) + "
    " + escapeHTML(item.email) + '
    ' ) + .appendTo( ul ); + }; + } + + this.delegateEvents(); + + return this; + }, + + /** + * @returns {Function} from Handlebars + * @private + */ + template: function () { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template; + } + + }); + + OC.Share.ShareDialogLinkShareView = ShareDialogLinkShareView; + +})(); diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js new file mode 100644 index 000000000000..600e2ecbf56d --- /dev/null +++ b/core/js/sharedialogresharerinfoview.js @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if (!OC.Share) { + OC.Share = {}; + } + + var TEMPLATE = + '' + + ' {{#if avatarEnabled}}' + + '
    ' + + ' {{/if}}' + + ' {{sharedByText}}' + + '

    ' + ; + + /** + * @class OCA.Share.ShareDialogView + * @member {OC.Share.ShareItemModel} model + * @member {jQuery} $el + * @memberof OCA.Sharing + * @classdesc + * + * Represents the GUI of the share dialogue + * + */ + var ShareDialogResharerInfoView = OC.Backbone.View.extend({ + /** @type {string} **/ + id: 'shareDialogResharerInfo', + + /** @type {string} **/ + tagName: 'div', + + /** @type {string} **/ + className: 'reshare', + + /** @type {OC.Share.ShareConfigModel} **/ + configModel: undefined, + + /** @type {Function} **/ + _template: undefined, + + initialize: function(options) { + var view = this; + + this.model.on('change:reshare', function() { + view.render(); + }); + + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } else { + throw 'missing OC.Share.ShareConfigModel'; + } + }, + + render: function() { + if (!this.model.hasReshare() + || this.model.getReshareOwner() === OC.currentUser) + { + this.$el.empty(); + return this; + } + + var reshareTemplate = this.template(); + var ownerDisplayName = this.model.getReshareOwnerDisplayname(); + var sharedByText = ''; + if (this.model.getReshareType() === OC.Share.SHARE_TYPE_GROUP) { + sharedByText = t( + 'core', + 'Shared with you and the group {group} by {owner}', + { + group: this.model.getReshareWith(), + owner: ownerDisplayName + } + ); + } else { + sharedByText = t( + 'core', + 'Shared with you by {owner}', + { owner: ownerDisplayName } + ); + } + + this.$el.html(reshareTemplate({ + avatarEnabled: this.configModel.areAvatarsEnabled(), + reshareOwner: this.model.getReshareOwner(), + sharedByText: sharedByText + })); + + if(this.configModel.areAvatarsEnabled()) { + this.$el.find('.avatar').each(function() { + var $this = $(this); + $this.avatar($this.data('username'), 32); + }); + } + + return this; + }, + + /** + * @returns {Function} from Handlebars + * @private + */ + template: function () { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template; + } + + }); + + OC.Share.ShareDialogResharerInfoView = ShareDialogResharerInfoView; + +})(); diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js new file mode 100644 index 000000000000..41be49dc2418 --- /dev/null +++ b/core/js/sharedialogshareelistview.js @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if (!OC.Share) { + OC.Share = {}; + } + + var TEMPLATE = + '' + ; + + /** + * @class OCA.Share.ShareDialogShareeListView + * @member {OC.Share.ShareItemModel} model + * @member {jQuery} $el + * @memberof OCA.Sharing + * @classdesc + * + * Represents the sharee list part in the GUI of the share dialogue + * + */ + var ShareDialogShareeListView = OC.Backbone.View.extend({ + /** @type {string} **/ + id: 'shareDialogLinkShare', + + /** @type {OC.Share.ShareConfigModel} **/ + configModel: undefined, + + /** @type {Function} **/ + _template: undefined, + + /** @type {boolean} **/ + showLink: true, + + /** @type {object} **/ + _collections: {}, + + events: { + 'click .unshare': 'onUnshare', + 'click .permissions': 'onPermissionChange', + 'click .showCruds': 'onCrudsToggle', + 'click .mailNotification': 'onSendMailNotification' + }, + + initialize: function(options) { + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } else { + throw 'missing OC.Share.ShareConfigModel'; + } + + var view = this; + this.model.on('change:shares', function() { + view.render(); + }); + }, + + processCollectionShare: function(shareIndex) { + var type = this.model.getCollectionType(shareIndex); + var id = this.model.getCollectionPath(shareIndex); + if(type !== 'file' && type !== 'folder') { + id = this.model.getCollectionSource(shareIndex); + } + var displayName = this.model.getShareWithDisplayName(shareIndex); + if(!_.isUndefined(this._collections[id])) { + this._collections[id].text = this._collections[id].text + ", " + displayName; + } else { + this._collections[id] = {}; + this._collections[id].text = t('core', 'Shared in {item} with {user}', {'item': id, user: displayName}); + this._collections[id].id = id; + this._collections[id].isCollection = true; + } + }, + + /** + * + * @param {OC.Share.Types.ShareInfo} shareInfo + * @returns {object} + */ + getShareeObject: function(shareIndex) { + var shareWith = this.model.getShareWith(shareIndex); + var shareWithDisplayName = this.model.getShareWithDisplayName(shareIndex); + var shareType = this.model.getShareType(shareIndex); + + var hasPermissionOverride = {}; + if (shareType === OC.Share.SHARE_TYPE_GROUP) { + shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')'; + } else if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')'; + hasPermissionOverride = { + createPermissionPossible: true, + updatePermissionPossible: true + }; + } + + return _.extend(hasPermissionOverride, { + hasSharePermission: this.model.hasSharePermission(shareIndex), + hasEditPermission: this.model.hasEditPermission(shareIndex), + hasCreatePermission: this.model.hasCreatePermission(shareIndex), + hasUpdatePermission: this.model.hasUpdatePermission(shareIndex), + hasDeletePermission: this.model.hasDeletePermission(shareIndex), + wasMailSent: this.model.notificationMailWasSent(shareIndex), + shareWith: shareWith, + shareWithDisplayName: shareWithDisplayName, + shareType: shareType, + modSeed: shareType !== OC.Share.SHARE_TYPE_USER, + isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE + }); + }, + + getShareeList: function() { + var universal = { + avatarEnabled: this.configModel.areAvatarsEnabled(), + mailPublicNotificationEnabled: this.configModel.isMailPublicNotificationEnabled(), + notifyByMailLabel: t('core', 'notify by email'), + unshareLabel: t('core', 'Unshare'), + unshareImage: OC.imagePath('core', 'actions/delete'), + canShareLabel: t('core', 'can share'), + canEditLabel: t('core', 'can edit'), + createPermissionLabel: t('core', 'create'), + updatePermissionLabel: t('core', 'change'), + deletePermissionLabel: t('core', 'delete'), + crudsLabel: t('core', 'access control'), + triangleSImage: OC.imagePath('core', 'actions/triangle-s'), + isResharingAllowed: this.configModel.get('isResharingAllowed'), + sharePermissionPossible: this.model.sharePermissionPossible(), + editPermissionPossible: this.model.editPermissionPossible(), + createPermissionPossible: this.model.createPermissionPossible(), + updatePermissionPossible: this.model.updatePermissionPossible(), + deletePermissionPossible: this.model.deletePermissionPossible(), + sharePermission: OC.PERMISSION_SHARE, + createPermission: OC.PERMISSION_CREATE, + updatePermission: OC.PERMISSION_UPDATE, + deletePermission: OC.PERMISSION_DELETE + }; + + this._collections = {}; + + if(!this.model.hasUserShares()) { + return []; + } + + var shares = this.model.get('shares'); + var list = []; + for(var index = 0; index < shares.length; index++) { + if(this.model.isCollection(index)) { + this.processCollectionShare(index); + } else { + // first empty {} is necessary, otherwise we get in trouble + // with references + list.push(_.extend({}, universal, this.getShareeObject(index))); + } + } + list = _.union(_.values(this._collections), list); + + return list; + }, + + render: function() { + var shareeListTemplate = this.template(); + this.$el.html(shareeListTemplate({ + sharees: this.getShareeList() + })); + + if(this.configModel.areAvatarsEnabled()) { + this.$el.find('.avatar').each(function() { + var $this = $(this); + if ($this.hasClass('imageplaceholderseed')) { + $this.css({width: 32, height: 32}); + $this.imageplaceholder($this.data('seed')); + } else { + $this.avatar($this.data('username'), 32); + } + }); + } + + this.delegateEvents(); + + return this; + }, + + /** + * @returns {Function} from Handlebars + * @private + */ + template: function () { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template; + }, + + onUnshare: function(event) { + var $element = $(event.target); + + if($element.hasClass('icon-loading-small')) { + // in process + return; + } + $element.empty().addClass('icon-loading-small'); + + var $li = $element.closest('li'); + var shareType = $li.data('share-type'); + var shareWith = $li.attr('data-share-with'); + + this.model.removeShare(shareType, shareWith); + + return false; + }, + + onPermissionChange: function(event) { + var $element = $(event.target); + var $li = $element.closest('li'); + var shareType = $li.data('share-type'); + var shareWith = $li.attr('data-share-with'); + + // adjust checkbox states + var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]'); + var checked; + if ($element.attr('name') === 'edit') { + checked = $element.is(':checked'); + // Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck + $($checkboxes).attr('checked', checked); + } else { + var numberChecked = $checkboxes.filter(':checked').length; + checked = numberChecked > 0; + $('input[name="edit"]', $li).attr('checked', checked); + } + + var permissions = OC.PERMISSION_READ; + $('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) { + permissions |= $(checkbox).data('permissions'); + }); + + this.model.setPermissions(shareType, shareWith, permissions); + }, + + onCrudsToggle: function(event) { + var $target = $(event.target); + $target.closest('li').find('.cruds').toggleClass('hidden'); + return false; + }, + + onSendMailNotification: function(event) { + var $target = $(event.target); + var $li = $(event.target).closest('li'); + var shareType = $li.data('share-type'); + var shareWith = $li.attr('data-share-with'); + + this.model.sendNotificationForShare(shareType, shareWith, $target.is(':checked')); + } + }); + + OC.Share.ShareDialogShareeListView = ShareDialogShareeListView; + +})(); diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js new file mode 100644 index 000000000000..2b61dab3ceb7 --- /dev/null +++ b/core/js/sharedialogview.js @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if(!OC.Share) { + OC.Share = {}; + } + + var TEMPLATE_BASE = + '
    ' + + '{{#if isSharingAllowed}}' + + '' + + '
    ' + + ' ' + + ' '+ + '{{{remoteShareInfo}}}' + + '
    ' + + '{{/if}}' + + '
    ' + + '
    ' + + '
    ' + ; + + var TEMPLATE_REMOTE_SHARE_INFO = + ''; + + /** + * @class OCA.Share.ShareDialogView + * @member {OC.Share.ShareItemModel} model + * @member {jQuery} $el + * @memberof OCA.Sharing + * @classdesc + * + * Represents the GUI of the share dialogue + * + */ + var ShareDialogView = OC.Backbone.View.extend({ + /** @type {Object} **/ + _templates: {}, + + /** @type {boolean} **/ + _showLink: true, + + /** @type {string} **/ + tagName: 'div', + + /** @type {OC.Share.ShareConfigModel} **/ + configModel: undefined, + + /** @type {object} **/ + resharerInfoView: undefined, + + /** @type {object} **/ + linkShareView: undefined, + + /** @type {object} **/ + expirationView: undefined, + + /** @type {object} **/ + shareeListView: undefined, + + initialize: function(options) { + var view = this; + + this.model.on('fetchError', function() { + OC.Notification.showTemporary(t('core', 'Share details could not be loaded for this item.')); + }); + + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } else { + throw 'missing OC.Share.ShareConfigModel'; + } + + this.configModel.on('change:isRemoteShareAllowed', function() { + view.render(); + }); + this.model.on('change:permissions', function() { + view.render(); + }); + + var subViewOptions = { + model: this.model, + configModel: this.configModel + }; + + var subViews = { + resharerInfoView: 'ShareDialogResharerInfoView', + linkShareView: 'ShareDialogLinkShareView', + expirationView: 'ShareDialogExpirationView', + shareeListView: 'ShareDialogShareeListView' + }; + + for(var name in subViews) { + var className = subViews[name]; + this[name] = _.isUndefined(options[name]) + ? new OC.Share[className](subViewOptions) + : options[name]; + } + + _.bindAll(this, 'autocompleteHandler', '_onSelectRecipient'); + }, + + autocompleteHandler: function (search, response) { + var view = this; + var $loading = this.$el.find('.shareWithLoading'); + $loading.removeClass('hidden'); + $loading.addClass('inlineblock'); + $.get(OC.filePath('core', 'ajax', 'share.php'), { + fetch: 'getShareWith', + search: search.term.trim(), + limit: 200, + itemShares: OC.Share.itemShares, + itemType: view.model.get('itemType') + }, function (result) { + $loading.addClass('hidden'); + $loading.removeClass('inlineblock'); + if (result.status == 'success' && result.data.length > 0) { + $("#shareWith").autocomplete("option", "autoFocus", true); + response(result.data); + } else { + response(); + } + }).fail(function () { + $loading.addClass('hidden'); + $loading.removeClass('inlineblock'); + OC.Notification.show(t('core', 'An error occured. Please try again')); + window.setTimeout(OC.Notification.hide, 5000); + }); + }, + + autocompleteRenderItem: function(ul, item) { + var insert = $(""); + var text = item.label; + if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) { + text = text + ' ('+t('core', 'group')+')'; + } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) { + text = text + ' ('+t('core', 'remote')+')'; + } + insert.text(text); + if(item.value.shareType === OC.Share.SHARE_TYPE_GROUP) { + insert = insert.wrapInner(''); + } + return $("
  • ") + .addClass((item.value.shareType === OC.Share.SHARE_TYPE_GROUP) ? 'group' : 'user') + .append(insert) + .appendTo(ul); + }, + + _onSelectRecipient: function(e, s) { + e.preventDefault(); + $(e.target).val(''); + this.model.addShare(s.item.value); + }, + + render: function() { + var baseTemplate = this._getTemplate('base', TEMPLATE_BASE); + + this.$el.html(baseTemplate({ + shareLabel: t('core', 'Share'), + sharePlaceholder: this._renderSharePlaceholderPart(), + remoteShareInfo: this._renderRemoteShareInfoPart(), + isSharingAllowed: this.model.sharePermissionPossible() + })); + + var $shareField = this.$el.find('#shareWith'); + if ($shareField.length) { + $shareField.autocomplete({ + minLength: 2, + delay: 750, + source: this.autocompleteHandler, + select: this._onSelectRecipient + }).data('ui-autocomplete')._renderItem = this.autocompleteRenderItem; + } + + this.resharerInfoView.$el = this.$el.find('.resharerInfoView'); + this.resharerInfoView.render(); + + this.linkShareView.$el = this.$el.find('.linkShareView'); + this.linkShareView.render(); + + this.expirationView.$el = this.$el.find('.expirationView'); + this.expirationView.render(); + + this.shareeListView.$el = this.$el.find('.shareeListView'); + this.shareeListView.render(); + + this.$el.find('.hasTooltip').tooltip(); + + return this; + }, + + /** + * sets whether share by link should be displayed or not. Default is + * true. + * + * @param {bool} showLink + */ + setShowLink: function(showLink) { + this._showLink = (typeof showLink === 'boolean') ? showLink : true; + this.linkShareView.showLink = this._showLink; + }, + + _renderRemoteShareInfoPart: function() { + var remoteShareInfo = ''; + if(this.configModel.get('isRemoteShareAllowed')) { + var infoTemplate = this._getRemoteShareInfoTemplate(); + remoteShareInfo = infoTemplate({ + docLink: this.configModel.getFederatedShareDocLink(), + tooltip: t('core', 'Share with people on other ownClouds using the syntax username@example.com/owncloud') + }); + } + + return remoteShareInfo; + }, + + _renderSharePlaceholderPart: function () { + var sharePlaceholder = t('core', 'Share with users or groups …'); + if (this.configModel.get('isRemoteShareAllowed')) { + sharePlaceholder = t('core', 'Share with users, groups or remote users …'); + } + return sharePlaceholder; + }, + + /** + * + * @param {string} key - an identifier for the template + * @param {string} template - the HTML to be compiled by Handlebars + * @returns {Function} from Handlebars + * @private + */ + _getTemplate: function (key, template) { + if (!this._templates[key]) { + this._templates[key] = Handlebars.compile(template); + } + return this._templates[key]; + }, + + /** + * returns the info template for remote sharing + * + * @returns {Function} + * @private + */ + _getRemoteShareInfoTemplate: function() { + return this._getTemplate('remoteShareInfo', TEMPLATE_REMOTE_SHARE_INFO); + } + }); + + OC.Share.ShareDialogView = ShareDialogView; + +})(); diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js new file mode 100644 index 000000000000..ff0d3a6d8009 --- /dev/null +++ b/core/js/shareitemmodel.js @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + if(!OC.Share) { + OC.Share = {}; + OC.Share.Types = {}; + } + + /** + * @typedef {object} OC.Share.Types.LinkShareInfo + * @property {bool} isLinkShare + * @property {string} token + * @property {string|null} password + * @property {string} link + * @property {number} permissions + * @property {Date} expiration + * @property {number} stime share time + */ + + /** + * @typedef {object} OC.Share.Types.Collection + * @property {string} item_type + * @property {string} path + * @property {string} item_source TODO: verify + */ + + /** + * @typedef {object} OC.Share.Types.Reshare + * @property {string} uid_owner + * @property {number} share_type + * @property {string} share_with + * @property {string} displayname_owner + * @property {number} permissions + */ + + /** + * @typedef {object} OC.Share.Types.ShareInfo + * @property {number} share_type + * @property {number} permissions + * @property {number} file_source optional + * @property {number} item_source + * @property {string} token + * @property {string} share_with + * @property {string} share_with_displayname + * @property {string} share_mail_send + * @property {OC.Share.Types.Collection|undefined} collection + * @property {Date} expiration optional? + * @property {number} stime optional? + */ + + /** + * @typedef {object} OC.Share.Types.ShareItemInfo + * @property {OC.Share.Types.Reshare} reshare + * @property {OC.Share.Types.ShareInfo[]} shares + * @property {OC.Share.Types.LinkShareInfo|undefined} linkShare + */ + + /** + * @class OCA.Share.ShareItemModel + * @classdesc + * + * Represents the GUI of the share dialogue + * + * // FIXME: use OC Share API once #17143 is done + * + * // TODO: this really should be a collection of share item models instead, + * where the link share is one of them + */ + var ShareItemModel = OC.Backbone.Model.extend({ + initialize: function(attributes, options) { + if(!_.isUndefined(options.configModel)) { + this.configModel = options.configModel; + } + if(!_.isUndefined(options.fileInfoModel)) { + /** @type {OC.Files.FileInfo} **/ + this.fileInfoModel = options.fileInfoModel; + } + + _.bindAll(this, 'addShare'); + }, + + defaults: { + allowPublicUploadStatus: false, + permissions: 0, + linkShare: {} + }, + + /** + * Saves the current link share information. + * + * This will trigger an ajax call and refetch the model afterwards. + * + * TODO: this should be a separate model + */ + saveLinkShare: function(attributes, options) { + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + // TODO: use backbone's default value mechanism once this is a separate model + var requiredAttributes = [ + { name: 'password', defaultValue: '' }, + { name: 'permissions', defaultValue: OC.PERMISSION_READ }, + { name: 'expiration', defaultValue: this.configModel.getDefaultExpirationDateString() } + ]; + + attributes = attributes || {}; + + // get attributes from the model and fill in with default values + _.each(requiredAttributes, function(attribute) { + // a provided options overrides a present value of the link + // share. If neither is given, the default value is used. + if(_.isUndefined(attribute[attribute.name])) { + attributes[attribute.name] = attribute.defaultValue; + var currentValue = model.get('linkShare')[attribute.name]; + if(!_.isUndefined(currentValue)) { + attributes[attribute.name] = currentValue; + } + } + }); + + OC.Share.share( + itemType, + itemSource, + OC.Share.SHARE_TYPE_LINK, + attributes.password, + attributes.permissions, + this.fileInfoModel.get('name'), + attributes.expiration, + function(result) { + if (!result || result.status !== 'success') { + model.fetch({ + success: function() { + if (options && _.isFunction(options.success)) { + options.success(model); + } + } + }); + } else { + if (options && _.isFunction(options.error)) { + options.error(model); + } + } + }, + function(result) { + var msg = t('core', 'Error'); + if (result.data && result.data.message) { + msg = result.data.message; + } + + if (options && _.isFunction(options.error)) { + options.error(model, msg); + } else { + OC.dialogs.alert(msg, t('core', 'Error while sharing')); + } + } + ); + }, + + removeLinkShare: function() { + this.removeShare(OC.Share.SHARE_TYPE_LINK, ''); + }, + + /** + * Sets the public upload flag + * + * @param {bool} allow whether public upload is allowed + */ + setPublicUpload: function(allow) { + var permissions = OC.PERMISSION_READ; + if(allow) { + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + } + + this.get('linkShare').permissions = permissions; + }, + + /** + * Sets the expiration date of the public link + * + * @param {string} expiration expiration date + */ + setExpirationDate: function(expiration) { + this.get('linkShare').expiration = expiration; + }, + + /** + * Set password of the public link share + * + * @param {string} password + */ + setPassword: function(password) { + this.get('linkShare').password = password; + }, + + addShare: function(attributes, options) { + var shareType = attributes.shareType; + var shareWith = attributes.shareWith; + var fileName = this.fileInfoModel.get('name'); + options = options || {}; + + // Default permissions are Edit (CRUD) and Share + // Check if these permissions are possible + var permissions = OC.PERMISSION_READ; + if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + permissions = OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ; + } else { + if (this.updatePermissionPossible()) { + permissions = permissions | OC.PERMISSION_UPDATE; + } + if (this.createPermissionPossible()) { + permissions = permissions | OC.PERMISSION_CREATE; + } + if (this.deletePermissionPossible()) { + permissions = permissions | OC.PERMISSION_DELETE; + } + if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { + permissions = permissions | OC.PERMISSION_SHARE; + } + } + + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + OC.Share.share(itemType, itemSource, shareType, shareWith, permissions, fileName, options.expiration, function() { + model.fetch(); + }); + }, + + setPermissions: function(shareType, shareWith, permissions) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + // TODO: in the future, only set the permissions on the model but don't save directly + OC.Share.setPermissions(itemType, itemSource, shareType, shareWith, permissions); + }, + + removeShare: function(shareType, shareWith) { + var model = this; + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + OC.Share.unshare(itemType, itemSource, shareType, shareWith, function() { + model.fetch(); + }); + }, + + /** + * @returns {boolean} + */ + isPublicUploadAllowed: function() { + return this.get('allowPublicUploadStatus'); + }, + + /** + * @returns {boolean} + */ + isFolder: function() { + return this.get('itemType') === 'folder'; + }, + + /** + * @returns {boolean} + */ + isFile: function() { + return this.get('itemType') === 'file'; + }, + + /** + * whether this item has reshare information + * @returns {boolean} + */ + hasReshare: function() { + var reshare = this.get('reshare'); + return _.isObject(reshare) && !_.isUndefined(reshare.uid_owner); + }, + + /** + * whether this item has user share information + * @returns {boolean} + */ + hasUserShares: function() { + var shares = this.get('shares'); + return _.isArray(shares) && shares.length > 0; + }, + + /** + * Returns whether this item has a link share + * + * @return {bool} true if a link share exists, false otherwise + */ + hasLinkShare: function() { + var linkShare = this.get('linkShare'); + if (linkShare && linkShare.isLinkShare) { + return true; + } + return false; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionType: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.item_type; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionPath: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.path; + }, + + /** + * @param {number} shareIndex + * @returns {string} + */ + getCollectionSource: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } else if(_.isUndefined(share.collection)) { + throw "Share is not a collection"; + } + + return share.collection.item_source; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + isCollection: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + if(_.isUndefined(share.collection)) { + return false; + } + return true; + }, + + + /** + * @returns {string} + */ + getReshareOwner: function() { + return this.get('reshare').uid_owner; + }, + + /** + * @returns {string} + */ + getReshareOwnerDisplayname: function() { + return this.get('reshare').displayname_owner; + }, + + /** + * @returns {string} + */ + getReshareWith: function() { + return this.get('reshare').share_with; + }, + + /** + * @returns {number} + */ + getReshareType: function() { + return this.get('reshare').share_type; + }, + + /** + * @param shareIndex + * @returns {string} + */ + getShareWith: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_with; + }, + + /** + * @param shareIndex + * @returns {string} + */ + getShareWithDisplayName: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_with_displayname; + }, + + getShareType: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_type; + }, + + /** + * whether a share from shares has the requested permission + * + * @param {number} shareIndex + * @param {number} permission + * @returns {boolean} + * @private + */ + _shareHasPermission: function(shareIndex, permission) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + if( share.share_type === OC.Share.SHARE_TYPE_REMOTE + && ( permission === OC.PERMISSION_SHARE + || permission === OC.PERMISSION_DELETE)) + { + return false; + } + return (share.permissions & permission) === permission; + }, + + notificationMailWasSent: function(shareIndex) { + /** @type OC.Share.Types.ShareInfo **/ + var share = this.get('shares')[shareIndex]; + if(!_.isObject(share)) { + throw "Unknown Share"; + } + return share.share_mail_send === '1'; + }, + + /** + * Sends an email notification for the given share + * + * @param {int} shareType share type + * @param {string} shareWith recipient + * @param {bool} state whether to set the notification flag or remove it + */ + sendNotificationForShare: function(shareType, shareWith, state) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + + return $.post( + OC.generateUrl('core/ajax/share.php'), + { + action: state ? 'informRecipients' : 'informRecipientsDisabled', + recipient: shareWith, + shareType: shareType, + itemSource: itemSource, + itemType: itemType + }, + function(result) { + if (result.status !== 'success') { + // FIXME: a model should not show dialogs + OC.dialogs.alert(t('core', result.data.message), t('core', 'Warning')); + } + } + ); + }, + + /** + * Send the link share information by email + * + * @param {string} recipientEmail recipient email address + */ + sendEmailPrivateLink: function(recipientEmail) { + var itemType = this.get('itemType'); + var itemSource = this.get('itemSource'); + var linkShare = this.get('linkShare'); + + return $.post( + OC.generateUrl('core/ajax/share.php'), { + action: 'email', + toaddress: recipientEmail, + link: linkShare.link, + itemType: itemType, + itemSource: itemSource, + file: this.fileInfoModel.get('name'), + expiration: linkShare.expiration || '' + }, + function(result) { + if (!result || result.status !== 'success') { + // FIXME: a model should not show dialogs + OC.dialogs.alert(result.data.message, t('core', 'Error while sending notification')); + } + }); + }, + + /** + * @returns {boolean} + */ + sharePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_SHARE) === OC.PERMISSION_SHARE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasSharePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_SHARE); + }, + + /** + * @returns {boolean} + */ + createPermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_CREATE) === OC.PERMISSION_CREATE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasCreatePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_CREATE); + }, + + /** + * @returns {boolean} + */ + updatePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_UPDATE) === OC.PERMISSION_UPDATE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasUpdatePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_UPDATE); + }, + + /** + * @returns {boolean} + */ + deletePermissionPossible: function() { + return (this.get('permissions') & OC.PERMISSION_DELETE) === OC.PERMISSION_DELETE; + }, + + /** + * @param {number} shareIndex + * @returns {boolean} + */ + hasDeletePermission: function(shareIndex) { + return this._shareHasPermission(shareIndex, OC.PERMISSION_DELETE); + }, + + /** + * @returns {boolean} + */ + editPermissionPossible: function() { + return this.createPermissionPossible() + || this.updatePermissionPossible() + || this.deletePermissionPossible(); + }, + + /** + * @returns {boolean} + */ + hasEditPermission: function(shareIndex) { + return this.hasCreatePermission(shareIndex) + || this.hasUpdatePermission(shareIndex) + || this.hasDeletePermission(shareIndex); + }, + + fetch: function() { + var model = this; + OC.Share.loadItem(this.get('itemType'), this.get('itemSource'), function(data) { + model.set(model.parse(data)); + }); + }, + + /** + * Updates OC.Share.itemShares and OC.Share.statuses. + * + * This is required in case the user navigates away and comes back, + * the share statuses from the old arrays are still used to fill in the icons + * in the file list. + */ + _legacyFillCurrentShares: function(shares) { + var fileId = this.fileInfoModel.get('id'); + if (!shares || !shares.length) { + delete OC.Share.statuses[fileId]; + return; + } + + var currentShareStatus = OC.Share.statuses[fileId]; + if (!currentShareStatus) { + currentShareStatus = {link: false}; + OC.Share.statuses[fileId] = currentShareStatus; + } + currentShareStatus.link = false; + + OC.Share.currentShares = {}; + OC.Share.itemShares = []; + _.each(shares, + /** + * @param {OC.Share.Types.ShareInfo} share + */ + function(share) { + if (share.share_type === OC.Share.SHARE_TYPE_LINK) { + OC.Share.itemShares[share.share_type] = true; + currentShareStatus.link = true; + } else { + if (!OC.Share.itemShares[share.share_type]) { + OC.Share.itemShares[share.share_type] = []; + } + OC.Share.itemShares[share.share_type].push(share.share_with); + } + } + ); + }, + + parse: function(data) { + if(data === false) { + console.warn('no data was returned'); + trigger('fetchError'); + return {}; + } + + var permissions = this.get('possiblePermissions'); + if(!_.isUndefined(data.reshare) && !_.isUndefined(data.reshare.permissions) && data.reshare.uid_owner !== OC.currentUser) { + permissions = permissions & data.reshare.permissions; + } + + var allowPublicUploadStatus = false; + if(!_.isUndefined(data.shares)) { + $.each(data.shares, function (key, value) { + if (value.share_type === OC.Share.SHARE_TYPE_LINK) { + allowPublicUploadStatus = (value.permissions & OC.PERMISSION_CREATE) ? true : false; + return true; + } + }); + } + + /** @type {OC.Share.Types.ShareInfo[]} **/ + var shares = _.toArray(data.shares); + this._legacyFillCurrentShares(shares); + + var linkShare = { isLinkShare: false }; + // filter out the share by link + shares = _.reject(shares, + /** + * @param {OC.Share.Types.ShareInfo} share + */ + function(share) { + var isShareLink = + share.share_type === OC.Share.SHARE_TYPE_LINK + && ( share.file_source === this.get('itemSource') + || share.item_source === this.get('itemSource')); + + if (isShareLink) { + var link = window.location.protocol + '//' + window.location.host; + if (!share.token) { + // pre-token link + var fullPath = this.fileInfoModel.get('path') + '/' + + this.fileInfoModel.get('name'); + var location = '/' + OC.currentUser + '/files' + fullPath; + var type = this.fileInfoModel.isDirectory() ? 'folder' : 'file'; + link += OC.linkTo('', 'public.php') + '?service=files&' + + type + '=' + encodeURIComponent(location); + } else { + link += OC.generateUrl('/s/') + share.token; + } + linkShare = { + isLinkShare: true, + token: share.token, + password: share.share_with, + link: link, + permissions: share.permissions, + // currently expiration is only effective for link shares. + expiration: share.expiration, + stime: share.stime + }; + + return share; + } + }, + this + ); + + return { + reshare: data.reshare, + shares: shares, + linkShare: linkShare, + permissions: permissions, + allowPublicUploadStatus: allowPublicUploadStatus + }; + }, + + /** + * Parses a string to an valid integer (unix timestamp) + * @param time + * @returns {*} + * @internal Only used to work around a bug in the backend + */ + _parseTime: function(time) { + if (_.isString(time)) { + // skip empty strings and hex values + if (time === '' || (time.length > 1 && time[0] === '0' && time[1] === 'x')) { + return null; + } + time = parseInt(time, 10); + if(isNaN(time)) { + time = null; + } + } + return time; + } + }); + + OC.Share.ShareItemModel = ShareItemModel; +})(); diff --git a/core/js/tests/specs/shareSpec.js b/core/js/tests/specs/shareSpec.js index 5a59a117d77d..9e80f4fe19d6 100644 --- a/core/js/tests/specs/shareSpec.js +++ b/core/js/tests/specs/shareSpec.js @@ -21,1090 +21,6 @@ /* global oc_appconfig */ describe('OC.Share tests', function() { - describe('dropdown', function() { - var $container; - var oldAppConfig; - var loadItemStub; - var autocompleteStub; - var oldEnableAvatars; - var avatarStub; - var placeholderStub; - var oldCurrentUser; - - beforeEach(function() { - $('#testArea').append($('
    ')); - // horrible parameters - $('#testArea').append(''); - $('#testArea').append(''); - $container = $('#shareContainer'); - /* jshint camelcase:false */ - oldAppConfig = _.extend({}, oc_appconfig.core); - oc_appconfig.core.enforcePasswordForPublicLink = false; - - loadItemStub = sinon.stub(OC.Share, 'loadItem'); - loadItemStub.returns({ - reshare: [], - shares: [] - }); - - autocompleteStub = sinon.stub($.fn, 'autocomplete', function() { - // dummy container with the expected attributes - if (!$(this).length) { - // simulate the real autocomplete that returns - // nothing at all when no element is specified - // (and potentially break stuff) - return null; - } - var $el = $('
    ').data('ui-autocomplete', {}); - return $el; - }); - - oldEnableAvatars = oc_config.enable_avatars; - oc_config.enable_avatars = false; - avatarStub = sinon.stub($.fn, 'avatar'); - placeholderStub = sinon.stub($.fn, 'imageplaceholder'); - - oldCurrentUser = OC.currentUser; - OC.currentUser = 'user0'; - }); - afterEach(function() { - OC.currentUser = oldCurrentUser; - /* jshint camelcase:false */ - oc_appconfig.core = oldAppConfig; - loadItemStub.restore(); - - autocompleteStub.restore(); - avatarStub.restore(); - placeholderStub.restore(); - oc_config.enable_avatars = oldEnableAvatars; - $('#dropdown').remove(); - }); - it('calls loadItem with the correct arguments', function() { - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - expect(loadItemStub.calledOnce).toEqual(true); - expect(loadItemStub.calledWith('file', 123)).toEqual(true); - }); - it('shows the dropdown with default values', function() { - var $el; - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - $el = $container.find('#dropdown'); - expect($el.length).toEqual(1); - expect($el.attr('data-item-type')).toEqual('file'); - expect($el.attr('data-item-source')).toEqual('123'); - // TODO: expect that other parts are rendered correctly - }); - describe('Share with link', function() { - // TODO: test ajax calls - // TODO: test password field visibility (whenever enforced or not) - it('update password on focus out', function() { - $('#allowShareWithLink').val('yes'); - - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - - // Toggle linkshare - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - // Enable password, enter password and focusout - $('#dropdown [name=showPassword]').click(); - $('#dropdown #linkPassText').focus(); - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').focusout(); - - expect(fakeServer.requests[1].method).toEqual('POST'); - var body = OC.parseQueryString(fakeServer.requests[1].requestBody); - expect(body['shareWith']).toEqual('foo'); - - // Set password response - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect($('#dropdown #linkPassText').val()).toEqual(''); - expect($('#dropdown #linkPassText').attr('placeholder')).toEqual('Password protected'); - }); - it('update password on enter', function() { - $('#allowShareWithLink').val('yes'); - - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - - // Toggle linkshare - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - // Enable password and enter password - $('#dropdown [name=showPassword]').click(); - $('#dropdown #linkPassText').focus(); - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - - expect(fakeServer.requests[1].method).toEqual('POST'); - var body = OC.parseQueryString(fakeServer.requests[1].requestBody); - expect(body['shareWith']).toEqual('foo'); - - // Set password response - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect($('#dropdown #linkPassText').val()).toEqual(''); - expect($('#dropdown #linkPassText').attr('placeholder')).toEqual('Password protected'); - }); - it('shows share with link checkbox when allowed', function() { - $('#allowShareWithLink').val('yes'); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - expect($('#dropdown #linkCheckbox').length).toEqual(1); - }); - it('does not show share with link checkbox when not allowed', function() { - $('#allowShareWithLink').val('no'); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - expect($('#dropdown #linkCheckbox').length).toEqual(0); - }); - it('Reset link when password is enforced and link is toggled', function() { - var old = oc_appconfig.core.enforcePasswordForPublicLink; - oc_appconfig.core.enforcePasswordForPublicLink = true; - $('#allowShareWithLink').val('yes'); - - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - - // Toggle linkshare - $('#dropdown [name=linkCheckbox]').click(); - expect($('#dropdown #linkText').val()).toEqual(''); - - // Set password - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - // Remove link - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - - /* - * Try to share again - * The linkText should be emptied - */ - $('#dropdown [name=linkCheckbox]').click(); - expect($('#dropdown #linkText').val()).toEqual(''); - - /* - * Do not set password but untoggle - * Since there is no share this should not result in another request to the server - */ - $('#dropdown [name=linkCheckbox]').click(); - expect(fakeServer.requests.length).toEqual(2); - - oc_appconfig.core.enforcePasswordForPublicLink = old; - }); - - it('Reset password placeholder when password is enforced and link is toggled', function() { - var old = oc_appconfig.core.enforcePasswordForPublicLink; - oc_appconfig.core.enforcePasswordForPublicLink = true; - $('#allowShareWithLink').val('yes'); - - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - - // Toggle linkshare - $('#dropdown [name=linkCheckbox]').click(); - expect($('#dropdown #linkPassText').attr('placeholder')).toEqual('Choose a password for the public link'); - - // Set password - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - expect($('#dropdown #linkPassText').attr('placeholder')).toEqual('**********'); - - // Remove link - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - - // Try to share again - $('#dropdown [name=linkCheckbox]').click(); - expect($('#dropdown #linkPassText').attr('placeholder')).toEqual('Choose a password for the public link'); - - oc_appconfig.core.enforcePasswordForPublicLink = old; - }); - it('reset password on toggle of share', function() { - $('#allowShareWithLink').val('yes'); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - //Password protection should be unchecked and password field not visible - expect($('#dropdown [name=showPassword]').prop('checked')).toEqual(false); - expect($('#dropdown #linkPass').is(":visible")).toEqual(false); - - // Toggle and set password - $('#dropdown [name=showPassword]').click(); - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz2'}, status: 'success'}) - ); - - // Unshare - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[2].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - - // Toggle share again - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[3].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz3'}, status: 'success'}) - ); - - - // Password checkbox should be unchecked - expect($('#dropdown [name=showPassword]').prop('checked')).toEqual(false); - expect($('#dropdown #linkPass').is(":visible")).toEqual(false); - }); - it('reset expiration on toggle of share', function() { - $('#allowShareWithLink').val('yes'); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - //Expiration should be unchecked and expiration field not visible - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); - expect($('#dropdown #expirationDate').is(":visible")).toEqual(false); - - // Toggle and set password - $('#dropdown [name=expirationCheckbox]').click(); - d = new Date(); - d.setDate(d.getDate() + 1); - date=d.getDate() + '-' + (d.getMonth()+1) + '-' + d.getFullYear(); - $('#dropdown #expirationDate').val(date); - $('#dropdown #expirationDate').change(); - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz2'}, status: 'success'}) - ); - - // Unshare - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[2].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - - // Toggle share again - $('#dropdown [name=linkCheckbox]').click(); - fakeServer.requests[3].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz3'}, status: 'success'}) - ); - - // Recheck expire visibility - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); - expect($('#dropdown #expirationDate').is(":visible")).toEqual(false); - }); - it('shows populated link share when a link share exists', function() { - loadItemStub.returns({ - reshare: [], - /* jshint camelcase: false */ - shares: [{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'folder' - ); - expect($('#dropdown #linkCheckbox').prop('checked')).toEqual(true); - // this is how the OC.Share class does it... - var link = parent.location.protocol + '//' + location.host + - OC.generateUrl('/s/') + 'tehtoken'; - expect($('#dropdown #linkText').val()).toEqual(link); - }); - it('does not show populated link share when a link share exists for a different file', function() { - loadItemStub.returns({ - reshare: [], - /* jshint camelcase: false */ - shares: [{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }] - }); - OC.Share.showDropDown( - 'file', - 456, // another file - $container, - true, - 31, - 'folder' - ); - expect($('#dropdown #linkCheckbox').prop('checked')).toEqual(false); - }); - it('shows correct link share when a nest link share exists along with parent one', function() { - loadItemStub.returns({ - reshare: [], - /* jshint camelcase: false */ - shares: [{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'file', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }, { - displayname_owner: 'root', - expiration: null, - file_source: 456, - file_target: '/file_in_folder.txt', - id: 21, - item_source: '456', - item_type: 'file', - mail_send: '0', - parent: null, - path: '/folder/file_in_folder.txt', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884509, - storage: 1, - token: 'anothertoken', - uid_owner: 'root' - }] - }); - - // parent one - OC.Share.showDropDown( - 'folder', - 123, - $container, - true, - 31, - 'folder' - ); - expect($('#dropdown #linkCheckbox').prop('checked')).toEqual(true); - // this is how the OC.Share class does it... - var link = parent.location.protocol + '//' + location.host + - OC.generateUrl('/s/') + 'tehtoken'; - expect($('#dropdown #linkText').val()).toEqual(link); - - // nested one - OC.Share.showDropDown( - 'file', - 456, - $container, - true, - 31, - 'file_in_folder.txt' - ); - expect($('#dropdown #linkCheckbox').prop('checked')).toEqual(true); - // this is how the OC.Share class does it... - link = parent.location.protocol + '//' + location.host + - OC.generateUrl('/s/') + 'anothertoken'; - expect($('#dropdown #linkText').val()).toEqual(link); - }); - describe('expiration date', function() { - var shareData; - var shareItem; - var clock; - var expectedMinDate; - - function showDropDown() { - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'folder' - ); - } - - beforeEach(function() { - // pick a fake date - clock = sinon.useFakeTimers(new Date(2014, 0, 20, 14, 0, 0).getTime()); - expectedMinDate = new Date(2014, 0, 21, 14, 0, 0); - shareItem = { - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }; - shareData = { - reshare: [], - shares: [] - }; - loadItemStub.returns(shareData); - oc_appconfig.core.defaultExpireDate = 7; - oc_appconfig.core.enforcePasswordForPublicLink = false; - oc_appconfig.core.defaultExpireDateEnabled = false; - oc_appconfig.core.defaultExpireDateEnforced = false; - }); - afterEach(function() { - clock.restore(); - }); - - it('does not check expiration date checkbox when no date was set', function() { - shareItem.expiration = null; - shareData.shares.push(shareItem); - showDropDown(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); - expect($('#dropdown #expirationDate').val()).toEqual(''); - }); - it('does not check expiration date checkbox for new share', function() { - showDropDown(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); - expect($('#dropdown #expirationDate').val()).toEqual(''); - }); - it('checks expiration date checkbox and populates field when expiration date was set', function() { - shareItem.expiration = 1234; - shareData.shares.push(shareItem); - showDropDown(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - expect($('#dropdown #expirationDate').val()).toEqual('1234'); - }); - it('sets default date when default date setting is enabled', function() { - /* jshint camelcase:false */ - oc_appconfig.core.defaultExpireDateEnabled = true; - showDropDown(); - $('#dropdown [name=linkCheckbox]').click(); - // enabled by default - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - // TODO: those zeros must go... - expect($('#dropdown #expirationDate').val()).toEqual('2014-1-27 00:00:00'); - - // disabling is allowed - $('#dropdown [name=expirationCheckbox]').click(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(false); - }); - it('enforces default date when enforced date setting is enabled', function() { - /* jshint camelcase:false */ - oc_appconfig.core.defaultExpireDateEnabled = true; - oc_appconfig.core.defaultExpireDateEnforced = true; - showDropDown(); - $('#dropdown [name=linkCheckbox]').click(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - // TODO: those zeros must go... - expect($('#dropdown #expirationDate').val()).toEqual('2014-1-27 00:00:00'); - - // disabling is not allowed - expect($('#dropdown [name=expirationCheckbox]').prop('disabled')).toEqual(true); - $('#dropdown [name=expirationCheckbox]').click(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - }); - it('enforces default date when enforced date setting is enabled and password is enforced', function() { - /* jshint camelcase:false */ - oc_appconfig.core.enforcePasswordForPublicLink = true; - oc_appconfig.core.defaultExpireDateEnabled = true; - oc_appconfig.core.defaultExpireDateEnforced = true; - showDropDown(); - $('#dropdown [name=linkCheckbox]').click(); - - //Enter password - $('#dropdown #linkPassText').val('foo'); - $('#dropdown #linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - // TODO: those zeros must go... - expect($('#dropdown #expirationDate').val()).toEqual('2014-1-27 00:00:00'); - - // disabling is not allowed - expect($('#dropdown [name=expirationCheckbox]').prop('disabled')).toEqual(true); - $('#dropdown [name=expirationCheckbox]').click(); - expect($('#dropdown [name=expirationCheckbox]').prop('checked')).toEqual(true); - }); - it('displayes email form when sending emails is enabled', function() { - $('input[name=mailPublicNotificationEnabled]').val('yes'); - showDropDown(); - expect($('#emailPrivateLink').length).toEqual(1); - }); - it('not renders email form when sending emails is disabled', function() { - $('input[name=mailPublicNotificationEnabled]').val('no'); - showDropDown(); - expect($('#emailPrivateLink').length).toEqual(0); - }); - it('sets picker minDate to today and no maxDate by default', function() { - showDropDown(); - $('#dropdown [name=linkCheckbox]').click(); - $('#dropdown [name=expirationCheckbox]').click(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(null); - }); - it('limits the date range to X days after share time when enforced', function() { - /* jshint camelcase:false */ - oc_appconfig.core.defaultExpireDateEnabled = true; - oc_appconfig.core.defaultExpireDateEnforced = true; - showDropDown(); - $('#dropdown [name=linkCheckbox]').click(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); - }); - it('limits the date range to X days after share time when enforced, even when redisplayed the next days', function() { - // item exists, was created two days ago - shareItem.expiration = '2014-1-27'; - // share time has time component but must be stripped later - shareItem.stime = new Date(2014, 0, 20, 11, 0, 25).getTime() / 1000; - shareData.shares.push(shareItem); - /* jshint camelcase:false */ - oc_appconfig.core.defaultExpireDateEnabled = true; - oc_appconfig.core.defaultExpireDateEnforced = true; - showDropDown(); - expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); - expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); - }); - }); - }); - describe('check for avatar', function() { - beforeEach(function() { - loadItemStub.returns({ - reshare: { - share_type: OC.Share.SHARE_TYPE_USER, - uid_owner: 'owner', - displayname_owner: 'Owner', - permissions: 31 - }, - shares: [{ - id: 100, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - },{ - id: 101, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group', - share_with_displayname: 'group' - },{ - id: 102, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_REMOTE, - share_with: 'foo@bar.com/baz', - share_with_displayname: 'foo@bar.com/baz' - - }] - }); - }); - - describe('avatars enabled', function() { - beforeEach(function() { - oc_config.enable_avatars = true; - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - }); - - afterEach(function() { - oc_config.enable_avatars = false; - }); - - it('test correct function calls', function() { - expect(avatarStub.calledTwice).toEqual(true); - expect(placeholderStub.calledTwice).toEqual(true); - expect($('#shareWithList').children().length).toEqual(3); - expect($('.avatar').length).toEqual(4); - }); - - it('test avatar owner', function() { - var args = avatarStub.getCall(0).args; - expect(args.length).toEqual(2); - expect(args[0]).toEqual('owner'); - }); - - it('test avatar user', function() { - var args = avatarStub.getCall(1).args; - expect(args.length).toEqual(2); - expect(args[0]).toEqual('user1'); - }); - - it('test avatar for groups', function() { - var args = placeholderStub.getCall(0).args; - expect(args.length).toEqual(1); - expect(args[0]).toEqual('group ' + OC.Share.SHARE_TYPE_GROUP); - }); - - it('test avatar for remotes', function() { - var args = placeholderStub.getCall(1).args; - expect(args.length).toEqual(1); - expect(args[0]).toEqual('foo@bar.com/baz ' + OC.Share.SHARE_TYPE_REMOTE); - }); - }); - - describe('avatars disabled', function() { - beforeEach(function() { - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - }); - - it('no avatar classes', function() { - expect($('.avatar').length).toEqual(0); - expect(avatarStub.callCount).toEqual(0); - expect(placeholderStub.callCount).toEqual(0); - }); - }); - }); - describe('"sharesChanged" event', function() { - var autocompleteOptions; - var handler; - beforeEach(function() { - handler = sinon.stub(); - loadItemStub.returns({ - reshare: [], - shares: [{ - id: 100, - item_source: 123, - permissions: 31, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - share_with_displayname: 'User One' - }] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - 31, - 'shared_file_name.txt' - ); - $('#dropdown').on('sharesChanged', handler); - autocompleteOptions = autocompleteStub.getCall(0).args[0]; - }); - afterEach(function() { - autocompleteOptions = null; - handler = null; - }); - it('triggers "sharesChanged" event when adding shares', function() { - // simulate autocomplete selection - autocompleteOptions.select(new $.Event('select'), { - item: { - label: 'User Two', - value: { - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - } - } - }); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - expect(handler.calledOnce).toEqual(true); - var shares = handler.getCall(0).args[0].shares; - expect(shares).toBeDefined(); - expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); - expect(shares[OC.Share.SHARE_TYPE_USER][1].share_with_displayname).toEqual('User Two'); - expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); - }); - it('triggers "sharesChanged" event when deleting shares', function() { - $('#dropdown .unshare:eq(0)').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - expect(handler.calledOnce).toEqual(true); - var shares = handler.getCall(0).args[0].shares; - expect(shares).toBeDefined(); - expect(shares[OC.Share.SHARE_TYPE_USER]).toEqual([]); - expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); - }); - it('triggers "sharesChanged" event when toggling link share', function() { - // simulate autocomplete selection - $('#dropdown #linkCheckbox').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success', data: { token: 'abc' }}) - ); - expect(handler.calledOnce).toEqual(true); - var shares = handler.getCall(0).args[0].shares; - expect(shares).toBeDefined(); - expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); - expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); - - handler.reset(); - - // uncheck checkbox - $('#dropdown #linkCheckbox').click(); - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) - ); - - expect(handler.calledOnce).toEqual(true); - shares = handler.getCall(0).args[0].shares; - expect(shares).toBeDefined(); - expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); - expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); - }); - }); - describe('share permissions', function() { - beforeEach(function() { - oc_appconfig.core.resharingAllowed = true; - }); - - /** - * Tests sharing with the given possible permissions - * - * @param {int} possiblePermissions - * @return {int} permissions sent to the server - */ - function testWithPermissions(possiblePermissions) { - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - possiblePermissions, - 'shared_file_name.txt' - ); - var autocompleteOptions = autocompleteStub.getCall(0).args[0]; - // simulate autocomplete selection - autocompleteOptions.select(new $.Event('select'), { - item: { - label: 'User Two', - value: { - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - } - } - }); - autocompleteStub.reset(); - var requestBody = OC.parseQueryString(_.last(fakeServer.requests).requestBody); - return parseInt(requestBody.permissions, 10); - } - - describe('regular sharing', function() { - it('shares with given permissions with default config', function() { - loadItemStub.returns({ - reshare: [], - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_SHARE); - }); - it('removes share permission when not allowed', function() { - oc_appconfig.core.resharingAllowed = false; - loadItemStub.returns({ - reshare: [], - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE); - }); - it('automatically adds READ permission even when not specified', function() { - oc_appconfig.core.resharingAllowed = false; - loadItemStub.returns({ - reshare: [], - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_UPDATE); - }); - it('does not show sharing options when sharing not allowed', function() { - loadItemStub.returns({ - reshare: [], - shares: [] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - OC.PERMISSION_READ, - 'shared_file_name.txt' - ); - expect($('#dropdown #shareWithList').length).toEqual(0); - }); - }); - describe('resharing', function() { - it('shares with given permissions when original share had all permissions', function() { - loadItemStub.returns({ - reshare: { - permissions: OC.PERMISSION_ALL - }, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); - }); - it('reduces reshare permissions to the ones from the original share', function() { - loadItemStub.returns({ - reshare: { - permissions: OC.PERMISSION_READ, - uid_owner: 'user1' - }, - shares: [] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - OC.PERMISSION_ALL, - 'shared_file_name.txt' - ); - // no resharing allowed - expect($('#dropdown #shareWithList').length).toEqual(0); - }); - it('reduces reshare permissions to possible permissions', function() { - loadItemStub.returns({ - reshare: { - permissions: OC.PERMISSION_ALL, - uid_owner: 'user1' - }, - shares: [] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - OC.PERMISSION_READ, - 'shared_file_name.txt' - ); - // no resharing allowed - expect($('#dropdown #shareWithList').length).toEqual(0); - }); - it('does not show sharing options when resharing not allowed', function() { - loadItemStub.returns({ - reshare: { - permissions: OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_DELETE, - uid_owner: 'user1' - }, - shares: [] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - OC.PERMISSION_ALL, - 'shared_file_name.txt' - ); - expect($('#dropdown #shareWithList').length).toEqual(0); - }); - it('allows owner to share their own share when they are also the recipient', function() { - OC.currentUser = 'user1'; - loadItemStub.returns({ - reshare: { - permissions: OC.PERMISSION_READ, - uid_owner: 'user1' - }, - shares: [] - }); - OC.Share.showDropDown( - 'file', - 123, - $container, - true, - OC.PERMISSION_ALL, - 'shared_file_name.txt' - ); - // sharing still allowed - expect($('#dropdown #shareWithList').length).toEqual(1); - }); - }); - }); - }); describe('markFileAsShared', function() { var $file; var tipsyStub; @@ -1316,21 +232,5 @@ describe('OC.Share tests', function() { }); }); }); - describe('OC.Share utils', function() { - it('parseTime should properly parse strings', function() { - - _.each([ - [ '123456', 123456], - [ 123456 , 123456], - ['0123456', 123456], - ['abcdefg', null], - ['0x12345', null], - [ '', null], - ], function(value) { - expect(OC.Share._parseTime(value[0])).toEqual(value[1]); - }); - - }); - }); }); diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js new file mode 100644 index 000000000000..de6f99440947 --- /dev/null +++ b/core/js/tests/specs/sharedialogviewSpec.js @@ -0,0 +1,582 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2015 Vincent Petry +* +* 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 . +* +*/ + +/* global oc_appconfig */ +describe('OC.Share.ShareDialogView', function() { + var $container; + var oldAppConfig; + var autocompleteStub; + var oldEnableAvatars; + var avatarStub; + var placeholderStub; + var oldCurrentUser; + + var fetchStub; + + var configModel; + var shareModel; + var fileInfoModel; + var dialog; + + beforeEach(function() { + // horrible parameters + $('#testArea').append(''); + $('#testArea').append(''); + $container = $('#shareContainer'); + /* jshint camelcase:false */ + oldAppConfig = _.extend({}, oc_appconfig.core); + oc_appconfig.core.enforcePasswordForPublicLink = false; + + fetchStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'fetch'); + + 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 + }; + configModel = new OC.Share.ShareConfigModel({ + enforcePasswordForPublicLink: false, + isResharingAllowed: true, + enforcePasswordForPublicLink: false, + isDefaultExpireDateEnabled: false, + isDefaultExpireDateEnforced: false, + defaultExpireDate: 7 + }); + shareModel = new OC.Share.ShareItemModel(attributes, { + configModel: configModel, + fileInfoModel: fileInfoModel + }); + dialog = new OC.Share.ShareDialogView({ + configModel: configModel, + model: shareModel + }); + + // triggers rendering + shareModel.set({ + shares: [], + linkShare: {isLinkShare: false} + }); + + autocompleteStub = sinon.stub($.fn, 'autocomplete', function() { + // dummy container with the expected attributes + if (!$(this).length) { + // simulate the real autocomplete that returns + // nothing at all when no element is specified + // (and potentially break stuff) + return null; + } + var $el = $('
    ').data('ui-autocomplete', {}); + return $el; + }); + + oldEnableAvatars = oc_config.enable_avatars; + oc_config.enable_avatars = false; + avatarStub = sinon.stub($.fn, 'avatar'); + placeholderStub = sinon.stub($.fn, 'imageplaceholder'); + + oldCurrentUser = OC.currentUser; + OC.currentUser = 'user0'; + }); + afterEach(function() { + OC.currentUser = oldCurrentUser; + /* jshint camelcase:false */ + oc_appconfig.core = oldAppConfig; + + fetchStub.restore(); + + autocompleteStub.restore(); + avatarStub.restore(); + placeholderStub.restore(); + oc_config.enable_avatars = oldEnableAvatars; + }); + describe('Share with link', function() { + // TODO: test ajax calls + // TODO: test password field visibility (whenever enforced or not) + it('update password on focus out', function() { + $('#allowShareWithLink').val('yes'); + + dialog.render(); + + // Toggle linkshare + dialog.$el.find('[name=linkCheckbox]').click(); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({data: {token: 'xyz'}, status: 'success'}) + ); + + // Enable password, enter password and focusout + dialog.$el.find('[name=showPassword]').click(); + dialog.$el.find('#linkPassText').focus(); + dialog.$el.find('#linkPassText').val('foo'); + dialog.$el.find('#linkPassText').focusout(); + + expect(fakeServer.requests[1].method).toEqual('POST'); + var body = OC.parseQueryString(fakeServer.requests[1].requestBody); + expect(body.shareWith).toEqual('foo'); + + fetchStub.reset(); + + // Set password response + fakeServer.requests[1].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({data: {token: 'xyz'}, status: 'success'}) + ); + + expect(fetchStub.calledOnce).toEqual(true); + // fetching the model will rerender the view + dialog.render(); + + expect(dialog.$el.find('#linkPassText').val()).toEqual(''); + expect(dialog.$el.find('#linkPassText').attr('placeholder')).toEqual('**********'); + }); + it('update password on enter', function() { + $('#allowShareWithLink').val('yes'); + + dialog.render(); + + // Toggle linkshare + dialog.$el.find('[name=linkCheckbox]').click(); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({data: {token: 'xyz'}, status: 'success'}) + ); + + // Enable password and enter password + dialog.$el.find('[name=showPassword]').click(); + dialog.$el.find('#linkPassText').focus(); + dialog.$el.find('#linkPassText').val('foo'); + dialog.$el.find('#linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); + + expect(fakeServer.requests[1].method).toEqual('POST'); + var body = OC.parseQueryString(fakeServer.requests[1].requestBody); + expect(body.shareWith).toEqual('foo'); + + fetchStub.reset(); + + // Set password response + fakeServer.requests[1].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({data: {token: 'xyz'}, status: 'success'}) + ); + + expect(fetchStub.calledOnce).toEqual(true); + // fetching the model will rerender the view + dialog.render(); + + expect(dialog.$el.find('#linkPassText').val()).toEqual(''); + expect(dialog.$el.find('#linkPassText').attr('placeholder')).toEqual('**********'); + }); + it('shows share with link checkbox when allowed', function() { + $('#allowShareWithLink').val('yes'); + + dialog.render(); + + expect(dialog.$el.find('#linkCheckbox').length).toEqual(1); + }); + it('does not show share with link checkbox when not allowed', function() { + $('#allowShareWithLink').val('no'); + + dialog.render(); + + expect(dialog.$el.find('#linkCheckbox').length).toEqual(0); + }); + it('shows populated link share when a link share exists', function() { + // this is how the OC.Share class does it... + var link = parent.location.protocol + '//' + location.host + + OC.generateUrl('/s/') + 'tehtoken'; + shareModel.set('linkShare', { + isLinkShare: true, + token: 'tehtoken', + link: link, + expiration: '', + permissions: OC.PERMISSION_READ, + stime: 1403884258, + }); + + dialog.render(); + + expect(dialog.$el.find('#linkCheckbox').prop('checked')).toEqual(true); + expect(dialog.$el.find('#linkText').val()).toEqual(link); + }); + describe('expiration date', function() { + var shareData; + var shareItem; + var clock; + var expectedMinDate; + + beforeEach(function() { + // pick a fake date + clock = sinon.useFakeTimers(new Date(2014, 0, 20, 14, 0, 0).getTime()); + expectedMinDate = new Date(2014, 0, 21, 14, 0, 0); + + configModel.set({ + enforcePasswordForPublicLink: false, + isDefaultExpireDateEnabled: false, + isDefaultExpireDateEnforced: false, + defaultExpireDate: 7 + }); + + shareModel.set('linkShare', { + isLinkShare: true, + token: 'tehtoken', + permissions: OC.PERMISSION_READ, + expiration: null + }); + }); + afterEach(function() { + clock.restore(); + }); + + it('does not check expiration date checkbox when no date was set', function() { + shareModel.get('linkShare').expiration = null; + dialog.render(); + + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); + expect(dialog.$el.find('#expirationDate').val()).toEqual(''); + }); + it('does not check expiration date checkbox for new share', function() { + dialog.render(); + + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); + expect(dialog.$el.find('#expirationDate').val()).toEqual(''); + }); + it('checks expiration date checkbox and populates field when expiration date was set', function() { + shareModel.get('linkShare').expiration = 1234; + dialog.render(); + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + expect(dialog.$el.find('#expirationDate').val()).toEqual('1234'); + }); + it('sets default date when default date setting is enabled', function() { + configModel.set('isDefaultExpireDateEnabled', true); + dialog.render(); + dialog.$el.find('[name=linkCheckbox]').click(); + // here fetch would be called and the server returns the expiration date + shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; + dialog.render(); + + // enabled by default + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + // TODO: those zeros must go... + expect(dialog.$el.find('#expirationDate').val()).toEqual('2014-1-27 00:00:00'); + + // disabling is allowed + dialog.$el.find('[name=expirationCheckbox]').click(); + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false); + }); + it('enforces default date when enforced date setting is enabled', function() { + configModel.set({ + isDefaultExpireDateEnabled: true, + isDefaultExpireDateEnforced: true + }); + dialog.render(); + dialog.$el.find('[name=linkCheckbox]').click(); + // here fetch would be called and the server returns the expiration date + shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; + dialog.render(); + + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + // TODO: those zeros must go... + expect(dialog.$el.find('#expirationDate').val()).toEqual('2014-1-27 00:00:00'); + + // disabling is not allowed + expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true); + dialog.$el.find('[name=expirationCheckbox]').click(); + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + }); + it('enforces default date when enforced date setting is enabled and password is enforced', function() { + configModel.set({ + enforcePasswordForPublicLink: true, + isDefaultExpireDateEnabled: true, + isDefaultExpireDateEnforced: true + }); + dialog.render(); + dialog.$el.find('[name=linkCheckbox]').click(); + // here fetch would be called and the server returns the expiration date + shareModel.get('linkShare').expiration = '2014-1-27 00:00:00'; + dialog.render(); + + //Enter password + dialog.$el.find('#linkPassText').val('foo'); + dialog.$el.find('#linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({data: {token: 'xyz'}, status: 'success'}) + ); + + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + // TODO: those zeros must go... + expect(dialog.$el.find('#expirationDate').val()).toEqual('2014-1-27 00:00:00'); + + // disabling is not allowed + expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true); + dialog.$el.find('[name=expirationCheckbox]').click(); + expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true); + }); + it('displayes email form when sending emails is enabled', function() { + $('input[name=mailPublicNotificationEnabled]').val('yes'); + dialog.render(); + expect(dialog.$('#emailPrivateLink').length).toEqual(1); + }); + it('not renders email form when sending emails is disabled', function() { + $('input[name=mailPublicNotificationEnabled]').val('no'); + dialog.render(); + expect(dialog.$('#emailPrivateLink').length).toEqual(0); + }); + it('sets picker minDate to today and no maxDate by default', function() { + dialog.render(); + dialog.$el.find('[name=linkCheckbox]').click(); + dialog.$el.find('[name=expirationCheckbox]').click(); + expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); + expect($.datepicker._defaults.maxDate).toEqual(null); + }); + it('limits the date range to X days after share time when enforced', function() { + configModel.set({ + isDefaultExpireDateEnabled: true, + isDefaultExpireDateEnforced: true + }); + dialog.render(); + dialog.$el.find('[name=linkCheckbox]').click(); + expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); + expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); + }); + it('limits the date range to X days after share time when enforced, even when redisplayed the next days', function() { + // item exists, was created two days ago + var shareItem = shareModel.get('linkShare'); + shareItem.expiration = '2014-1-27'; + // share time has time component but must be stripped later + shareItem.stime = new Date(2014, 0, 20, 11, 0, 25).getTime() / 1000; + configModel.set({ + isDefaultExpireDateEnabled: true, + isDefaultExpireDateEnforced: true + }); + dialog.render(); + expect($.datepicker._defaults.minDate).toEqual(expectedMinDate); + expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0)); + }); + }); + }); + describe('check for avatar', function() { + beforeEach(function() { + shareModel.set({ + reshare: { + share_type: OC.Share.SHARE_TYPE_USER, + uid_owner: 'owner', + displayname_owner: 'Owner', + permissions: 31 + }, + shares: [{ + id: 100, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_USER, + share_with: 'user1', + share_with_displayname: 'User One' + },{ + id: 101, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group', + share_with_displayname: 'group' + },{ + id: 102, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_REMOTE, + share_with: 'foo@bar.com/baz', + share_with_displayname: 'foo@bar.com/baz' + + }] + }); + }); + + describe('avatars enabled', function() { + beforeEach(function() { + oc_config.enable_avatars = true; + avatarStub.reset(); + dialog.render(); + }); + + afterEach(function() { + oc_config.enable_avatars = false; + }); + + it('test correct function calls', function() { + expect(avatarStub.calledTwice).toEqual(true); + expect(placeholderStub.calledTwice).toEqual(true); + expect(dialog.$('#shareWithList').children().length).toEqual(3); + expect(dialog.$('.avatar').length).toEqual(4); + }); + + it('test avatar owner', function() { + var args = avatarStub.getCall(0).args; + expect(args.length).toEqual(2); + expect(args[0]).toEqual('owner'); + }); + + it('test avatar user', function() { + var args = avatarStub.getCall(1).args; + expect(args.length).toEqual(2); + expect(args[0]).toEqual('user1'); + }); + + it('test avatar for groups', function() { + var args = placeholderStub.getCall(0).args; + expect(args.length).toEqual(1); + expect(args[0]).toEqual('group ' + OC.Share.SHARE_TYPE_GROUP); + }); + + it('test avatar for remotes', function() { + var args = placeholderStub.getCall(1).args; + expect(args.length).toEqual(1); + expect(args[0]).toEqual('foo@bar.com/baz ' + OC.Share.SHARE_TYPE_REMOTE); + }); + }); + + describe('avatars disabled', function() { + beforeEach(function() { + dialog.render(); + }); + + it('no avatar classes', function() { + expect($('.avatar').length).toEqual(0); + expect(avatarStub.callCount).toEqual(0); + expect(placeholderStub.callCount).toEqual(0); + }); + }); + }); + describe('share permissions', function() { + beforeEach(function() { + oc_appconfig.core.resharingAllowed = true; + }); + + /** + * Tests sharing with the given possible permissions + * + * @param {int} possiblePermissions + * @return {int} permissions sent to the server + */ + function testWithPermissions(possiblePermissions) { + shareModel.set({ + permissions: possiblePermissions, + possiblePermissions: possiblePermissions + }); + dialog.render(); + var autocompleteOptions = autocompleteStub.getCall(0).args[0]; + // simulate autocomplete selection + autocompleteOptions.select(new $.Event('select'), { + item: { + label: 'User Two', + value: { + shareType: OC.Share.SHARE_TYPE_USER, + shareWith: 'user2' + } + } + }); + autocompleteStub.reset(); + var requestBody = OC.parseQueryString(_.last(fakeServer.requests).requestBody); + return parseInt(requestBody.permissions, 10); + } + + describe('regular sharing', function() { + it('shares with given permissions with default config', function() { + shareModel.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_SHARE); + }); + it('removes share permission when not allowed', function() { + configModel.set('isResharingAllowed', false); + shareModel.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE); + }); + it('automatically adds READ permission even when not specified', function() { + configModel.set('isResharingAllowed', false); + shareModel.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_UPDATE); + }); + it('does not show sharing options when sharing not allowed', function() { + shareModel.set({ + reshare: {}, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('#shareWith').prop('disabled')).toEqual(true); + }); + it('shows reshare owner', function() { + shareModel.set({ + reshare: { + uid_owner: 'user1' + }, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); + }); + it('does not show reshare owner if owner is current user', function() { + shareModel.set({ + reshare: { + uid_owner: OC.currentUser + }, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(0); + }); + }); + }); +}); + diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js new file mode 100644 index 000000000000..c1d820052e24 --- /dev/null +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -0,0 +1,283 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2015 Vincent Petry +* +* 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 . +* +*/ + +/* global oc_appconfig */ +describe('OC.Share.ShareItemModel', function() { + var loadItemStub; + var fileInfoModel, configModel, model; + var oldCurrentUser; + + beforeEach(function() { + oldCurrentUser = OC.currentUser; + + loadItemStub = sinon.stub(OC.Share, 'loadItem'); + + 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: fileInfoModel.get('sharePermissions') + }; + configModel = new OC.Share.ShareConfigModel(); + model = new OC.Share.ShareItemModel(attributes, { + configModel: configModel, + fileInfoModel: fileInfoModel + }); + }); + afterEach(function() { + loadItemStub.restore(); + OC.currentUser = oldCurrentUser; + }); + + describe('Fetching and parsing', function() { + it('fetching calls loadItem with the correct arguments', function() { + model.fetch(); + + expect(loadItemStub.calledOnce).toEqual(true); + expect(loadItemStub.calledWith('file', 123)).toEqual(true); + }); + it('populates attributes with parsed response', function() { + loadItemStub.yields({ + /* jshint camelcase: false */ + reshare: { + share_type: OC.Share.SHARE_TYPE_USER, + uid_owner: 'owner', + displayname_owner: 'Owner', + permissions: 31 + }, + shares: [{ + id: 100, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_USER, + share_with: 'user1', + share_with_displayname: 'User One' + }, { + id: 101, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group', + share_with_displayname: 'group' + }, { + id: 102, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_REMOTE, + share_with: 'foo@bar.com/baz', + share_with_displayname: 'foo@bar.com/baz' + + }, { + displayname_owner: 'root', + expiration: null, + file_source: 123, + file_target: '/folder', + id: 20, + item_source: '123', + item_type: 'folder', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }] + }); + model.fetch(); + + var shares = model.get('shares'); + expect(shares.length).toEqual(3); + expect(shares[0].id).toEqual(100); + expect(shares[0].permissions).toEqual(31); + 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'); + + var linkShare = model.get('linkShare'); + expect(linkShare.isLinkShare).toEqual(true); + + // TODO: check more attributes + }); + it('does not parse link share when for a different file', function() { + loadItemStub.yields({ + reshare: [], + /* jshint camelcase: false */ + shares: [{ + displayname_owner: 'root', + expiration: null, + file_source: 456, + file_target: '/folder', + id: 20, + item_source: '456', + item_type: 'folder', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }] + }); + + model.fetch(); + + var shares = model.get('shares'); + // remaining share appears in this list + expect(shares.length).toEqual(1); + + var linkShare = model.get('linkShare'); + expect(linkShare.isLinkShare).toEqual(false); + }); + it('parses correct link share when a nested link share exists along with parent one', function() { + loadItemStub.yields({ + reshare: [], + /* jshint camelcase: false */ + shares: [{ + displayname_owner: 'root', + expiration: 1111, + file_source: 123, + file_target: '/folder', + id: 20, + item_source: '123', + item_type: 'file', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }, { + displayname_owner: 'root', + expiration: 2222, + file_source: 456, + file_target: '/file_in_folder.txt', + id: 21, + item_source: '456', + item_type: 'file', + mail_send: '0', + parent: null, + path: '/folder/file_in_folder.txt', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884509, + storage: 1, + token: 'anothertoken', + uid_owner: 'root' + }] + }); + + model.fetch(); + + var shares = model.get('shares'); + // the parent share remains in the list + expect(shares.length).toEqual(1); + + var linkShare = model.get('linkShare'); + expect(linkShare.isLinkShare).toEqual(true); + expect(linkShare.token).toEqual('tehtoken'); + + // TODO: check child too + }); + it('reduces reshare permissions to the ones from the original share', function() { + loadItemStub.yields({ + reshare: { + permissions: OC.PERMISSION_READ, + uid_owner: 'user1' + }, + shares: [] + }); + model.fetch(); + + // no resharing allowed + expect(model.get('permissions')).toEqual(OC.PERMISSION_READ); + }); + it('reduces reshare permissions to possible permissions', function() { + loadItemStub.yields({ + reshare: { + permissions: OC.PERMISSION_ALL, + uid_owner: 'user1' + }, + shares: [] + }); + + model.set('possiblePermissions', OC.PERMISSION_READ); + model.fetch(); + + // no resharing allowed + expect(model.get('permissions')).toEqual(OC.PERMISSION_READ); + }); + it('allows owner to share their own share when they are also the recipient', function() { + OC.currentUser = 'user1'; + loadItemStub.yields({ + reshare: { + permissions: OC.PERMISSION_READ, + uid_owner: 'user1' + }, + shares: [] + }); + + model.fetch(); + + // sharing still allowed + expect(model.get('permissions') & OC.PERMISSION_SHARE).toEqual(OC.PERMISSION_SHARE); + }); + }); + + describe('Util', function() { + it('parseTime should properly parse strings', function() { + + _.each([ + [ '123456', 123456], + [ 123456 , 123456], + ['0123456', 123456], + ['abcdefg', null], + ['0x12345', null], + [ '', null], + ], function(value) { + expect(OC.Share.ShareItemModel.prototype._parseTime(value[0])).toEqual(value[1]); + }); + + }); + }); +}); + diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 6ad36d60fe88..802b146cfb62 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -83,6 +83,13 @@ public static function registerBackend($itemType, $class, $collectionOf = null, 'supportedFileExtensions' => $supportedFileExtensions ); if(count(self::$backendTypes) === 1) { + \OC_Util::addScript('core', 'shareconfigmodel'); + \OC_Util::addScript('core', 'shareitemmodel'); + \OC_Util::addScript('core', 'sharedialogresharerinfoview'); + \OC_Util::addScript('core', 'sharedialoglinkshareview'); + \OC_Util::addScript('core', 'sharedialogexpirationview'); + \OC_Util::addScript('core', 'sharedialogshareelistview'); + \OC_Util::addScript('core', 'sharedialogview'); \OC_Util::addScript('core', 'share'); \OC_Util::addStyle('core', 'share'); } diff --git a/tests/karma.config.js b/tests/karma.config.js index d3280b2939ac..64a94ef230bc 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -55,7 +55,8 @@ module.exports = function(config) { 'apps/files_sharing/js/sharedfilelist.js', 'apps/files_sharing/js/share.js', 'apps/files_sharing/js/external.js', - 'apps/files_sharing/js/public.js' + 'apps/files_sharing/js/public.js', + 'apps/files_sharing/js/sharetabview.js' ], testFiles: ['apps/files_sharing/tests/js/*.js'] },