diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d0c70a0c..00000000 --- a/.babelrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "presets": [ - ["es2015", {"modules": false}] - ] -} \ No newline at end of file diff --git a/Makefile b/Makefile index a595ad1c..8c47f766 100644 --- a/Makefile +++ b/Makefile @@ -36,34 +36,14 @@ endif endif endif -all: build - -# Fetch JS dependencies and compile the JS -.PHONY: build -build: npm - npm run build - -# Fetch JS dependencies and compile the JS -.PHONY: dev -dev: npm - npm run dev - -# Node modules -.PHONY: npm -npm: - npm install - # Remove the appstore build and generated guests bundle .PHONY: clean clean: rm -rf ./build - rm -f js/guests.bundle.js # Same as clean but also removes dependencies installed by npm .PHONY: distclean distclean: clean - rm -rf node_modules - rm -f package-lock.json # Build the source and appstore package .PHONY: dist @@ -72,7 +52,7 @@ dist: # Build the source package for the app store, ignores php and js tests .PHONY: appstore -appstore: build +appstore: rm -rf $(dist_dir) mkdir -p $(dist_dir)/$(app_name) cp -R $(all_src) $(dist_dir)/$(app_name) diff --git a/README.md b/README.md index 2443f039..31b0e5f3 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,7 @@ Create a guest user by typing his email address in to the sharing dialog. The gu will receive an email invite with a link to create an account. He has only access to files which are shared with him. - Furthermore, the administrator has to whitelist the applications that guests can use. -By default settings,avatar,files_trashbin,files_versions,files_sharing,files_texteditor,activity,firstrunwizard,gallery are allowed. - - -## How to set up your frontend development environment - -The front end section is based on the [Vue.js 2.0](https://vuejs.org/) framework. The build process is based on Webpack. To get started, you need the latest version of [node.js and npm](https://nodejs.org) installed. - -*** - -### Setup - -You can install the development environment and get webpack started (watcher included) by running: - -`make dev` - -*** - -### Building - -To build the uglified / minified muted version run the following: +By default settings, avatar, files_trashbin, files_versions, files_sharing, +files_texteditor, activity, firstrunwizard, gallery are allowed. -`make` diff --git a/appinfo/app.php b/appinfo/app.php index 9423d13f..eee9e73c 100755 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,5 +1,6 @@ * @author Felix Heidecke * @author Ilja Neumann * @author Jörn Friedrich Dreyer @@ -22,10 +23,16 @@ * */ -\OCP\Util::addScript('guests', 'guests.bundle'); +$eventDispatcher = \OC::$server->getEventDispatcher(); +$eventDispatcher->addListener( + 'OCA\Files::loadAdditionalScripts', + function() { + \OCP\Util::addScript('guests', 'guestshare'); + } +); $config = \OC::$server->getConfig(); -$groupName = $config->getAppValue('guests', 'group', 'guest_app'); +$groupName = $config->getAppValue('guests', 'group', \OCA\Guests\GroupBackend::DEFAULT_NAME); \OC::$server->getGroupManager()->addBackend(new \OCA\Guests\GroupBackend($groupName)); \OCP\Util::connectHook('OCP\Share', 'post_shared', '\OCA\Guests\Hooks', 'postShareHook'); diff --git a/appinfo/routes.php b/appinfo/routes.php index 33f2af54..8f35df96 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -54,5 +54,5 @@ 'url' => '/users', 'verb' => 'PUT' ] - ], + ] ]; diff --git a/controller/settingscontroller.php b/controller/settingscontroller.php index bd0d709e..07116572 100644 --- a/controller/settingscontroller.php +++ b/controller/settingscontroller.php @@ -76,7 +76,7 @@ public function getConfig() { $whitelist = $this->config->getAppValue('guests', 'whitelist', AppWhitelist::DEFAULT_WHITELIST); $whitelist = explode(',', $whitelist); return new DataResponse([ - 'group' => $this->config->getAppValue('guests', 'group', 'guests'), + 'group' => $this->config->getAppValue('guests', 'group', \OCA\Guests\GroupBackend::DEFAULT_NAME), 'useWhitelist' => $useWhitelist, 'whitelist' => $whitelist, ]); diff --git a/js/guests.js b/js/guests.js index 21cfc02a..7448d162 100644 --- a/js/guests.js +++ b/js/guests.js @@ -1,9 +1,9 @@ /** - * ownCloud - * * @author Jörn Friedrich Dreyer * @author Thomas Heinisch - * @copyright (C) 2015-2017 ownCloud, Inc. + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license GPL-2.0 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,8 +15,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * */ (function() { diff --git a/js/guestshare.js b/js/guestshare.js new file mode 100644 index 00000000..e979ac48 --- /dev/null +++ b/js/guestshare.js @@ -0,0 +1,142 @@ +/** + * @author Felix Heidecke + * @author Thomas Heinisch + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license GPL-2.0 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +var GuestShare = { + model: null, + email: null, + + addGuest: function (model, email) { + this.model = model; + this.email = email; + + var self = this; + var xhrObject = { + type: 'PUT', + url: OC.generateUrl('/apps/guests/users'), + dataType: 'text', + data: { + displayName: this.email, + username: this.email, + email: this.email + } + }; + + $.ajax(xhrObject).done(function (xhr) { + self._addGuestShare(); + }).fail(function (xhr) { + var response = JSON.parse(xhr.responseText); + var error = response.errorMessages; + OC.dialogs.alert( + error.email, // text + t('core', 'Error') // title + ); + }); + }, + + _addGuestShare: function () { + var self = this; + var attributes = { + shareType: 0, + shareWith: this.email, + permissions: OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE, + path: this.model.fileInfoModel.getFullPath() + }; + + return $.ajax({ + type: 'POST', + url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'shares?format=json', + data: attributes, + dataType: 'json' + }).done(function () { + if (self.model) { + self.model.fetch(); + } + }).fail(function () { + OCdialogs.alert( + t('core', 'Error while sharing'), // text + t('core', 'Error') // title + ); + }); + }, +}; + +OC.Plugins.register('OC.Share.ShareDialogView', { + attach: function (obj) { + + // Override ShareDialogView + var oldHandler = obj.autocompleteHandler; + obj.autocompleteHandler = function(search, response) { + + return oldHandler.call(obj, search, function(result) { + var searchTerm = search.term.trim(); + + // Add potential guests to the suggestions + if (searchTerm.search("@") !== -1) { + result.push({ + label: t('core', 'Add {unknown} (guest)', {unknown: searchTerm}), + value: { + shareType: 4, + shareWith: searchTerm + } + }); + } + + if (result !== undefined) { + result.sort(function (a, b) { + return OC.Util.naturalSortCompare(a.label, b.label); + }); + response(result); + } else { + response(); + } + }); + }; + + obj._onSelectRecipient = function (e, s) { + e.preventDefault(); + + var $this = $(e.target), + $loading = obj.$el.find('.shareWithLoading'); + + $this.attr('disabled', true).val(s.item.label); + $loading.removeClass('hidden').addClass('inlineblock'); + + if (s.item.value.shareType === OC.Share.SHARE_TYPE_GUEST) { + if (!GuestShare.addGuest(obj.model, s.item.value.shareWith)) { + $this.val('').attr('disabled', false); + $loading.addClass('hidden').removeClass('inlineblock'); + } + } else { + obj.model.addShare(s.item.value, { + success: function () { + $this.val('').attr('disabled', false); + $loading.addClass('hidden').removeClass('inlineblock'); + }, error: function (obj, msg) { + OC.Notification.showTemporary(msg); + $this.attr('disabled', false).autocomplete('search', $this.val()); + $loading.addClass('hidden').removeClass('inlineblock'); + } + }); + } + }; + } +}); diff --git a/lib/groupbackend.php b/lib/groupbackend.php index 76295658..114ea047 100644 --- a/lib/groupbackend.php +++ b/lib/groupbackend.php @@ -1,6 +1,7 @@ + * @author Thomas Heinisch * * @copyright Copyright (c) 2017, ownCloud GmbH * @license GPL-2.0 @@ -19,8 +20,8 @@ * along with this program. If not, see . * */ -namespace OCA\Guests; +namespace OCA\Guests; use OCP\GroupInterface; @@ -32,6 +33,8 @@ */ class GroupBackend implements GroupInterface { + const DEFAULT_NAME = 'guest_app'; + private $guestMembers = []; protected $possibleActions = [ @@ -40,7 +43,7 @@ class GroupBackend implements GroupInterface { private $groupName; - public function __construct($groupName = 'guest_app') { + public function __construct($groupName = self::DEFAULT_NAME) { $this->groupName = $groupName; } diff --git a/package.json b/package.json deleted file mode 100644 index 31eec56b..00000000 --- a/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "guests", - "version": "0.4.1", - "description": "Allow classifying users or contacts as guests.", - "main": "src/default.js", - "directories": { - "test": "tests" - }, - "scripts": { - "dev": "webpack --progress --colors --watch", - "build": "webpack -p" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/owncloud/guests.git" - }, - "keywords": [ - "share", - "guest", - "user" - ], - "author": "Felix Heidecke, Ilja Neumann", - "license": "AGPL-3.0", - "bugs": { - "url": "https://github.com/owncloud/guests/issues" - }, - "homepage": "https://github.com/owncloud/guests#readme", - "dependencies": { - "babel-core": "^6.24.1", - "babel-loader": "^7.0.0", - "babel-preset-es2015": "^6.24.1", - "css-loader": "^0.28.0", - "less": "^2.7.2", - "less-loader": "^4.0.3", - "style-loader": "^0.16.1", - "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", - "vue": "^2.2.6", - "webpack": "^2.4.1" - } -} diff --git a/src/default.js b/src/default.js deleted file mode 100644 index 3a33c58f..00000000 --- a/src/default.js +++ /dev/null @@ -1,335 +0,0 @@ -require('./styles/default.less'); - -// ------------------------------------------------------------- Vue plugins --- - -import Vue from 'vue/dist/vue' - -const Guests = new Vue({ - data: { - - // OC.Backbone model - // ----------------- - model: null, - - // current state of the view - // ------------------------- - state: { - modalIsOpen: false - }, - - // guest data - // ---------- - guest: { - fullname: null, - username: null, - email: null - }, - - error: { - username: false, - email: false - } - }, - watch: { - 'guest.email': function () { - if (this.guest.email) { - this.guest.username = this.guest.email.toLowerCase(); - } - else { - this.guest.username = ''; - } - - var self = this; - - _.delay(function() { - self._resetErrors(); - }, 250); - } - }, - methods: { - populateTitle: function () { - if (this.guest.email) { - return this.guest.email; - } else if (this.guest.fullname) { - return this.guest.fullname; - } else { - return ''; - } - }, - - populateForm: function (model, name) { - if (name.search("@") !== -1) { - this.guest.email = (name) ? name : ''; - } else { - this.guest.fullname = name ? name : ''; - } - this.model = (model) ? model : false; - }, - - openModal: function () { - this.state.modalIsOpen = true; - }, - - closeModal: function () { - this.state.modalIsOpen = false; - this._resetForm(); - - $('#shareTabView .shareWithField').val('').removeAttr('disabled'); - $('#shareTabView .shareWithLoading').toggleClass('hidden inlineblock'); - }, - - addGuest: function () { - var self = this; - let xhrObject = { - type: 'PUT', - url: OC.generateUrl('/apps/guests/users'), - dataType: 'text', - data: { - displayName: this.guest.fullname, - username: this.guest.username, - email: this.guest.email - } - } - - $.ajax(xhrObject).done(function (xhr) { - self._addGuestShare(); - - }).fail(function (xhr) { - let response = JSON.parse(xhr.responseText); - let error = response.errorMessages; - - self.error.email = (error.email) ? error.email : false; - self.error.username = (error.username) ? error.username : false; - }); - }, - - _addGuestShare: function () { - let self = this; - let attributes = { - shareType: 0, - shareWith: this.guest.username, - permissions: OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE, - path: this.model.fileInfoModel.getFullPath() - } - - return $.ajax({ - type: 'POST', - url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'shares?format=json', - data: attributes, - dataType: 'json' - }).done(function () { - - self.closeModal(); - - if (self.model) - self.model.fetch(); - - }).fail(function () { - - // @NOTE: will be expendable in the near future - OCdialogs.alert( - t('core', 'Error while sharing'), // text - t('core', 'Error'), // title - false, // callback - true // modal - ); - }); - }, - - _resetForm: function () { - this.guest.fullname = this.guest.username = this.guest.email = null; - }, - - _resetErrors: function () { - this.error.username = this.error.email = false; - }, - - // Translating - t: function (string, replace) { - - replace = replace || null; - return t('core', string, replace); - } - }, - filters: { - addUserText: function (name) { - return t('core', 'Share with {myname}', {myname: name}); - } - } -}); - -OC.Plugins.register('OC.Share.ShareDialogView', { - attach: function (obj) { - - // Override ShareDigalogView - - obj.autocompleteHandler = function (search, response) { - var view = obj; - var $loading = obj.$el.find('.shareWithLoading'); - $loading.removeClass('hidden'); - $loading.addClass('inlineblock'); - $.get( - OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees', - { - format: 'json', - search: search.term.trim(), - perPage: 200, - itemType: view.model.get('itemType') - }, - function (result) { - $loading.addClass('hidden'); - $loading.removeClass('inlineblock'); - if (result.ocs.meta.statuscode == 100) { - var searchTerm = search.term.trim(); - var users = result.ocs.data.exact.users.concat(result.ocs.data.users); - var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups); - var remotes = result.ocs.data.exact.remotes.concat(result.ocs.data.remotes); - var unknown = []; - - var usersLength; - var groupsLength; - var remotesLength; - - var i, j; - - // Add potential guests to the suggestions - if (searchTerm) { - - unknown = [{ - label: t('core', 'Add {unknown} (guest)', {unknown: searchTerm}), - value: { - shareType: 4, - shareWith: searchTerm - } - }]; - } - - //Filter out the current user - usersLength = users.length; - for (i = 0; i < usersLength; i++) { - if (users[i].value.shareWith === OC.currentUser) { - users.splice(i, 1); - break; - } - } - - // Filter out the owner of the share - if (view.model.hasReshare()) { - usersLength = users.length; - for (i = 0; i < usersLength; i++) { - if (users[i].value.shareWith === view.model.getReshareOwner()) { - users.splice(i, 1); - break; - } - } - } - - var shares = view.model.get('shares'); - var sharesLength = shares.length; - - // Now filter out all sharees that are already shared with - for (i = 0; i < sharesLength; i++) { - var share = shares[i]; - - if (share.share_type === OC.Share.SHARE_TYPE_USER) { - usersLength = users.length; - for (j = 0; j < usersLength; j++) { - if (users[j].value.shareWith === share.share_with) { - users.splice(j, 1); - break; - } - } - } else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) { - groupsLength = groups.length; - for (j = 0; j < groupsLength; j++) { - if (groups[j].value.shareWith === share.share_with) { - groups.splice(j, 1); - break; - } - } - } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) { - remotesLength = remotes.length; - for (j = 0; j < remotesLength; j++) { - if (remotes[j].value.shareWith === share.share_with) { - remotes.splice(j, 1); - break; - } - } - } - } - - var suggestions = users.concat(groups).concat(remotes).concat(unknown); - - if (suggestions.length > 0) { - $('.shareWithField').removeClass('error') - .tooltip('hide') - .autocomplete("option", "autoFocus", true); - response(suggestions); - } else { - var title = t('core', 'No users or groups found for {search}', {search: $('.shareWithField').val()}); - if (!view.configModel.get('allowGroupSharing')) { - title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()}); - } - $('.shareWithField').addClass('error') - .attr('data-original-title', title) - .tooltip('hide') - .tooltip({ - placement: 'bottom', - trigger: 'manual' - }) - .tooltip('fixTitle') - .tooltip('show'); - response(); - } - } else { - response(); - } - } - ).fail(function () { - $loading.addClass('hidden'); - $loading.removeClass('inlineblock'); - OC.Notification.show(t('core', 'An error occurred. Please try again')); - window.setTimeout(OC.Notification.hide, 5000); - }); - }; - - obj._onSelectRecipient = function (e, s) { - e.preventDefault(); - - // vars starting with $ are jQuery DOM objects - // --- - var $this = $(e.target), - $loading = obj.$el.find('.shareWithLoading'); - - $this.attr('disabled', true).val(s.item.label); - $loading.removeClass('hidden').addClass('inlineblock'); - - // Init OCA.Guests.App if share is of type guest - // --- - if (s.item.value.shareType === OC.Share.SHARE_TYPE_GUEST) { - Guests.populateForm(obj.model, s.item.value.shareWith); - Guests.openModal(); - } - else { - obj.model.addShare(s.item.value, { - success: function () { - $this.val('').attr('disabled', false); - $loading.addClass('hidden').removeClass('inlineblock'); - }, error: function (obj, msg) { - OC.Notification.showTemporary(msg); - $this.attr('disabled', false).autocomplete('search', $this.val()); - $loading.addClass('hidden').removeClass('inlineblock'); - } - }); - } - } - } -}); - -$(document).ready(function () { - - $('body').append('
'); - - - Guests.$mount('#app-guests'); -}); diff --git a/src/styles/default.less b/src/styles/default.less deleted file mode 100644 index 7f6df483..00000000 --- a/src/styles/default.less +++ /dev/null @@ -1,70 +0,0 @@ -// Use #app-guest to establish a namespace - -#app-guests { - - @modal-width: 500px; - @modal-height: 360px; - @modal-gutter: 20px; - - @moda-z-index: 500; - - .modal { - box-sizing: border-box; - position: absolute; - z-index: @moda-z-index + 1; - left: ~"calc(50% - "(@modal-width / 2)~")"; - top: 20%; - - width: @modal-width; - background: white; - padding: @modal-gutter; - - &-title, - &-body { - margin-bottom: @modal-gutter * 2; - } - - .form { - &-group { - margin: @modal-gutter / 2 0; - } - - &-label, - &-input { - box-sizing: border-box; - display: block; - width: 100%; - transition: - background .333s ease-out, - border .333s ease-out; - - &._error { - background: fade(red, 10%); - border: 1px solid fade(red, 20%); - color: red; - } - } - - } - - &-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, .5); - z-index: @moda-z-index; - content: ''; - width: 100vw; - height: 100vh; - display: block; - } - } - - // Mods - - &.-animate-to { - opacity: 1; - } -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index cefb4c71..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - entry: './src/default.js', - output : { - filename : './js/guests.bundle.js' - }, - module: { - loaders: [{ - test: /\.js?$/, - exclude: /node_modules/, - loader: 'babel-loader', - }, { - test: /\.less?$/, - loader: 'style-loader!css-loader!less-loader' - }] - } -}