]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1277 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 26 Nov 2007 23:19:01 +0000 (23:19 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 26 Nov 2007 23:19:01 +0000 (23:19 +0000)
46 files changed:
ChangeLog
SOPE/NGCards/ChangeLog
SOPE/NGCards/NGVCard.m
SOPE/NGCards/iCalCalendar.m
SoObjects/Appointments/SOGoAppointmentFolder.h
SoObjects/Appointments/SOGoAppointmentFolder.m
SoObjects/Contacts/SOGoContactGCSFolder.m
SoObjects/Mailer/SOGoDraftObject.m
SoObjects/Mailer/SOGoMailForward.m
SoObjects/Mailer/SOGoMailObject.m
SoObjects/Mailer/SOGoMailReply.m
SoObjects/SOGo/LDAPSource.h
SoObjects/SOGo/LDAPSource.m
SoObjects/SOGo/LDAPUserManager.m
SoObjects/SOGo/SOGoUser.h
SoObjects/SOGo/SOGoUser.m
SoObjects/SOGo/SOGoUserFolder.m
UI/Common/UIxPageFrame.m
UI/Common/WODirectAction+SOGo.h
UI/Common/WODirectAction+SOGo.m
UI/MailPartViewers/UIxMailPartAlternativeViewer.m
UI/MailPartViewers/UIxMailPartICalViewer.m
UI/MailPartViewers/UIxMailPartViewer.m
UI/MailerUI/English.lproj/Localizable.strings
UI/MailerUI/French.lproj/Localizable.strings
UI/MailerUI/German.lproj/Localizable.strings
UI/MailerUI/UIxMailAccountActions.m
UI/MailerUI/UIxMailEditor.m
UI/PreferencesUI/UIxPreferences.m
UI/SOGoUI/UIxComponent.m
UI/Scheduler/English.lproj/Localizable.strings
UI/Scheduler/French.lproj/Localizable.strings
UI/Scheduler/German.lproj/Localizable.strings
UI/Scheduler/UIxAppointmentEditor.m
UI/Scheduler/UIxCalListingActions.h
UI/Scheduler/UIxCalListingActions.m
UI/Scheduler/UIxTaskEditor.m
UI/Templates/MailerUI/UIxMailMainFrame.wox
UI/Templates/PreferencesUI/UIxPreferences.wox
UI/Templates/UIxPageFrame.wox
UI/WebServerResources/MailerUI.js
UI/WebServerResources/SchedulerUI.js
UI/WebServerResources/UIxAppointmentEditor.css
UI/WebServerResources/UIxMailEditor.js
UI/WebServerResources/generic.css
UI/WebServerResources/generic.js

index f3a0e7f047fc704b020c7871e747d99d67056115..58209d70d3b28e64dde5e6a906c1d93de6d19247 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,92 @@
+2007-11-26  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * UI/PreferencesUI/UIxPreferences.m ([UIxPreferences -userHasCalendarAccess])
+       ([UIxPreferences -userHasMailAccess]): same as below, for
+       displaying preference tabs.
+
+       * UI/Common/UIxPageFrame.m ([UIxPageFrame
+       -userHasCalendarAccess]): new accessor for the link banner.
+       ([UIxPageFrame -userHasMailAccess]): same as above.
+
+       * SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder
+       -toManyRelationshipKeys]): do not report the path to modules to
+       which the user has no access. No longer cache this information
+       statically, the array will be generated at each call.
+       ([SOGoUserFolder -lookupName:_keyinContext:_ctxacquire:_flag]):
+       ignore the path to modules to which the user has no access.
+
+       * SoObjects/SOGo/SOGoUser.m ([SOGoUser -canAccessModule:module]):
+       new method that returns whether the user has access to the
+       specified module.
+
+       * SoObjects/SOGo/LDAPSource.m ([LDAPSource
+       -initFromUDSource:udSource]): take a new parameter named
+       'ModulesContraints' that defines a set of constraints for
+       accessing specified named modules. This is an optout, meaning the
+       modules will be present unless a constraint is specified.
+
+       * SoObjects/Appointments/SOGoAppointmentFolder.m
+       ([SOGoAppointmentFolder
+       -roleForComponentsWithAccessClass:accessClassforUser:uid]): cache
+       acl to reduce method calls.
+       ([SOGoAppointmentFolder
+       -fetchFields:_fieldsfromFolder:_folderfrom:_startDateto:_endDatetitle:titlecomponent:_component]):
+       if the user is not the current user, we strip the useless info
+       from the meta data.
+
+       * UI/Common/WODirectAction+SOGo.m ([WODirectAction
+       -labelForKey:key]): new category method.
+
+       * UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor
+       -event]): same as below.
+
+       * UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor -todo]): request a
+       secured version of the component.
+
+       * UI/MailPartViewers/UIxMailPartViewer.m
+       ([-fallbackStringEncoding]): removed method.
+       ([UIxMailPartViewer -flatContentAsString]): we no longer use [self
+       fallbackStringEncoding]. Instead we directly specify
+       NSISOLatin1StringEncoding if UTF-8 has failed.
+
+       * UI/MailPartViewers/UIxMailPartICalViewer.m
+       ([-fallbackStringEncoding]): removed method.
+
+2007-11-25  Ludovic Marcotte <ludovic@inverse.ca>
+
+        * SoObjects/Mailer/SOGoMailForward.m
+        Use [sourceMail decodedSubject] instead of [sourceMail subject]
+        Signature fix in -signature - see the comment
+        for SOGoMailReply.
+
+        * SoObjects/Mailer/SOGoMailObject.m
+        Improved -stringFromData: to try UTF-8 then fallback to Latin1
+
+        * SoObjects/Mailer/SOGoMailReply.m
+        Modified -messageBody to strip the signature from the reply.
+        Also modified -signature to add "-- \n%@" instead of the
+        broken ""--\r\n%@" pattern.
+
+        * UI/MailPartViewers/UIxMailPartAlternativeViewer.m
+        We now favor text/calendar parts over text/html and
+        text/plain parts when viewing a multipart/alternative mail.
+        This allows us to show the email invitations coming from
+        Microsoft Outlook.
+
+        * UI/MailerUI/UIxMailAccountActions.m
+        Signature fix in -composeAction - see the comment
+        for SOGoMailReply.
+
+        * UI/MailPartViewers/UIxMailPartViewer.m
+        Modified -flatContentAsString to use UTF-8 as the
+        default fallback encoding for 8-bit content.
+
+       * SoObjects/Mailer/SOGoDraftObject.m
+       Modified _fillInReplyAddresses:replyToAll:envelope:
+       so that if there's no recipient, we add at least
+       ourself to the list.    
+       Fixed a mem leak in the same method.
+       
 2007-11-22  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * UI/MailPartViewers/UIxMailRenderingContext.m
index 75c6d547e09840161b8c4ed6491e8f639fb20ce4..e13917d6756207cafa1ff1af87c3ab2329429d77 100644 (file)
@@ -1,3 +1,12 @@
+2007-11-26  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * iCalCalendar.m ([iCalCalendar -versitString]): same as below,
+       with "2.0" as value.
+
+       * NGVCard.m ([NGVCard -versitString]): overriden method by forcing
+       version to "3.0" since it's the format we comply with.
+       
+
 2007-11-22  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * iCalEntityObject.m ([iCalEntityObject -compare:otherObject]):
index faf7eaa5392fa7d72cbc3f589aee9be3b690fe66..cf457c4edc3b9c106f24700043254cfed8f1089a 100644 (file)
 
 @implementation NGVCard
 
-+ (id) parseSingleFromSource: (id) source
-{
-  NGVCard *newCard;
-
-  newCard = [super parseSingleFromSource: source];
-  [newCard setVersion: @"3.0"];
-
-  return newCard;
-}
-
 + (id) cardWithUid: (NSString *) _uid
 {
   NGVCard *newCard;
   return [self _preferredElementWithTag: @"adr"];
 }
 
+- (NSString *) versitString
+{
+  [self setVersion: @"3.0"];
+
+  return [super versitString];
+}
+
 /* description */
 
 - (void) appendAttributesToDescription: (NSMutableString *) _ms
index b478b7f91d465d02cb8a9b75a26513abcc6f7f18..1771c65d3ae675e2a23e06981c2b98d46177b999 100644 (file)
   return ma;
 }
 
+- (NSString *) versitString
+{
+  [self setVersion: @"2.0"];
+
+  return [super versitString];
+}
+
 /* ical typing */
 
 - (NSString *) entityName
index 851b4855b88b852ef7faaf8ce696976e67d938eb..c359f65e01ec6af0a39bd488bb2c071006308a4e 100644 (file)
@@ -38,6 +38,8 @@
 
 #import "SOGo/SOGoGCSFolder.h"
 
+#import <NGCards/iCalEntityObject.h>
+
 @class NSArray;
 @class NSCalendarDate;
 @class NSException;
 @class NSTimeZone;
 @class GCSFolder;
 
-#import <NGCards/iCalEntityObject.h>
 @interface SOGoAppointmentFolder : SOGoGCSFolder
 {
   NSTimeZone *timeZone;
   NSMutableDictionary *uidToFilename;
+  NSMutableDictionary *aclMatrix;
+  NSMutableArray *stripFields;
 }
 
 - (BOOL) isActive;
index 9d740ed7f6832fa7fb63cca2f77674474ded0831..cafd2173bc8b973601b93b1fdac434b5c2924601 100644 (file)
@@ -35,6 +35,7 @@
 #import <GDLContentStore/GCSFolder.h>
 #import <DOM/DOMProtocols.h>
 #import <EOControl/EOQualifier.h>
+#import <NGCards/iCalCalendar.h>
 #import <NGCards/iCalDateTime.h>
 #import <NGCards/iCalPerson.h>
 #import <NGCards/iCalRecurrenceCalculator.h>
@@ -110,6 +111,9 @@ static NSNumber   *sharedYes = nil;
   if ((self = [super initWithName: name inContainer: newContainer]))
     {
       timeZone = [[context activeUser] timeZone];
+      aclMatrix = [NSMutableDictionary new];
+      stripFields = nil;
+      uidToFilename = nil;
     }
 
   return self;
@@ -117,6 +121,8 @@ static NSNumber   *sharedYes = nil;
 
 - (void) dealloc
 {
+  [aclMatrix release];
+  [stripFields release];
   [uidToFilename release];
   [super dealloc];
 }
@@ -765,7 +771,7 @@ static NSNumber   *sharedYes = nil;
                    [self _privacyClassificationStringsForUID: login],
                    email, email, email];
     }
-  
+
   return privacySqlString;
 }
 
@@ -774,6 +780,7 @@ static NSNumber   *sharedYes = nil;
 {
   NSString *accessRole, *prefix, *currentRole, *suffix;
   NSEnumerator *acls;
+  NSMutableDictionary *userRoles;
 
   accessRole = nil;
 
@@ -784,20 +791,75 @@ static NSNumber   *sharedYes = nil;
   else
     prefix = @"Confidential";
 
-  acls = [[self aclsForUser: uid] objectEnumerator];
-  currentRole = [acls nextObject];
-  while (currentRole && !accessRole)
-    if ([currentRole hasPrefix: prefix])
-      {
-       suffix = [currentRole substringFromIndex: [prefix length]];
-       accessRole = [NSString stringWithFormat: @"Component%@", suffix];
-      }
-    else
+  userRoles = [aclMatrix objectForKey: uid];
+  if (!userRoles)
+    {
+      userRoles = [NSMutableDictionary dictionaryWithCapacity: 3];
+      [aclMatrix setObject: userRoles forKey: uid];
+    }
+
+  accessRole = [userRoles objectForKey: prefix];
+  if (!accessRole)
+    {
+      acls = [[self aclsForUser: uid] objectEnumerator];
       currentRole = [acls nextObject];
+      while (currentRole && !accessRole)
+       if ([currentRole hasPrefix: prefix])
+         {
+           suffix = [currentRole substringFromIndex: [prefix length]];
+           accessRole = [NSString stringWithFormat: @"Component%@", suffix];
+         }
+       else
+         currentRole = [acls nextObject];
+      if (!accessRole)
+       accessRole = @"";
+      [userRoles setObject: accessRole forKey: prefix];
+    }
 
   return accessRole;
 }
 
+- (void) _buildStripFieldsFromFields: (NSArray *) fields
+{
+  stripFields = [[NSMutableArray alloc] initWithCapacity: [fields count]];
+  [stripFields setArray: fields];
+  [stripFields removeObjectsInArray: [NSArray arrayWithObjects: @"c_name",
+                                             @"c_uid", @"c_startdate",
+                                             @"c_enddate", @"c_isallday",
+                                             @"c_iscycle",
+                                             @"c_classification",
+                                             @"c_component", nil]];
+}
+
+- (void) _fixupProtectedInformation: (NSEnumerator *) ma
+                          inFields: (NSArray *) fields
+                           forUser: (NSString *) uid
+{
+  NSMutableDictionary *currentRecord;
+  NSString *roles[] = {nil, nil, nil};
+  iCalAccessClass accessClass;
+  NSString *role;
+
+  if (!stripFields)
+    [self _buildStripFieldsFromFields: fields];
+
+#warning we do not take the participation status into account
+  while ((currentRecord = [ma nextObject]))
+    {
+      accessClass
+       = [[currentRecord objectForKey: @"c_classification"] intValue];
+      role = roles[accessClass];
+      if (!role)
+       {
+         role = [[self roleForComponentsWithAccessClass: accessClass
+                       forUser: uid] substringFromIndex: 9];
+         roles[accessClass] = role;
+       }
+      if ([role isEqualToString: @"DAndTViewer"])
+       [currentRecord removeObjectsForKeys: stripFields];
+    }
+}
+
 - (NSArray *) fetchFields: (NSArray *) _fields
                fromFolder: (GCSFolder *) _folder
                      from: (NSCalendarDate *) _startDate
@@ -809,15 +871,16 @@ static NSNumber   *sharedYes = nil;
   NSMutableArray *fields, *ma = nil;
   NSArray *records;
   NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
-               *privacySqlString;
+    *privacySqlString, *currentLogin;
   NGCalendarDateRange *r;
 
-  if (_folder == nil) {
-    [self errorWithFormat:@"(%s): missing folder for fetch!",
+  if (!_folder)
+    {
+      [self errorWithFormat:@"(%s): missing folder for fetch!",
             __PRETTY_FUNCTION__];
-    return nil;
-  }
-  
+      return nil;
+    }
+
   if (_startDate && _endDate)
     {
       r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
@@ -896,6 +959,11 @@ static NSNumber   *sharedYes = nil;
   if (logger)
     [self debugWithFormat:@"returning %i records", [ma count]];
 
+  currentLogin = [[context activeUser] login];
+  if (![currentLogin isEqualToString: owner])
+    [self _fixupProtectedInformation: [ma objectEnumerator]
+         inFields: _fields
+         forUser: currentLogin];
 //   [ma makeObjectsPerform: @selector (setObject:forKey:)
 //       withObject: owner
 //       withObject: @"owner"];
index 7fd56ba8dc09e4f4803647e582ec4bd935bb10bd..074048b8a2f5f38a2e96d319ec6a770fca5d0939 100644 (file)
       qs = [NSString stringWithFormat:
                        @"(c_sn isCaseInsensitiveLike: '%@%%') OR "
                      @"(c_givenname isCaseInsensitiveLike: '%@%%') OR "
-                     @"(c_mail isCaseInsensitiveLike: '%@%%') OR "
+                     @"(c_mail isCaseInsensitiveLike: '%%%@%%') OR "
                      @"(c_telephonenumber isCaseInsensitiveLike: '%%%@%%')",
                      filter, filter, filter, filter];
       qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
index f56c1be80340e577a9eabe0dfc0190a67f600b52..0ebb3432067e1a8654f367da59506561602ff784 100644 (file)
@@ -488,7 +488,18 @@ static BOOL        showTextAttachmentsInline  = NO;
       [to release];
     }
 
+  /* If "to" is empty, we add at least ourself as a recipient!
+     This is for emails in the "Sent" folder that we reply to... */
+  if (![to count])
+    {
+      if ([[_envelope replyTo] count])
+       [self _addEMailsOfAddresses: [_envelope replyTo] toArray: to];
+      else
+       [self _addEMailsOfAddresses: [_envelope from] toArray: to];
+    }
+
   [allRecipients release];
+  [addrs release];
 }
 
 - (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
index e666f9b8f7badcfc7f147f1afd3760b12db01fb1..37bce7a5b94ab66e484a3c1384e8ed3e20ddbc0d 100644 (file)
@@ -55,7 +55,7 @@
 
 - (NSString *) subject
 {
-  return [sourceMail subject];
+  return [sourceMail decodedSubject];
 }
 
 - (NSString *) date
 
   signature = [[context activeUser] signature];
   if ([signature length])
-    mailSignature = [NSString stringWithFormat: @"--\r\n%@", signature];
+    mailSignature = [NSString stringWithFormat: @"-- \n%@", signature];
   else
     mailSignature = @"";
 
index f59ab0f0faec8a9bb15c24b825158d91acf071a9..a28d752e68c9c36caf38d96aa393faff4775461c 100644 (file)
@@ -659,17 +659,21 @@ static BOOL debugSoParts       = NO;
       charset = [[_info valueForKey: @"parameterList"] valueForKey: @"charset"];
       if (![charset length])
        {
-         s = [[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding];
-         [s autorelease];
+         s = nil;
        }
       else
        {
          s = [NSString stringWithData: mailData usingEncodingNamed: charset];
-         
-         // If it has failed, we try at least using UTF-8. Normally, this can NOT fail
-         if (!s)
-           s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
        }
+      
+      // If it has failed, we try at least using UTF-8. Normally, this can NOT fail.
+      // Unfortunately, it seems to fail under GNUstep so we try latin1 if that's
+      // the case
+      if (!s)
+       s = [[[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding] autorelease];
+      
+      if (!s)
+       s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
     }
   else
     s = nil;
index 9016b469d52c64a3463b1b1b7724a79f5debf92a..f196b33c2431ed8f48d9db93884d8d4e98a026ae 100644 (file)
 
 - (NSString *) messageBody
 {
-  return [[sourceMail contentForEditing] stringByApplyingMailQuoting];
+  NSString *s;
+  
+  s = [sourceMail contentForEditing];
+
+  if (s)
+    {
+      NSRange r;
+
+      r = [s rangeOfString: @"\n-- \n"  options: NSBackwardsSearch];
+
+      if (r.length)
+       s = [s substringToIndex: r.location];
+    }
+
+  return [s stringByApplyingMailQuoting];
 }
 
 - (NSString *) signature
@@ -79,7 +93,7 @@
 
   signature = [[context activeUser] signature];
   if ([signature length])
-    mailSignature = [NSString stringWithFormat: @"--\r\n%@", signature];
+    mailSignature = [NSString stringWithFormat: @"-- \n%@", signature];
   else
     mailSignature = @"";
 
index c2edec3de3d3fdf5662fa3b0dc3d69f6e7f4abdb..2124b609fa1d5a8fbadb70a2bcc97566bc2c1c8c 100644 (file)
@@ -44,6 +44,8 @@
   NSArray *mailFields;
   NSString *bindFields;
 
+  NSDictionary *modulesConstraints;
+
   NGLdapConnection *ldapConnection;
   NSMutableArray *searchAttributes;
 }
index b2cd7ad32c7774df1ce49463360c2532746e0f17..f3239afbe4e0c4e12f3dd404fcd5469708493e2c 100644 (file)
@@ -170,6 +170,7 @@ static int sizeLimit;
   [bindFields release];
   [ldapConnection release];
   [sourceID release];
+  [modulesConstraints release];
   [super dealloc];
 }
 
@@ -189,6 +190,7 @@ static int sizeLimit;
        UIDField: [udSource objectForKey: @"UIDFieldName"]
        mailFields: [udSource objectForKey: @"MailFieldNames"]
        andBindFields: [udSource objectForKey: @"bindFields"]];
+  ASSIGN (modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
 
   return self;
 }
@@ -351,6 +353,20 @@ static int sizeLimit;
   return [EOQualifier qualifierWithQualifierFormat: qs];
 }
 
+- (NSArray *) _contraintsFields
+{
+  NSMutableArray *fields;
+  NSEnumerator *values;
+  NSDictionary *currentConstraint;
+
+  fields = [NSMutableArray array];
+  values = [[modulesConstraints allValues] objectEnumerator];
+  while ((currentConstraint = [values nextObject]))
+    [fields addObjectsFromArray: [currentConstraint allKeys]];
+
+  return fields;
+}
+
 - (NSArray *) _searchAttributes
 {
   if (!searchAttributes)
@@ -361,6 +377,7 @@ static int sizeLimit;
       if (UIDField)
        [searchAttributes addObject: UIDField];
       [searchAttributes addObjectsFromArray: mailFields];
+      [searchAttributes addObjectsFromArray: [self _contraintsFields]];
       [searchAttributes addObjectsFromArray: commonSearchFields];
     }
 
@@ -408,7 +425,8 @@ static int sizeLimit;
   emailFields = [mailFields objectEnumerator];
   while ((currentFieldName = [emailFields nextObject]))
     {
-      value = [[ldapEntry attributeWithName: currentFieldName] stringValueAtIndex: 0];
+      value = [[ldapEntry attributeWithName: currentFieldName]
+               stringValueAtIndex: 0];
       if (value)
        [emails addObject: value];
     }
@@ -416,6 +434,38 @@ static int sizeLimit;
   [contactEntry setObject: emails forKey: @"c_emails"];
 }
 
+- (void) _fillConstraints: (NGLdapEntry *) ldapEntry
+               forModule: (NSString *) module
+        intoContactEntry: (NSMutableDictionary *) contactEntry
+{
+  NSDictionary *constraints;
+  NSEnumerator *matches;
+  NSString *currentMatch, *currentValue, *ldapValue;
+  BOOL result;
+
+  result = YES;
+
+  constraints = [modulesConstraints objectForKey: module];
+  if (constraints)
+    {
+      matches = [[constraints allKeys] objectEnumerator];
+      currentMatch = [matches nextObject];
+      while (result && currentMatch)
+       {
+         ldapValue = [[ldapEntry attributeWithName: currentMatch]
+                       stringValueAtIndex: 0];
+         currentValue = [constraints objectForKey: currentMatch];
+         if ([ldapValue isEqualToString: currentValue])
+           currentMatch = [matches nextObject];
+         else
+           result = NO;
+       }
+    }
+
+  [contactEntry setObject: [NSNumber numberWithBool: result]
+               forKey: [NSString stringWithFormat: @"%@Access", module]];
+}
+
 - (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry
 {
   NSMutableDictionary *contactEntry;
@@ -446,6 +496,10 @@ static int sizeLimit;
     value = @"";
   [contactEntry setObject: value forKey: @"c_cn"];
   [self _fillEmailsOfEntry: ldapEntry intoContactEntry: contactEntry];
+  [self _fillConstraints: ldapEntry forModule: @"Calendar"
+       intoContactEntry: (NSMutableDictionary *) contactEntry];
+  [self _fillConstraints: ldapEntry forModule: @"Mail"
+       intoContactEntry: (NSMutableDictionary *) contactEntry];
 
   return contactEntry;
 }
index 6dcdd3afafaf8c5e0ce3713907ec71d60b68dcdb..0e11688526c21abdbdbfe947302524d470c7e9ef 100644 (file)
@@ -303,11 +303,17 @@ static NSString *defaultMailDomain = nil;
   LDAPSource *currentSource;
   NSString *cn, *c_uid;
   NSArray *c_emails;
-  
+  BOOL access;
+
   emails = [NSMutableArray array];
   cn = nil;
   c_uid = nil;
 
+  [currentUser setObject: [NSNumber numberWithBool: YES]
+              forKey: @"CalendarAccess"];
+  [currentUser setObject: [NSNumber numberWithBool: YES]
+              forKey: @"MailAccess"];
+
   ldapSources = [sources objectEnumerator];
   currentSource = [ldapSources nextObject];
   while (currentSource)
@@ -322,6 +328,14 @@ static NSString *defaultMailDomain = nil;
          c_emails = [userEntry objectForKey: @"c_emails"];
          if ([c_emails count])
            [emails addObjectsFromArray: c_emails];
+         access = [[userEntry objectForKey: @"CalendarAccess"] boolValue];
+         if (!access)
+           [currentUser setObject: [NSNumber numberWithBool: NO]
+                        forKey: @"CalendarAccess"];
+         access = [[userEntry objectForKey: @"MailAccess"] boolValue];
+         if (!access)
+           [currentUser setObject: [NSNumber numberWithBool: NO]
+                        forKey: @"MailAccess"];
        }
       currentSource = [ldapSources nextObject];
     }
index d8f80102eb2fdb1c9cbf749f3c62c0c8b50199ee..bc051398825afde4c5bc070bc210706b7fe7c1a9 100644 (file)
@@ -124,6 +124,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
 
 - (BOOL) isSuperUser;
 
+/* module access */
+- (BOOL) canAccessModule: (NSString *) module;
+
 /* folders */
 
 - (SOGoUserFolder *) homeFolderInContext: (id) context;
index ee12335961abc72850cb0e22e38553f72d6789a9..bdf37fa0edc39daaaec3e1143a675d77f4cafc51 100644 (file)
@@ -622,4 +622,15 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
   return [superUsernames containsObject: login];
 }
 
+/* module access */
+- (BOOL) canAccessModule: (NSString *) module
+{
+  NSString *accessValue;
+
+  accessValue = [self _fetchFieldForUser:
+                       [NSString stringWithFormat: @"%@Access", module]];
+
+  return [accessValue boolValue];
+}
+
 @end /* SOGoUser */
index 5003c54d26f1a8a52783ac5974b412d358443faf..035cbcb47e017156105f438a455ebbed81f94023 100644 (file)
@@ -24,6 +24,8 @@
 
 #import <NGObjWeb/NSException+HTTP.h>
 #import <NGObjWeb/SoClassSecurityInfo.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
+
 #import <NGExtensions/NSObject+Logs.h>
 
 #import <Appointments/SOGoAppointmentFolders.h>
 
 - (NSArray *) toManyRelationshipKeys
 {
-  static NSArray *children = nil;
+  NSMutableArray *children;
+  SOGoUser *currentUser;
+
+  children = [NSMutableArray arrayWithCapacity: 4];
 
-  if (!children)
-    children = [[NSArray alloc] initWithObjects:
-                                 @"Calendar", @"Contacts", @"Mail",
-                               @"Preferences",  nil];
+  currentUser = [context activeUser];
+  if ([currentUser canAccessModule: @"Calendar"])
+    [children addObject: @"Calendar"];
+  [children addObject: @"Contacts"];
+  if ([currentUser canAccessModule: @"Mail"])
+    [children addObject: @"Mail"];
+  [children addObject: @"Preferences"];
 
   return children;
 }
           acquire: (BOOL) _flag
 {
   id obj;
+  SOGoUser *currentUser;
   
   /* first check attributes directly bound to the application */
   obj = [super lookupName: _key inContext: _ctx acquire: NO];
   if (!obj)
     {
-      if ([_key isEqualToString: @"Calendar"])
+      currentUser = [_ctx activeUser];
+      if ([_key isEqualToString: @"Calendar"]
+         && [currentUser canAccessModule: _key])
        obj = [self privateCalendars: @"Calendar" inContext: _ctx];
 //           if (![_key isEqualToString: @"Calendar"])
 //             obj = [obj lookupName: [_key pathExtension] 
         obj = [self privateContacts: _key inContext: _ctx];
 //       else if ([_key isEqualToString: @"Groups"])
 //         obj = [self groupsFolder: _key inContext: _ctx];
-      else if ([_key isEqualToString: @"Mail"])
+      else if ([_key isEqualToString: @"Mail"]
+              && [currentUser canAccessModule: _key])
         obj = [self mailAccountsFolder: _key inContext: _ctx];
       else if ([_key isEqualToString: @"Preferences"])
         obj = [$(@"SOGoPreferencesFolder") objectWithName: _key
index 47f9433ca098dd4b9975376776c9d0479d422d83..b3565daed4a77c8e5d9c292ffbe5f9ac58b32ed1 100644 (file)
          && [user isSuperUser]);
 }
 
+- (BOOL) userHasCalendarAccess
+{
+  SOGoUser *user;
+
+  user = [context activeUser];
+
+  return [user canAccessModule: @"Calendar"];
+}
+
+- (BOOL) userHasMailAccess
+{
+  SOGoUser *user;
+
+  user = [context activeUser];
+
+  return [user canAccessModule: @"Mail"];
+}
+
 /* browser/os identification */
 
 - (BOOL) isCompatibleBrowser
index 067046b69e5e1951314a72316d98e42a760a6ffb..aa518c6d63002c9fec12a44fe74f5f10d39b7ba4 100644 (file)
@@ -34,6 +34,8 @@
 - (WOResponse *) responseWith204;
 - (WOResponse *) redirectToLocation: (NSString *) newLocation;
 
+- (NSString *) labelForKey: (NSString *) _str;
+
 @end
 
 #endif /* WODIRECTACTION_SOGO_H */
index 13c8b86d86cb9c34213ec86b325d5076d7ef20d0..6c1c3adfaaac1ff31caeaabc1034e2c9a947088b 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
+#import <Foundation/NSBundle.h>
 
-#import <NGObjWeb/WOContext.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
 #import <NGObjWeb/WOResponse.h>
 
+#import <SoObjects/SOGo/NSDictionary+Utilities.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+
 #import "WODirectAction+SOGo.h"
 
 @implementation WODirectAction (SOGoExtension)
   return response;
 }
 
+- (NSString *) labelForKey: (NSString *) key
+{
+  NSString *userLanguage, *label;
+  NSArray *paths;
+  NSBundle *bundle;
+  NSDictionary *strings;
+
+  bundle = [NSBundle bundleForClass: [self class]];
+  if (!bundle)
+    bundle = [NSBundle mainBundle];
 
+  userLanguage = [[context activeUser] language];
+  paths = [bundle pathsForResourcesOfType: @"strings"
+                 inDirectory: [NSString stringWithFormat: @"%@.lproj",
+                                        userLanguage]
+                 forLocalization: userLanguage];
+  if ([paths count] > 0)
+    {
+      strings = [NSDictionary
+                 dictionaryFromStringsFile: [paths objectAtIndex: 0]];
+      label = [strings objectForKey: key];
+      if (!label)
+       label = key;
+    }
+  else
+    label = key;
+  
+  return label;
+}
 @end
index f93a18f7e17cf53b17341e6f5d52790d4303a26d..72ed9b3a0c49d0b97141fc3b7a403ec2178d95f2 100644 (file)
@@ -92,6 +92,8 @@
   if ((count = [_types count]) == 0)
     return NSNotFound;
   
+  if ((i = [_types indexOfObject:@"text/calendar"]) != NSNotFound)
+    return i;
   if ((i = [_types indexOfObject:@"text/html"]) != NSNotFound)
     return i;
   if ((i = [_types indexOfObject:@"text/plain"]) != NSNotFound)
index b30a88174f4c5b138260c8239907c29819f73244..3ee2c8f61cb5dcde1c1ec35acf1b53b218f502ba 100644 (file)
   [item release]; item = nil;
 }
 
-/* raw content handling */
-
-- (NSStringEncoding) fallbackStringEncoding
-{
-  /*
-    iCalendar invitations sent by Outlook 2002 have the annoying bug that the
-    mail states an UTF-8 content encoding but the actual iCalendar content is
-    encoding in Latin-1 (or Windows Western?).
-    As a result the content decoding will fail (TODO: always?). In this case we
-    try to decode with Latin-1.
-    Note: we could check for the Outlook x-mailer, but it was considered better
-    to try Latin-1 as a fallback in any case (be tolerant).
-  */
-  return NSISOLatin1StringEncoding;
-}
-
 /* accessors */
 
 - (iCalCalendar *) inCalendar
index 3639789c273209f231728c0bdc6272a26d486683..f3a44db78af44bc69799e3e3d69241001ea14f49 100644 (file)
   return content;
 }
 
-- (NSStringEncoding) fallbackStringEncoding
-{
-  return 0;
-}
-
 - (NSString *) flatContentAsString
 {
   /* Note: we even have the line count in the body-info! */
       s = [NSString stringWithData: content usingEncodingNamed: charset];
       if (![s length])
        {
-         /* latin 1 is used as a 8bit fallback charset... but does this
-            encoding accept any byte from 0 to 255? */
+         /* UTF-8 is used as a 8bit fallback charset... */
          s = [[NSString alloc] initWithData: content
-                               encoding: NSISOLatin1StringEncoding];
+                               encoding: NSUTF8StringEncoding];
          [s autorelease];
        }
 
       if (!s)
        {
-         /* 
-            Note: this can happend with iCalendar invitations sent by Outlook 2002.
-            It will mark the content as UTF-8 but actually deliver it as
-            Latin-1 (or Windows encoding?).
+         /*
+           iCalendar invitations sent by Outlook 2002 have the annoying bug that the
+           mail states an UTF-8 content encoding but the actual iCalendar content is
+           encoding in Latin-1 (or Windows Western?).
+               
+           As a result the content decoding will fail (TODO: always?). In this case we
+           try to decode with Latin-1.
+           Note: we could check for the Outlook x-mailer, but it was considered better
+           to try Latin-1 as a fallback in any case (be tolerant).
          */
-         [self errorWithFormat:@"could not convert content to text, charset: '%@'",
-               charset];
-         if ([self fallbackStringEncoding] > 0)
-           {
-             s = [[NSString alloc] initWithData:content 
-                                   encoding: [self fallbackStringEncoding]];
-             if (s)
-               [s autorelease];
-             else
-               [self errorWithFormat:
-                       @"an attempt to use fallback encoding failed to."];
-           }
+
+         s = [[NSString alloc] initWithData:content 
+                               encoding: NSISOLatin1StringEncoding];
+         if (!s)
+           [self errorWithFormat: @"an attempt to use"
+                 @" NSISOLatin1StringEncoding as callback failed"];
+         [s autorelease];
        }
     }
   else
index 3bfff1730ff21b353c663c2b25f30a0d94e6e8f2..d745a63847d59b0fa33b1e8986e92a2d563bb2de 100644 (file)
 "Compose Mail To" = "Compose Mail To";
 "Create Filter From Message..." = "Create Filter From Message...";
 
+/* Image Popup menu */
+"View Image" = "View Image";
+
 /* Mailbox popup menus */
 "Open in New Mail Window" = "Open in New Mail Window";
 "Copy Folder Location" = "Copy Folder Location";
index 8a3d11b344d81e06cc56847649a1588112633319..46d1e91603fea685ffbdee97fd881ab690115b26 100644 (file)
 "Compose Mail To" = "Écrire Ã ";
 "Create Filter From Message..." = "Créer un filtre Ã  partir du message...";
 
+/* Image Popup menu */
+"View Image" = "Voir l'image";
+
 /* Mailbox popup menus */
 "Open in New Mail Window" = "Ouvrir dans une nouvelle fenétre";
 "Copy Folder Location" = "Copier l'adresse du dossier";
index 6812036f17deb7d5aed0beb4b9e6c9fd28468412..21748193257b071d4fad391da513bb1df03cfa45 100644 (file)
 "Compose Mail To" = "Verfassen";
 "Create Filter From Message..." = "Filter aus Nachricht erstellen...";
 
+/* Image Popup menu */
+"View Image" = "View Image";
+
 /* Mailbox popup menus */
 "Open in New Mail Window" = "In neuem Fenster Ã¶ffnen";
 "Copy Folder Location" = "Ordner-Adresse kopieren";
index 69fe254d95b79f8ce65d07b69502e8377b4ccc93..ea243bca77aa617dccc606cb6c5b9edafec958d9 100644 (file)
   if ([signature length])
     {
       [newDraftMessage
-       setText: [NSString stringWithFormat: @"\r\n--\r\n%@", signature]];
+       setText: [NSString stringWithFormat: @"\n-- \n%@", signature]];
       save = YES;
     }
   if (save)
index 2950bd39d38cc0b4ad2131f8a62e4e530873cbe7..31e33f02af7b7dac69a4ed2b2e8f0a4f5aebcdc4 100644 (file)
@@ -443,7 +443,7 @@ static NSArray *infoKeys = nil;
        {
          result = [[self clientObject] sendMail];
          if (!result)
-           result = [self jsCloseWithRefreshMethod: @"refreshFolderByType(\"sent\")"];
+           result = [self jsCloseWithRefreshMethod: @"refreshCurrentFolder()"];
        }
       else
        result = [self failedToSaveFormResponse];
index 192d8267e2d289b1b9488d00faac14dbcf4db2f1..c5f5bac426f56742bb35c2ad359649cdf577f245 100644 (file)
@@ -483,7 +483,7 @@ static BOOL shouldDisplayPasswordChange = NO;
 
 - (NSString *) itemIdentityText
 {
-  return [item keysWithFormat: @"%{fullName} <%{email}>"];
+  return [(NSDictionary *) item keysWithFormat: @"%{fullName} <%{email}>"];
 }
 
 - (NSMutableDictionary *) defaultIdentity
@@ -538,6 +538,16 @@ static BOOL shouldDisplayPasswordChange = NO;
   return [[request method] isEqualToString: @"POST"];
 }
 
+- (BOOL) userHasCalendarAccess
+{
+  return [user canAccessModule: @"Calendar"];
+}
+
+- (BOOL) userHasMailAccess
+{
+  return [user canAccessModule: @"Mail"];
+}
+
 - (BOOL) shouldDisplayPasswordChange
 {
   return shouldDisplayPasswordChange;
index a376ba6077037e8eb5a9a2e31fba28501a1dd720..50e6bf253c28cc32521870927b9aae8003c7bafd 100644 (file)
@@ -489,12 +489,6 @@ static BOOL uixDebugEnabled = NO;
   if ([lTable hasPrefix:@"$"])
     lTable = [self valueForKeyPath:[lTable substringFromIndex:1]];
   
-#if 0
-  if ([lVal hasPrefix:@"$"])
-    lVal = [self valueForKeyPath:[lVal substringFromIndex:1]];
-  
-#endif
-  
   /* lookup string */
   return [rm stringForKey: lKey
              inTableNamed: lTable
index dbc48efc5fcacc56b1bd3ffad908b5a150ddd7b6..aa8e4c08a49c24ba4f4ca5ba31badaf344c9d54d 100644 (file)
@@ -413,5 +413,13 @@ validate_endbeforestart    = "Enddate is before startdate!";
 "Location" = "Location";
 "(Private Event)" = "(Private Event)";
 
+vevent_class0 = "(Public event)";
+vevent_class1 = "(Private event)";
+vevent_class2 = "(Confidential event)";
+
+vtodo_class0 = "(Public task)";
+vtodo_class1 = "(Private task)";
+vtodo_class2 = "(Confidential task)";
+
 "closeThisWindowMessage" = "Thank you! You may now close this window or view your ";
 "Multicolumn Day View" = "Multicolumn Day View";
index f04263bb28d5917dcb0c0515ba75236bb71f8668..d0f002d0fb958670b7af1c7e4523c86f4e6d2967 100644 (file)
@@ -412,5 +412,13 @@ validate_endbeforestart    = "La date de fin est avant la date de début !";
 "Location" = "Lieu";
 "(Private Event)" = "(Événement privé)";
 
+vevent_class0 = "(Événement public)";
+vevent_class1 = "(Événement privé)";
+vevent_class2 = "(Événement confidentiel)";
+
+vtodo_class0 = "(Tâche publique)";
+vtodo_class1 = "(Tâche privée)";
+vtodo_class2 = "(Tâche confidentielle)";
+
 "closeThisWindowMessage" = "Merci! Vous pouvez maintenant fermer cette fenêtre ou consulter votre ";
 "Multicolumn Day View" = "Multicolonne";
index da5f595b226fda8e5ffe31676f55b4cf156c22df..1f65637c7d0ad0744649dac6fe360000856c71e8 100644 (file)
@@ -400,5 +400,14 @@ validate_endbeforestart    = "Ihr Beginn ist nach dem Ende";
 "Location" = "Ort";
 "(Private Event)" = "(Privater Termin)";
 
+/* event list */
+vevent_class0 = "(Public event)";
+vevent_class1 = "(Private event)";
+vevent_class2 = "(Confidential event)";
+
+vtodo_class0 = "(Public task)";
+vtodo_class1 = "(Private task)";
+vtodo_class2 = "(Confidential task)";
+
 "closeThisWindowMessage" = "Vielen Dank! Sie können dieses Fenster jetzt schließen.";
 "Multicolumn Day View" = "Multicolonne";
index 23d899902b9584a6e8f11a6ce1cca694e20b531b..a5147130fb48c27acde2e02fa535935bf4c00559 100644 (file)
@@ -71,7 +71,7 @@
 {
   if (!event)
     {
-      event = (iCalEvent *) [[self clientObject] component: NO secure: NO];
+      event = (iCalEvent *) [[self clientObject] component: NO secure: YES];
       [event retain];
     }
 
   unsigned int minutes;
   iCalRecurrenceRule *rule;
 
-  event = (iCalEvent *) [[self clientObject] component: NO secure: YES];
+  [self event];
   if (event)
     {
       startDate = [event startDate];
   iCalRecurrenceRule *rule;
 
   clientObject = [self clientObject];
-  event = (iCalEvent *) [clientObject component: YES secure: NO];
+  [self event];
 
   [super takeValuesFromRequest: _rq inContext: _ctx];
 
index cb81e40f7d9158fc7aba61d243ce754150182355..99f4ee0e68d71eddbe4fb4897f98e234f819e49f 100644 (file)
@@ -23,8 +23,7 @@
 #ifndef UIXCALLISTINGACTIONVIEW_H
 #define UIXCALLISTINGACTIONVIEW_H
 
-
-#import <NGObjWeb/WODirectAction.h>
+#import <Common/WODirectAction+SOGo.h>
 
 @class NSCalendarDate;
 @class NSMutableDictionary;
index 3346c739e6ce5fae55659c351394bd745c44ab66..f5a25d5a9443ef8c19616c62d4815ba6bb79bfd1 100644 (file)
     }
 }
 
-- (void) _updatePrivacyInComponent: (NSMutableDictionary *) component
-                       fromFolder: (SOGoAppointmentFolder *) folder
-{
-  int privacyFlag;
-  NSString *roleString;
-
-  privacyFlag = [[component objectForKey: @"classification"] intValue];
-  roleString = [folder roleForComponentsWithAccessClass: privacyFlag
-                      forUser: userLogin];
-  if ([roleString isEqualToString: @"ComponentDAndTViewer"])
-    {
-      [component setObject: @"" forKey: @"c_title"];
-      [component setObject: @"" forKey: @"c_location"];
-    }
-}
-
 - (SOGoAppointmentFolder *) _aptFolder: (NSString *) folder
                      withClientObject: (SOGoAppointmentFolder *) clientObject
 {
   return aptFolder;
 }
 
+- (void) _fixComponentTitle: (NSMutableDictionary *) component
+                   withType: (NSString *) type
+{
+  NSString *labelKey;
+
+  labelKey = [NSString stringWithFormat: @"%@_class%@",
+                      type, [component objectForKey: @"c_classification"]];
+  [component setObject: [self labelForKey: labelKey]
+            forKey: @"c_title"];
+}
+
 - (NSArray *) _fetchFields: (NSArray *) fields
        forComponentOfType: (NSString *) component
 {
 
   marker = [NSNull null];
 
-   clientObject = [self clientObject];
+  clientObject = [self clientObject];
 
   folders = [[clientObject subFolders] objectEnumerator];
   currentFolder = [folders nextObject];
 
   infos = [NSMutableArray array];
+
   while (currentFolder)
     {
       if ([currentFolder isActive])
        {
-         currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
-                                        to: endDate
-                                        title: title
-                                        component: component] objectEnumerator];
+         currentInfos
+           = [[currentFolder fetchCoreInfosFrom: startDate
+                             to: endDate
+                             title: title
+                             component: component] objectEnumerator];
 
          while ((newInfo = [currentInfos nextObject]))
            {
-             [self _updatePrivacyInComponent: newInfo
-                   fromFolder: currentFolder];
              [newInfo setObject: [currentFolder nameInContainer]
                       forKey: @"c_folder"];
-             
+             if (![[newInfo objectForKey: @"c_title"] length])
+               [self _fixComponentTitle: newInfo withType: component];
              [infos addObject: [newInfo objectsForKeys: fields
                                         notFoundMarker: marker]];
            }
   newEvents = [NSMutableArray array];
   fields = [NSArray arrayWithObjects: @"c_name", @"c_folder", @"c_status",
                    @"c_title", @"c_startdate", @"c_enddate", @"c_location",
-                   @"c_isallday", nil];
+                   @"c_isallday", @"c_classification", nil];
   events = [[self _fetchFields: fields
                  forComponentOfType: @"vevent"] objectEnumerator];
   oldEvent = [events nextObject];
   [self _setupContext];
 
   fields = [NSArray arrayWithObjects: @"c_name", @"c_folder", @"c_status",
-                   @"c_title", @"c_enddate", nil];
+                   @"c_title", @"c_enddate", @"c_classification", nil];
 
   tasks = [[self _fetchFields: fields
                 forComponentOfType: @"vtodo"] objectEnumerator];
index 8ea3b9edf1b1641d45fce7cc022121c7e83c64d0..759726dd1885b9627b1d9524ec5612649fefdd0a 100644 (file)
@@ -73,7 +73,7 @@
 {
   if (!todo)
     {
-      todo = (iCalToDo *) [[self clientObject] component: NO secure: NO];
+      todo = (iCalToDo *) [[self clientObject] component: NO secure: YES];
       [todo retain];
     }
 
   NSString *duration;
   unsigned int minutes;
 
-  todo = (iCalToDo *) [[self clientObject] component: NO secure: YES];
+  [self todo];
   if (todo)
     {
       startDate = [todo startDate];
 - (void) takeValuesFromRequest: (WORequest *) _rq
                      inContext: (WOContext *) _ctx
 {
-  SOGoTaskObject *clientObject;
-
-  clientObject = [self clientObject];
-  todo = (iCalToDo *) [clientObject component: YES secure: NO];
+  [self todo];
 
   [super takeValuesFromRequest: _rq inContext: _ctx];
 
 
 - (id) changeStatusAction
 {
-  SOGoTaskObject *clientObject;
   NSString *newStatus;
 
-  clientObject = [self clientObject];
-  todo = (iCalToDo *) [clientObject component: NO secure: NO];
-  [todo retain];
+  [self todo];
   if (todo)
     {
       newStatus = [self queryParameterForKey: @"status"];
          [todo setPercentComplete: @"0"];
          [todo setStatus: @"IN-PROCESS"];
        }
-      [clientObject saveComponent: todo];
+      [[self clientObject] saveComponent: todo];
     }
 
   return [self responseWith204];
index 669cb7fcd7cf9630900f7a13005bbee9e09abecb..2e555aed1dc2f1c7d2e305ac672d3aa997d74126 100644 (file)
     </ul>
   </div>
 
+  <div class="menu" id="messagesListMenu">
+    <ul>
+      <li><var:string label:value="Forward"/></li>
+      <li><!-- separator --></li>
+      <li><var:string label:value="Move To"/></li>
+      <li><var:string label:value="Copy To"/></li>
+      <li><var:string label:value="Label"/></li>
+      <li><var:string label:value="Mark"/></li>
+      <li><!-- separator --></li>
+      <li><var:string label:value="Print Preview"/></li>
+      <li><var:string label:value="Print..."/></li>
+      <li><var:string label:value="Delete Selected Messages"/></li>
+    </ul>
+  </div>
+
   <div class="menu" id="messageContentMenu">
     <ul>
       <li><var:string label:value="Reply to Sender Only"/></li>
     </ul>
   </div>
 
+  <div class="menu" id="imageMenu">
+    <ul>
+      <li id="view_image"><var:string label:value="View Image"/></li>
+    </ul>
+  </div>
+
   <div id="leftPanel">
     <div class="titlediv"><var:string label:value="Folders" /></div>
     <div id="folderTreeContent"><!-- space --></div>
index bb01400d4fcd3e60b645811773653a6798cd748e..fde540e73b3c15c9e7bf6b5ebee8eee40e80b43b 100644 (file)
       <ul>
        <li target="generalView"><var:string
            label:value="General"/></li>
-       <li target="calendarOptionsView"><var:string
-           label:value="Calendar Options"/></li>
-       <li target="mailOptionsView"><var:string
-           label:value="Mail Options"/></li>
-       <li target="identitiesView"><var:string
-           label:value="Identities"/></li>
+       <var:if condition="userHasCalendarAccess">
+         <li target="calendarOptionsView"><var:string
+             label:value="Calendar Options"/></li>
+       </var:if>
+       <var:if condition="userHasMailAccess">
+         <li target="mailOptionsView"><var:string
+             label:value="Mail Options"/></li>
+         <li target="identitiesView"><var:string
+             label:value="Identities"/></li>
+       </var:if>
        <var:if condition="shouldDisplayPasswordChange">
          <li target="passwordView"><var:string label:value="Password"/></li>
        </var:if>
              /></label>
        </div>
       </div>
-      <div id="calendarOptionsView" class="tab">
-       <label><var:string label:value="Week begins on :"/>
-         <var:popup list="daysList" item="item"
-           string="itemWeekStartDay" selection="userWeekStartDay"
-           /></label><br/>
-       <label><var:string label:value="Day start time :"/>
-         <var:popup list="hoursList" item="item"
-           string="item" selection="userDayStartTime"
-           /></label>
-       <label><var:string label:value="Day end time :"/>
-         <var:popup list="hoursList" item="item"
-           string="item" selection="userDayEndTime"
-           /></label><br/>
-       <label><var:string label:value="First week of year :"/>
-         <var:popup list="firstWeekList" item="item"
-           string="itemFirstWeekText" selection="userFirstWeek"
-           /></label><br/>
-       <br/>
-       <label><input class="checkBox"
-           type="checkbox" var:selection="reminderEnabled"
-           var:checked="reminderEnabled"/><var:string
-           label:value="Enable reminders for Calendar items"/></label><br/>
-       <label><input class="checkBox"
-           type="checkbox" var:selection="remindWithASound"
-           var:checked="remindWithASound"/><var:string
-           label:value="Play a sound when a reminder comes due"/></label><br/>
-       <label><var:string label:value="Default reminder :"/>
-         <var:popup list="reminderTimesList" item="item"
-           string="itemReminderTimeText" selection="userReminderTime"/></label>
-      </div>
-      <div id="mailOptionsView" class="tab">
-       <label><var:string label:value="Check for new mail:"/>
-         <var:popup list="messageCheckList" item="item"
-           string="itemMessageCheckText" selection="userMessageCheck"/></label><br/>
-       <label><var:string label:value="Forward messages:"/>
-         <var:popup list="messageForwardingList" item="item"
-           string="itemMessageForwardingText" selection="userMessageForwarding"/></label><br/>
+      <var:if condition="userHasCalendarAccess">
+       <div id="calendarOptionsView" class="tab">
+         <label><var:string label:value="Week begins on :"/>
+           <var:popup list="daysList" item="item"
+             string="itemWeekStartDay" selection="userWeekStartDay"
+             /></label><br/>
+         <label><var:string label:value="Day start time :"/>
+           <var:popup list="hoursList" item="item"
+             string="item" selection="userDayStartTime"
+             /></label>
+         <label><var:string label:value="Day end time :"/>
+           <var:popup list="hoursList" item="item"
+             string="item" selection="userDayEndTime"
+             /></label><br/>
+         <label><var:string label:value="First week of year :"/>
+           <var:popup list="firstWeekList" item="item"
+             string="itemFirstWeekText" selection="userFirstWeek"
+             /></label><br/>
+         <br/>
+         <label><input class="checkBox"
+             type="checkbox" var:selection="reminderEnabled"
+             var:checked="reminderEnabled"/><var:string
+             label:value="Enable reminders for Calendar items"/></label><br/>
+         <label><input class="checkBox"
+             type="checkbox" var:selection="remindWithASound"
+             var:checked="remindWithASound"/><var:string
+             label:value="Play a sound when a reminder comes due"/></label><br/>
+         <label><var:string label:value="Default reminder :"/>
+           <var:popup list="reminderTimesList" item="item"
+             string="itemReminderTimeText" selection="userReminderTime"/></label>
+       </div>
+      </var:if>
+      <var:if condition="userHasMailAccess">
+       <div id="mailOptionsView" class="tab">
+         <label><var:string label:value="Check for new mail:"/>
+           <var:popup list="messageCheckList" item="item"
+             string="itemMessageCheckText" selection="userMessageCheck"/></label><br/>
+         <label><var:string label:value="Forward messages:"/>
+           <var:popup list="messageForwardingList" item="item"
+             string="itemMessageForwardingText" selection="userMessageForwarding"/></label><br/>
        <!--    <label><input
        const:name="inTheOffice" type="radio" const:value="YES"
        var:selection="inTheOffice"/>
        <label><var:string label:value="AutoReply only once to each sender with the following text :"/><br/>
        <textarea const:name="autoReplyText" var:value="autoReplyText"/>
       </label> -->
-      </div>
-      <div id="identitiesView" class="tab">
-       <!--<var:multiselection const:id="identitiesList" item="item"
+       </div>
+       <div id="identitiesView" class="tab">
+         <!--<var:multiselection const:id="identitiesList" item="item"
          list="identitiesList" displayString="itemIdentityText">
        </var:multiselection>
-       <br/>-->
-       <var:string label:value="Signature:"/><br/>
-       <textarea const:id="signature" const:name="signature"
-         var:value="signature"/>
-      </div>
+         <br/>-->
+         <var:string label:value="Signature:"/><br/>
+         <textarea const:id="signature" const:name="signature"
+           var:value="signature"/>
+       </div>
+      </var:if>
       <var:if condition="shouldDisplayPasswordChange">
        <div id="passwordView" class="tab">
          <label><var:string label:value="New password:"
index ef1a2d6f09204e6d4a45714c3adec56ce746b135..d942676ed6070fe596e0e734669e99af8624c413 100644 (file)
@@ -42,7 +42,8 @@
          >
          <var:if condition="shortUserNameForDisplay" const:value="anonymous"
            const:negate="YES"
-           ><var:if condition="shortUserNameForDisplay" const:value="wrongusernamepassword"
+           ><var:if condition="shortUserNameForDisplay"
+             const:value="wrongusernamepassword"
              const:negate="YES"
              ><var:if condition="isPopup" const:negate="YES"
                ><var:if condition="context.isUIxDebugEnabled"
                <div id="linkBanner" class="linkbanner">
                  <a id="logoff" var:href="logoffPath"
                    ><var:string label:value="Disconnect" /></a>
-                 <a var:href="relativeCalendarPath"
-                   ><var:string label:value="Calendar" /></a> |
-                 <a var:href="relativeContactsPath"
+                 <var:if condition="userHasCalendarAccess">
+                   <a id="calendarBannerLink"
+                     var:href="relativeCalendarPath"
+                     ><var:string label:value="Calendar" /></a> |
+                 </var:if>
+                 <a id="contactsBannerLink"
+                   var:href="relativeContactsPath"
                    ><var:string label:value="Address Book" /></a> |
-                 <a var:href="relativeMailPath"
-                   ><var:string label:value="Mail" /></a> |
-                 <a var:href="relativePreferencesPath"
+                 <var:if condition="userHasMailAccess">
+                   <a id="mailBannerLink" var:href="relativeMailPath"
+                     ><var:string label:value="Mail" /></a> |
+                 </var:if>
+                 <a id="preferencesBannerLink"
+                   var:href="relativePreferencesPath"
                    ><var:string label:value="Preferences" /></a>
                  <var:if condition="context.isUIxDebugEnabled"
-                   >| <a href="#"><var:string
+                   >| <a id="consoleBannerLink"
+                     href="#"><var:string
                        label:value="Log Console (dev.)" /></a
                      ></var:if>
                </div>
          <var:foreach list="additionalJSFiles" item="item"
            ><script type="text/javascript" var:src="item"><!-- space --></script>
          </var:foreach>
-         <script type="text/javascript">addEvent(window, 'load', onFinalLoadHandler);</script>
+         <script type="text/javascript">FastInit.addOnLoad(onFinalLoadHandler);</script>
         </var:if>
         <var:if condition="isCompatibleBrowser" const:negate="YES">
          <div id="loginScreen">
index e452b4367c147007a4465b5a7cadbf79774092f2..657ea7557efeb8d03ecfe3d3ad0c44a1b544fc91 100644 (file)
@@ -10,11 +10,11 @@ if (typeof textMailAccounts != 'undefined') {
 }
 
 var Mailer = {
+ currentMailbox: null,
+ currentMailboxType: "",
  currentMessages: {},
  maxCachedMessages: 20,
- cachedMessages: new Array(),
- currentMailbox: null,
- currentMailboxType: ""
+ cachedMessages: new Array()
 };
 
 var usersRightsWindowHeight = 320;
@@ -238,6 +238,7 @@ function deleteSelectedMessagesCallback(http) {
     if (isHttpStatus204(http.status)) {
       var data = http.callbackData;
       deleteCachedMessage(data["messageId"]);
+      deleteMessageRequestCount--;
       if (Mailer.currentMailbox == data["mailbox"]) {
        
        var div = $('messageContent');
@@ -247,13 +248,17 @@ function deleteSelectedMessagesCallback(http) {
        }
 
        var row = $("row_" + data["id"]);
+       var nextRow = row.next("tr");
+       if (!nextRow)
+         nextRow = row.previous("tr");
        row.parentNode.removeChild(row);
 //     row.addClassName("deleted"); // when we'll offer "mark as deleted"
-
-       deleteMessageRequestCount--;
       
-       if (deleteMessageRequestCount == 0)
+       if (deleteMessageRequestCount == 0) {
+         if (nextRow)
+           Mailer.currentMessages[Mailer.currentMailbox] = nextRow.getAttribute("id").substr(4);
          openMailbox(data["mailbox"], true);
+       }
       }
     }
   }
@@ -363,19 +368,30 @@ function onMailboxTreeItemClick(event) {
 
 function _onMailboxMenuAction(menuEntry, error, actionName) {
   var targetMailbox = menuEntry.mailbox.fullName();
+  var messages = new Array();
 
   if (targetMailbox == Mailer.currentMailbox)
     window.alert(labels[error]);
   else {
-    var message;
     if (document.menuTarget.tagName == "DIV")
-      message = Mailer.currentMessages[Mailer.currentMailbox];
+      // Menu called from message content view
+      messages.push(Mailer.currentMessages[Mailer.currentMailbox]);
+    else if (Object.isArray(document.menuTarget))
+      // Menu called from multiple selection in messages list view
+      messages = $(document.menuTarget).collect(function(row) {
+         return row.getAttribute("id").substr(4);
+       });
     else
-      message = document.menuTarget.getAttribute("id").substr(4);
-
-    var urlstr = (URLForFolderID(Mailer.currentMailbox) + "/" + message
-                 + "/" + actionName + "?folder=" + targetMailbox);
-    triggerAjaxRequest(urlstr, folderRefreshCallback, Mailer.currentMailbox);
+      // Menu called from one selection in messages list view
+      messages.push(document.menuTarget.getAttribute("id").substr(4));
+
+    var url_prefix = URLForFolderID(Mailer.currentMailbox) + "/";
+    messages.each(function(msgid, i) {
+       var url = url_prefix + msgid + "/" + actionName
+         + "?folder=" + targetMailbox;
+       triggerAjaxRequest(url, folderRefreshCallback,
+                          ((i == messages.size() - 1)?Mailer.currentMailbox:""));
+      });
   }
 }
 
@@ -477,7 +493,7 @@ function messageListCallback(http) {
   
   if (http.readyState == 4
       && http.status == 200) {
-    document.messageListAjaxRequest = null;    
+    document.messageListAjaxRequest = null;
 
     if (table) {
       // Update table
@@ -570,16 +586,15 @@ function quotasCallback(http) {
 
 function onMessageContextMenu(event) {
   var menu = $('messageListMenu');
-  Event.observe(menu, "hideMenu", onMessageContextMenuHide);
-  popupMenu(event, "messageListMenu", this);
-
   var topNode = $('messageList');
   var selectedNodes = topNode.getSelectedRows();
-  for (var i = 0; i < selectedNodes.length; i++)
-    selectedNodes[i].deselect();
-  topNode.menuSelectedRows = selectedNodes;
-  topNode.menuSelectedEntry = this;
-  this.select();
+
+  Event.observe(menu, "hideMenu", onMessageContextMenuHide);
+  
+  if (selectedNodes.length > 1)
+    popupMenu(event, "messagesListMenu", selectedNodes);
+  else
+    popupMenu(event, "messageListMenu", this);    
 }
 
 function onMessageContextMenuHide(event) {
@@ -738,6 +753,7 @@ function configureLinksInMessage() {
                                                       messageDiv)[0];
   if (!document.body.hasClassName("popup"))
     mailContentDiv.observe("contextmenu", onMessageContentMenu);
+
   var anchors = messageDiv.getElementsByTagName('a');
   for (var i = 0; i < anchors.length; i++)
     if (anchors[i].href.substring(0,7) == "mailto:") {
@@ -747,6 +763,10 @@ function configureLinksInMessage() {
     else
       $(anchors[i]).observe("click", onMessageAnchorClick);
 
+  var images = messageDiv.getElementsByTagName('img');
+  for (var i = 0; i < images.length; i++)
+    $(images[i]).observe("contextmenu", onImageClick);
+
   var editDraftButton = $("editDraftButton");
   if (editDraftButton)
     Event.observe(editDraftButton, "click",
@@ -810,7 +830,8 @@ function resizeMailContent() {
 
 function onMessageContentMenu(event) {
   var element = getTarget(event);
-  if (element.tagName == 'A' && element.href.substring(0,7) == "mailto:")
+  if ((element.tagName == 'A' && element.href.substring(0,7) == "mailto:")
+      || element.tagName == 'IMG')
     // Don't show the default contextual menu; let the click propagate to 
     // other observers
     return true;
@@ -832,6 +853,12 @@ function onMessageAnchorClick(event) {
   preventDefault(event);
 }
 
+function onImageClick(event) {
+  popupMenu(event, 'imageMenu', this);
+  preventDefault(event);
+  return false;
+}
+
 function messageCallback(http) {
   var div = $('messageContent');
 
@@ -937,6 +964,11 @@ function onMenuViewMessageSource(event) {
   preventDefault(event);
 }
 
+function viewImage(event) {
+  var img = document.menuTarget;
+  window.open(img.getAttribute("src"),'_blank','resizable=1'); 
+}
+
 /* contacts */
 function newContactFromEmail(event) {
   var mailto = document.menuTarget.innerHTML;
@@ -1267,6 +1299,7 @@ function updateMailboxTreeInPage() {
                  onFolderMenuClick.bindAsEventListener(nodes[i]));
     if (!inboxFound
        && nodes[i].parentNode.getAttribute("datatype") == "inbox") {
+      Mailer.currentMailboxType = "inbox";
       openInbox(nodes[i]);
       inboxFound = true;
     }
@@ -1355,7 +1388,7 @@ function updateMailboxMenus() {
       menu.appendChild(menuEntry);
       var mailbox = accounts[mailAccounts[i]];
       var newSubmenuId = generateMenuForMailbox(mailbox,
-                                             key, mailboxActions[key]);
+                                               key, mailboxActions[key]);
       submenuIds.push(newSubmenuId);
     }
     initMenu(menuDIV, submenuIds);
@@ -1500,27 +1533,59 @@ function onMenuChangeToTrashFolder(event) {
 }
 
 function onMenuLabelNone() {
-  var rowId = document.menuTarget.getAttribute("id").substr(4);
-  var messageId = Mailer.currentMailbox + "/" + rowId;
-  var urlstr = ApplicationBaseURL + messageId + "/removeAllLabels";
-  triggerAjaxRequest(urlstr, messageFlagCallback,
-                    { mailbox: Mailer.currentMailbox, msg: rowId, label: null } );
+  var messages = new Array();
+
+  if (document.menuTarget.tagName == "DIV")
+    // Menu called from message content view
+    messages.push(Mailer.currentMessages[Mailer.currentMailbox]);
+  else if (Object.isArray(document.menuTarget))
+    // Menu called from multiple selection in messages list view
+    $(document.menuTarget).collect(function(row) {
+       messages.push(row.getAttribute("id").substr(4));
+      });
+  else
+    // Menu called from one selection in messages list view
+    messages.push(document.menuTarget.getAttribute("id").substr(4));
+  
+  var url = ApplicationBaseURL + Mailer.currentMailbox + "/";
+  messages.each(function(id) {
+      triggerAjaxRequest(url + id + "/removeAllLabels",
+                        messageFlagCallback,
+                        { mailbox: Mailer.currentMailbox, msg: id, label: null } );
+    });  
 }
 
 function _onMenuLabelFlagX(flag) {
-  var flags = document.menuTarget.getAttribute("labels").split(" ");
-
-  var rowId = document.menuTarget.getAttribute("id").substr(4);
-  var messageId = Mailer.currentMailbox + "/" + rowId;
+  var messages = new Hash();
+
+  if (document.menuTarget.tagName == "DIV")
+    // Menu called from message content view
+    messages.set(Mailer.currentMessages[Mailer.currentMailbox],
+                $('tr#row_' + Mailer.currentMessages[Mailer.currentMailbox]).getAttribute("labels"));
+  else if (Object.isArray(document.menuTarget))
+    // Menu called from multiple selection in messages list view
+    $(document.menuTarget).collect(function(row) {
+       messages.set(row.getAttribute("id").substr(4),
+                    row.getAttribute("labels"));
+      });
+  else
+    // Menu called from one selection in messages list view
+    messages.set(document.menuTarget.getAttribute("id").substr(4),
+                document.menuTarget.getAttribute("labels"));
+  
+  var url = ApplicationBaseURL + Mailer.currentMailbox + "/";
+  messages.keys().each(function(id) {
+      var flags = messages.get(id).split(" ");
+      var operation = "add";
+      
+      if (flags.indexOf("label" + flag) > -1)
+       operation = "remove";
 
-  var operation = "add";
-  if (flags.indexOf("label" + flag) > -1)
-    operation = "remove";
-  var urlstr = (ApplicationBaseURL + messageId
-               + "/" + operation + "Label" + flag);
-  triggerAjaxRequest(urlstr, messageFlagCallback,
-                    { mailbox: Mailer.currentMailbox, msg: rowId,
-                      label: operation + flag } );
+      triggerAjaxRequest(url + id + "/" + operation + "Label" + flag,
+                        messageFlagCallback,
+                        { mailbox: Mailer.currentMailbox, msg: id,
+                            label: operation + flag } );
+    });
 }
 
 function onMenuLabelFlag1() {
@@ -1650,6 +1715,13 @@ function getMenus() {
                                       "mark-menu", "-", null,
                                       onMenuViewMessageSource, null,
                                       null, onMenuDeleteMessage);
+  menus["messagesListMenu"] = new Array(onMenuForwardMessage,
+                                       "-", "moveMailboxMenu",
+                                      "copyMailboxMenu", "label-menu",
+                                      "mark-menu", "-",
+                                       null, null,
+                                       onMenuDeleteMessage);
+  menus["imageMenu"] = new Array(viewImage);
   menus["messageContentMenu"] = new Array(onMenuReplyToSender,
                                          onMenuReplyToAll,
                                          onMenuForwardMessage,
index 5ca53f52154cc4b96db8859074afdc1b2ddd6707..32ea684afbbadd260c88a731fa105860a5e44a95 100644 (file)
@@ -356,12 +356,12 @@ function eventsListCallback(http) {
        td = document.createElement("td");
        row.appendChild(td);
        Event.observe(td, "mousedown", listRowMouseDownHandler, true);
-       td.appendChild(document.createTextNode(data[i][8]));
+       td.appendChild(document.createTextNode(data[i][9]));
 
        td = document.createElement("td");
        row.appendChild(td);
        Event.observe(td, "mousedown", listRowMouseDownHandler, true);
-       td.appendChild(document.createTextNode(data[i][9]));
+       td.appendChild(document.createTextNode(data[i][10]));
       
        td = document.createElement("td");
        row.appendChild(td);
@@ -1476,7 +1476,7 @@ function onMenuModify(event) {
   var selected = folders.getSelectedNodes()[0];
 
   if (UserLogin == selected.getAttribute("owner")) {
-    var node = selected.childNodes[4];
+    var node = selected.childNodes[selected.childNodes.length - 1];
     var currentName = node.nodeValue.trim();
     var newName = window.prompt(labels["Name of the Calendar"],
                                currentName);
index 0a0383293eeb3cb59fee6948fd51181ca965c341..f9a5f5c6ea471e0b87cb52c86e10f201d05a1b1b 100644 (file)
@@ -93,7 +93,7 @@ TEXTAREA
   padding-bottom: 0em; }
 
 SELECT#calendarList
-{ width: 18em; }
+{ width: 17em; }
 
 A#changeUrlButton
 { margin-left: 1em; }
index c5a6f602b4626017fa19044aefc87d4eeb78a3ff..ba470c44c1ddf01fe951f9b1ecaffe5f53e59fa0 100644 (file)
@@ -161,7 +161,7 @@ function clickedEditorSend(sender) {
   window.shouldPreserve = true;
   document.pageform.action = "send";
   document.pageform.submit();
-
+  
   return false;
 }
 
@@ -256,7 +256,7 @@ function onTextFocus() {
   }
   if (signatureLength > 0) {
     var length = this.getValue().length - signatureLength - 1;
-    this.setSelectionRange(length, length);
+    this.selectText(length, length);
   }
   Event.stopObserving(this, "focus", onTextFocus);
 }
index ba274a4e6f172d417e9a0746f991a8eb99d9021b..62776f379a5f74f69faab0c8e33275b6b1cdbece 100644 (file)
@@ -311,6 +311,7 @@ SPAN.toolbarButton:active
   list-style-image: none;
   margin: 0px;
   padding: 0px;
+  padding-top: 1px;
   border-top: 1px solid #fff;
   border-left: 1px solid #fff;
   border-right: 1px solid #9e9a92;
@@ -319,8 +320,8 @@ SPAN.toolbarButton:active
 .menu LI
 { padding-left: 1em;
   padding-right: 1em;
-  padding-top: .2em;
-  padding-bottom: .2em;
+  padding-top: .15em;
+  padding-bottom: .15em;
   margin: 0px;
   width: auto;
   white-space: nowrap; }
index a7f526f33eecf88343eda1c54beb18326abdd461..ce1bace1bb87a762e93cf3d805451994461ccd54 100644 (file)
@@ -241,9 +241,10 @@ function openMailComposeWindow(url, wId) {
   else {
     var r = new RegExp("[\.\/-]", "g");
     wId = wId.replace(r, "_");
-    if (document.body.hasClassName("popup"))
-      parentWindow = window.opener;
   }
+  
+  if (document.body.hasClassName("popup"))
+    parentWindow = window.opener;
 
   var w = parentWindow.open(url, wId,
                       "width=680,height=520,resizable=1,scrollbars=1,toolbar=0,"
@@ -508,6 +509,11 @@ function onRowClick(event) {
   }
 
   var initialSelection = $(node.parentNode).getSelectedNodes();
+
+  if (initialSelection.length > 0 && !Event.isLeftClick(event))
+    // Ignore non primary-click (ie right-click)
+    return true;
+  
   if ((event.shiftKey == 1 || event.ctrlKey == 1)
       && (lastClickedRow >= 0)
       && (acceptMultiSelect(node.parentNode)
@@ -758,10 +764,14 @@ function popupSubmenu(event) {
 
     var menuTop = (parentNode.offsetTop - 1
                   + this.offsetTop);
+
     if (window.height()
-       < (menuTop + submenuNode.offsetHeight)
-       && submenuNode.offsetHeight < window.height())
-      menuTop -= submenuNode.offsetHeight - this.offsetHeight - 4;
+       < (menuTop + submenuNode.offsetHeight))
+      if (submenuNode.offsetHeight < window.height())
+       menuTop = window.height() - submenuNode.offsetHeight;
+      else
+       menuTop = 0;
+
     var menuLeft = (parentNode.offsetLeft + parentNode.offsetWidth - 3);
     if (window.width()
        < (menuLeft + submenuNode.offsetWidth))
@@ -1107,7 +1117,7 @@ function initMenus() {
 }
 
 function initMenu(menuDIV, callbacks) {
-  var lis = $(menuDIV.childNodesWithTag("ul")[0]).childNodesWithTag("li");
+  var lis = $(menuDIV.down("ul")).childNodesWithTag("li");
   for (var j = 0; j < lis.length; j++) {
     var node = $(lis[j]);
     node.observe("mousedown", listRowMouseDownHandler, false);
@@ -1355,15 +1365,24 @@ function onPreferencesClick(event) {
 function configureLinkBanner() {
   var linkBanner = $("linkBanner");
   if (linkBanner) {
-    var anchors = linkBanner.childNodesWithTag("a");
-    for (var i = 1; i < 3; i++) {
-      $(anchors[i]).observe("mousedown", listRowMouseDownHandler);
-      $(anchors[i]).observe("click", onLinkBannerClick);
+    var moduleLinks = [ "calendar", "contacts", "mail" ];
+    for (var i = 0; i < moduleLinks.length; i++) {
+      var link = $(moduleLinks[i] + "BannerLink");
+      if (link) {
+       link.observe("mousedown", listRowMouseDownHandler);
+       link.observe("click", onLinkBannerClick);
+      }
+    }
+    link = $("preferencesBannerLink");
+    if (link) {
+      link.observe("mousedown", listRowMouseDownHandler);
+      link.observe("click", onPreferencesClick);
+    }
+    link = $("consoleBannerLink");
+    if (link) {
+      link.observe("mousedown", listRowMouseDownHandler);
+      link.observe("click", toggleLogConsole);
     }
-    $(anchors[4]).observe("mousedown", listRowMouseDownHandler);
-    $(anchors[4]).observe("click", onPreferencesClick);
-    if (anchors.length > 5)
-      $(anchors[5]).observe("click", toggleLogConsole);
   }
 }