+2007-11-19 Wolfgang Sourdeau <wsourdeau@inverse.ca>
+
+ * UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor
+ -toolbar]): rewrote in a way that ensures that each case is
+ handled properly.
+
+ * SoObjects/SOGo/SOGoUser.m ([SOGoUser -isEqual:otherUser]): new
+ override method.
+
+ * UI/Scheduler/UIxTaskEditor.m ([-acceptAction])
+ ([-declineAction]): commented out unused methods.
+
+ * UI/MailPartViewers/UIxMailPartICalActions.m
+ ([UIxMailPartICalActions -deleteFromCalendarAction]): actually
+ delete the found object.
+
+2007-11-18 Ludovic Marcotte <ludovic@inverse.ca>
+
+ * SoObjects/Mailer/SOGoMailBodyPart.m
+ SoObjects/Mailer/SOGoMailObject.m
+ UI/MailPartViewers/UIxMailPartViewer.m
+ Added support of messages containing non-textual
+ content and no parts.
+
+ * UI/MailerUI/UIxMailView.m
+ SoObjects/Mailer/SOGoMailObject.m
+ UI/Templates/MailerUI/UIxMailView.wox
+ Added support for the Reply-To header upon
+ message display.
+
2007-11-18 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor -saveAction]):
if (data == nil) return nil;
/* check for content encodings */
-
- if ((enc = [[self partInfo] valueForKey:@"encoding"]) != nil) {
- enc = [enc uppercaseString];
-
- if ([enc isEqualToString:@"BASE64"])
- data = [data dataByDecodingBase64];
- else if ([enc isEqualToString:@"7BIT"])
- ; /* keep data as is */ // TODO: do we need to change encodings?
- else
- [self errorWithFormat:@"unsupported encoding: %@", enc];
- }
+ enc = [[self partInfo] valueForKey: @"encoding"];
+
+ /* if we haven't found one, check out the main message's encoding
+ as we could be trying to fetch the message's content as a part */
+ if (!enc)
+ enc = [[[[self mailObject] fetchCoreInfos] valueForKey: @"body"]
+ valueForKey: @"encoding"];
+
+ if (enc)
+ {
+ enc = [enc uppercaseString];
+
+ if ([enc isEqualToString:@"BASE64"])
+ data = [data dataByDecodingBase64];
+ else if ([enc isEqualToString:@"7BIT"])
+ ; /* keep data as is */ // TODO: do we need to change encodings?
+ else
+ [self errorWithFormat:@"unsupported encoding: %@", enc];
+ }
return data;
}
would address the MIME part 1.2.3 of the mail 12345 in the folder INBOX.
*/
-@class NSData, NSString, NSArray, NSCalendarDate, NSException, NSDictionary;
-@class NGImap4Envelope, NGImap4EnvelopeAddress;
+@class NSArray;
+@class NSCalendarDate;
+@class NSData;
+@class NSDictionary;
+@class NSException;
+@class NSString;
+
+@class NGImap4Envelope;
+@class NGImap4EnvelopeAddress;
@interface SOGoMailObject : SOGoMailBaseObject
{
/* core infos */
-- (BOOL)doesMailExist;
-- (id)fetchCoreInfos; // TODO: what does it do?
+- (BOOL) doesMailExist;
+- (id) fetchCoreInfos; // TODO: what does it do?
-- (NGImap4Envelope *)envelope;
-- (NSString *)subject;
-- (NSString *)decodedSubject;
-- (NSCalendarDate *)date;
-- (NSArray *)fromEnvelopeAddresses;
-- (NSArray *)toEnvelopeAddresses;
-- (NSArray *)ccEnvelopeAddresses;
+- (NGImap4Envelope *) envelope;
+- (NSString *) subject;
+- (NSString *) decodedSubject;
+- (NSCalendarDate *) date;
+- (NSArray *) fromEnvelopeAddresses;
+- (NSArray *) replyToEnvelopeAddresses;
+- (NSArray *) toEnvelopeAddresses;
+- (NSArray *) ccEnvelopeAddresses;
- (NSDictionary *) mailHeaders;
-- (id)bodyStructure;
-- (id)lookupInfoForBodyPart:(id)_path;
+- (id) bodyStructure;
+- (id) lookupInfoForBodyPart:(id)_path;
/* content */
-- (NSData *)content;
-- (NSString *)contentAsString;
+- (NSData *) content;
+- (NSString *) contentAsString;
/* bulk fetching of plain/text content */
-- (NSArray *)plainTextContentFetchKeys;
-- (NSDictionary *)fetchPlainTextParts:(NSArray *)_fetchKeys;
-- (NSDictionary *)fetchPlainTextParts;
-- (NSDictionary *)fetchPlainTextStrings:(NSArray *)_fetchKeys;
+- (NSArray *) plainTextContentFetchKeys;
+- (NSDictionary *) fetchPlainTextParts:(NSArray *)_fetchKeys;
+- (NSDictionary *) fetchPlainTextParts;
+- (NSDictionary *) fetchPlainTextStrings:(NSArray *)_fetchKeys;
/* flags */
-- (NSException *)addFlags:(id)_f;
-- (NSException *)removeFlags:(id)_f;
+- (NSException *) addFlags:(id)_f;
+- (NSException *) removeFlags:(id)_f;
/* deletion */
-- (BOOL)isDeletionAllowed;
+- (BOOL) isDeletionAllowed;
- (NSException *) trashInContext:(id)_ctx;
- (NSException *) copyToFolderNamed: (NSString *) folderName
inContext: (id)_ctx;
return [[self envelope] cc];
}
+- (NSArray *) replyToEnvelopeAddresses
+{
+ return [[self envelope] replyTo];
+}
+
- (NSData *) mailHeaderData
{
return [[self fetchCoreInfos] valueForKey: @"header"];
{
if ([content isKindOfClass: [NSData class]])
{
+#warning we ignore the charset here?
s = [[NSString alloc] initWithData: content
encoding: NSISOLatin1StringEncoding];
if (s)
NSString *mimeType;
parts = [[self bodyStructure] objectForKey: @"parts"];
+
+ /* We don't have parts here but we're trying to download the message's
+ content that could be an image/jpeg, as an example */
+ if ([parts count] == 0)
+ {
+ return [SOGoMailBodyPart objectWithName: @"1" inContainer: self];
+ }
+
part = [_key intValue] - 1;
if (part > -1 && part < [parts count])
{
- (NSException *) copyToFolderNamed: (NSString *) folderName
inContext: (id)_ctx
{
- SOGoMailAccounts *destFolder;
+ SOGoMailFolder *destFolder;
NSEnumerator *folders;
NSString *currentFolderName, *reason;
// TODO: check for safe HTTP method
- destFolder = [self mailAccountsFolder];
+ destFolder = (SOGoMailFolder *) [self mailAccountsFolder];
folders = [[folderName componentsSeparatedByString: @"/"] objectEnumerator];
currentFolderName = [folders nextObject];
currentFolderName = [folders nextObject];
return rolesForObject;
}
+- (BOOL) isEqual: (id) otherUser
+{
+ return ([otherUser isKindOfClass: [SoUser class]]
+ && [login isEqualToString: [otherUser login]]);
+}
+
@end /* SOGoUser */
{
SOGoAppointmentFolder *personalFolder;
SOGoAppointmentObject *eventObject;
+ NSString *cname;
+
+ eventObject = nil;
personalFolder = [user personalCalendarFolderInContext: context];
- eventObject = [personalFolder lookupName: uid
- inContext: context acquire: NO];
- if (![eventObject isKindOfClass: [SOGoAppointmentObject class]])
+ cname = [personalFolder resourceNameForEventUID: uid];
+ if (cname)
+ {
+ eventObject = [personalFolder lookupName: cname
+ inContext: context acquire: NO];
+ if (![eventObject isKindOfClass: [SOGoAppointmentObject class]])
+ eventObject = nil;
+ }
+
+ if (!eventObject)
eventObject = [SOGoAppointmentObject objectWithName: uid
inContainer: personalFolder];
chosenEvent = emailEvent;
else
{
- calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO];
+ calendarEvent = (iCalEvent *) [*eventObject component: NO
+ secure: NO];
if ([calendarEvent compare: emailEvent] == NSOrderedAscending)
chosenEvent = emailEvent;
else
if (emailEvent)
{
eventObject = [self _eventObjectWithUID: [emailEvent uid]];
+ [eventObject delete];
response = [self responseWith204];
}
else
if (![url hasSuffix: @"/"])
url = [url stringByAppendingString: @"/"];
- /* mail relative path to body-part */
-
- /* eg this was nil for a draft containing an HTML message */
+ /* if we get a message with an image/* or application/*
+ Content-Type, we must generate a 'fake' part since our
+ decoded mail won't have any. Also see SOGoMailBodyPart: -fetchBLOB
+ and SOGoMailObject: -lookupImap4BodyPartKey: inContext for
+ other workarounds */
+ if (!partPath || [partPath count] == 0)
+ partPath = [NSArray arrayWithObject: @"0"];
+
+ /* mail relative path to body-part
+ eg this was nil for a draft containing an HTML message */
if ([(n = [partPath componentsJoinedByString:@"/"]) isNotNull])
url = [url stringByAppendingString:n];
}; */
deleteFromCalendar = {
protectedBy = "View";
- actionClass = "UIxMailPartICalAction";
+ actionClass = "UIxMailPartICalActions";
actionName = "deleteFromCalendar";
};
};
return [[[self clientObject] ccEnvelopeAddresses] count] > 0 ? YES : NO;
}
+- (BOOL) hasReplyTo
+{
+ return [[[self clientObject] replyToEnvelopeAddresses] count] > 0 ? YES : NO;
+}
+
/* viewers */
- (id) contentViewerComponent
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
+#import <SoObjects/Appointments/iCalEntityObject+SOGo.h>
#import <SoObjects/Appointments/iCalPerson+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentFolders.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import <SoObjects/Appointments/SOGoTaskObject.h>
+#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
#import <SoObjects/SOGo/LDAPUserManager.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
[component setLastModified: now];
}
-- (NSString *) toolbar
+#warning the following methods probably share some code...
+- (NSString *) _toolbarForOwner: (SOGoUser *) ownerUser
{
- SOGoCalendarComponent *clientObject;
NSString *toolbarFilename;
- iCalPerson *participant;
iCalPersonPartStat participationStatus;
- SoSecurityManager *sm;
- NSString *owner;
-
- sm = [SoSecurityManager sharedSecurityManager];
- clientObject = [self clientObject];
- owner = [clientObject ownerInContext: context];
- participant = [clientObject findParticipantWithUID: owner];
-
- if (participant
- && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
- onObject: clientObject
- inContext: context])
+ if ([[component attendees] count]
+ && [component userIsParticipant: ownerUser]
+ && ![component userIsOrganizer: ownerUser])
{
- participationStatus = [participant participationStatus];
+ participationStatus
+ = [[component findParticipant: ownerUser] participationStatus];
/* Lightning does not manage participation status within tasks */
if (participationStatus == iCalPersonPartStatAccepted)
toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
else
toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
}
- else if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
- onObject: clientObject
- inContext: context])
+ else
{
- if ([[clientObject componentTag] isEqualToString: @"vevent"])
+ if ([component isKindOfClass: [iCalEvent class]])
toolbarFilename = @"SOGoAppointmentObject.toolbar";
else
toolbarFilename = @"SOGoTaskObject.toolbar";
}
+
+ return toolbarFilename;
+}
+
+- (NSString *) _toolbarForDelegate: (SOGoUser *) ownerUser
+{
+ SOGoCalendarComponent *clientObject;
+ SoSecurityManager *sm;
+ NSString *toolbarFilename, *adminToolbar;
+ iCalPersonPartStat participationStatus;
+
+ clientObject = [self clientObject];
+
+ if ([component isKindOfClass: [iCalEvent class]])
+ adminToolbar = @"SOGoAppointmentObject.toolbar";
else
- toolbarFilename = @"SOGoComponentClose.toolbar";
+ adminToolbar = @"SOGoTaskObject.toolbar";
+
+ sm = [SoSecurityManager sharedSecurityManager];
+ if ([[component attendees] count])
+ {
+ if ([component userIsOrganizer: ownerUser]
+ && ![sm validatePermission: SOGoCalendarPerm_ModifyComponent
+ onObject: clientObject
+ inContext: context])
+ toolbarFilename = adminToolbar;
+ else if ([component userIsParticipant: ownerUser]
+ && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
+ onObject: clientObject
+ inContext: context])
+ {
+ participationStatus
+ = [[component findParticipant: ownerUser] participationStatus];
+ /* Lightning does not manage participation status within tasks */
+ if (participationStatus == iCalPersonPartStatAccepted)
+ toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
+ else if (participationStatus == iCalPersonPartStatDeclined)
+ toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
+ else
+ toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
+ }
+ else
+ toolbarFilename = @"SOGoComponentClose.toolbar";
+ }
+ else
+ {
+ if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
+ onObject: clientObject
+ inContext: context])
+ toolbarFilename = adminToolbar;
+ else
+ toolbarFilename = @"SOGoComponentClose.toolbar";
+ }
+
+ return toolbarFilename;
+}
+
+- (NSString *) toolbar
+{
+ SOGoCalendarComponent *clientObject;
+ NSString *toolbarFilename;
+ SOGoUser *ownerUser;
+
+ clientObject = [self clientObject];
+ ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]
+ roles: nil];
+
+ if ([ownerUser isEqual: [context activeUser]])
+ toolbarFilename = [self _toolbarForOwner: ownerUser];
+ else
+ toolbarFilename = [self _toolbarForDelegate: ownerUser];
+
return toolbarFilename;
}
// TODO: add tentatively
-- (id) acceptOrDeclineAction: (BOOL) _accept
-{
- [[self clientObject] changeParticipationStatus:
- _accept ? @"ACCEPTED" : @"DECLINED"];
+// - (id) acceptOrDeclineAction: (BOOL) _accept
+// {
+// [[self clientObject] changeParticipationStatus:
+// _accept ? @"ACCEPTED" : @"DECLINED"];
- return self;
-}
+// return self;
+// }
-- (id) acceptAction
-{
- return [self acceptOrDeclineAction: YES];
-}
+// - (id) acceptAction
+// {
+// return [self acceptOrDeclineAction: YES];
+// }
-- (id) declineAction
-{
- return [self acceptOrDeclineAction: NO];
-}
+// - (id) declineAction
+// {
+// return [self acceptOrDeclineAction: NO];
+// }
- (id) changeStatusAction
{
};
};
methods = {
-// delete = {
-// protectedBy = "Delete Objects";
-// pageName = "UIxTaskView";
-// actionName = "delete";
-// };
edit = {
protectedBy = "ViewAllComponent";
pageName = "UIxTaskEditor";
pageName = "UIxTaskEditor";
actionName = "changeStatus";
};
- accept = {
- protectedBy = "RespondToComponent";
- pageName = "UIxTaskEditor";
- actionName = "accept";
- };
- decline = {
- protectedBy = "RespondToComponent";
- pageName = "UIxTaskEditor";
- actionName = "decline";
- };
};
};
};
</td>
</tr>
</var:if>
+ <var:if condition="hasReplyTo">
+ <tr class="mailer_fieldrow">
+ <td class="mailer_fieldname"><var:string label:value="Reply-To"/>:</td>
+ <td class="mailer_fieldvalue">
+ <var:foreach list="clientObject.replyToEnvelopeAddresses"
+ item="currentAddress">
+ <a var:href="currentAddressLink"
+ ><var:string value="currentAddress"
+ formatter="context.mailEnvelopeFullAddressFormatter"
+ /></a>
+ </var:foreach>
+ </td>
+ </tr>
+ </var:if>
</table>
<div class="mailer_mailcontent">