GUI for mobile version of AclUsers

Conflicts:

	UI/Common/UIxAclEditor.m
	UI/Common/UIxUserRightsEditor.m
	UI/WebServerResources/js/Common/acl-model.js
pull/91/head
Alexandre Cloutier 2014-10-08 13:44:37 -04:00 committed by Francis Lachapelle
parent e4b1c0d5b9
commit f3c26671af
7 changed files with 365 additions and 22 deletions

View File

@ -31,7 +31,7 @@
BOOL publishInFreeBusy;
NSArray *aclUsers;
NSArray *savedUIDs;
NSMutableArray *users;
NSMutableDictionary *users;
NSString *currentUser;
NSString *defaultUserID;
}

View File

@ -46,7 +46,7 @@
aclUsers = nil;
prepared = NO;
publishInFreeBusy = NO;
users = [NSMutableArray new];
users = [NSMutableDictionary new];
currentUser = nil;
defaultUserID = nil;
savedUIDs = nil;
@ -96,9 +96,23 @@
{
if (!([currentUID isEqualToString: ownerLogin]
|| [currentUID isEqualToString: defaultUserID]
|| [currentUID isEqualToString: @"anonymous"]))
[users addObjectUniquely: currentUID];
|| [currentUID isEqualToString: @"anonymous"]))
{
// Set the current user in order to get information associated with it
[self setCurrentUser: currentUID];
// Build the object associated with the key; currentUID
object = [NSDictionary dictionaryWithObjectsAndKeys: currentUser, @"uid",
[self currentUserClass], @"userClass",
[self currentUserDisplayName], @"displayName",
[NSNumber numberWithBool:[self currentUserIsSubscribed]], @"isSubscribed", nil];
[users setObject:object forKey: currentUID];
}
}
// Adding the Any authenticated user and the public access
[users setObject:[NSDictionary dictionaryWithObjectsAndKeys: @"<default>", @"uid", [self labelForKey: @"Any Authenticated User"], @"displayName", @"public-user", @"userClass", nil] forKey: @"<default>"];
if ([self isPublicAccessEnabled])
[users setObject:[NSDictionary dictionaryWithObjectsAndKeys: @"anonymous", @"uid", [self labelForKey: @"Public Access"], @"displayName", @"public-user", @"userClass", nil] forKey: @"anonymous"];
prepared = YES;
}

View File

@ -187,11 +187,25 @@
{
id <WOActionResults> response;
SOGoDomainDefaults *dd;
NSDictionary *jsonObject, *currentObject;
NSEnumerator *enumerator;
NSArray *o;
id key;
if (![self _initRights])
response = [NSException exceptionWithHTTPStatus: 403
reason: @"No such user."];
else
value = [[self context] request];
jsonObject = [[value contentAsString] objectFromJSONString];
enumerator = [jsonObject keyEnumerator];
while((key = [enumerator nextObject]))
{
currentObject = [jsonObject objectForKey: key];
if(![self _initRightsWithParameter: [currentObject objectForKey: @"uid"]])
{
response = [self responseWithStatus: 403
andString: @"No such user."];
return response;
}
else
{
NSArray *o;

View File

@ -440,7 +440,8 @@
NSString *uid;
NSDictionary *contact;
NSString *contactInfo, *login;
NSMutableArray *jsonResponse, *jsonLine;
NSMutableArray *jsonResponse;
NSMutableDictionary *jsonLine;
NSArray *allUsers;
int count, max;
BOOL activeUserIsInDomain;
@ -462,16 +463,15 @@
// We do NOT return the current authenticated user
if (!activeUserIsInDomain || ![uid isEqualToString: login])
{
jsonLine = [NSMutableArray arrayWithCapacity: 4];
if ([domain length] && [uid rangeOfString: @"@"].location == NSNotFound)
jsonLine = [NSMutableDictionary dictionary];
if ([domain length])
uid = [NSString stringWithFormat: @"%@@%@", uid, domain];
[jsonLine addObject: uid];
[jsonLine addObject: [contact objectForKey: @"cn"]];
[jsonLine addObject: [contact objectForKey: @"c_email"]];
[jsonLine addObject: [NSNumber numberWithBool: [[contact objectForKey: @"isGroup"] boolValue]]];
[jsonLine setObject: uid forKey: @"uid"];
[jsonLine setObject: [NSString stringWithFormat:@"%@ <%@>",[contact objectForKey: @"cn"], [contact objectForKey: @"c_email"]] forKey: @"displayName"];
[jsonLine setObject: [NSNumber numberWithBool: [[contact objectForKey: @"isGroup"] boolValue]] forKey: @"isGroup"];
contactInfo = [contact objectForKey: @"c_info"];
if (contactInfo)
[jsonLine addObject: contactInfo];
[jsonLine setObject: contactInfo forKey: @"c_info"];
[jsonResponse addObject: jsonLine];
}
}

View File

@ -8,7 +8,7 @@
xmlns:label="OGo:label"
xmlns:rsrc="OGo:url"
const:userDefaultsKeys="SOGoContactsCategories"
const:jsFiles="Common/resource.js, Contacts/card-model.js, Contacts/addressbook-model.js"
const:jsFiles="Common/resource.js, Common/acl-model.js, Contacts/card-model.js, Contacts/addressbook-model.js"
className="UIxPageFrame"
title="name"
var:popup="isPopup">
@ -364,4 +364,55 @@
</ion-popover-view>
</script>
<script type="text/ng-template" id="acl-modal.html">
<ion-modal-view>
<ion-header-bar class="bar-positive">
<div class="buttons" ng-show="onGoingSearch">
<button class="button button-icon" ng-click="closeModal()">close</button>
</div>
<div class="buttons" ng-hide="onGoingSearch">
<button class="button button-icon" ng-click="cancelSearch()">back</button>
</div>
<h1 class="title">{{addressbook.name}}</h1>
<div class="buttons">
<button class="button button-icon" ng-click="saveModal()">save</button>
</div>
</ion-header-bar>
<ion-content>
<ion-list show-delete="showDelete" on-tap="toggleDelete(false)">
<div ng-repeat="user in users | orderBy:['userClass', 'displayName']">
<ion-item class="item-stable item-icon-left item-icon-right" ng-click="toggleUser(user)" on-swipe-right="toggleDelete(true)">
<i class="icon" ng-class="(user.userClass == 'public-user') ? 'ion-ios7-people' : 'ion-ios7-person'"><!-- spacer --></i>
{{user.displayName}}
<i class="icon" ng-class="displayIcon(user)"><!-- spacer --></i>
<ion-delete-button class="ion-minus-circled" ng-disabled="user.userClass == 'public-user' || !user.inAclList"
ng-click="removeUser(user)"></ion-delete-button>
</ion-item>
<div class="item-accordion" ng-show="isUserShown(user)">
<ion-toggle ng-show="displaySubscribeUser()" ng-model="userSelected.isSubscribed" ng-checked="userSelected.isSubscribed"
ng-disabled="!userSelected.canSubscribeUser" ng-change="markUserAsDirty()">
<var:string label:value="Subscribe user"/></ion-toggle>
<ion-checkbox ng-show="displayUserRights()" ng-checked="userSelected.aclOptions.canCreateObjects"
ng-model="userSelected.aclOptions.canCreateObjects" ng-change="markUserAsDirty()">
<var:string label:value="This person can add cards to this addressbook."/></ion-checkbox>
<ion-checkbox ng-show="displayUserRights()" ng-checked="userSelected.aclOptions.canEditObjects"
ng-model="userSelected.aclOptions.canEditObjects" ng-change="markUserAsDirty()">
<var:string label:value="This person can edit the cards of this addressbook."/></ion-checkbox>
<ion-checkbox ng-show="displayUserRights()" ng-checked="userSelected.aclOptions.canEraseObjects"
ng-model="userSelected.aclOptions.canEraseObjects" ng-change="markUserAsDirty()">
<var:string label:value="This person can erase cards from this addressbook."/></ion-checkbox>
<ion-checkbox ng-checked="userSelected.aclOptions.canViewObjects"
ng-model="userSelected.aclOptions.canViewObjects" ng-change="markUserAsDirty()">
<var:string label:value="This person can read the cards of this addressbook."/></ion-checkbox>
</div>
</div>
</ion-list>
</ion-content>
<ion-footer-bar class="bar-footer">
<ion-search class="item item-light" placeholder="Search a user/group" min-length="2" model="usersFound"
source="getContacts(str)" clear="cancelSearch()">
</ion-search>
</ion-footer-bar>
</ion-modal-view>
</script>
</var:component>

View File

@ -0,0 +1,59 @@
(function() {
'use strict';
function AclUsers(addressbook) {
this.addressbook_id = addressbook.id;
this.addressbook_name = addressbook.name;
this.addressbook_owner = addressbook.owner;
}
/* The factory we'll use to register with Angular */
AclUsers.factory = ['$q', '$timeout', 'sgSettings', 'sgResource', function($q, $timeout, Settings, Resource) {
angular.extend(AclUsers, {
$q: $q,
$timeout: $timeout,
$$resource: new Resource(Settings.baseURL)
});
return AclUsers; // return constructor
}];
/* Factory registration in Angular module */
angular.module('SOGo.Common').factory('sgAclUsers', AclUsers.factory);
/* Instance methods
* Public method, assigned to prototype
*/
AclUsers.prototype.getUsers = function() {
return AclUsers.$$resource.fetch(this.addressbook_id, "getUsersForObject");
};
AclUsers.prototype.searchUsers = function(inputText) {
var param = "search=" + inputText;
return AclUsers.$$resource.fetch(null, "usersSearch", param);
};
AclUsers.prototype.openRightsForUserId = function(user) {
var param = "uid=" + user;
return AclUsers.$$resource.fetch(this.addressbook_id, "userRights", param);
};
AclUsers.prototype.addUser = function(user) {
var param = "uid=" + user;
AclUsers.$$resource.fetch(this.addressbook_id, "addUserInAcls", param);
};
AclUsers.prototype.subscribeUsers = function(users) {
var param = "uids=" + users;
AclUsers.$$resource.fetch(this.addressbook_id, "subscribeUsers", param);
};
AclUsers.prototype.removeUser = function(user) {
var userId = "uid=" + user.uid;
AclUsers.$$resource.fetch(this.addressbook_id, "removeUserFromAcls", userId);
};
AclUsers.prototype.saveUsersRights = function(dirtyObjects) {
AclUsers.$$resource.saveAclUsers(this.addressbook_id, "saveUserRights", dirtyObjects);
};
})();

View File

@ -107,6 +107,49 @@
$urlRouterProvider.otherwise('/app/addressbooks');
})
.directive('ionSearch', function() {
return {
restrict: 'E',
replace: true,
scope: {
getData: '&source',
clearData: '&clear',
model: '=?',
search: '=?filter'
},
link: function(scope, element, attrs) {
attrs.minLength = attrs.minLength || 0;
scope.placeholder = attrs.placeholder || '';
scope.search = {value: ''};
if (attrs.class)
element.addClass(attrs.class);
if (attrs.source) {
scope.$watch('search.value', function (newValue, oldValue) {
if (newValue.length > attrs.minLength) {
scope.getData({str: newValue}).then(function (results) {
scope.model = results;
});
}
else {
scope.model = [];
}
});
}
scope.clearSearch = function() {
scope.search.value = '';
scope.clearData();
};
},
template: '<div class="item-input-wrapper">' +
'<i class="icon ion-android-search"></i>' +
'<input type="search" placeholder="{{placeholder}}" ng-model="search.value">' +
'<i ng-if="search.value.length > 0" ng-click="clearSearch()" class="icon ion-close"></i>' +
'</div>'
};
})
.controller('AppCtrl', ['$scope', '$http', function($scope, $http) {
$scope.UserLogin = UserLogin;
$scope.UserFolderURL = UserFolderURL;
@ -119,7 +162,7 @@
// };
}])
.controller('AddressBooksCtrl', ['$scope', '$rootScope', '$ionicModal', '$ionicListDelegate', '$ionicActionSheet', 'sgDialog', 'sgAddressBook', function($scope, $rootScope, $ionicModal, $ionicListDelegate, $ionicActionSheet, Dialog, AddressBook) {
.controller('AddressBooksCtrl', ['$scope', '$rootScope', '$ionicModal', '$ionicListDelegate', '$ionicActionSheet', 'sgDialog', 'sgAddressBook', 'sgAclUsers', function($scope, $rootScope, $ionicModal, $ionicListDelegate, $ionicActionSheet, Dialog, AddressBook, sgAclUsers) {
// Initialize with data from template
$scope.addressbooks = AddressBook.$findAll(contactFolders);
$scope.newAddressbook = function() {
@ -141,19 +184,181 @@
$scope.edit = function(addressbook) {
$ionicActionSheet.show({
buttons: [
{ text: l('Rename') }
{ text: l('Rename') },
{ text: l('Sharing') }
],
destructiveText: l('Delete'),
cancelText: l('Cancel'),
buttonClicked: function(index) {
// Rename addressbook
Dialog.prompt(l('Rename addressbook'),
addressbook.name)
if(index == 0) {
// Rename addressbook
Dialog.prompt(l('Rename addressbook'),
addressbook.name)
.then(function(name) {
if (name && name.length > 0) {
addressbook.$rename(name);
}
});
}
else if(index == 1) {
// Build modal editor
$ionicModal.fromTemplateUrl('acl-modal.html', { scope: $scope }).then(function(modal) {
if ($scope.$aclEditorModal) {
$scope.$aclEditorModal.remove();
}
// Variables in scope
$scope.$aclEditorModal = modal;
$scope.addressbook = addressbook;
$scope.AclUsers = new sgAclUsers(addressbook);
var aclUsers = {};
$scope.AclUsers.getUsers().then(function(users) {
$scope.refreshUsers(users);
});
$scope.showDelete = false;
$scope.onGoingSearch = true;
// Variables in javascript
var dirtyObjects = {};
// Local functions
$scope.refreshUsers = function(users) {
$scope.users = [];
$scope.onGoingSearch = true;
angular.forEach(users, function(user){
user.inAclList = true;
user.canSubscribeUser = (user.isSubscribed) ? false : true;
$scope.users.push(user);
aclUsers[user.uid] = user;
})
};
// Function in scope
$scope.closeModal = function() {
$scope.$aclEditorModal.remove();
};
$scope.saveModal = function() {
if(Object.keys(dirtyObjects).length > 0) {
if(dirtyObjects["anonymous"])
{
Dialog.confirm(l("Warning"), l("Any user with an account on this system will be able to access your mailbox \"%{0}\". Are you certain you trust them all?")).then(function(res){
if(res){Dialog.alert("okay!")};
})
}
else if (dirtyObjects["<default>"]) {
Dialog.confirm(l("Warning"), l("Potentially anyone on the Internet will be able to access your calendar \"%{0}\", even if they do not have an account on this system. Is this information suitable for the public Internet?")).then(function(res){
if(res){Dialog.alert("okay!")};
})
}
else {
$scope.AclUsers.saveUsersRights(dirtyObjects);
var usersToSubscribe = [];
angular.forEach(dirtyObjects, function(dirtyObject){
if(dirtyObject.canSubscribeUser && dirtyObject.isSubscribed){
usersToSubscribe.push(dirtyObject.uid);
}
})
$scope.AclUsers.subscribeUsers(usersToSubscribe);
}
}
$scope.$aclEditorModal.remove();
};
$scope.cancelSearch = function() {
$scope.AclUsers.getUsers().then(function(users) {
$scope.refreshUsers(users);
});
};
$scope.toggleDelete = function(boolean) {
$scope.showDelete = boolean;
};
$scope.removeUser = function(user) {
if (user) {
if(dirtyObjects[user.uid])
delete dirtyObjects[user.uid];
delete aclUsers[user.uid];
$scope.AclUsers.removeUser(user);
$scope.AclUsers.getUsers().then(function(users) {
$scope.refreshUsers(users);
});
$scope.userSelected = {};
}
};
$scope.addUser = function (user) {
if (user) {
$scope.AclUsers.addUser(user.uid);
$scope.AclUsers.getUsers().then(function(users) {
$scope.refreshUsers(users);
});
}
else {
// TODO : Write a better msg and add the string inside the .string
Dialog.alert(l('Warning'), l('This user is already added to your AclUsers list'));
}
};
$scope.editUser = function(user) {
if ($scope.userSelected != user){
$scope.userSelected = user;
if (dirtyObjects[$scope.userSelected.uid]) {
// If the user already made changes on the user rights, it is saved inside an object called dirty.
// We preverse these changes untill the user decide to save or discard them.
$scope.userSelected.aclOptions = dirtyObjects[$scope.userSelected.uid].aclOptions;
}
else {
// Otherwise, if it's the first time the user consult the user rights; fetch from server
$scope.AclUsers.openRightsForUserId($scope.userSelected.uid).then(function(userRights) {
$scope.userSelected.aclOptions = userRights;
});
}
}
};
$scope.getContacts = function(value){
$scope.users = [];
$scope.onGoingSearch = false;
return $scope.AclUsers.searchUsers(value).then(function(usersFound) {
angular.forEach(usersFound, function(userFound){
userFound.inAclList = (aclUsers[userFound.uid]) ? true : false;
$scope.users.push(userFound);
})
});
};
$scope.toggleUser = function(user) {
if (user.inAclList) {
if ($scope.isUserShown(user)) {
$scope.shownUser = null;
}
else {
$scope.shownUser = user;
$scope.editUser(user);
}
}
else {
$scope.addUser(user);
}
};
$scope.isUserShown = function(user) {
return $scope.shownUser === user;
};
$scope.markUserAsDirty = function() {
dirtyObjects[$scope.userSelected.uid] = $scope.userSelected;
};
$scope.displayUserRights = function() {
// Does the rights applies on the user/group
return ($scope.userSelected && $scope.userSelected.uid != "anonymous") ? true : false;
};
$scope.displaySubscribeUser = function() {
// Is the user/group available for subscription
return ($scope.userSelected && !($scope.userSelected.uid == "anonymous" || $scope.userSelected.uid == "<default>")) ? true : false;
};
$scope.displayIcon = function(user) {
if (user.inAclList)
return ($scope.isUserShown(user) ? 'ion-ios7-arrow-down' : 'ion-ios7-arrow-right');
else
return 'ion-plus';
}
// Show modal
$scope.$aclEditorModal.show();
});
}
return true;
},
destructiveButtonClicked: function() {