]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1287 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 4 Dec 2007 23:29:51 +0000 (23:29 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 4 Dec 2007 23:29:51 +0000 (23:29 +0000)
31 files changed:
ChangeLog
Main/GNUmakefile
Main/SOGo.m
SoObjects/Appointments/SOGoAppointmentFolder.m
SoObjects/Mailer/SOGoDraftObject.m
SoObjects/Mailer/SOGoMailBodyPart.m
SoObjects/SOGo/SOGoGCSFolder.m
SoObjects/SOGo/SOGoObject.m
SoObjects/SOGo/SOGoUserFolder.h
SoObjects/SOGo/SOGoUserFolder.m
UI/Common/UIxUserRightsEditor.m
UI/Contacts/English.lproj/Localizable.strings
UI/Contacts/French.lproj/Localizable.strings
UI/Contacts/German.lproj/Localizable.strings
UI/Contacts/UIxContactFoldersView.m
UI/MailerUI/UIxMailToSelection.m
UI/SOGoUI/SOGoACLAdvisory.h
UI/SOGoUI/SOGoACLAdvisory.m
UI/Scheduler/English.lproj/Localizable.strings
UI/Scheduler/French.lproj/Localizable.strings
UI/Scheduler/German.lproj/Localizable.strings
UI/Templates/ContactsUI/UIxContactsListView.wox
UI/Templates/SOGoACLEnglishAdditionAdvisory.wox
UI/Templates/SOGoACLEnglishRemovalAdvisory.wox
UI/Templates/SOGoACLFrenchAdditionAdvisory.wox
UI/Templates/SOGoACLFrenchRemovalAdvisory.wox
UI/Templates/SOGoACLGermanAdditionAdvisory.wox
UI/Templates/SOGoACLGermanRemovalAdvisory.wox
UI/WebServerResources/ContactsUI.js
UI/WebServerResources/SchedulerUI.js
UI/WebServerResources/UIxMailToSelection.js

index 9f43ee5ef900be8056acd4f957c12dbc1b255a3f..37b7b4aef41130f0d6f6959b9bd2a85a6c59ec20 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,62 @@
+2007-12-04  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * SoObjects/Appointments/SOGoAppointmentFolder.m
+       ([SOGoAppointmentFolder -davCalendarQuery:queryContext]): fixed a leak.
+
+       * SoObjects/SOGo/SOGoGCSFolder.m ([SOGoGCSFolder -davSubscribe:localContext])
+       ([SOGoGCSFolder -davUnsubscribe:localContext]): subscribe an
+       unsubscribe from DAV-based accesses. We could be compatible with
+       Microsoft's extensions but we have no need for a "subcription id",
+       so we implement our own.
+
+       * SoObjects/SOGo/SOGoObject.m ([SOGoObject
+       -POSTAction:localContext]): new method to intercept DAV POSTs,
+       which we now use to implement certain custom commands such as
+       "subscribe" and "unsubscribe".
+
+       * SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder
+       -davNamespaces]): declare the
+       "urn:inverse:params:xml:ns:inverse-dav" xml ns.
+       ([SOGoUserFolder -foldersOfType:folderTypeforUID:uid]): new method
+       designed to replace the UIxContactFoldersView.m mechanism for
+       displaying folders to subcribe to, as a common code base for both
+       Web and DAV-based subscriptions.
+       ([SOGoUserFolder -foldersOfType:typematchingUID:uid]): same as
+       above.
+       ([SOGoUserFolder -davCollectionQuery:queryContext]): new method
+       that implement a custom DAV-based protocol query for querying
+       folder based on specified attributes.
+
+2007-12-03  Ludovic Marcotte <ludovic@inverse.ca>
+
+       * Main/NSException+Stacktrace.{h,m} - new files
+       to handle automatic stack trace generation
+       upon an uncaught exception.
+
+       * Updated the templates and Localizable.string files
+       to fix typos and add new strings.
+
+       * SoObjects/Mailer/SOGoMailBodyPart.m
+       Fixed attachment retreival when the first character
+       is a digit.
+
+       * SoObjects/SOGo/SOGoGCSFolder.m
+       Fixed the sending of emails when folders are created/removed.
+       Notifications are sent if the defaults SOGoFoldersSendEMailNotifications
+       is set to YES.
+
+       * UI/Common/UIxUserRightsEditor.m
+       * UI/Templates/SOGoACLEnglishModificationAdvisory.wox
+       * UI/Templates/SOGoACLFrenchModificationAdvisory.wox
+       * UI/Templates/SOGoACLGermanModificationAdvisory.wox
+       Added the capabilities to email notifications when ACLs have
+       changed on a DAV collection or an IMAP mailbox. Also added
+       new templates (3 .wox) to deal with this. 
+
+       * UI/WebServerResources/ContactsUI.js
+       UI/WebServerResources/SchedulerUI.js
+       Added warnings on operations w/o selection.
+
 2007-11-30  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder
index 53a8adcfe7f14e6878ffee2b54f17c664a51ddb4..cac596f5de6f12624d0f977368de4142c9420510 100644 (file)
@@ -6,7 +6,7 @@ include ../Version
 include ./Version
 
 ADDITIONAL_INCLUDE_DIRS += -I../SOPE/
-ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/
+ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/ -lbfd
 
 SOGOD     = sogod-$(MAJOR_VERSION).$(MINOR_VERSION)
 TOOL_NAME = $(SOGOD)
@@ -17,6 +17,7 @@ all::
 
 $(SOGOD)_OBJC_FILES += \
        sogod.m                 \
+       NSException+Stacktrace.m\
        SOGo.m                  \
        SOGoProductLoader.m     \
        build.m
index 32be4cedd250a429633e0d73bd5241a3799dc212..6bdb86e3246fe502e70591459342b3b6b71b5c29 100644 (file)
@@ -51,6 +51,7 @@
 
 #import "build.h"
 #import "SOGoProductLoader.h"
+#import "NSException+Stacktrace.h"
 
 @interface SOGo : SoApplication
 {
index f4753e7c362e5941bfc199e6b45a887e43068b93..6deb3e3c5aed0593ff36c861fb7c8aae1b263eee 100644 (file)
@@ -238,8 +238,7 @@ static NSNumber   *sharedYes = nil;
   NSMutableArray *filters;
   NSDictionary *filter;
 
-  filters = [NSMutableArray new];
-
+  filters = [NSMutableArray array];
   children = [[parentNode getElementsByTagName: @"comp-filter"]
               objectEnumerator];
   node = [children nextObject];
@@ -268,8 +267,7 @@ static NSNumber   *sharedYes = nil;
   max = [filters count];
   for (count = 0; count < max; count++)
     {
-#warning huh? why not objectAtIndex: count?
-      currentFilter = [filters objectAtIndex: 0];
+      currentFilter = [filters objectAtIndex: count];
       apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
                    to: [currentFilter objectForKey: @"end"]
                   title: [currentFilter objectForKey: @"title"]
index 0ebb3432067e1a8654f367da59506561602ff784..4aac23ded836f8839415d92079a94c861cb2ff0b 100644 (file)
@@ -467,7 +467,6 @@ static BOOL        showTextAttachmentsInline  = NO;
   [_info setObject: to forKey: @"to"];
 
   /* CC processing if we reply-to-all: add all 'to' and 'cc'  */
-
   if (_replyToAll)
     {
       to = [NSMutableArray new];
@@ -498,6 +497,17 @@ static BOOL        showTextAttachmentsInline  = NO;
        [self _addEMailsOfAddresses: [_envelope from] toArray: to];
     }
 
+  /* If we have no To but we have Cc recipients, let's move the Cc
+     to the To bucket... */
+  if ([[_info objectForKey: @"to"] count] == 0 && [_info objectForKey: @"cc"])
+    {
+      id o;
+
+      o = [_info objectForKey: @"cc"];
+      [_info setObject: o  forKey: @"to"];
+      [_info removeObjectForKey: @"cc"];
+    }
+
   [allRecipients release];
   [addrs release];
 }
index c3bcf78dbb280d7f30fd72f906f68950d5906eb6..c60f34b9b179c6d325a2d8f64748796039081a87 100644 (file)
@@ -144,6 +144,26 @@ static BOOL debugOn = NO;
   return [clazz objectWithName: _key inContainer: self];
 }
 
+/* We overwrite the super's class method in order to make sure
+   we aren't dealing with our actual filename as the _key. That
+   could lead to problems if we weren't doing this as our filename
+   could start with a digit, leading to a wrong assumption in
+   the super class
+*/
+- (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx
+{
+  NSString *s;
+  
+  s = [[[self partInfo] objectForKey: @"parameterList"] objectForKey: @"name"];
+
+  if (!s)
+    s = [[[[self partInfo] objectForKey: @"disposition"] objectForKey: @"parameterList"] objectForKey: @"filename"];
+  
+  if (s && [s isEqualToString: _key]) return NO;
+
+  return [super isBodyPartKey: _key  inContext: _ctx];
+}
+
 - (id) lookupName: (NSString *) _key
        inContext: (id) _ctx
          acquire: (BOOL) _flag
index 69e2a35614a65fd86413972b5f8f55405b3e3924..b1f0e6df4c44d33365e83fe852f1793afd43ef66 100644 (file)
 #import <Foundation/NSException.h>
 #import <Foundation/NSKeyValueCoding.h>
 #import <Foundation/NSURL.h>
+#import <Foundation/NSUserDefaults.h>
 
 #import <NGObjWeb/NSException+HTTP.h>
 #import <NGObjWeb/SoObject.h>
 #import <NGObjWeb/SoObject+SoDAV.h>
 #import <NGObjWeb/WOContext+SoObjects.h>
 #import <NGObjWeb/WOApplication.h>
+#import <NGObjWeb/WOResponse.h>
 #import <NGExtensions/NSNull+misc.h>
 #import <NGExtensions/NSObject+Logs.h>
 #import <EOControl/EOQualifier.h>
 #import "SOGoGCSFolder.h"
 
 static NSString *defaultUserID = @"<default>";
+static BOOL sendFolderAdvisories = NO;
 
 @implementation SOGoGCSFolder
 
++ (void) initialize
+{
+  NSUserDefaults *ud;
+
+  ud = [NSUserDefaults standardUserDefaults];
+  sendFolderAdvisories = [ud boolForKey: @"SOGoFoldersSendEMailNotifications"];
+}
+
 + (id) folderWithSubscriptionReference: (NSString *) reference
                           inContainer: (id) aContainer
 {
@@ -254,8 +265,6 @@ static NSString *defaultUserID = @"<default>";
   [page send];
 }
 
-//   if (!result) [self sendFolderAdvisoryTemplate: @"Addition"];
-
 - (BOOL) create
 {
   NSException *result;
@@ -264,6 +273,8 @@ static NSString *defaultUserID = @"<default>";
                                 withName: displayName
                                  atPath: ocsPath];
 
+  if (!result && sendFolderAdvisories) [self sendFolderAdvisoryTemplate: @"Addition"];
+
   return (result == nil);
 }
 
@@ -280,11 +291,11 @@ static NSString *defaultUserID = @"<default>";
   else
     error = [[self folderManager] deleteFolderAtPath: ocsPath];
 
+  if (!error && sendFolderAdvisories) [self sendFolderAdvisoryTemplate: @"Removal"];
+
   return error;
 }
 
-//   if (!error) [self sendFolderAdvisoryTemplate: @"Removal"];
-
 - (void) renameTo: (NSString *) newName
 {
   GCSChannelManager *cm;
@@ -423,6 +434,75 @@ static NSString *defaultUserID = @"<default>";
   return names;
 }
 
+#warning this code should be cleaned up
+#warning this code is a dup of UIxFolderActions,\
+         we should remove the methods there instead
+- (WOResponse *) _subscribe: (BOOL) reallyDo
+                 inContext: (WOContext *) localContext
+{
+  WOResponse *response;
+  NSMutableArray *folderSubscription;
+  NSString *subscriptionPointer, *baseFolder, *folder;
+  SOGoUser *activeUser;
+  NSUserDefaults *ud;
+  NSArray *realFolderPath;
+  NSMutableDictionary *moduleSettings;
+
+  activeUser = [localContext activeUser];
+  ud = [activeUser userSettings];
+  baseFolder = [container nameInContainer];
+  moduleSettings = [ud objectForKey: baseFolder];
+
+  response = [localContext response];
+  if ([owner isEqualToString: [activeUser login]])
+    {
+      [response setStatus: 403];
+      [response appendContentString:
+                @"You cannot (un)subscribe to a folder that you own!"];
+    }
+  else
+    {
+      folderSubscription
+       = [moduleSettings objectForKey: @"SubscribedFolders"];
+      if (!(folderSubscription
+           && [folderSubscription isKindOfClass: [NSMutableArray class]]))
+       {
+         folderSubscription = [NSMutableArray array];
+         [moduleSettings setObject: folderSubscription
+                         forKey: @"SubscribedFolders"];
+       }
+
+      realFolderPath = [nameInContainer componentsSeparatedByString: @"_"];
+      if ([realFolderPath count] > 1)
+       folder = [realFolderPath objectAtIndex: 1];
+      else
+       folder = [realFolderPath objectAtIndex: 0];
+
+      subscriptionPointer = [NSString stringWithFormat: @"%@:%@/%@",
+                                     owner, baseFolder, folder];
+      if (reallyDo)
+       [folderSubscription addObjectUniquely: subscriptionPointer];
+      else
+       [folderSubscription removeObject: subscriptionPointer];
+
+      [ud synchronize];
+
+      [response setStatus: 204];
+    }
+
+  return response;
+}
+
+- (id <WOActionResults>) davSubscribe: (WOContext *) localContext
+{
+  return [self _subscribe: YES inContext: localContext];
+}
+
+- (id <WOActionResults>) davUnsubscribe: (WOContext *) localContext
+{
+  return [self _subscribe: NO inContext: localContext];
+}
+
 /* acls as a container */
 
 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
index ff29a3c463f236cc076969c4dbe2677372285126..a9a4900392d705fcd85c56590d117a38d2d520f2 100644 (file)
@@ -46,6 +46,7 @@
 #import <NGObjWeb/NSException+HTTP.h>
 #import <NGExtensions/NSObject+Logs.h>
 #import <NGExtensions/NSString+misc.h>
+#import <DOM/DOMProtocols.h>
 #import <NGCards/NSDictionary+NGCards.h>
 #import <UI/SOGoUI/SOGoACLAdvisory.h>
 
@@ -646,6 +647,42 @@ static BOOL kontactGroupDAV = YES;
   return response;
 }
 
+- (NSString *) _parseXMLCommand: (id <DOMDocument>) document
+{
+  NSString *command;
+
+  command = [[document firstChild] nodeName];
+
+  return [NSString stringWithFormat: @"%@:", command];
+}
+
+- (id) POSTAction: (id) localContext
+{
+  id obj;
+  NSString *cType, *command;
+  id <DOMDocument> document;
+  SEL commandSel;
+  WORequest *rq;
+
+  obj = nil;
+
+  rq = [localContext request];
+  if ([rq isSoWebDAVRequest])
+    {
+      cType = [rq headerForKey: @"content-type"];
+      if ([cType isEqualToString: @"application/xml"])
+       {
+         document = [rq contentAsDOMDocument];
+         command = [[self _parseXMLCommand: document] davMethodToObjC];
+         commandSel = NSSelectorFromString (command);
+         if ([self respondsToSelector: commandSel])
+           obj = [self performSelector: commandSel withObject: localContext];
+       }
+    }
+
+  return obj;
+}
+
 - (id) GETAction: (id) localContext
 {
   // TODO: I guess this should really be done by SOPE (redirect to
index 970f5e8430bd716e45500849465fc0f2ed56ab45..ccb30545efcd90e01b9df650bc6265248d04849a 100644 (file)
@@ -36,6 +36,8 @@
     /SOGo/so/znek/Calendar
 */
 
+@class NSArray;
+@class NSDictionary;
 @class NSString;
 @class WOContext;
 
 
 - (NSString *) ownerInContext: (WOContext *) _ctx;
 
+- (NSArray *) foldersOfType: (NSString *) folderType
+                    forUID: (NSString *) uid;
+- (NSDictionary *) foldersOfType: (NSString *) type
+                    matchingUID: (NSString *) uid;
+
 /* TODO: not implemented, bad bad */
 // - (id)lookupFreeBusyObject;
 
index 035cbcb47e017156105f438a455ebbed81f94023..688d3f04358f04632137abe6248d50638fe0c37f 100644 (file)
 
 #import <Foundation/NSArray.h>
 #import <Foundation/NSString.h>
+#import <Foundation/NSURL.h>
 
 #import <NGObjWeb/NSException+HTTP.h>
 #import <NGObjWeb/SoClassSecurityInfo.h>
+#import <NGObjWeb/SoSecurityManager.h>
 #import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGObjWeb/WOResponse.h>
 
 #import <NGExtensions/NSObject+Logs.h>
+#import <NGExtensions/NSString+misc.h>
+#import <DOM/DOMDocument.h>
+#import <DOM/DOMNode.h>
+#import <DOM/DOMProtocols.h>
+#import <SaxObjC/SaxObjC.h>
+#import <SaxObjC/XMLNamespaces.h>
 
 #import <Appointments/SOGoAppointmentFolders.h>
 #import <Appointments/SOGoFreeBusyObject.h>
 #import <Contacts/SOGoContactFolders.h>
 #import <Mailer/SOGoMailAccounts.h>
 
+#import "NSDictionary+Utilities.h"
+#import "LDAPUserManager.h"
 #import "SOGoPermissions.h"
 #import "SOGoUser.h"
 
   return self;
 }
 
+- (NSArray *) davNamespaces
+{
+  return [NSArray arrayWithObject: @"urn:inverse:params:xml:ns:inverse-dav"];
+}
+
+- (NSDictionary *) _parseCollectionFilters: (id <DOMDocument>) parentNode
+{
+  NSEnumerator *children;
+  NGDOMNode *node;
+  NSMutableDictionary *filter;
+  NSString *componentName;
+    
+  filter = [NSMutableDictionary dictionaryWithCapacity: 2];
+  children = [[parentNode getElementsByTagName: @"prop-match"]
+              objectEnumerator];
+  while ((node = [children nextObject]))
+    {
+      componentName = [[node attribute: @"name"] lowercaseString];
+      [filter setObject: [node textValue] forKey: componentName];
+    }
+
+  return filter;
+}
+
+#warning UIxContactsFoldersView should use these methods\
+         instead from now on...
+
+- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder
+{
+  NSMutableArray *folders;
+  NSEnumerator *subfolders;
+  SOGoFolder *currentFolder;
+  NSString *folderName;
+  NSMutableDictionary *currentDictionary;
+  SoSecurityManager *securityManager;
+
+  securityManager = [SoSecurityManager sharedSecurityManager];
+   
+  folders = [NSMutableArray array];
+
+  subfolders = [[parentFolder subFolders] objectEnumerator];
+  while ((currentFolder = [subfolders nextObject]))
+    {
+      if (![securityManager validatePermission: SOGoPerm_AccessObject
+                           onObject: currentFolder inContext: context])
+       {
+         folderName = [NSString stringWithFormat: @"/%@/%@",
+                                [parentFolder nameInContainer],
+                                [currentFolder nameInContainer]];
+         currentDictionary
+           = [NSMutableDictionary dictionaryWithCapacity: 3];
+         [currentDictionary setObject: [currentFolder displayName]
+                            forKey: @"displayName"];
+         [currentDictionary setObject: folderName forKey: @"name"];
+         [currentDictionary setObject: [currentFolder folderType]
+                            forKey: @"type"];
+         [folders addObject: currentDictionary];
+       }
+    }
+
+  return folders;
+}
+
+- (NSArray *) foldersOfType: (NSString *) folderType
+                    forUID: (NSString *) uid
+{
+  NSObject *userFolder;
+  SOGoParentFolder *parentFolder;
+  NSMutableArray *folders;
+
+  folders = [NSMutableArray array];
+
+  userFolder = [container lookupName: uid inContext: context acquire: NO];
+
+  /* FIXME: should be moved in the SOGo* classes. Maybe by having a SOGoFolderManager. */
+  if ([folderType length] == 0 || [folderType isEqualToString: @"calendar"])
+    {
+      parentFolder = [userFolder lookupName: @"Calendar"
+                                inContext: context acquire: NO];
+      [folders
+       addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]];
+    }
+  if ([folderType length] == 0 || [folderType isEqualToString: @"contact"])
+    {
+      parentFolder = [userFolder lookupName: @"Contacts"
+                                inContext: context acquire: NO];
+      [folders
+       addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]];
+    }
+
+  return folders;
+}
+
+- (NSDictionary *) foldersOfType: (NSString *) type
+                    matchingUID: (NSString *) uid
+{
+  NSArray *contacts, *folders;
+  NSEnumerator *enumerator;
+  NSDictionary *contact;
+  NSMutableDictionary *results;
+
+  results = [NSMutableDictionary dictionary];
+
+  contacts
+    = [[LDAPUserManager sharedUserManager] fetchContactsMatching: uid];
+  enumerator = [contacts objectEnumerator];
+  while ((contact = [enumerator nextObject]))
+    {
+      uid = [contact objectForKey: @"c_uid"];
+      folders = [self foldersOfType: type
+                     forUID: [contact objectForKey: @"c_uid"]];
+      [results setObject: folders forKey: contact];
+    }
+
+  return results;
+}
+
+- (NSString *) _baseDAVURLWithSuffix: (NSString *) suffix
+{
+  NSURL *prefixURL;
+
+  prefixURL = [NSURL URLWithString: [NSString stringWithFormat: @"../%@", suffix]
+                    relativeToURL: [self davURL]];
+
+  return [[prefixURL standardizedURL] absoluteString];
+}
+
+- (void) _appendFolders: (NSDictionary *) users
+            toResponse: (WOResponse *) r
+{
+  NSDictionary *currentContact, *currentFolder;
+  NSEnumerator *keys, *folders;
+  NSString *baseHREF, *data;
+
+  baseHREF = [self _baseDAVURLWithSuffix: @"./"];
+
+  keys = [[users allKeys] objectEnumerator];
+  while ((currentContact = [keys nextObject]))
+    {
+      folders = [[users objectForKey: currentContact] objectEnumerator];
+      while ((currentFolder = [folders nextObject]))
+       {
+         [r appendContentString: @"<D:response><D:href>"];
+         data = [NSString stringWithFormat: @"%@%@%@", baseHREF,
+                          [currentContact objectForKey: @"c_uid"],
+                          [currentFolder objectForKey: @"name"]];
+         [r appendContentString: data];
+         [r appendContentString: @"</D:href><D:propstat>"];
+         [r appendContentString: @"<D:status>HTTP/1.1 200 OK</D:status>"];
+         [r appendContentString: @"</D:propstat><D:owner>"];
+         data = [NSString stringWithFormat: @"%@users/%@", baseHREF,
+                          [currentContact objectForKey: @"c_uid"]];
+         [r appendContentString: data];
+         [r appendContentString: @"</D:owner><ownerdisplayname>"];
+         data = [currentContact keysWithFormat: @"%{cn} <%{c_email}>"];
+         [r appendContentString: [data stringByEscapingXMLString]];
+         [r appendContentString: @"</ownerdisplayname><D:displayname>"];
+         data = [currentFolder objectForKey: @"displayName"];
+         [r appendContentString: [data stringByEscapingXMLString]];
+         [r appendContentString: @"</D:displayname></D:response>\r\n"];
+       }
+    }
+}
+
+- (void) _appendCollectionsMatchingFilter: (NSDictionary *) filter
+                              toResponse: (WOResponse *) r
+{
+  NSString *prefix, *queryOwner, *uid;
+  NSDictionary *folders;
+
+  prefix = [self _baseDAVURLWithSuffix: @"users/"];
+  queryOwner = [filter objectForKey: @"owner"];
+  if ([queryOwner hasPrefix: prefix])
+    {
+      uid = [queryOwner substringFromIndex: [prefix length]];
+      folders = [self foldersOfType: [filter objectForKey: @"resource-type"]
+                     matchingUID: uid];
+      [self _appendFolders: folders toResponse: r];
+    }
+}
+
+- (id) davCollectionQuery: (id) queryContext
+{
+  WOResponse *r;
+  NSDictionary *filter;
+  id <DOMDocument> document;
+
+  r = [context response];
+  [r setStatus: 207];
+  [r setContentEncoding: NSUTF8StringEncoding];
+  [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
+  [r setHeader: @"no-cache" forKey: @"pragma"];
+  [r setHeader: @"no-cache" forKey: @"cache-control"];
+  [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
+  [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
+     @" xmlns=\"urn:ietf:params:xml:ns:inverse-dav\">\r\n"];
+
+  document = [[context request] contentAsDOMDocument];
+  filter = [self _parseCollectionFilters: document];
+  [self _appendCollectionsMatchingFilter: filter toResponse: r];
+
+  [r appendContentString:@"</D:multistatus>\r\n"];
+
+  return r;
+}
+
 // - (SOGoGroupsFolder *) lookupGroupsFolder
 // {
 //   return [self lookupName: @"Groups" inContext: nil acquire: NO];
index ad674f63ec1f010ba990070f9f6a6b2919607ccc..d447ae84497220be582dfd25095519f757bb7e77 100644 (file)
  */
 
 #import <NGObjWeb/NSException+HTTP.h>
+#import <NGObjWeb/WOApplication.h>
 #import <NGObjWeb/WOResponse.h>
 #import <NGObjWeb/WORequest.h>
 #import <SoObjects/SOGo/LDAPUserManager.h>
 #import <SoObjects/SOGo/SOGoPermissions.h>
 #import <SoObjects/SOGo/SOGoObject.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+#import <UI/SOGoUI/SOGoACLAdvisory.h>
+#import <Foundation/NSUserDefaults.h>
 
 #import "UIxUserRightsEditor.h"
 
+static BOOL sendACLAdvisories = NO;
+
 @implementation UIxUserRightsEditor
 
++ (void) initialize
+{
+  NSUserDefaults *ud;
+
+  ud = [NSUserDefaults standardUserDefaults];
+  sendACLAdvisories = [ud boolForKey: @"SOGoACLsSendEMailNotifications"];
+}
+
 - (id) init
 {
   if ((self = [super init]))
   return response;
 }
 
+- (void) sendACLAdvisoryTemplateForObject: (id) theObject
+{
+  NSString *language, *pageName;
+  SOGoUser *user;
+  SOGoACLAdvisory *page;
+  WOApplication *app;
+
+  user = [SOGoUser userWithLogin: uid roles: nil];
+  language = [user language];
+  pageName = [NSString stringWithFormat: @"SOGoACL%@ModificationAdvisory",
+                      language];
+
+  app = [WOApplication application];
+  page = [app pageWithName: pageName inContext: context];
+  [page setACLObject: theObject];
+  [page setRecipientUID: uid];
+  [page send];
+}
+
 - (id <WOActionResults>) saveUserRightsAction
 {
   id <WOActionResults> response;
                            reason: @"No such user."];
   else
     {
+      NSArray *o;
+
+      o = [NSArray arrayWithArray: userRights];
+
       [self updateRights];
       [[self clientObject] setRoles: userRights forUser: uid];
+
+      if (![o isEqualToArray: userRights] && sendACLAdvisories)
+       {
+         [self sendACLAdvisoryTemplateForObject: [self clientObject]];
+       } 
+      
       response = [self jsCloseWithRefreshMethod: nil];
     }
 
index f1dbba2eabac6cad289a45758e1326b730452a09..9d17ab3ea93227794a9ef17b17ab744896a05a96 100644 (file)
@@ -31,6 +31,7 @@
 "edit" = "edit";
 "invalidemailwarn" = "invalidemailwarn";
 "new" = "new";
+"Preferred Phone"   = "Preferred Phone";
 
 /* Folders */
 "Personal Address Book" = "Personal Address Book";
 
 "The selected contact has no email address."
 = "The selected contact has no email address.";
+
+"Please select a contact." = "Please select a contact.";
index cb2df47d536d9f460e8f8506bbad4af5d346ae5a..4854e4855a3bf442774d1f26ea8b1251fc8fc183 100644 (file)
@@ -38,6 +38,7 @@
 "edit"              = "Éditer";
 "invalidemailwarn"  = "Champ de l'email invalide, continuer quand même ?";
 "new"               = "Nouveau";
+"Preferred Phone"   = "Numéro préféré";
 
 /* Folders */
 "Personal Address Book" = "Carnet d'adresses personnel";
 "The selected contact has no email address."
 = "Cette personne n'a pas d'adresse courriel.";
 
+"Please select a contact." = "Veuillez sélectionner un contact..";
\ No newline at end of file
index b220e74f215b85e5a27d29b89cb4bd5287675344..c5ffe0bcf475dfcee9c47bca77499aac3e4550e3 100644 (file)
@@ -38,6 +38,7 @@
 "edit"              = "Éditer";
 "invalidemailwarn"  = "Champ de l'email invalide, continuer quand même ?";
 "new"               = "Neu";
+"Preferred Phone"   = "FIXME";
 
 /* Folders */
 "Personal Address Book" = "Personal Address Book";
 
 "The selected contact has no email address."
 = "The selected contact has no email address.";
+
+"Please select a contact." = "Bitte einen Kontakt auswählen.";
\ No newline at end of file
index 9d1f87a1af219ea529f7549565a7fa1f6ee26fe4..9605ed56edffccf820b05f36ed223329cc861136 100644 (file)
   SoSecurityManager *securityManager;
 
   securityManager = [SoSecurityManager sharedSecurityManager];
-   
+
 //   return (([securityManager validatePermission: SoPerm_AccessContentsInformation
 //                             onObject: contactFolder
 //                             inContext: context] == nil)
 
       responseString = [NSMutableString new];
       contacts = [results objectEnumerator];
-      contact = [contacts nextObject];
-      while (contact)
+      while ((contact = [contacts nextObject]))
        {
          uid = [contact objectForKey: @"c_uid"];
          folders = [self _foldersForUID: uid ofType: folderType];
                          [contact objectForKey: @"cn"],
                          [contact objectForKey: @"c_email"],
                          foldersString];
-         contact = [contacts nextObject];
        }
       [response appendContentString: responseString];
       [responseString release];
index c241105eb5efeadb49895c02631c0670b17bdb81..8e53fa40a50358f1971815355b955cd9262ee9a6 100644 (file)
@@ -86,7 +86,7 @@ static NSArray *headers = nil;
 - (id) init
 {
   if ((self = [super init]))
-    currentIndex = 0;
+    currentIndex = -1;
 
   return self;
 }
@@ -213,13 +213,23 @@ static NSArray *headers = nil;
 
 /* identifiers */
 
+- (NSString *) nextId
+{
+  currentIndex++;
+
+  return @"";
+}
+
 - (NSString *) currentRowId
 {
+  [self nextId];
+  
   return [NSString stringWithFormat: @"row_%d", currentIndex];
 }
 
 - (NSString *) currentPopUpId
 {
+  
   return [NSString stringWithFormat: @"popup_%d", currentIndex];
 }
 
@@ -228,13 +238,6 @@ static NSArray *headers = nil;
   return [NSString stringWithFormat: @"addr_%d", currentIndex];
 }
 
-- (NSString *) nextId
-{
-  currentIndex++;
-
-  return @"";
-}
-
 /* handling requests */
 
 - (void) _fillAddresses: (NSMutableArray *) addresses
@@ -326,13 +329,4 @@ static NSArray *headers = nil;
   return [to count] + [cc count] + [bcc count];
 }
 
-- (int) currentIndex
-{
-  int count;
-
-  count = [self addressCount];
-
-  return count > 0 ? count - 1 : 0;
-}
-
 @end /* UIxMailToSelection */
index 762b89a1d05dd78719890505ddbf75f977f9b5d6..d991dbad03c205d9513f644c8d3a2e9fdcdc1fef 100644 (file)
 @interface SOGoACLGermanAdditionAdvisory : SOGoACLAdditionAdvisory
 @end
 
+@interface SOGoACLEnglishModificationAdvisory : SOGoACLAdditionAdvisory
+@end
+
+@interface SOGoACLFrenchModificationAdvisory : SOGoACLAdditionAdvisory
+@end
+
+@interface SOGoACLGermanModificationAdvisory : SOGoACLAdditionAdvisory
+@end
+
 @interface SOGoACLEnglishRemovalAdvisory : SOGoACLRemovalAdvisory
 @end
 
index b52ebcc193f60b97e151133b8256522a46dddec0..97994ae3c7de013cb6e7eab29f98bc52d4f8c107 100644 (file)
   return url;
 }
 
+- (NSString *) httpFolderURL
+{
+  NSString *absoluteString;
+  NSMutableString *url;
+
+#warning the url returned by SOGoMail may be empty, we need to handle that
+  absoluteString = [[aclObject soURL] absoluteString];
+  url = [NSMutableString stringWithString: absoluteString];
+
+  if (![url hasSuffix: @"/"])
+    [url appendString: @"/"];
+
+  return url;
+}
+
 - (NSString *) resourceName
 {
   return [aclObject nameInContainer];
 
 @end
 
+@implementation SOGoACLModificationAdvisory
+
+- (NSString *) aclMethod { return @"modify"; }
+
+@end
+
 @implementation SOGoACLEnglishAdditionAdvisory
 @end
 
 @implementation SOGoACLGermanAdditionAdvisory
 @end
 
+@implementation SOGoACLEnglishModificationAdvisory
+@end
+
+@implementation SOGoACLFrenchModificationAdvisory
+@end
+
+@implementation SOGoACLGermanModificationAdvisory
+@end
+
 @implementation SOGoACLEnglishRemovalAdvisory
 @end
 
index 494b6d747e101b4f2c6529c7dfb2e7269470800f..c6e1c5b0bde20b533b6addf85898a49aaca62b52 100644 (file)
@@ -423,3 +423,5 @@ vtodo_class2 = "(Confidential task)";
 
 "closeThisWindowMessage" = "Thank you! You may now close this window or view your ";
 "Multicolumn Day View" = "Multicolumn Day View";
+
+"Please select an event or a task." = "Please select an event or a task.";
\ No newline at end of file
index 1688824d978b236020b397a2be882aafc8d83366..725f035841ff47cd6426f53760b25733f90c5172 100644 (file)
@@ -422,3 +422,5 @@ vtodo_class2 = "(Tâche confidentielle)";
 
 "closeThisWindowMessage" = "Merci! Vous pouvez maintenant fermer cette fenêtre ou consulter votre ";
 "Multicolumn Day View" = "Multicolonne";
+
+"Please select an event or a task." = "Veuillez sélectionner un événement ou une tâche.";
\ No newline at end of file
index 1f65637c7d0ad0744649dac6fe360000856c71e8..15ab314545721bab705c1421c904825a409b37bf 100644 (file)
@@ -411,3 +411,5 @@ vtodo_class2 = "(Confidential task)";
 
 "closeThisWindowMessage" = "Vielen Dank! Sie können dieses Fenster jetzt schließen.";
 "Multicolumn Day View" = "Multicolonne";
+
+"Please select an event or a task." = "Veuillez sélectionner un événement ou une tâche.";
\ No newline at end of file
index efc8c859fe14d56d7efd93f0a3a7f601aa21467b..0593bb3fb9a87186a0e7352dfcd8b9725a730b11 100644 (file)
@@ -23,7 +23,7 @@
            ><td class="tbtv_headercell sortableTableHeader" id="orgHeader"
            ><var:string label:value="Organization" /></td
            ><td class="tbtv_headercell sortableTableHeader" id="phoneHeader"
-           ><var:string label:value="Work Phone" /></td
+           ><var:string label:value="Preferred Phone" /></td
            ></tr>
       </thead>
       <tbody>
index b15cd0da6ce4f72c17693f8a182ca09c2d31163d..250cb5189c940f69b3f23d3c2524b97f53e01dd5 100644 (file)
 
 <var:if condition="isBody">
 <var:string value="currentUserName"/> has added you to the access list for his '<var:string value="resourceName"/>' folder.
+
 You can subscribe directly to that folder by following this link:
     <var:string value="httpAdvisoryURL"/>subscribe?mail-invitation=YES
 
 Otherwise, you will be able to subscribe later from the SOGo web interface.
+
+You can also access this resource remotely using the following URL:
+
+<var:string value="httpFolderURL"/>
 </var:if>
 
 </container>
index eff944b00c7a93bb10677e433685e63a1dfdff30..6a4009357cf9e9856e186c428219b6d5167a3da1 100644 (file)
 
 <var:if condition="isBody">
 <var:string value="currentUserName"/> has removed you from the access list for his '<var:string value="resourceName"/>' folder.
+
 You can unsubscribe directly to that folder by following this link:
     <var:string value="httpAdvisoryURL"/>unsubscribe?mail-invitation=YES
 
 Otherwise, you will be able to unsubscribe later from the SOGo web interface.
+
+You can also no longer access this resource using the following URL:
+
+<var:string value="httpFolderURL"/>
 </var:if>
 
 </container>
index 4686ccf75bb0299e324c737a986678fbd73fe16a..3166ecc23dd7672fa23f863a9cfdd54a9f74f06e 100644 (file)
@@ -8,15 +8,20 @@
   xmlns:label="OGo:label">
 
 <var:if condition="isSubject">
-    <var:string value="currentUserName"/> vous a ajouté
+    <var:string value="currentUserName"/> a modifié les droits
 </var:if>
 
 <var:if condition="isBody">
-<var:string value="currentUserName"/> vous a ajouté a sa liste de permission pour son dossier '<var:string value="resourceName"/>'.
-Vous pouvez vous inscrire directement a ce dossier en cliquant sur le lien suivant:
+<var:string value="currentUserName"/> vous a ajouté à sa liste de permission pour son dossier '<var:string value="resourceName"/>'.
+
+Vous pouvez vous inscrire directement à ce dossier en cliquant sur le lien suivant:
     <var:string value="httpAdvisoryURL"/>unsubscribe?mail-invitation=YES
 
 Autrement, il vous sera toujours possible de vous inscrire plus tard via l'interface web de SOGo.
+
+De plus, vous pouvez aussi accéder au dossier en utilisant le lien suivant:
+
+<var:string value="httpFolderURL"/>
 </var:if>
 
 </container>
index 7679866abb88cdcc26ea309819d7c7a4f1b3fbe0..497a54eaf2dd0252707e21492c598ba9338a4c63 100644 (file)
 
 <var:if condition="isBody">
 <var:string value="currentUserName"/> vous a enlevé de sa liste de permission pour son dossier '<var:string value="resourceName"/>'.
+
 Vous pouvez vous désinscrire directement en cliquant sur le lien suivant:
     <var:string value="httpAdvisoryURL"/>unsubscribe?mail-invitation=YES
 
 Autrement, il vous sera toujours possible de vous désinscrire plus tard via l'interface web de SOGo.
+
+De plus, vous ne pouvez plus accéder au dossier en utilisant le lien suivant:
+
+<var:string value="httpFolderURL"/>
 </var:if>
 
 </container>
index 313f9a448aaf7cd22c7d52c0240d1689d79b4f94..d53f17b879edc556bdb0d9123a4235a8154ad36a 100644 (file)
@@ -13,6 +13,7 @@
 
 <var:if condition="isBody">
 <var:string value="currentUserName"/> hat Ihnen den Zugang zu seinem Ordner '<var:string value="resourceName"/>' erlaubt.
+
 Folgende URL erlaubt Ihnen, sich sofort an diesem Ordner zu abonnieren:
     <var:string value="httpAdvisoryURL"/>unsubscribe?mail-invitation=YES
 
index 9638f657cff561b5f1878eac6dea0a3035320340..9e02af19b18aeb5382e1475a85f7af38c28aa84c 100644 (file)
@@ -13,6 +13,7 @@
 
 <var:if condition="isBody">
 <var:string value="currentUserName"/> erlaubt Ihnen nicht mehr den Zugang zu seinem Order '<var:string value="resourceName"/>'.
+
 Sie können sich sofort an folgender URL von diesem Order des-abonnieren:
     <var:string value="httpAdvisoryURL"/>unsubscribe?mail-invitation=YES
 
index 8b449799ce4ffa3e12c4fc62555f7369f027a3f8..087d4f84847c1bb79c6c2b292fb28bc3f5a6fc4d 100644 (file)
@@ -310,20 +310,20 @@ function onMenuEditContact(event) {
 }
 
 function onMenuWriteToContact(event) {
-   var contactId = document.menuTarget.getAttribute('id');
-   var contactRow = $(contactId);
-   var emailCell = contactRow.down('td', 1);
+  var contactId = document.menuTarget.getAttribute('id');
+  var contactRow = $(contactId);
+  var emailCell = contactRow.down('td', 1);
 
-   if (!emailCell.firstChild) { // .nodeValue is the contact email address
-     window.alert(labels["The selected contact has no email address."]);
-     return false;
-   }
+  if (!emailCell.firstChild) { // .nodeValue is the contact email address
+    window.alert(labels["The selected contact has no email address."]);
+    return false;
+  }
 
-   openMailComposeWindow(ApplicationBaseURL + currentContactFolder
-                        + "/" + contactId + "/write");
+  openMailComposeWindow(ApplicationBaseURL + currentContactFolder
+                       + "/" + contactId + "/write");
 
-   if (document.body.hasClassName("popup"))
-     window.close();
+  if (document.body.hasClassName("popup"))
+    window.close();
 }
 
 function onMenuDeleteContact(event) {
@@ -334,6 +334,11 @@ function onToolbarEditSelectedContacts(event) {
   var contactsList = $('contactsList');
   var rows = contactsList.getSelectedRowsId();
 
+  if (rows.length == 0) {
+    window.alert(labels["Please select a contact."]);
+    return false;
+  }
+
   for (var i = 0; i < rows.length; i++) {
     openContactWindow(URLForFolderID(currentContactFolder)
                       + "/" + rows[i] + "/edit", rows[i]);
@@ -347,8 +352,10 @@ function onToolbarWriteToSelectedContacts(event) {
   var rows = contactsList.getSelectedRowsId();
   var rowsWithEmail = 0;
 
-  if (rows.length == 0)
+  if (rows.length == 0) {
+    window.alert(labels["Please select a contact."]);
     return false;
+  }
 
   for (var i = 0; i < rows.length; i++) {
     var emailCell = $(rows[i]).down('td', 1);
@@ -373,6 +380,11 @@ function uixDeleteSelectedContacts(sender) {
   var contactsList = $('contactsList');
   var rows = contactsList.getSelectedRowsId();
 
+  if (rows.length == 0) {
+    window.alert(labels["Please select a contact."]);
+    return false;
+  }
+
   var contactView = $('contactView');
   contactView.innerHTML = '';
 
index 79be003e6dafc8d7d70431dcb23f5ce2cc777b94..da4c678d8138d947f0ee0475b744e2e8d1897cb2 100644 (file)
@@ -81,12 +81,19 @@ function editEvent() {
   if (listOfSelection) {
     var nodes = listOfSelection.getSelectedRows();
 
+    if (nodes.length == 0) {
+      window.alert(labels["Please select an event or a task."]);
+      return false;
+    }
+
     for (var i = 0; i < nodes.length; i++)
       _editEventId(nodes[i].getAttribute("id"),
                    nodes[i].calendar);
   } else if (selectedCalendarCell) {
-      _editEventId(selectedCalendarCell[0].cname,
-                   selectedCalendarCell[0].calendar);
+    _editEventId(selectedCalendarCell[0].cname,
+                 selectedCalendarCell[0].calendar);
+  } else {
+    window.alert(labels["Please select an event or a task."]);
   }
 
   return false; /* stop following the link */
@@ -135,6 +142,8 @@ function deleteEvent() {
         }
         _batchDeleteEvents();
       }
+    } else {
+      window.alert(labels["Please select an event or a task."]);
     }
   }
   else if (selectedCalendarCell) {
@@ -150,7 +159,7 @@ function deleteEvent() {
      }
   }
   else
-    window.alert("no selection");
+    window.alert(labels["Please select an event or a task."]);
 
   return false;
 }
index 4593693d895c08bc062df7bf671faaaa86f1cd18..4a30bdf51478df9359cb247dd024d58f92ffb9bc 100644 (file)
@@ -98,16 +98,15 @@ function fancyAddRow(shouldEdit, text) {
   lastChild = $("lastRow");
   
   currentIndex++;
-
-  proto = $('row_' + lastIndex);
+  proto = lastChild.previous("tr");
   row = proto.cloneNode(true);
-  row.id = 'row_' + currentIndex;
+  row.setAttribute("id", 'row_' + currentIndex);
 
   // select popup
   var rowNodes = row.childNodesWithTag("td");
   select = $(rowNodes[0]).childNodesWithTag("select")[0];
   select.name = 'popup_' + currentIndex;
-//   select.value = row.childNodesWithTag("span")[0].childNodesWithTag("select")[0].value;
+  select.value = proto.down("select").value;
   input = $(rowNodes[1]).childNodesWithTag("input")[0];
   input.name  = 'addr_' + currentIndex;
   input.id = 'addr_' + currentIndex;
@@ -136,7 +135,7 @@ function addressFieldGotFocus(sender) {
 
 function addressFieldLostFocus(sender) {
   lastIndex = this.getIndexFromIdentifier(sender.id);
-
+  
   return false;
 }
 
@@ -147,7 +146,7 @@ function removeLastEditedRowIfEmpty() {
   if (idx == 0) return;
   addr = $('addr_' + idx);
   if (!addr) return;
-  if (addr.value != '') return;
+  if (addr.value.strip() != '') return;
   addr = this.findAddressWithIndex(idx);
   if(addr) {
     var addresses = $('addr_addresses');
@@ -223,5 +222,8 @@ function hasRecipients() {
   return (count > 0)
 }
 
-/* addressbook helpers */
+function initMailToSelection() {
+  currentIndex = lastIndex = $$("table#addressList tr").length - 2;
+}
 
+FastInit.addOnLoad(initMailToSelection);