]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailReplyAction.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1017 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / MailerUI / UIxMailReplyAction.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "UIxMailEditorAction.h"
23
24 @interface UIxMailReplyAction : UIxMailEditorAction
25 @end
26
27 #include <SoObjects/Mailer/SOGoMailObject.h>
28 #include <SoObjects/Mailer/SOGoDraftObject.h>
29 #include <NGImap4/NGImap4EnvelopeAddress.h>
30 #include <NGImap4/NGImap4Envelope.h>
31 #include "common.h"
32
33 @implementation UIxMailReplyAction
34
35 - (BOOL)hasReplyPrefix:(NSString *)_subject {
36   static NSString *replyPrefixes[] = {
37     @"Re:", // regular
38     @"RE:", // Outlook v11 (English?)
39     @"AW:", // German Outlook v11
40     @"Re[", // numbered Re, eg "Re[2]:"
41     nil
42   };
43   unsigned i;
44   for (i = 0; replyPrefixes[i] != nil; i++) {
45     if ([_subject hasPrefix:replyPrefixes[i]])
46       return YES;
47   }
48   return NO;
49 }
50
51 - (NSString *)replySubject:(NSString *)_subject {
52   if (![_subject isNotNull] || [_subject length] == 0)
53     return _subject;
54   
55   if ([self hasReplyPrefix:_subject]) {
56     /* do not do: "Re: Re: Re: My Mail" - a single Re is sufficient ;-) */
57     return _subject;
58   }
59   
60   return [@"Re: " stringByAppendingString:_subject];
61 }
62
63 - (void)addEMailsOfAddresses:(NSArray *)_addrs toArray:(NSMutableArray *)_ma {
64   unsigned i, count;
65   
66   for (i = 0, count = [_addrs count]; i < count; i++)
67     [_ma addObject:[(NGImap4EnvelopeAddress *)[_addrs objectAtIndex:i] email]];
68 }
69
70 - (void)fillInReplyAddresses:(NSMutableDictionary *)_info
71   replyToAll:(BOOL)_replyToAll
72   envelope:(NGImap4Envelope *)_envelope
73 {
74   /*
75     The rules as implemented by Thunderbird:
76     - if there is a 'reply-to' header, only include that (as TO)
77     - if we reply to all, all non-from addresses are added as CC
78     - the from is always the lone TO (except for reply-to)
79     
80     Note: we cannot check reply-to, because Cyrus even sets a reply-to in the
81           envelope if none is contained in the message itself! (bug or
82           feature?)
83     
84     TODO: what about sender (RFC 822 3.6.2)
85   */
86   NSMutableArray *to;
87   NSArray *addrs;
88   
89   to = [NSMutableArray arrayWithCapacity:2];
90
91   /* first check for "reply-to" */
92   
93   addrs = [_envelope replyTo];
94   if ([addrs count] == 0) {
95     /* no "reply-to", try "from" */
96     addrs = [_envelope from];
97   }
98   [self addEMailsOfAddresses:addrs toArray:to];
99   [_info setObject:to forKey:@"to"];
100   
101   /* CC processing if we reply-to-all: add all 'to' and 'cc'  */
102   
103   if (_replyToAll) {
104     to = [NSMutableArray arrayWithCapacity:8];
105     
106     [self addEMailsOfAddresses:[_envelope to] toArray:to];
107     [self addEMailsOfAddresses:[_envelope cc] toArray:to];
108     
109     [_info setObject:to forKey:@"cc"];
110   }
111 }
112
113 - (NSString *)contentForReplyOnParts:(NSDictionary *)_prts keys:(NSArray *)_k {
114   static NSString *textPartSeparator = @"\n---\n";
115   NSMutableString *ms;
116   unsigned i, count;
117   
118   ms = [NSMutableString stringWithCapacity:16000];
119   
120   for (i = 0, count = [_k count]; i < count; i++) {
121     NSString *k, *v;
122     
123     k = [_k objectAtIndex:i];
124     
125     // TODO: this is DUP code to SOGoMailObject
126     if ([k isEqualToString:@"body[text]"])
127       k = @"";
128     else if ([k hasPrefix:@"body["]) {
129       k = [k substringFromIndex:5];
130       if ([k length] > 0) k = [k substringToIndex:([k length] - 1)];
131     }
132     
133     v = [_prts objectForKey:k];
134     if (![v isKindOfClass:[NSString class]]) {
135       [self logWithFormat:@"Note: cannot show part %@", k];
136       continue;
137     }
138     if ([v length] == 0)
139       continue;
140     
141     if (i != 0) [ms appendString:textPartSeparator];
142     [ms appendString:[v stringByApplyingMailQuoting]];
143   }
144   return ms;
145 }
146
147 - (NSString *)contentForReply {
148   NSArray      *keys;
149   NSDictionary *parts;
150   
151   keys = [[self clientObject] plainTextContentFetchKeys];
152   if ([keys count] == 0)
153     return nil;
154   
155   if ([keys count] > 1) {
156     /* filter keys, only include top-level, or if none, the first */
157     NSMutableArray *topLevelKeys = nil;
158     unsigned i;
159     
160     for (i = 0; i < [keys count]; i++) {
161       NSRange r;
162       
163       r = [[keys objectAtIndex:i] rangeOfString:@"."];
164       if (r.length > 0)
165         continue;
166       
167       if (topLevelKeys == nil) 
168         topLevelKeys = [NSMutableArray arrayWithCapacity:4];
169       [topLevelKeys addObject:[keys objectAtIndex:i]];
170     }
171     
172     if ([topLevelKeys count] > 0) {
173       /* use top-level keys if we have some */
174       keys = topLevelKeys;
175     }
176     else {
177       /* just take the first part */
178       keys = [NSArray arrayWithObject:[keys objectAtIndex:0]];
179     }
180   }
181   
182   parts = [[self clientObject] fetchPlainTextStrings:keys];
183   return [self contentForReplyOnParts:parts keys:keys];
184 }
185
186 - (id)replyToAll:(BOOL)_replyToAll {
187   NSMutableDictionary *info;
188   NSException *error;
189   id result;
190   id tmp;
191   
192   /* ensure mail exists and is filled */
193   
194   // TODO: we could transport the body structure in a hidden field of the mail
195   //       viewer to avoid refetching the core-info?
196   tmp = [[self clientObject] fetchCoreInfos];
197   if ([tmp isKindOfClass:[NSException class]])
198     return tmp;
199   if (![tmp isNotNull])
200     return [self didNotFindMailError];
201
202   /* setup draft */
203   
204   if ((error = [self _setupNewDraft]) != nil)
205     return error;
206   
207   /* fill draft info */
208   
209   info = [NSMutableDictionary dictionaryWithCapacity:16];
210   
211   [info setObject:[self replySubject:[[self clientObject] subject]]
212         forKey:@"subject"];
213   [self fillInReplyAddresses:info replyToAll:_replyToAll 
214         envelope:[[self clientObject] envelope]];
215   
216   /* fill in text content */
217   
218   if ((tmp = [self contentForReply]) != nil)
219     [info setObject:tmp forKey:@"text"];
220   
221   /* save draft info */
222
223   if ((error = [self->newDraft storeInfo:info]) != nil)
224     return error;
225   
226   // TODO: we might want to pass the original URL to the editor for a final
227   //       redirect back to the message?
228   result = [self redirectToEditNewDraft];
229   [self reset];
230   return result;
231 }
232
233 - (id)replyAction {
234   return [self replyToAll:NO];
235 }
236 - (id)replyallAction {
237   return [self replyToAll:YES];
238 }
239
240 @end /* UIxMailReplyAction */