diff --git a/core/css/styles.css b/core/css/styles.css index 4a84568244ca..43ee9158355a 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -309,7 +309,7 @@ input[type="submit"].enabled { position: fixed; top:45px; right: 0; - left: 0; + left:0; height: 44px; width: 100%; padding: 0; diff --git a/core/js/js.js b/core/js/js.js index 21a2d4c1b35c..096cc3ad7c13 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1087,6 +1087,7 @@ function initCore() { $('a.action.delete').tipsy({gravity:'e', fade:true, live:true}); $('a.action').tipsy({gravity:'s', fade:true, live:true}); $('td .modified').tipsy({gravity:'s', fade:true, live:true}); + $('td.lastLogin').tipsy({gravity:'s', fade:true, html:true}); $('input').tipsy({gravity:'w', fade:true}); // toggle for menus diff --git a/lib/private/group/group.php b/lib/private/group/group.php index 3e245ab6bbe8..e0a359248547 100644 --- a/lib/private/group/group.php +++ b/lib/private/group/group.php @@ -186,7 +186,7 @@ public function searchUsers($search, $limit = null, $offset = null) { * @param string $search * @return int|bool */ - public function count($search) { + public function count($search = '') { $users = false; foreach ($this->backends as $backend) { if($backend->implementsActions(OC_GROUP_BACKEND_COUNT_USERS)) { diff --git a/lib/private/group/metadata.php b/lib/private/group/metadata.php new file mode 100644 index 000000000000..1883ba727e0b --- /dev/null +++ b/lib/private/group/metadata.php @@ -0,0 +1,188 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Group; + +class MetaData { + const SORT_NONE = 0; + const SORT_USERCOUNT = 1; + + /** + * @var string $user + */ + protected $user; + + /** + * @var bool $isAdmin + */ + protected $isAdmin; + + /** + * @var string[] $groups + */ + protected $groups = array(); + + /** + * @var \OC\Group\Manager $groupManager + */ + protected $groupManager; + + /** + * @var int $sorting + */ + protected $sorting = false; + + /** + * @var string $lastSearch + */ + protected $lastSearch; + + /** + * @param string the uid of the current user + * @param bool whether the current users is an admin + * @param \OC\Group\Manager + */ + public function __construct( + $user, + $isAdmin, + \OC\Group\Manager $groupManager + ) { + $this->user = $user; + $this->isAdmin = (bool)$isAdmin; + $this->groupManager = $groupManager; + } + + /** + * returns an array with meta data about all available groups + * the array is structured as follows: + * [0] array containing meta data about admin groups + * [1] array containing meta data about unprivileged groups + * @param string only effective when instance was created with isAdmin being + * true + * @return array + */ + public function get($search = '') { + if($this->lastSearch !== $search) { + $this->lastSearch = $search; + $this->groups = array(); + } + + $adminGroups = array(); + $groups = array(); + $sortGroupsIndex = 0; + $sortGroupsKeys = array(); + $sortAdminGroupsIndex = 0; + $sortAdminGroupsKeys = array(); + + foreach($this->getGroups($search) as $group) { + $groupMetaData = $this->generateGroupMetaData($group); + if (strtolower($group->getGID()) !== 'admin') { + $this->addEntry( + $groups, + $sortGroupsKeys, + $sortGroupsIndex, + $groupMetaData); + } else { + //admin group is hard coded to 'admin' for now. In future, + //backends may define admin groups too. Then the if statement + //has to be adjusted accordingly. + $this->addEntry( + $adminGroups, + $sortAdminGroupsKeys, + $sortAdminGroupsIndex, + $groupMetaData); + } + } + + //whether sorting is necessary is will be checked in sort() + $this->sort($groups, $sortGroupsKeys); + $this->sort($adminGroups, $sortAdminGroupsKeys); + + return array($adminGroups, $groups); + } + + /** + * @brief sets the sort mode, currently 0 (none) and 1 (user entries, + * descending) are supported + * @param int the sortMode (SORT_NONE, SORT_USERCOUNT) + */ + public function setSorting($sortMode) { + if($sortMode >= 0 && $sortMode <= 1) { + $this->sorting = $sortMode; + } else { + $this->sorting = 0; + } + } + + /** + * @brief adds an group entry to the resulting array + * @param array the resulting array, by reference + * @param array the sort key array, by reference + * @param array the sort key index, by reference + * @param array the group's meta data as returned by generateGroupMetaData() + * @return null + */ + private function addEntry(&$entries, &$sortKeys, &$sortIndex, $data) { + $entries[] = $data; + if($this->sorting === 1) { + $sortKeys[$sortIndex] = $data['usercount']; + $sortIndex++; + } + } + + /** + * @brief creates an array containing the group meta data + * @param \OC\Group\Group + * @return array with the keys 'id', 'name' and 'usercount' + */ + private function generateGroupMetaData(\OC\Group\Group $group) { + return array( + 'id' => str_replace(' ','', $group->getGID()), + 'name' => $group->getGID(), + 'usercount' => $group->count() + ); + } + + /** + * @brief sorts the result array, if applicable + * @param array the result array, by reference + * @param array the array containing the sort keys + * @param return null + */ + private function sort(&$entries, $sortKeys) { + if($this->sorting > 0) { + array_multisort($sortKeys, SORT_DESC, $entries); + } + } + + /** + * @brief returns the available groups + * @param string a search string + * @return \OC\Group\Group[] + */ + private function getGroups($search = '') { + if(count($this->groups) === 0) { + $this->fetchGroups($search); + } + return $this->groups; + } + + /** + * @brief fetches the group using the group manager or the subAdmin API + * @param string a search string + * @return null + */ + private function fetchGroups($search = '') { + if($this->isAdmin) { + $this->groups = $this->groupManager->search($search); + } else { + $this->groups = \OC_SubAdmin::getSubAdminsGroups($this->user); + } + } +} diff --git a/settings/ajax/creategroup.php b/settings/ajax/creategroup.php index 0a79527c219f..854f2c37189a 100644 --- a/settings/ajax/creategroup.php +++ b/settings/ajax/creategroup.php @@ -4,6 +4,7 @@ OC_JSON::checkAdminUser(); $groupname = $_POST["groupname"]; +$l = OC_L10N::get('settings'); // Does the group exist? if( in_array( $groupname, OC_Group::getGroups())) { diff --git a/settings/ajax/createuser.php b/settings/ajax/createuser.php index 94b56fa03494..ae1d8856f43f 100644 --- a/settings/ajax/createuser.php +++ b/settings/ajax/createuser.php @@ -43,12 +43,15 @@ OC_Group::addToGroup( $username, $i ); } - OC_JSON::success(array("data" => + $userManager = \OC_User::getManager(); + $user = $userManager->get($username); + OCP\JSON::success(array("data" => array( // returns whether the home already existed "homeExists" => $homeExists, "username" => $username, - "groups" => OC_Group::getUserGroups( $username )))); + "groups" => OC_Group::getUserGroups( $username ), + 'storageLocation' => $user->getHome()))); } catch (Exception $exception) { - OC_JSON::error(array("data" => array( "message" => $exception->getMessage()))); + OCP\JSON::error(array("data" => array( "message" => $exception->getMessage()))); } diff --git a/settings/ajax/grouplist.php b/settings/ajax/grouplist.php new file mode 100644 index 000000000000..91700adc3599 --- /dev/null +++ b/settings/ajax/grouplist.php @@ -0,0 +1,48 @@ + + * + * 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 . + * + */ + +OC_JSON::callCheck(); +OC_JSON::checkSubAdminUser(); +if (isset($_GET['pattern']) && !empty($_GET['pattern'])) { + $pattern = $_GET['pattern']; +} else { + $pattern = ''; +} +$groups = array(); +$adminGroups = array(); +$groupManager = \OC_Group::getManager(); +$isAdmin = OC_User::isAdminUser(OC_User::getUser()); + +//we pass isAdmin as true, because OC_SubAdmin has no search feature, +//groups will be filtered out later +$groupsInfo = new \OC\Group\MetaData(OC_User::getUser(), true, $groupManager); +$groupsInfo->setSorting($groupsInfo::SORT_USERCOUNT); +list($adminGroups, $groups) = $groupsInfo->get($pattern); + +$accessibleGroups = $groupManager->search($pattern); +if(!$isAdmin) { + $subadminGroups = OC_SubAdmin::getSubAdminsGroups(OC_User::getUser()); + $accessibleGroups = array_intersect($groups, $subadminGroups); +} + +OC_JSON::success( + array('data' => array('adminGroups' => $adminGroups, 'groups' => $groups))); diff --git a/settings/ajax/userlist.php b/settings/ajax/userlist.php index 4abf54b8987e..32237d60b6e8 100644 --- a/settings/ajax/userlist.php +++ b/settings/ajax/userlist.php @@ -32,26 +32,55 @@ } else { $limit = 10; } +if (isset($_GET['gid']) && !empty($_GET['gid'])) { + $gid = $_GET['gid']; +} else { + $gid = false; +} +if (isset($_GET['pattern']) && !empty($_GET['pattern'])) { + $pattern = $_GET['pattern']; +} else { + $pattern = ''; +} $users = array(); +$userManager = \OC_User::getManager(); if (OC_User::isAdminUser(OC_User::getUser())) { - $batch = OC_User::getDisplayNames('', $limit, $offset); - foreach ($batch as $user => $displayname) { + if($gid !== false) { + $batch = OC_Group::displayNamesInGroup($gid, $pattern, $limit, $offset); + } else { + $batch = OC_User::getDisplayNames($pattern, $limit, $offset); + } + foreach ($batch as $uid => $displayname) { + $user = $userManager->get($uid); $users[] = array( - 'name' => $user, + 'name' => $uid, 'displayname' => $displayname, - 'groups' => join(', ', OC_Group::getUserGroups($user)), - 'subadmin' => join(', ', OC_SubAdmin::getSubAdminsGroups($user)), - 'quota' => OC_Preferences::getValue($user, 'files', 'quota', 'default')); + 'groups' => join(', ', OC_Group::getUserGroups($uid)), + 'subadmin' => join(', ', OC_SubAdmin::getSubAdminsGroups($uid)), + 'quota' => OC_Preferences::getValue($uid, 'files', 'quota', 'default'), + 'storageLocation' => $user->getHome(), + 'lastLogin' => $user->getLastLogin(), + ); } } else { $groups = OC_SubAdmin::getSubAdminsGroups(OC_User::getUser()); - $batch = OC_Group::usersInGroups($groups, '', $limit, $offset); - foreach ($batch as $user) { + if($gid !== false && in_array($gid, $groups)) { + $groups = array($gid); + } elseif($gid !== false) { + //don't you try to investigate loops you must not know about + $groups = array(); + } + $batch = OC_Group::usersInGroups($groups, $pattern, $limit, $offset); + foreach ($batch as $uid) { + $user = $userManager->get($uid); $users[] = array( 'name' => $user, - 'displayname' => OC_User::getDisplayName($user), - 'groups' => join(', ', OC_Group::getUserGroups($user)), - 'quota' => OC_Preferences::getValue($user, 'files', 'quota', 'default')); + 'displayname' => $user->getDisplayName(), + 'groups' => join(', ', OC_Group::getUserGroups($uid)), + 'quota' => OC_Preferences::getValue($uid, 'files', 'quota', 'default'), + 'storageLocation' => $user->getHome(), + 'lastLogin' => $user->getLastLogin(), + ); } } OC_JSON::success(array('data' => $users)); diff --git a/settings/css/settings.css b/settings/css/settings.css index be6cfe1e9bfe..87b21cb8e2bb 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -5,6 +5,13 @@ select#languageinput, select#timezone { width:15em; } input#openid, input#webdav { width:20em; } +#user-controls { + -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; + position: fixed; right: 0; left:380px; height: 44px; + padding: 0; margin: 0; + background: #eee; border-bottom: 1px solid #e7e7e7; + z-index: 50; +} /* PERSONAL */ #rootcert_import { @@ -44,7 +51,37 @@ table.nostyle label { margin-right: 2em; } table.nostyle td { padding: 0.2em 0; } /* USERS */ +#newgroup-init a span { margin-left: 20px; } +#newgroup-init a span:before { + position: absolute; left: 12px; top:-2px; + content: '+'; font-weight: bold; font-size: 150%; +} +.usercount { float: left; margin: 5px; } +li.active span.utils .delete { + float: left; position: relative; opacity: 0.5; + top: -7px; left: 7px; width: 44px; height: 44px; +} +li.active .rename { + padding: 8px 14px 20px 14px; + top: 0px; position: absolute; width: 16px; height: 16px; + opacity: 0.5; + display: inline-block !important; +} +li.active span.utils .delete img { margin: 14px; } +li.active .rename { opacity: 0.5; } +li.active span.utils .delete:hover, li.active .rename:hover { opacity: 1; } +span.utils .delete, .rename { display: none; } +#app-navigation ul li.active > span.utils .delete, +#app-navigation ul li.active > span.utils .rename { display: block; } +#usersearchform { position: absolute; top: 4px; right: 10px; } +#usersearchform label { font-weight: 700; } form { display:inline; } + +/* display table at full width */ +table.grid { + width: 100%; +} + table.grid th { height:2em; color:#999; } table.grid th, table.grid td { border-bottom:1px solid #ddd; padding:0 .5em; padding-left:.8em; text-align:left; font-weight:normal; } td.name, td.password { padding-left:.8em; } @@ -57,9 +94,8 @@ tr:hover>td.password>span, tr:hover>td.displayName>span { margin:0; cursor:point tr:hover>td.remove>a, tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; } tr:hover>td.remove>a { float:right; } -table.grid { width:100%; } div.quota { - float: right; + margin: 10px; display: block; } div.quota-select-wrapper { position: relative; } @@ -78,6 +114,8 @@ div.quota>span { } select.quota.active { background: #fff; } +input.userFilter {width: 200px;} + /* positioning fixes */ #newuser .multiselect { min-width: 150px !important; diff --git a/settings/js/users.js b/settings/js/users.js deleted file mode 100644 index eef3c2372770..000000000000 --- a/settings/js/users.js +++ /dev/null @@ -1,546 +0,0 @@ -/** - * Copyright (c) 2011, Robin Appelman - * This file is licensed under the Affero General Public License version 3 or later. - * See the COPYING-README file. - */ - -function setQuota (uid, quota, ready) { - $.post( - OC.filePath('settings', 'ajax', 'setquota.php'), - {username: uid, quota: quota}, - function (result) { - if (ready) { - ready(result.data.quota); - } - } - ); -} - -var UserList = { - useUndo: true, - availableGroups: [], - offset: 30, //The first 30 users are there. No prob, if less in total. - //hardcoded in settings/users.php - - usersToLoad: 10, //So many users will be loaded when user scrolls down - - /** - * @brief Initiate user deletion process in UI - * @param string uid the user ID to be deleted - * - * Does not actually delete the user; it sets them for - * deletion when the current page is unloaded, at which point - * finishDelete() completes the process. This allows for 'undo'. - */ - do_delete: function (uid) { - if (typeof UserList.deleteUid !== 'undefined') { - //Already a user in the undo queue - UserList.finishDelete(null); - } - UserList.deleteUid = uid; - - // Set undo flag - UserList.deleteCanceled = false; - - // Provide user with option to undo - $('#notification').data('deleteuser', true); - OC.Notification.showHtml(t('settings', 'deleted') + ' ' + escapeHTML(uid) + '' + t('settings', 'undo') + ''); - }, - - /** - * @brief Delete a user via ajax - * @param bool ready whether to use ready() upon completion - * - * Executes deletion via ajax of user identified by property deleteUid - * if 'undo' has not been used. Completes the user deletion procedure - * and reflects success in UI. - */ - finishDelete: function (ready) { - - // Check deletion has not been undone - if (!UserList.deleteCanceled && UserList.deleteUid) { - - // Delete user via ajax - $.ajax({ - type: 'POST', - url: OC.filePath('settings', 'ajax', 'removeuser.php'), - async: false, - data: { username: UserList.deleteUid }, - success: function (result) { - if (result.status === 'success') { - // Remove undo option, & remove user from table - OC.Notification.hide(); - $('tr').filterAttr('data-uid', UserList.deleteUid).remove(); - UserList.deleteCanceled = true; - if (ready) { - ready(); - } - } else { - OC.dialogs.alert(result.data.message, t('settings', 'Unable to remove user')); - } - } - }); - } - }, - - add: function (username, displayname, groups, subadmin, quota, sort) { - var tr = $('tbody tr').first().clone(); - var subadminsEl; - var subadminSelect; - var groupsSelect; - if (tr.find('div.avatardiv').length){ - $('div.avatardiv', tr).avatar(username, 32); - } - tr.attr('data-uid', username); - tr.attr('data-displayName', displayname); - tr.find('td.name').text(username); - tr.find('td.displayName > span').text(displayname); - - // make them look like the multiselect buttons - // until they get time to really get initialized - groupsSelect = $('') - .attr('data-username', username) - .data('user-groups', groups); - if (tr.find('td.subadmins').length > 0) { - subadminSelect = $(''); - img.css('display', 'none'); - img.parent().children('span').replaceWith(input); - input.focus(); - input.keypress(function (event) { - if (event.keyCode === 13) { - if ($(this).val().length > 0) { - var recoveryPasswordVal = $('input:password[id="recoveryPassword"]').val(); - $.post( - OC.generateUrl('/settings/users/changepassword'), - {username: uid, password: $(this).val(), recoveryPassword: recoveryPasswordVal}, - function (result) { - if (result.status != 'success') { - OC.Notification.show(t('admin', result.data.message)); - } - } - ); - input.blur(); - } else { - input.blur(); - } - } - }); - input.blur(function () { - $(this).replaceWith($('●●●●●●●')); - img.css('display', ''); - }); - }); - $('input:password[id="recoveryPassword"]').keyup(function(event) { - OC.Notification.hide(); - }); - - $('table').on('click', 'td.password', function (event) { - $(this).children('img').click(); - }); - - $('table').on('click', 'td.displayName>img', function (event) { - event.stopPropagation(); - var img = $(this); - var uid = img.parent().parent().attr('data-uid'); - var displayName = escapeHTML(img.parent().parent().attr('data-displayName')); - var input = $(''); - img.css('display', 'none'); - img.parent().children('span').replaceWith(input); - input.focus(); - input.keypress(function (event) { - if (event.keyCode === 13) { - if ($(this).val().length > 0) { - $.post( - OC.filePath('settings', 'ajax', 'changedisplayname.php'), - {username: uid, displayName: $(this).val()}, - function (result) { - if (result && result.status==='success'){ - img.parent().parent().find('div.avatardiv').avatar(result.data.username, 32); - } - } - ); - input.blur(); - } else { - input.blur(); - } - } - }); - input.blur(function () { - var input = $(this), - displayName = input.val(); - input.closest('tr').attr('data-displayName', displayName); - input.replaceWith('' + escapeHTML(displayName) + ''); - img.css('display', ''); - }); - }); - $('table').on('click', 'td.displayName', function (event) { - $(this).children('img').click(); - }); - - $('select.quota, select.quota-user').singleSelect().on('change', function () { - var select = $(this); - var uid = $(this).parent().parent().attr('data-uid'); - var quota = $(this).val(); - setQuota(uid, quota, function(returnedQuota){ - if (quota !== returnedQuota) { - select.find(':selected').text(returnedQuota); - } - }); - }); - - $('#newuser').submit(function (event) { - event.preventDefault(); - var username = $('#newusername').val(); - var password = $('#newuserpassword').val(); - if ($.trim(username) === '') { - OC.dialogs.alert( - t('settings', 'A valid username must be provided'), - t('settings', 'Error creating user')); - return false; - } - if ($.trim(password) === '') { - OC.dialogs.alert( - t('settings', 'A valid password must be provided'), - t('settings', 'Error creating user')); - return false; - } - var groups = $('#newusergroups').prev().children('div').data('settings').checked; - $('#newuser').get(0).reset(); - $.post( - OC.filePath('settings', 'ajax', 'createuser.php'), - { - username: username, - password: password, - groups: groups - }, - function (result) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, - t('settings', 'Error creating user')); - } else { - if (result.data.groups) { - var addedGroups = result.data.groups; - UserList.availableGroups = $.unique($.merge(UserList.availableGroups, addedGroups)); - } - if (result.data.homeExists){ - OC.Notification.hide(); - OC.Notification.show(t('settings', 'Warning: Home directory for user "{user}" already exists', {user: result.data.username})); - if (UserList.notificationTimeout){ - window.clearTimeout(UserList.notificationTimeout); - } - UserList.notificationTimeout = window.setTimeout( - function(){ - OC.Notification.hide(); - UserList.notificationTimeout = null; - }, 10000); - } - if($('tr[data-uid="' + username + '"]').length === 0) { - UserList.add(username, username, result.data.groups, null, 'default', true); - } - } - } - ); - }); - // Handle undo notifications - OC.Notification.hide(); - $('#notification').on('click', '.undo', function () { - if ($('#notification').data('deleteuser')) { - $('tbody tr').filterAttr('data-uid', UserList.deleteUid).show(); - UserList.deleteCanceled = true; - } - OC.Notification.hide(); - }); - UserList.useUndo = ('onbeforeunload' in window); - $(window).bind('beforeunload', function () { - UserList.finishDelete(null); - }); -}); diff --git a/settings/js/users/deleteHandler.js b/settings/js/users/deleteHandler.js new file mode 100644 index 000000000000..894744ba3e95 --- /dev/null +++ b/settings/js/users/deleteHandler.js @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2014, Arthur Schiwon + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +/** + * takes care of deleting things represented by an ID + * + * @class + * @param {string} endpoint the corresponding ajax PHP script. Currently limited + * to settings - ajax path. + * @param {string} paramID the by the script expected parameter name holding the + * ID of the object to delete + * @param {markCallback} markCallback function to be called after successfully + * marking the object for deletion. + * @param {removeCallback} removeCallback the function to be called after + * successful delete. + */ +function DeleteHandler(endpoint, paramID, markCallback, removeCallback) { + this.oidToDelete = false; + this.canceled = false; + + this.ajaxEndpoint = endpoint; + this.ajaxParamID = paramID; + + this.markCallback = markCallback; + this.removeCallback = removeCallback; + this.undoCallback = false; + + this.notifier = false; + this.notificationDataID = false; + this.notificationMessage = false; + this.notificationPlaceholder = '%oid'; +} + +/** + * The function to be called after successfully marking the object for deletion + * @callback markCallback + * @param {string} oid the ID of the specific user or group + */ + +/** + * The function to be called after successful delete. The id of the object will + * be passed as argument. Unsuccessful operations will display an error using + * OC.dialogs, no callback is fired. + * @callback removeCallback + * @param {string} oid the ID of the specific user or group + */ + +/** + * This callback is fired after "undo" was clicked so the consumer can update + * the web interface + * @callback undoCallback + * @param {string} oid the ID of the specific user or group + */ + +/** + * enabled the notification system. Required for undo UI. + * + * @param {object} notifier Usually OC.Notification + * @param {string} dataID an identifier for the notifier, e.g. 'deleteuser' + * @param {string} message the message that should be shown upon delete. %oid + * will be replaced with the affected id of the item to be deleted + * @param {undoCallback} undoCallback called after "undo" was clicked + */ +DeleteHandler.prototype.setNotification = function(notifier, dataID, message, undoCallback) { + this.notifier = notifier; + this.notificationDataID = dataID; + this.notificationMessage = message; + this.undoCallback = undoCallback; + + var dh = this; + + $('#notification').on('click', '.undo', function () { + if ($('#notification').data(dh.notificationDataID)) { + var oid = dh.oidToDelete; + dh.cancel(); + if(typeof dh.undoCallback !== 'undefined') { + dh.undoCallback(oid); + } + } + dh.notifier.hide(); + }); +}; + +/** + * shows the Undo Notification (if configured) + */ +DeleteHandler.prototype.showNotification = function() { + if(this.notifier !== false) { + if(!this.notifier.isHidden()) { + this.hideNotification(); + } + $('#notification').data(this.notificationDataID, true); + var msg = this.notificationMessage.replace(this.notificationPlaceholder, + this.oidToDelete); + this.notifier.showHtml(msg); + } +}; + +/** + * hides the Undo Notification + */ +DeleteHandler.prototype.hideNotification = function() { + if(this.notifier !== false) { + $('#notification').removeData(this.notificationDataID); + this.notifier.hide(); + } +}; + +/** + * initializes the delete operation for a given object id + * + * @param {string} oid the object id + */ +DeleteHandler.prototype.mark = function(oid) { + if(this.oidToDelete !== false) { + this.delete(); + } + this.oidToDelete = oid; + this.canceled = false; + this.markCallback(oid); + this.showNotification(); +}; + +/** + * cancels a delete operation + */ +DeleteHandler.prototype.cancel = function() { + this.canceled = true; + this.oidToDelete = false; +}; + +/** + * executes a delete operation. Requires that the operation has been + * initialized by mark(). On error, it will show a message via + * OC.dialogs.alert. On success, a callback is fired so that the client can + * update the web interface accordingly. + */ +DeleteHandler.prototype.delete = function() { + if(this.canceled || this.oidToDelete === false) { + return false; + } + + var dh = this; + if($('#notification').data(this.notificationDataID) === true) { + dh.hideNotification(); + } + + var payload = {}; + payload[dh.ajaxParamID] = dh.oidToDelete; + $.ajax({ + type: 'POST', + url: OC.filePath('settings', 'ajax', dh.ajaxEndpoint), + async: false, + data: payload, + success: function (result) { + if (result.status === 'success') { + // Remove undo option, & remove user from table + + //TODO: following line + dh.removeCallback(dh.oidToDelete); + dh.canceled = true; + } else { + OC.dialogs.alert(result.data.message, t('settings', 'Unable to delete ' + escapeHTML(dh.oidToDelete))); + dh.undoCallback(dh.oidToDelete); + } + } + }); +}; diff --git a/settings/js/users/filter.js b/settings/js/users/filter.js new file mode 100644 index 000000000000..1f7a29de0c99 --- /dev/null +++ b/settings/js/users/filter.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2014, Arthur Schiwon + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +/** + * @brief this object takes care of the filter functionality on the user + * management page + * @param jQuery input element that works as the user text input field + * @param object the UserList object + */ +function UserManagementFilter(filterInput, userList, groupList) { + this.filterInput = filterInput; + this.userList = userList; + this.groupList = groupList; + this.thread = undefined; + this.oldval = this.filterInput.val(); + + this.init(); +} + +/** + * @brief sets up when the filter action shall be triggered + */ +UserManagementFilter.prototype.init = function() { + var umf = this; + this.filterInput.keyup(function(e) { + //we want to react on any printable letter, plus on modifying stuff like + //Backspace and Delete. extended https://stackoverflow.com/a/12467610 + var valid = + e.keyCode === 0 || e.keyCode === 8 || // like ö or ж; backspace + e.keyCode === 9 || e.keyCode === 46 || // tab; delete + e.keyCode === 32 || // space + (e.keyCode > 47 && e.keyCode < 58) || // number keys + (e.keyCode > 64 && e.keyCode < 91) || // letter keys + (e.keyCode > 95 && e.keyCode < 112) || // numpad keys + (e.keyCode > 185 && e.keyCode < 193) || // ;=,-./` (in order) + (e.keyCode > 218 && e.keyCode < 223); // [\]' (in order) + + //besides the keys, the value must have been changed compared to last + //time + if(valid && umf.oldVal !== umf.getPattern()) { + umf.run(); + } + + umf.oldVal = umf.getPattern(); + }); +}; + +/** + * @brief the filter action needs to be done, here the accurate steps are being + * taken care of + */ +UserManagementFilter.prototype.run = _.debounce(function() { + this.userList.empty(); + this.userList.update(GroupList.getCurrentGID()); + this.groupList.empty(); + this.groupList.update(); + }, + 300 +); + +/** + * @brief returns the filter String + * @returns string + */ +UserManagementFilter.prototype.getPattern = function() { + return this.filterInput.val(); +}; + +/** + * @brief adds reset functionality to an HTML element + * @param jQuery the jQuery representation of that element + */ +UserManagementFilter.prototype.addResetButton = function(button) { + var umf = this; + button.click(function(){ + umf.filterInput.val(''); + umf.run(); + }); +}; diff --git a/settings/js/users/groups.js b/settings/js/users/groups.js new file mode 100644 index 000000000000..0ff8bdd63841 --- /dev/null +++ b/settings/js/users/groups.js @@ -0,0 +1,292 @@ +/** + * Copyright (c) 2014, Raghu Nayyar + * Copyright (c) 2014, Arthur Schiwon + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +var $userGroupList; + +var GroupList; +GroupList = { + activeGID: '', + + addGroup: function (gid, usercount) { + var $li = $userGroupList.find('.isgroup:last-child').clone(); + $li + .data('gid', gid) + .find('.groupname').text(gid); + GroupList.setUserCount($li, usercount); + + $li.appendTo($userGroupList); + + GroupList.sortGroups(); + + return $li; + }, + + setUserCount: function (groupLiElement, usercount) { + var $groupLiElement = $(groupLiElement); + if (usercount === undefined || usercount === 0) { + usercount = ''; + } + $groupLiElement.data('usercount', usercount); + $groupLiElement.find('.usercount').text(usercount); + }, + + getCurrentGID: function () { + return GroupList.activeGID; + }, + + sortGroups: function () { + var lis = $('.isgroup').get(); + + lis.sort(function (a, b) { + return UserList.alphanum( + $(a).find('a span').text(), + $(b).find('a span').text() + ); + }); + + var items = []; + $.each(lis, function (index, li) { + items.push(li); + if (items.length === 100) { + $userGroupList.append(items); + items = []; + } + }); + if (items.length > 0) { + $userGroupList.append(items); + } + }, + + createGroup: function (groupname) { + $.post( + OC.filePath('settings', 'ajax', 'creategroup.php'), + { + groupname: groupname + }, + function (result) { + if (result.status !== 'success') { + OC.dialogs.alert(result.data.message, + t('settings', 'Error creating group')); + } + else { + if (result.data.groupname) { + var addedGroup = result.data.groupname; + UserList.availableGroups = $.unique($.merge(UserList.availableGroups, [addedGroup])); + GroupList.addGroup(result.data.groupname); + + $('.groupsselect, .subadminsselect') + .append($('