]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailReplyAction.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1026 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 #import "UIxMailEditorAction.h"
23
24 @interface UIxMailReplyAction : UIxMailEditorAction
25 @end
26
27 #import <SoObjects/Mailer/SOGoMailObject.h>
28 #import <SoObjects/Mailer/SOGoDraftObject.h>
29 #import <NGImap4/NGImap4EnvelopeAddress.h>
30 #import <NGImap4/NGImap4Envelope.h>
31 #import "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
114                                  keys: (NSArray *) _k
115 {
116   static NSString *textPartSeparator = @"\n---\n";
117   NSMutableString *ms;
118   unsigned i, count;
119   
120   ms = [NSMutableString stringWithCapacity:16000];
121   for (i = 0, count = [_k count]; i < count; i++) {
122     NSString *k, *v;
123     
124     k = [_k objectAtIndex:i];
125    
126     // TODO: this is DUP code to SOGoMailObject
127     if ([k isEqualToString:@"body[text]"])
128       k = @"";
129     else if ([k hasPrefix:@"body["]) {
130       k = [k substringFromIndex:5];
131       if ([k length] > 0) k = [k substringToIndex:([k length] - 1)];
132     }
133     
134     v = [_prts objectForKey:k];
135     if (![v isKindOfClass:[NSString class]]) {
136       [self logWithFormat:@"Note: cannot show part %@", k];
137       continue;
138     }
139     if ([v length] == 0)
140       continue;
141     
142     if (i != 0) [ms appendString:textPartSeparator];
143     [ms appendString:[v stringByApplyingMailQuoting]];
144   }
145   return ms;
146 }
147
148 - (NSString *)contentForReply {
149   NSArray      *keys, *partInfos;
150   NSDictionary *parts, *infos;
151   SOGoMailObject *co;
152
153   co = [self clientObject];
154   keys = [co plainTextContentFetchKeys];
155   infos = [co fetchCoreInfos];
156   partInfos = [infos objectForKey: keys];
157   NSLog (@"infos: '%@'", infos);
158
159   if ([keys count] == 0)
160     return nil;
161   
162   if ([keys count] > 1) {
163     /* filter keys, only include top-level, or if none, the first */
164     NSMutableArray *topLevelKeys = nil;
165     unsigned i;
166     
167     for (i = 0; i < [keys count]; i++) {
168       NSRange r;
169       
170       r = [[keys objectAtIndex:i] rangeOfString:@"."];
171       if (r.length > 0)
172         continue;
173       
174       if (topLevelKeys == nil) 
175         topLevelKeys = [NSMutableArray arrayWithCapacity:4];
176       [topLevelKeys addObject:[keys objectAtIndex:i]];
177     }
178     
179     if ([topLevelKeys count] > 0) {
180       /* use top-level keys if we have some */
181       keys = topLevelKeys;
182     }
183     else {
184       /* just take the first part */
185       keys = [NSArray arrayWithObject:[keys objectAtIndex:0]];
186     }
187   }
188   
189   parts = [[self clientObject] fetchPlainTextStrings:keys];
190   return [self contentForReplyOnParts:parts keys:keys];
191 }
192
193 - (id)replyToAll:(BOOL)_replyToAll {
194   NSMutableDictionary *info;
195   NSException *error;
196   id result;
197   id tmp;
198   
199   /* ensure mail exists and is filled */
200   
201   // TODO: we could transport the body structure in a hidden field of the mail
202   //       viewer to avoid refetching the core-info?
203   tmp = [[self clientObject] fetchCoreInfos];
204   if ([tmp isKindOfClass:[NSException class]])
205     return tmp;
206   if (![tmp isNotNull])
207     return [self didNotFindMailError];
208
209   /* setup draft */
210   
211   if ((error = [self _setupNewDraft]) != nil)
212     return error;
213   
214   /* fill draft info */
215   
216   info = [NSMutableDictionary dictionaryWithCapacity:16];
217   
218   [info setObject:[self replySubject:[[self clientObject] subject]]
219         forKey:@"subject"];
220   [self fillInReplyAddresses:info replyToAll:_replyToAll 
221         envelope:[[self clientObject] envelope]];
222   
223   /* fill in text content */
224   
225   if ((tmp = [self contentForReply]) != nil)
226     [info setObject:tmp forKey:@"text"];
227   
228   /* save draft info */
229
230   if ((error = [self->newDraft storeInfo:info]) != nil)
231     return error;
232   
233   // TODO: we might want to pass the original URL to the editor for a final
234   //       redirect back to the message?
235   result = [self redirectToEditNewDraft];
236   [self reset];
237   return result;
238 }
239
240 - (id)replyAction {
241   return [self replyToAll:NO];
242 }
243 - (id)replyallAction {
244   return [self replyToAll:YES];
245 }
246
247 @end /* UIxMailReplyAction */