see ChangeLog

Monotone-Revision: 9054022ef1ca8aeba6e34842d27d9b94ce002b89

Monotone-Author: dev-unix.inverse.qc.ca
Monotone-Date: 2006-06-15T19:34:10
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
dev-unix.inverse.qc.ca
2006-06-15 19:34:10 +00:00
commit f18c764ffa
965 changed files with 99196 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
2005-08-02 Helge Hess <helge.hess@skyrix.com>
* SOGoAppointmentObject.[hm]: added method to change participation
status of logged-in user (v0.9.50)
2005-07-21 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: place a hint ('isRecurrent') into flattened
rows created from recurrent events so these can be signaled in the
UI (v0.9.49)
2005-07-20 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.48
* SOGoAptMailInvitation.m, SOGoAptMailUpdate.m, SOGoAptMailDeletion.m,
SOGoAptMailRemoval.m: updated to contain new classes for additional
languages
* SOGoAptMailInvitation.wo, SOGoAptMailUpdate.wo,
SOGoAptMailDeletion.wo, SOGoAptMailRemoval.wo: replaced with language
dependend counterparts
* SOGoAptMail{English,French}Invitation.wo,
SOGoAptMail{English,French}Update.wo,
SOGoAptMail{English,French}Deletion.wo,
SOGoAptMail{English,French}Removal.wo: new language dependend
templates. Please note that due to an unresolved bug we cannot use
the standard WO lookup mechanism here.
2005-07-15 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.47
* SOGoAptMailDeletion.[wo,m]: new template being used when appointment
is deleted
* SOGoAptMailRemoval.[wo,m]: new template being used when attendee is
removed from appointment
* SOGoAppointmentObject.m: now sends emails for removal of attendees
and deletion of appointment
2005-07-15 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: fixed Cocoa warnings (v0.9.46)
* SOGoAppointmentFolder.m: fixed gcc 4.0 warnings (v0.9.45)
2005-07-14 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.44
* SOGoAppointmentFolder.m: explicitly set encoding for text and
calendar attachment parts
* SOGoAptMailNotification.m: added -appointmentURL accessor
* SOGoAptMailInvitation.wo, SOGoAptMailUpdate.wo: display
-appointmentURL instead of -homePageURL
* v0.9.43
* SOGoAppointmentFolder.m: changed redirect from 'schedule' to
'weekoverview'
2005-07-14 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: moved +globallyUniqueObjectId method to
SOGoFolder.m baseclass (v0.9.42)
2005-07-13 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: added a method to determine the resource
name from a vevent UID (v0.9.41)
2005-07-12 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentObject.m: tries ambitiously to detect the baseURL in
-homePageURLForPerson: now. This code doesn't really belong here
but currently there's no appropriate controller for dealing with it
around. Also removed mail notification testing code. (v0.9.40)
2005-07-12 Helge Hess <helge.hess@opengroupware.org>
* fixed compilation (v0.9.39)
2005-07-12 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.38
* GNUmakefile: changed inclusion of bundle.make to wobundle.make
because of new components
* v0.9.37
* SOGoAptMailNotification.[hm]: new component forming the basis for
the mail notification templates
* SOGoAptMailInvitation.[wo, m]: invitation mail template, sent to all
new attendees of an appointment
* SOGoAptMailUpdate.[wo, m]: update mail template, sent to all
attendees when appointment time is changed
* SOGoAppointmentObject.m: rewritten to use the iCalEventChanges object
from NGiCal to discover changes to saved appointments. Additionaly
email notifications are emitted on certain occasions (this is not
full iMip, yet). In case the appointment time changes, state for
all attendees is reset to NEEDS-ACTION.
2005-07-08 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: added 'partmails' and 'partstates' to
infos returned by -fetchOverviewInfosFrom:to: (v0.9.36)
2005-07-06 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.35
* GNUMakefile: added SOGoFreeBusyObject.m
* product.plist: added SOGoFreeBusyObject
* SOGoFreeBusyObject.[hm]: new class resembling free/busy information
for a particular user. Because free/busy information isn't solely
based upon appointments, this is now a part of the user folder.
However the information contained therein is still based on
information provided by appointments, only. This should be fixed in
a later release.
* SOGoAppointmentFolder.m: new API to lookup freeBusyObjects for an
array of uids.
2005-07-05 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: fetch new priority field in core infos
(v0.9.34)
2005-07-04 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: fetch new priority field in overview infos
(v0.9.33)
2005-03-20 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: changed to use GDLContentStore (v0.9.32)
2005-03-03 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoGroupAppointmentFolder.m: fixed incorrect creation of a mutable
dictionary (was still immutable) (v0.9.31)
2005-03-02 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: make sure fetchedInfos are sorted
ascending - this simplifies client code (v0.9.30)
* SOGoAppointmentFolder.m: changed debugLog to use NGLogging (v0.9.29)
2005-03-01 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: significantly changed handling of cyclic
events. Information for cyclic event calculation is now contained
in the quick info, making full fetches obsolete - performance
should improve significantly this way (v0.9.28)
2005-02-22 Helge Hess <helge.hess@skyrix.com>
* SOGoAppointmentFolder.m: minor code cleanups (v0.9.27)
2005-02-17 Helge Hess <helge.hess@opengroupware.org>
* fixed for removal of SOGoLogic (v0.9.26)
2005-02-06 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentObject.m, SOGoAppointmentFolder.m: added proper
message/folder class (v0.9.25)
2004-11-09 Helge Hess <helge.hess@skyrix.com>
* SOGoAppointmentObject.m: improved error handling in save method
(v0.9.16)
2004-10-20 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: fixed a warning on MacOSX (v0.9.15)
* SOGoAppointmentObject.m(saveContentString:): fixed a warning with new
objects (v0.9.14)
2004-10-19 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.h: added -fetchAllSOGoAppointments method
which performs a bulk fetch on all files contained in the folder
and parses all contents into an array of SOGoAppointment objects
(used in the iCal file support, try to avoid this method, expensive!)
(v0.9.13)
2004-09-25 Helge Hess <helge.hess@opengroupware.org>
* fixed compilation on MacOSX (v0.9.12)
2004-09-08 Helge Hess <helge.hess@opengroupware.org>
* SOGoGroupAppointmentFolder.m: do not call -sleep in fetch method
(disconnects the folder from the parent folder), instead added a
-resetFolderCaches methods which performs some cleanups before a
fetch (v0.9.11)
2004-08-16 Helge Hess <helge.hess@skyrix.com>
* SOGoAppointmentFolder.m: added method to create a group folder and
a group calendar for a set of IDs (for internal queries) (v0.9.10)
* SOGoAppointmentObject.m, SOGoAppointmentFolder.m: moved calendar
folder lookup methods to folder class (the object uses its container
to lookup the folder) (v0.9.9)
* SOGoAppointmentObject.m: finished multi folder storage methods
(v0.9.8)
* SOGoAppointmentObject.m: added multi folder storage methods (v0.9.7)
2004-08-14 Helge Hess <helge.hess@skyrix.com>
* SOGoGroupAppointmentFolder.m: implemented proper URL generation for
group calendars (will lookup the appointment in the proper "exact"
folder (v0.9.6)
* SOGoAppointmentFolder.m: fetch location, isallday, iscycle and
partmails fields in -fetchCoreInfo (v0.9.5)
2004-08-12 Helge Hess <helge.hess@skyrix.com>
* v0.9.4
* SOGoAppointmentFolder.m: added -baseURLForAptWithUID:inContext: for
determining the resource URL of an appointment, used by calendar
views to locate the viewer page
2004-08-11 Helge Hess <helge.hess@skyrix.com>
* SOGoGroupAppointmentFolder: can merge input folders, tracks conflicts
in 'conflicts' key of the record (should be displayed somehow in the
UI) (v0.9.3)
* v0.9.2
* SOGoAppointmentFolder.m, SOGoGroupAppointmentFolder: added
-calendarUIDs method which returns the UIDs of the currently
displayed persons (eg the members of a group)
* started SOGoGroupAppointmentFolder
2004-07-02 Helge Hess <helge.hess@opengroupware.org>
* SOGoAppointmentFolder.m: added method to fetch core info from other
OCS folders, correct timezone to MET after fetch core info
2004-07-01 Helge Hess <helge.hess@opengroupware.org>
* redirect to weekoverview if GET is run on the folder itself
* created ChangeLog
+42
View File
@@ -0,0 +1,42 @@
# GNUstep makefile
include ../common.make
WOBUNDLE_NAME = Appointments
Appointments_PRINCIPAL_CLASS = SOGoAppointmentsProduct
# Appointments_LANGUAGES = English French
Appointments_OBJC_FILES = \
Product.m \
\
SOGoAppointmentObject.m \
SOGoAppointmentFolder.m \
SOGoGroupAppointmentFolder.m \
SOGoFreeBusyObject.m \
\
SOGoAptMailNotification.m \
SOGoAptMailInvitation.m \
SOGoAptMailUpdate.m \
SOGoAptMailRemoval.m \
SOGoAptMailDeletion.m \
Appointments_RESOURCE_FILES += \
Version \
product.plist \
Appointments_COMPONENTS += \
SOGoAptMailEnglishInvitation.wo \
SOGoAptMailFrenchInvitation.wo \
SOGoAptMailEnglishUpdate.wo \
SOGoAptMailFrenchUpdate.wo \
SOGoAptMailEnglishRemoval.wo \
SOGoAptMailFrenchRemoval.wo \
SOGoAptMailEnglishDeletion.wo \
SOGoAptMailFrenchDeletion.wo \
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/wobundle.make
-include GNUmakefile.postamble
include ../../fhsbundle.make
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: Product.m 98 2004-06-30 08:54:28Z helge $
#import <Foundation/NSObject.h>
@interface SOGoAppointmentsProduct : NSObject
{
}
@end
@implementation SOGoAppointmentsProduct
@end /* SOGoAppointmentsProduct */
@@ -0,0 +1,101 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Appointments_SOGoAppointmentFolder_H__
#define __Appointments_SOGoAppointmentFolder_H__
#include <SOGo/SOGoFolder.h>
/*
SOGoAppointmentFolder
Parent object: the SOGoUserFolder
Child objects: SOGoAppointmentObject
The SOGoAppointmentFolder maps to an GCS folder of type 'appointment', that
is, a content folder containing iCalendar files (and a proper quicktable).
Important:
The folder assumes a 1:1 mapping between the vevent UID field and the
resource name in the content store. In other words, you are not allowed to
create two different vevent-files with the same uid in the store.
*/
@class NSString, NSArray, NSCalendarDate, NSException, NSMutableDictionary;
@class GCSFolder;
@interface SOGoAppointmentFolder : SOGoFolder
{
NSMutableDictionary *uidToFilename;
}
/* selection */
- (NSArray *)calendarUIDs;
/* vevent UID handling */
- (NSString *)resourceNameForEventUID:(NSString *)_uid;
/* fetching */
- (NSArray *)fetchFields:(NSArray *)_fields
fromFolder:(GCSFolder *)_folder
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchFields:(NSArray *)_fields
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
/* URL generation */
- (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx;
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx;
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx;
- (NSArray *)lookupFreeBusyObjectsForUIDs:(NSArray *)_uids inContext:(id)_ctx;
- (NSArray *)uidsFromICalPersons:(NSArray *)_persons;
- (NSArray *)lookupCalendarFoldersForICalPerson:(NSArray *)_persons
inContext:(id)_ctx;
- (id)lookupGroupFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx;
- (id)lookupGroupCalendarFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx;
/* bulk fetches */
- (NSArray *)fetchAllSOGoAppointments;
@end
#endif /* __Appointments_SOGoAppointmentFolder_H__ */
@@ -0,0 +1,716 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAppointmentFolder.h"
#include <SOGo/SOGoCustomGroupFolder.h>
#include <SOGo/SOGoAppointment.h>
#include <SOGo/AgenorUserManager.h>
#include <GDLContentStore/GCSFolder.h>
#include <SaxObjC/SaxObjC.h>
#include <NGiCal/NGiCal.h>
#include <NGExtensions/NGCalendarDateRange.h>
#include "common.h"
#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
@interface NSDate(UsedPrivates)
- (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
@end
#endif
@implementation SOGoAppointmentFolder
static NGLogger *logger = nil;
static NSTimeZone *MET = nil;
static NSNumber *sharedYes = nil;
+ (int)version {
return [super version] + 1 /* v1 */;
}
+ (void)initialize {
NGLoggerManager *lm;
static BOOL didInit = NO;
if (didInit) return;
didInit = YES;
NSAssert2([super version] == 0,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
lm = [NGLoggerManager defaultLoggerManager];
logger = [lm loggerForDefaultKey:@"SOGoAppointmentFolderDebugEnabled"];
MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain];
sharedYes = [[NSNumber numberWithBool:YES] retain];
}
- (void)dealloc {
[self->uidToFilename release];
[super dealloc];
}
/* logging */
- (id)debugLogger {
return logger;
}
/* selection */
- (NSArray *)calendarUIDs {
/* this is used for group calendars (this folder just returns itself) */
NSString *s;
s = [[self container] nameInContainer];
return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
}
/* name lookup */
- (BOOL)isValidAppointmentName:(NSString *)_key {
if ([_key length] == 0)
return NO;
return YES;
}
- (id)appointmentWithName:(NSString *)_key inContext:(id)_ctx {
static Class aptClass = Nil;
id apt;
if (aptClass == Nil)
aptClass = NSClassFromString(@"SOGoAppointmentObject");
if (aptClass == Nil) {
[self errorWithFormat:@"missing SOGoAppointmentObject class!"];
return nil;
}
apt = [[aptClass alloc] initWithName:_key inContainer:self];
return [apt autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
return obj;
if ([self isValidAppointmentName:_key])
return [self appointmentWithName:_key inContext:_ctx];
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* timezone */
- (NSTimeZone *)viewTimeZone {
// TODO: should use a cookie for configuration? we default to MET
return MET;
}
/* vevent UID handling */
- (NSString *)resourceNameForEventUID:(NSString *)_u inFolder:(GCSFolder *)_f {
static NSArray *nameFields = nil;
EOQualifier *qualifier;
NSArray *records;
if (![_u isNotNull]) return nil;
if (_f == nil) {
[self errorWithFormat:@"(%s): missing folder for fetch!",
__PRETTY_FUNCTION__];
return nil;
}
if (nameFields == nil)
nameFields = [[NSArray alloc] initWithObjects:@"c_name", nil];
qualifier = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _u];
records = [_f fetchFields:nameFields matchingQualifier:qualifier];
if ([records count] == 1)
return [[records objectAtIndex:0] valueForKey:@"c_name"];
if ([records count] == 0)
return nil;
[self errorWithFormat:
@"The storage contains more than file with the same UID!"];
return [[records objectAtIndex:0] valueForKey:@"c_name"];
}
- (NSString *)resourceNameForEventUID:(NSString *)_uid {
/* caches UIDs */
GCSFolder *folder;
NSString *rname;
if (![_uid isNotNull])
return nil;
if ((rname = [self->uidToFilename objectForKey:_uid]) != nil)
return [rname isNotNull] ? rname : nil;
if ((folder = [self ocsFolder]) == nil) {
[self errorWithFormat:@"(%s): missing folder for fetch!",
__PRETTY_FUNCTION__];
return nil;
}
if (self->uidToFilename == nil)
self->uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
[self->uidToFilename setObject:[NSNull null] forKey:_uid];
else
[self->uidToFilename setObject:rname forKey:_uid];
return rname;
}
/* fetching */
- (NSMutableDictionary *)fixupRecord:(NSDictionary *)_record
fetchRange:(NGCalendarDateRange *)_r
{
NSMutableDictionary *md;
id tmp;
md = [[_record mutableCopy] autorelease];
if ((tmp = [_record objectForKey:@"startdate"])) {
tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
(NSTimeInterval)[tmp unsignedIntValue]];
[tmp setTimeZone:[self viewTimeZone]];
if (tmp) [md setObject:tmp forKey:@"startDate"];
[tmp release];
}
else
[self logWithFormat:@"missing 'startdate' in record?"];
if ((tmp = [_record objectForKey:@"enddate"])) {
tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
(NSTimeInterval)[tmp unsignedIntValue]];
[tmp setTimeZone:[self viewTimeZone]];
if (tmp) [md setObject:tmp forKey:@"endDate"];
[tmp release];
}
else
[self logWithFormat:@"missing 'enddate' in record?"];
return md;
}
- (NSMutableDictionary *)fixupCycleRecord:(NSDictionary *)_record
cycleRange:(NGCalendarDateRange *)_r
{
NSMutableDictionary *md;
id tmp;
md = [[_record mutableCopy] autorelease];
/* cycle is in _r */
tmp = [_r startDate];
[tmp setTimeZone:[self viewTimeZone]];
[md setObject:tmp forKey:@"startDate"];
tmp = [_r endDate];
[tmp setTimeZone:[self viewTimeZone]];
[md setObject:tmp forKey:@"endDate"];
return md;
}
- (void)_flattenCycleRecord:(NSDictionary *)_row
forRange:(NGCalendarDateRange *)_r
intoArray:(NSMutableArray *)_ma
{
NSMutableDictionary *row;
NSDictionary *cycleinfo;
NSCalendarDate *startDate, *endDate;
NGCalendarDateRange *fir;
NSArray *rules, *exRules, *exDates, *ranges;
unsigned i, count;
cycleinfo = [[_row objectForKey:@"cycleinfo"] propertyList];
if (cycleinfo == nil) {
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
return;
}
row = [self fixupRecord:_row fetchRange:_r];
[row removeObjectForKey:@"cycleinfo"];
[row setObject:sharedYes forKey:@"isRecurrentEvent"];
startDate = [row objectForKey:@"startDate"];
endDate = [row objectForKey:@"endDate"];
fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
endDate:endDate];
rules = [cycleinfo objectForKey:@"rules"];
exRules = [cycleinfo objectForKey:@"exRules"];
exDates = [cycleinfo objectForKey:@"exDates"];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
firstInstanceCalendarDateRange:fir
recurrenceRules:rules
exceptionRules:exRules
exceptionDates:exDates];
count = [ranges count];
for (i = 0; i < count; i++) {
NGCalendarDateRange *rRange;
id fixedRow;
rRange = [ranges objectAtIndex:i];
fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
if (fixedRow != nil) [_ma addObject:fixedRow];
}
}
- (NSArray *)fixupRecords:(NSArray *)_records
fetchRange:(NGCalendarDateRange *)_r
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
unsigned i, count;
if (_records == nil) return nil;
if ((count = [_records count]) == 0)
return _records;
ma = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
id row; // TODO: what is the type of the record?
row = [_records objectAtIndex:i];
row = [self fixupRecord:row fetchRange:_r];
if (row != nil) [ma addObject:row];
}
return ma;
}
- (NSArray *)fixupCyclicRecords:(NSArray *)_records
fetchRange:(NGCalendarDateRange *)_r
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
unsigned i, count;
if (_records == nil) return nil;
if ((count = [_records count]) == 0)
return _records;
ma = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
id row; // TODO: what is the type of the record?
row = [_records objectAtIndex:i];
[self _flattenCycleRecord:row forRange:_r intoArray:ma];
}
return ma;
}
- (NSArray *)fetchFields:(NSArray *)_fields
fromFolder:(GCSFolder *)_folder
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
EOQualifier *qualifier;
NSMutableArray *fields, *ma = nil;
NSArray *records;
NSString *sql;
NGCalendarDateRange *r;
if (_folder == nil) {
[self errorWithFormat:@"(%s): missing folder for fetch!",
__PRETTY_FUNCTION__];
return nil;
}
r = [NGCalendarDateRange calendarDateRangeWithStartDate:_startDate
endDate:_endDate];
/* prepare mandatory fields */
fields = [NSMutableArray arrayWithArray:_fields];
[fields addObject:@"uid"];
[fields addObject:@"startdate"];
[fields addObject:@"enddate"];
if (logger)
[self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
sql = [NSString stringWithFormat:@"(startdate < %d) AND (enddate > %d)"
@" AND (iscycle = 0)",
(unsigned int)[_endDate timeIntervalSince1970],
(unsigned int)[_startDate timeIntervalSince1970]];
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat:sql];
records = [_folder fetchFields:fields matchingQualifier:qualifier];
if (records != nil) {
records = [self fixupRecords:records fetchRange:r];
if (logger)
[self debugWithFormat:@"fetched %i records: %@",[records count],records];
ma = [NSMutableArray arrayWithArray:records];
}
/* fetch recurrent apts now */
sql = [NSString stringWithFormat:@"(startdate < %d) AND (cycleenddate > %d)"
@" AND (iscycle = 1)",
(unsigned int)[_endDate timeIntervalSince1970],
(unsigned int)[_startDate timeIntervalSince1970]];
qualifier = [EOQualifier qualifierWithQualifierFormat:sql];
[fields addObject:@"cycleinfo"];
records = [_folder fetchFields:fields matchingQualifier:qualifier];
if (records != nil) {
if (logger)
[self debugWithFormat:@"fetched %i cyclic records: %@",
[records count], records];
records = [self fixupCyclicRecords:records fetchRange:r];
if (!ma) ma = [NSMutableArray arrayWithCapacity:[records count]];
[ma addObjectsFromArray:records];
}
else if (ma == nil) {
[self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
/* NOTE: why do we sort here?
This probably belongs to UI but cannot be achieved as fast there as
we can do it here because we're operating on a mutable array -
having the apts sorted is never a bad idea, though
*/
[ma sortUsingSelector:@selector(compareAptsAscending:)];
if (logger)
[self debugWithFormat:@"returning %i records", [ma count]];
return ma;
}
/* override this in subclasses */
- (NSArray *)fetchFields:(NSArray *)_fields
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
GCSFolder *folder;
if ((folder = [self ocsFolder]) == nil) {
[self errorWithFormat:@"(%s): missing folder for fetch!",
__PRETTY_FUNCTION__];
return nil;
}
return [self fetchFields:_fields fromFolder:folder
from:_startDate to:_endDate];
}
- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
static NSArray *infos = nil; // TODO: move to a plist file
if (infos == nil) {
infos = [[NSArray alloc] initWithObjects:@"partmails", @"partstates", nil];
}
return [self fetchFields:infos from:_startDate to:_endDate];
}
- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
static NSArray *infos = nil; // TODO: move to a plist file
if (infos == nil) {
infos = [[NSArray alloc] initWithObjects:
@"title",
@"location", @"orgmail", @"status", @"ispublic",
@"isallday", @"priority",
@"partmails", @"partstates",
nil];
}
return [self fetchFields:infos
from:_startDate
to:_endDate];
}
- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
static NSArray *infos = nil; // TODO: move to a plist file
if (infos == nil) {
infos = [[NSArray alloc] initWithObjects:
@"title", @"location", @"orgmail",
@"status", @"ispublic",
@"isallday", @"isopaque",
@"participants", @"partmails",
@"partstates", @"sequence", @"priority", nil];
}
return [self fetchFields:infos
from:_startDate
to:_endDate];
}
/* URL generation */
- (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx {
// TODO: who calls this?
NSString *url;
if ([_uid length] == 0)
return nil;
url = [self baseURLInContext:_ctx];
if (![url hasSuffix:@"/"])
url = [url stringByAppendingString:@"/"];
// TODO: this should run a query to determine the uid!
return [url stringByAppendingString:_uid];
}
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
// TODO: DUP to SOGoGroupFolder
NSException *error = nil;
NSArray *path;
id ctx, result;
if (![_uid isNotNull])
return nil;
if (_ctx == nil) _ctx = [[WOApplication application] context];
/* create subcontext, so that we don't destroy our environment */
if ((ctx = [_ctx createSubContext]) == nil) {
[self errorWithFormat:@"could not create SOPE subcontext!"];
return nil;
}
/* build path */
path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
/* traverse path */
result = [[ctx application] traversePathArray:path inContext:ctx
error:&error acquire:NO];
if (error != nil) {
[self errorWithFormat:@"folder lookup failed (uid=%@): %@",
_uid, error];
return nil;
}
[self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
_uid, [path componentsJoinedByString:@"=>"], result];
return result;
}
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
/* Note: can return NSNull objects in the array! */
NSMutableArray *folders;
NSEnumerator *e;
NSString *uid;
if ([_uids count] == 0) return nil;
folders = [NSMutableArray arrayWithCapacity:16];
e = [_uids objectEnumerator];
while ((uid = [e nextObject])) {
id folder;
folder = [self lookupHomeFolderForUID:uid inContext:nil];
if ([folder isNotNull]) {
folder = [folder lookupName:@"Calendar" inContext:nil acquire:NO];
if ([folder isKindOfClass:[NSException class]])
folder = nil;
}
if (![folder isNotNull])
[self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
/* Note: intentionally add 'null' folders to allow a mapping */
[folders addObject:folder ? folder : [NSNull null]];
}
return folders;
}
- (NSArray *)lookupFreeBusyObjectsForUIDs:(NSArray *)_uids inContext:(id)_ctx {
/* Note: can return NSNull objects in the array! */
NSMutableArray *objs;
NSEnumerator *e;
NSString *uid;
if ([_uids count] == 0) return nil;
objs = [NSMutableArray arrayWithCapacity:16];
e = [_uids objectEnumerator];
while ((uid = [e nextObject])) {
id obj;
obj = [self lookupHomeFolderForUID:uid inContext:nil];
if ([obj isNotNull]) {
obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
if ([obj isKindOfClass:[NSException class]])
obj = nil;
}
if (![obj isNotNull])
[self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
/* Note: intentionally add 'null' folders to allow a mapping */
[objs addObject:obj ? obj : [NSNull null]];
}
return objs;
}
- (NSArray *)uidsFromICalPersons:(NSArray *)_persons {
/* Note: can return NSNull objects in the array! */
NSMutableArray *uids;
AgenorUserManager *um;
unsigned i, count;
if (_persons == nil)
return nil;
count = [_persons count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
um = [AgenorUserManager sharedUserManager];
for (i = 0; i < count; i++) {
iCalPerson *person;
NSString *email;
NSString *uid;
person = [_persons objectAtIndex:i];
email = [person rfc822Email];
if ([email isNotNull]) {
uid = [um getUIDForEmail:email];
}
else
uid = nil;
[uids addObject:(uid != nil ? uid : (id)[NSNull null])];
}
return uids;
}
- (NSArray *)lookupCalendarFoldersForICalPerson:(NSArray *)_persons
inContext:(id)_ctx
{
/* Note: can return NSNull objects in the array! */
NSArray *uids;
if ((uids = [self uidsFromICalPersons:_persons]) == nil)
return nil;
return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
}
- (id)lookupGroupFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx {
SOGoCustomGroupFolder *folder;
if (_uids == nil)
return nil;
folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
return [folder autorelease];
}
- (id)lookupGroupCalendarFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx {
SOGoCustomGroupFolder *folder;
if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
return nil;
folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
if (![folder isNotNull])
return nil;
if ([folder isKindOfClass:[NSException class]]) {
[self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
folder];
return nil;
}
return folder;
}
/* bulk fetches */
- (NSArray *)fetchAllSOGoAppointments {
/*
Note: very expensive method, do not use unless absolutely required.
returns an array of SOGoAppointment objects.
Note that we can leave out the filenames, supposed to be stored
in the 'uid' field of the iCalendar object!
*/
NSMutableArray *events;
NSDictionary *files;
NSEnumerator *contents;
NSString *content;
/* fetch all raw contents */
files = [self fetchContentStringsAndNamesOfAllObjects];
if (![files isNotNull]) return nil;
if ([files isKindOfClass:[NSException class]]) return (id)files;
/* transform to SOGo appointments */
events = [NSMutableArray arrayWithCapacity:[files count]];
contents = [files objectEnumerator];
while ((content = [contents nextObject]) != nil) {
SOGoAppointment *event;
event = [[SOGoAppointment alloc] initWithICalString:content];
if (![event isNotNull]) {
[self errorWithFormat:@"(%s): could not parse an iCal file!",
__PRETTY_FUNCTION__];
continue;
}
[events addObject:event];
[event release];
}
return events;
}
/* GET */
- (id)GETAction:(id)_ctx {
// TODO: I guess this should really be done by SOPE (redirect to
// default method)
WOResponse *r;
NSString *uri;
uri = [[(WOContext *)_ctx request] uri];
if (![uri hasSuffix:@"/"]) uri = [uri stringByAppendingString:@"/"];
uri = [uri stringByAppendingString:@"weekoverview"];
r = [_ctx response];
[r setStatus:302 /* moved */];
[r setHeader:uri forKey:@"location"];
return r;
}
/* folder type */
- (NSString *)outlookFolderClass {
return @"IPF.Appointment";
}
@end /* SOGoAppointmentFolder */
@@ -0,0 +1,72 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Appointments_SOGoAppointmentObject_H__
#define __Appointments_SOGoAppointmentObject_H__
#include <SOGo/SOGoContentObject.h>
/*
SOGoAppointmentObject
Represents a single appointment. This SOPE controller object manages all the
attendee storages (that is, it might store into multiple folders for meeting
appointments!).
Note: SOGoAppointmentObject do not need to exist yet. They can also be "new"
appointments with an externally generated unique key.
*/
@class NSString, NSArray, NSException, iCalEvent;
@interface SOGoAppointmentObject : SOGoContentObject
{
}
/* accessors */
- (NSString *)iCalString;
- (iCalEvent *)event;
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx;
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx;
/* raw saving */
- (NSException *)primarySaveContentString:(NSString *)_iCalString;
- (NSException *)primaryDelete;
/* "iCal multifolder saves" */
- (NSException *)saveContentString:(NSString *)_iCal baseSequence:(int)_v;
- (NSException *)deleteWithBaseSequence:(int)_v;
- (NSException *)saveContentString:(NSString *)_iCalString;
- (NSException *)delete;
- (NSException *)changeParticipationStatus:(NSString *)_status
inContext:(id)_ctx;
@end
#endif /* __Appointments_SOGoAppointmentObject_H__ */
@@ -0,0 +1,772 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAppointmentObject.h"
#include <SOGo/AgenorUserManager.h>
#include <SOGo/SOGoAppointment.h>
#include <SaxObjC/SaxObjC.h>
#include <NGiCal/NGiCal.h>
#include <NGMime/NGMime.h>
#include <NGMail/NGMail.h>
#include <NGMail/NGSendMail.h>
#include "SOGoAptMailNotification.h"
#include "common.h"
@interface NSMutableArray (iCalPersonConvenience)
- (void)removePerson:(iCalPerson *)_person;
@end
@interface SOGoAppointmentObject (PrivateAPI)
- (NSString *)homePageURLForPerson:(iCalPerson *)_person;
- (NSTimeZone *)viewTimeZoneForPerson:(iCalPerson *)_person;
- (void)sendEMailUsingTemplateNamed:(NSString *)_pageName
forOldAppointment:(SOGoAppointment *)_newApt
andNewAppointment:(SOGoAppointment *)_oldApt
toAttendees:(NSArray *)_attendees;
- (void)sendInvitationEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees;
- (void)sendAppointmentUpdateEMailForOldAppointment:(SOGoAppointment *)_oldApt
newAppointment:(SOGoAppointment *)_newApt
toAttendees:(NSArray *)_attendees;
- (void)sendAttendeeRemovalEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees;
- (void)sendAppointmentDeletionEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees;
@end
@implementation SOGoAppointmentObject
static id<NSObject,SaxXMLReader> parser = nil;
static SaxObjectDecoder *sax = nil;
static NGLogger *logger = nil;
static NSTimeZone *MET = nil;
static NSString *mailTemplateDefaultLanguage = nil;
+ (void)initialize {
NSUserDefaults *ud;
NGLoggerManager *lm;
SaxXMLReaderFactory *factory;
static BOOL didInit = NO;
if (didInit) return;
didInit = YES;
lm = [NGLoggerManager defaultLoggerManager];
logger = [lm loggerForClass:self];
factory = [SaxXMLReaderFactory standardXMLReaderFactory];
parser = [[factory createXMLReaderForMimeType:@"text/calendar"]
retain];
if (parser == nil)
[logger fatalWithFormat:@"did not find a parser for text/calendar!"];
sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGiCal"];
if (sax == nil)
[logger fatalWithFormat:@"could not create the iCal SAX handler!"];
[parser setContentHandler:sax];
[parser setErrorHandler:sax];
MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain];
ud = [NSUserDefaults standardUserDefaults];
mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
retain];
if (!mailTemplateDefaultLanguage)
mailTemplateDefaultLanguage = @"French";
}
- (void)dealloc {
[super dealloc];
}
/* accessors */
- (NSString *)iCalString {
// for UI-X appointment viewer
return [self contentAsString];
}
- (iCalEvent *)event {
NSString *iCalString;
iCalEvent *event;
iCalString = [self iCalString];
if ([iCalString length] > 0) {
iCalCalendar *cal;
[parser parseFromSource:iCalString];
cal = [sax rootObject];
[sax reset];
event = [[cal events] lastObject];
return event;
}
return nil;
}
/* iCal handling */
- (NSArray *)attendeeUIDsFromAppointment:(SOGoAppointment *)_apt {
AgenorUserManager *um;
NSMutableArray *uids;
NSArray *attendees;
unsigned i, count;
NSString *email, *uid;
if (![_apt isNotNull])
return nil;
if ((attendees = [_apt attendees]) == nil)
return nil;
count = [attendees count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
um = [AgenorUserManager sharedUserManager];
/* add organizer */
email = [[_apt organizer] rfc822Email];
if ([email isNotNull]) {
uid = [um getUIDForEmail:email];
if ([uid isNotNull]) {
[uids addObject:uid];
}
else
[self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
}
/* add attendees */
for (i = 0; i < count; i++) {
iCalPerson *person;
person = [attendees objectAtIndex:i];
email = [person rfc822Email];
if (![email isNotNull]) continue;
uid = [um getUIDForEmail:email];
if (![uid isNotNull]) {
[self logWithFormat:@"Note: got no uid for email: '%@'", email];
continue;
}
if (![uids containsObject:uid])
[uids addObject:uid];
}
return uids;
}
/* raw saving */
- (NSException *)primarySaveContentString:(NSString *)_iCalString {
return [super saveContentString:_iCalString];
}
- (NSException *)primaryDelete {
return [super delete];
}
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
// TODO: what does this do? lookup the home of the organizer?
return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
}
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
}
/* store in all the other folders */
- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
id ctx;
ctx = [[WOApplication application] context];
e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
objectEnumerator];
while ((folder = [e nextObject]) != nil) {
NSException *error;
SOGoAppointmentObject *apt;
if (![folder isNotNull]) /* no folder was found for given UID */
continue;
apt = [folder lookupName:[self nameInContainer] inContext:ctx
acquire:NO];
if (![apt isNotNull]) {
[self logWithFormat:@"Note: did not find '%@' in folder: %@",
[self nameInContainer], folder];
continue;
}
if ((error = [apt primarySaveContentString:_iCal]) != nil) {
[self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
- (NSException *)deleteInUIDs:(NSArray *)_uids {
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
id ctx;
ctx = [[WOApplication application] context];
e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
objectEnumerator];
while ((folder = [e nextObject])) {
NSException *error;
SOGoAppointmentObject *apt;
apt = [folder lookupName:[self nameInContainer] inContext:ctx
acquire:NO];
if (![apt isNotNull]) {
[self logWithFormat:@"Note: did not find '%@' in folder: %@",
[self nameInContainer], folder];
continue;
}
if ((error = [apt primaryDelete]) != nil) {
[self logWithFormat:@"Note: failed to delete in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
/* "iCal multifolder saves" */
- (NSException *)saveContentString:(NSString *)_iCal baseSequence:(int)_v {
/*
Note: we need to delete in all participants folders and send iMIP messages
for all external accounts.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- parse new content (ensure that sequence is increased!)
- extract new attendee list + organizer (make unique)
- make a diff => new, same, removed
- write to new, same
- delete in removed folders
- send iMIP mail for all folders not found
*/
AgenorUserManager *um;
SOGoAppointment *oldApt, *newApt;
iCalEventChanges *changes;
iCalPerson *organizer;
NSString *oldContent, *uid;
NSArray *uids, *props;
NSMutableArray *attendees, *storeUIDs, *removedUIDs;
NSException *storeError, *delError;
BOOL updateForcesReconsider;
updateForcesReconsider = NO;
if ([_iCal length] == 0) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"got no iCalendar content to store!"];
}
um = [AgenorUserManager sharedUserManager];
/* handle old content */
oldContent = [self iCalString]; /* if nil, this is a new appointment */
if ([oldContent length] == 0) {
/* new appointment */
[self debugWithFormat:@"saving new appointment: %@", _iCal];
oldApt = nil;
}
else {
oldApt =
[[[SOGoAppointment alloc] initWithICalString:oldContent] autorelease];
}
/* compare sequence if requested */
if (_v != 0) {
// TODO
}
/* handle new content */
newApt = [[[SOGoAppointment alloc] initWithICalString:_iCal] autorelease];
if (newApt == nil) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"could not parse iCalendar content!"];
}
/* diff */
changes = [iCalEventChanges changesFromEvent:[oldApt event]
toEvent:[newApt event]];
uids = [um getUIDsForICalPersons:[changes deletedAttendees]
applyStrictMapping:NO];
removedUIDs = [NSMutableArray arrayWithArray:uids];
uids = [um getUIDsForICalPersons:[newApt attendees]
applyStrictMapping:NO];
storeUIDs = [NSMutableArray arrayWithArray:uids];
props = [changes updatedProperties];
/* detect whether sequence has to be increased */
if ([changes hasChanges])
[newApt increaseSequence];
/* preserve organizer */
organizer = [[newApt event] organizer];
uid = [um getUIDForICalPerson:organizer];
if (uid) {
if (![storeUIDs containsObject:uid])
[storeUIDs addObject:uid];
[removedUIDs removeObject:uid];
}
/* organizer might have changed completely */
if ((oldApt != nil) && ([props containsObject:@"organizer"])) {
uid = [um getUIDForICalPerson:[[oldApt event] organizer]];
if (uid) {
if (![storeUIDs containsObject:uid]) {
if (![removedUIDs containsObject:uid]) {
[removedUIDs addObject:uid];
}
}
}
}
[self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
storeUIDs, removedUIDs];
/* if time did change, all participants have to re-decide ...
* ... exception from that rule: the organizer
*/
if (oldApt != nil &&
([props containsObject:@"startDate"] ||
[props containsObject:@"endDate"] ||
[props containsObject:@"duration"]))
{
NSArray *ps;
unsigned i, count;
ps = [newApt attendees];
count = [ps count];
for (i = 0; i < count; i++) {
iCalPerson *p;
p = [ps objectAtIndex:i];
if (![p hasSameEmailAddress:organizer])
[p setParticipationStatus:iCalPersonPartStatNeedsAction];
}
_iCal = [newApt iCalString];
updateForcesReconsider = YES;
}
/* perform storing */
storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
delError = [self deleteInUIDs:removedUIDs];
// TODO: make compound
if (storeError != nil) return storeError;
if (delError != nil) return delError;
/* email notifications */
attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
[attendees removePerson:organizer];
[self sendInvitationEMailForAppointment:newApt
toAttendees:attendees];
if (updateForcesReconsider) {
attendees = [NSMutableArray arrayWithArray:[[newApt event] attendees]];
[attendees removeObjectsInArray:[changes insertedAttendees]];
[attendees removePerson:organizer];
[self sendAppointmentUpdateEMailForOldAppointment:oldApt
newAppointment:newApt
toAttendees:attendees];
}
attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
[attendees removePerson:organizer];
if ([attendees count]) {
SOGoAppointment *canceledApt;
canceledApt = [newApt copy];
[canceledApt cancelWithoutIncreasingSequence];
[self sendAttendeeRemovalEMailForAppointment:canceledApt
toAttendees:attendees];
[canceledApt release];
}
return nil;
}
- (NSException *)deleteWithBaseSequence:(int)_v {
/*
Note: We need to delete in all participants folders and send iMIP messages
for all external accounts.
Delete is basically identical to save with all attendees and the
organizer being deleted.
Steps:
- fetch stored content
- parse old content
- check if sequence matches (or if 0=ignore)
- extract old attendee list + organizer (make unique)
- delete in removed folders
- send iMIP mail for all folders not found
*/
SOGoAppointment *apt;
NSString *econtent;
NSArray *removedUIDs;
NSMutableArray *attendees;
/* load existing content */
econtent = [self iCalString]; /* if nil, this is a new appointment */
apt = [[[SOGoAppointment alloc] initWithICalString:econtent] autorelease];
/* compare sequence if requested */
if (_v != 0) {
// TODO
}
removedUIDs = [self attendeeUIDsFromAppointment:apt];
/* send notification email to attendees excluding organizer */
attendees = [NSMutableArray arrayWithArray:[[apt event] attendees]];
[attendees removePerson:[apt organizer]];
/* flag appointment as being canceled */
[apt cancelAndIncreaseSequence];
/* remove all attendees to signal complete removal */
[apt removeAllAttendees];
/* send notification email */
[self sendAppointmentDeletionEMailForAppointment:apt
toAttendees:attendees];
/* perform */
return [self deleteInUIDs:removedUIDs];
}
- (NSException *)saveContentString:(NSString *)_iCalString {
return [self saveContentString:_iCalString baseSequence:0];
}
- (NSException *)delete {
return [self deleteWithBaseSequence:0];
}
- (NSException *)changeParticipationStatus:(NSString *)_status
inContext:(id)_ctx
{
SOGoAppointment *apt;
iCalPerson *p;
NSString *newContent;
NSException *ex;
NSString *myEMail;
// TODO: do we need to use SOGoAppointment? (prefer iCalEvent?)
apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
if (apt == nil) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"unable to parse appointment record"];
}
myEMail = [[_ctx activeUser] email];
if ((p = [apt findParticipantWithEmail:myEMail]) == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"user does not participate in this "
@"appointment"];
}
[p setPartStat:_status];
newContent = [[[apt iCalString] copy] autorelease];
// TODO: send iMIP reply mails?
[apt release]; apt = nil;
if (newContent == nil) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"Could not generate iCalendar data ..."];
}
if ((ex = [self saveContentString:newContent]) != nil) {
// TODO: why is the exception wrapped?
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:[ex reason]];
}
return nil /* means: no error */;
}
/* message type */
- (NSString *)outlookMessageClass {
return @"IPM.Appointment";
}
/* EMail Notifications */
- (NSString *)homePageURLForPerson:(iCalPerson *)_person {
static AgenorUserManager *um = nil;
static NSString *baseURL = nil;
NSString *uid;
if (!um) {
WOContext *ctx;
NSArray *traversalObjects;
um = [[AgenorUserManager sharedUserManager] retain];
/* generate URL from traversal stack */
ctx = [[WOApplication application] context];
traversalObjects = [ctx objectTraversalStack];
if ([traversalObjects count] >= 1) {
baseURL = [[[traversalObjects objectAtIndex:0] baseURLInContext:ctx]
retain];
}
else {
[self warnWithFormat:@"Unable to create baseURL from context!"];
baseURL = @"http://localhost/";
}
}
uid = [um getUIDForEmail:[_person rfc822Email]];
if (!uid) return nil;
return [NSString stringWithFormat:@"%@%@", baseURL, uid];
}
- (NSTimeZone *)viewTimeZoneForPerson:(iCalPerson *)_person {
/* TODO: get this from user config as soon as this is available and only
* fall back to default timeZone if config data is not available
*/
return MET;
}
- (void)sendEMailUsingTemplateNamed:(NSString *)_pageName
forOldAppointment:(SOGoAppointment *)_oldApt
andNewAppointment:(SOGoAppointment *)_newApt
toAttendees:(NSArray *)_attendees
{
NSString *pageName;
iCalPerson *organizer;
NSString *cn, *sender, *iCalString;
NGSendMail *sendmail;
WOApplication *app;
unsigned i, count;
if (![_attendees count]) return; // another job neatly done :-)
/* sender */
organizer = [_newApt organizer];
cn = [organizer cnWithoutQuotes];
if (cn) {
sender = [NSString stringWithFormat:@"%@ <%@>",
cn,
[organizer rfc822Email]];
}
else {
sender = [organizer rfc822Email];
}
/* generate iCalString once */
iCalString = [_newApt iCalString];
/* get sendmail object */
sendmail = [NGSendMail sharedSendMail];
/* get WOApplication instance */
app = [WOApplication application];
/* generate dynamic message content */
count = [_attendees count];
for (i = 0; i < count; i++) {
iCalPerson *attendee;
NSString *recipient;
SOGoAptMailNotification *p;
NSString *subject, *text, *header;
NGMutableHashMap *headerMap;
NGMimeMessage *msg;
NGMimeBodyPart *bodyPart;
NGMimeMultipartBody *body;
attendee = [_attendees objectAtIndex:i];
/* construct recipient */
cn = [attendee cn];
if (cn) {
recipient = [NSString stringWithFormat:@"%@ <%@>",
cn,
[attendee rfc822Email]];
}
else {
recipient = [attendee rfc822Email];
}
/* create page name */
// TODO: select user's default language?
pageName = [NSString stringWithFormat:@"SOGoAptMail%@%@",
mailTemplateDefaultLanguage,
_pageName];
/* construct message content */
p = [app pageWithName:pageName inContext:[WOContext context]];
[p setNewApt:_newApt];
[p setOldApt:_oldApt];
[p setHomePageURL:[self homePageURLForPerson:attendee]];
[p setViewTZ:[self viewTimeZoneForPerson:attendee]];
subject = [p getSubject];
text = [p getBody];
/* construct message */
headerMap = [NGMutableHashMap hashMapWithCapacity:5];
/* NOTE: multipart/alternative seems like the correct choice but
* unfortunately Thunderbird doesn't offer the rich content alternative
* at all. Mail.app shows the rich content alternative _only_
* so we'll stick with multipart/mixed for the time being.
*/
[headerMap setObject:@"multipart/mixed" forKey:@"content-type"];
[headerMap setObject:sender forKey:@"From"];
[headerMap setObject:recipient forKey:@"To"];
[headerMap setObject:[NSCalendarDate date] forKey:@"date"];
[headerMap setObject:subject forKey:@"Subject"];
msg = [NGMimeMessage messageWithHeader:headerMap];
/* multipart body */
body = [[NGMimeMultipartBody alloc] initWithPart:msg];
/* text part */
headerMap = [NGMutableHashMap hashMapWithCapacity:1];
[headerMap setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader:headerMap];
[bodyPart setBody:[text dataUsingEncoding:NSUTF8StringEncoding]];
/* attach text part to multipart body */
[body addBodyPart:bodyPart];
/* calendar part */
header = [NSString stringWithFormat:@"text/calendar; method=%@;"
@" charset=utf-8",
[_newApt method]];
headerMap = [NGMutableHashMap hashMapWithCapacity:1];
[headerMap setObject:header forKey:@"content-type"];
bodyPart = [NGMimeBodyPart bodyPartWithHeader:headerMap];
[bodyPart setBody:[iCalString dataUsingEncoding:NSUTF8StringEncoding]];
/* attach calendar part to multipart body */
[body addBodyPart:bodyPart];
/* attach multipart body to message */
[msg setBody:body];
[body release];
/* send the damn thing */
[sendmail sendMimePart:msg
toRecipients:[NSArray arrayWithObject:[attendee rfc822Email]]
sender:[organizer rfc822Email]];
}
}
- (void)sendInvitationEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees
{
if (![_attendees count]) return; // another job neatly done :-)
[self sendEMailUsingTemplateNamed:@"Invitation"
forOldAppointment:nil
andNewAppointment:_apt
toAttendees:_attendees];
}
- (void)sendAppointmentUpdateEMailForOldAppointment:(SOGoAppointment *)_oldApt
newAppointment:(SOGoAppointment *)_newApt
toAttendees:(NSArray *)_attendees
{
if (![_attendees count]) return;
[self sendEMailUsingTemplateNamed:@"Update"
forOldAppointment:_oldApt
andNewAppointment:_newApt
toAttendees:_attendees];
}
- (void)sendAttendeeRemovalEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees
{
if (![_attendees count]) return;
[self sendEMailUsingTemplateNamed:@"Removal"
forOldAppointment:nil
andNewAppointment:_apt
toAttendees:_attendees];
}
- (void)sendAppointmentDeletionEMailForAppointment:(SOGoAppointment *)_apt
toAttendees:(NSArray *)_attendees
{
if (![_attendees count]) return;
[self sendEMailUsingTemplateNamed:@"Deletion"
forOldAppointment:nil
andNewAppointment:_apt
toAttendees:_attendees];
}
@end /* SOGoAppointmentObject */
@implementation NSMutableArray (iCalPersonConvenience)
- (void)removePerson:(iCalPerson *)_person {
int i;
for (i = [self count] - 1; i >= 0; i--) {
iCalPerson *p;
p = [self objectAtIndex:i];
if ([p hasSameEmailAddress:_person])
[self removeObjectAtIndex:i];
}
}
@end /* NSMutableArray (iCalPersonConvenience) */
@@ -0,0 +1,38 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishDeletion : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishDeletion
@end
@interface SOGoAptMailFrenchDeletion : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchDeletion
@end
@@ -0,0 +1,4 @@
<#IsSubject>Appointment <#AptStartDate /> at <#AptStartTime /> has been canceled</#IsSubject>
<#IsBody>
The appointment at <#AptStartDate /> <#AptStartTime /> has been cancelled by <#Organizer />.
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,7 @@
<#IsSubject>Apt le <#AptStartDate /> at <#AptStartTime /></#IsSubject>
<#IsBody>
You're invited by <#Organizer /> to a meeting.
<#HasHomePageURL>
Please make a decision for this invitation at <#HomePageURL />.
</#HasHomePageURL>
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,4 @@
<#IsSubject>Removed from appointment <#AptStartDate /> at <#AptStartTime /></#IsSubject>
<#IsBody>
You have been removed by <#Organizer /> from the appointment scheduled at <#AptStartDate /> <#AptStartTime />.
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,8 @@
<#IsSubject>*E* Apt for the <#OldAptStartDate /> at <#OldAptStartTime /> changed</#IsSubject>
<#IsBody>
This appointment, previously set for <#OldAptStartDate />
at <#OldAptStartTime /> is now scheduled for <#NewAptStartDate /> at <#NewAptStartTime />
<#HasHomePageURL>
Please make a decision for these new settings at <#HomePageURL />.
</#HasHomePageURL>
</#IsBody>
@@ -0,0 +1,46 @@
OldAptStartDate: WOString {
value = oldStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
OldAptStartTime: WOString {
value = oldStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
NewAptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
NewAptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,4 @@
<#IsSubject>Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé</#IsSubject>
<#IsBody>
Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé par <#Organizer />.
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,7 @@
<#IsSubject>Rendez-vous le <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsBody>
Vous êtes invité par <#Organizer /> à une réunion.
<#HasHomePageURL>
Veuillez donner votre réponse à l'adresse <#HomePageURL />.
</#HasHomePageURL>
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,4 @@
<#IsSubject>Supprimé de la réunion du <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsBody>
Vous ne faites plus parti de la liste des invités de la réunion du <#AptStartDate /> à <#AptStartTime /> organisée par <#Organizer />.
</#IsBody>
@@ -0,0 +1,34 @@
AptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
AptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,5 @@
<#IsSubject>Rendez-vous du <#OldAptStartDate /> à <#OldAptStartTime /> modifié</#IsSubject>
<#IsBody>
La réunion qui devait se dérouler le <#OldAptStartDate /> à <#OldAptStartTime /> est maintenant prévue le <#NewAptStartDate /> à <#NewAptStartTime />.
Vous êtes invité à accepter ou refuser de participer à la réunion pour cette nouvelle date à l'adresse <#HomePageURL />.
</#IsBody>
@@ -0,0 +1,46 @@
OldAptStartDate: WOString {
value = oldStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
OldAptStartTime: WOString {
value = oldStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
NewAptStartDate: WOString {
value = newStartDate;
dateformat = "%d/%m/%y";
escapeHTML = NO;
}
NewAptStartTime: WOString {
value = newStartDate;
dateformat = "%H:%M";
escapeHTML = NO;
}
Organizer: WOString {
value = newApt.organizer.cnWithoutQuotes;
escapeHTML = NO;
}
HasHomePageURL: WOConditional {
condition = homePageURL.length;
}
HomePageURL: WOString {
value = appointmentURL;
escapeHTML = NO;
}
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
@@ -0,0 +1,38 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishInvitation : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishInvitation
@end
@interface SOGoAptMailFrenchInvitation : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchInvitation
@end
@@ -0,0 +1,68 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Appointments_SOGoAptMailNotification_H_
#define __Appointments_SOGoAptMailNotification_H_
#include <NGObjWeb/SoComponent.h>
@class NSString, NSTimeZone, NSCalendarDate;
/*
* NOTE: We inherit from SoComponent in order to get the correct
* resourceManager required for this product
*/
@interface SOGoAptMailNotification : SoComponent
{
id oldApt;
id newApt;
NSString *homePageURL;
NSTimeZone *viewTZ;
NSCalendarDate *oldStartDate;
NSCalendarDate *newStartDate;
BOOL isSubject;
}
- (id)oldApt;
- (void)setOldApt:(id)_oldApt;
- (id)newApt;
- (void)setNewApt:(id)_newApt;
- (NSString *)homePageURL;
- (void)setHomePageURL:(NSString *)_homePageURL;
- (NSTimeZone *)viewTZ;
- (void)setViewTZ:(NSTimeZone *)_viewTZ;
/* Helpers */
- (NSCalendarDate *)oldStartDate;
- (NSCalendarDate *)newStartDate;
/* Content Generation */
- (NSString *)getSubject;
- (NSString *)getBody;
@end
#endif /* __Appointments_SOGoAptMailNotification_H_ */
@@ -0,0 +1,143 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
#include <SOGo/SOGoAppointment.h>
#include "common.h"
@interface SOGoAptMailNotification (PrivateAPI)
- (BOOL)isSubject;
- (void)setIsSubject:(BOOL)_isSubject;
@end
@implementation SOGoAptMailNotification
static NSCharacterSet *wsSet = nil;
static NSTimeZone *MET = nil;
+ (void)initialize {
static BOOL didInit = NO;
if (didInit) return;
didInit = YES;
wsSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain];
}
- (void)dealloc {
[self->oldApt release];
[self->newApt release];
[self->homePageURL release];
[self->viewTZ release];
[self->oldStartDate release];
[self->newStartDate release];
[super dealloc];
}
- (id)oldApt {
return self->oldApt;
}
- (void)setOldApt:(id)_oldApt {
ASSIGN(self->oldApt, _oldApt);
}
- (id)newApt {
return self->newApt;
}
- (void)setNewApt:(id)_newApt {
ASSIGN(self->newApt, _newApt);
}
- (NSString *)homePageURL {
return self->homePageURL;
}
- (void)setHomePageURL:(NSString *)_homePageURL {
ASSIGN(self->homePageURL, _homePageURL);
}
- (NSString *)appointmentURL {
NSString *aptUID;
aptUID = [[self newApt] uid];
return [NSString stringWithFormat:@"%@/Calendar/%@/view?tab=participants",
[self homePageURL],
aptUID];
}
- (NSTimeZone *)viewTZ {
if (self->viewTZ) return self->viewTZ;
return MET;
}
- (void)setViewTZ:(NSTimeZone *)_viewTZ {
ASSIGN(self->viewTZ, _viewTZ);
}
- (BOOL)isSubject {
return self->isSubject;
}
- (void)setIsSubject:(BOOL)_isSubject {
self->isSubject = _isSubject;
}
/* Helpers */
- (NSCalendarDate *)oldStartDate {
if (!self->oldStartDate) {
ASSIGN(self->oldStartDate, [[self oldApt] startDate]);
[self->oldStartDate setTimeZone:[self viewTZ]];
}
return self->oldStartDate;
}
- (NSCalendarDate *)newStartDate {
if (!self->newStartDate) {
ASSIGN(self->newStartDate, [[self newApt] startDate]);
[self->newStartDate setTimeZone:[self viewTZ]];
}
return self->newStartDate;
}
/* Generate Response */
- (NSString *)getSubject {
NSString *subject;
[self setIsSubject:YES];
subject = [[[self generateResponse] contentAsString]
stringByTrimmingCharactersInSet:wsSet];
if (!subject) {
[self errorWithFormat:@"Failed to properly generate subject! Please check "
@"template for component '%@'!",
[self name]];
subject = @"ERROR: missing subject!";
}
return subject;
}
- (NSString *)getBody {
[self setIsSubject:NO];
return [[self generateResponse] contentAsString];
}
@end
@@ -0,0 +1,38 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishRemoval : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishRemoval
@end
@interface SOGoAptMailFrenchRemoval : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchRemoval
@end
@@ -0,0 +1,38 @@
/*
Copyright (C) 2000-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishUpdate : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishUpdate
@end
@interface SOGoAptMailFrenchUpdate : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchUpdate
@end
@@ -0,0 +1,53 @@
/*
Copyright (C) 2000-2004 SKYRIX Software AG
This file is part of OGo
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: SOGoFreeBusyObject.h 675 2005-07-06 20:56:09Z znek $
#ifndef __Appointments_SOGoFreeBusyObject_H_
#define __Appointments_SOGoFreeBusyObject_H_
#include <SOGo/SOGoContentObject.h>
/*
SOGoFreeBusyObject
Represents Free/Busy information for a single user as specified in RFC2445.
*/
@class NSArray, NSCalendarDate;
@interface SOGoFreeBusyObject : SOGoContentObject
{
}
/* accessors */
- (NSString *)iCalString;
- (NSString *)contentAsStringFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
@end
#endif /* __Appointments_SOGoFreeBusyObject_H_ */
+213
View File
@@ -0,0 +1,213 @@
/*
Copyright (C) 2000-2004 SKYRIX Software AG
This file is part of OGo
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: SOGoFreeBusyObject.m 675 2005-07-06 20:56:09Z znek $
#include "SOGoFreeBusyObject.h"
#include "common.h"
#include <SOGo/AgenorUserManager.h>
#include <NGiCal/NGiCal.h>
#include <NGiCal/iCalRenderer.h>
@interface NSDate(UsedPrivates)
- (NSString *)icalString; // declared in NGiCal
@end
@interface SOGoFreeBusyObject (PrivateAPI)
- (NSString *)iCalStringForFreeBusyInfos:(NSArray *)_infos
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
@end
@implementation SOGoFreeBusyObject
- (NSString *)iCalString {
// for UI-X appointment viewer
return [self contentAsString];
}
- (NSString *)contentAsString {
NSCalendarDate *startDate, *endDate;
startDate = [[[NSCalendarDate calendarDate] mondayOfWeek] beginOfDay];
endDate = [startDate dateByAddingYears:0
months:0
days:7
hours:23
minutes:59
seconds:59];
return [self contentAsStringFrom:startDate to:endDate];
}
- (NSString *)contentAsStringFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
NSArray *infos;
infos = [self fetchFreebusyInfosFrom:_startDate to:_endDate];
return [self iCalStringForFreeBusyInfos:infos from:_startDate to:_endDate];
}
- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
id userFolder, calFolder;
NSArray *infos;
AgenorUserManager *um;
NSString *email;
NSMutableArray *filtered;
unsigned i, count;
userFolder = [self container];
calFolder = [userFolder lookupName:@"Calendar" inContext:nil acquire:NO];
infos = [calFolder fetchFreebusyInfosFrom:_startDate to:_endDate];
um = [AgenorUserManager sharedUserManager];
email = [um getEmailForUID:[userFolder login]];
count = [infos count];
filtered = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
for (i = 0; i < count; i++) {
NSDictionary *info;
NSArray *partmails;
unsigned p, pCount;
info = [infos objectAtIndex:i];
partmails = [[info objectForKey:@"partmails"]
componentsSeparatedByString:@"\n"];
pCount = [partmails count];
for (p = 0; p < pCount; p++) {
NSString *pEmail;
pEmail = [partmails objectAtIndex:p];
if ([pEmail isEqualToString:email]) {
NSArray *partstates;
NSString *state;
partstates = [[info objectForKey:@"partstates"]
componentsSeparatedByString:@"\n"];
state = [partstates objectAtIndex:p];
if ([state intValue] == iCalPersonPartStatAccepted) {
// TODO: add tentative apts as well, but put state and email
// into info
[filtered addObject:info];
}
break;
}
}
}
return filtered;
}
/* Private API */
- (NSString *)iCalStringForFreeBusyInfos:(NSArray *)_infos
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
AgenorUserManager *um;
NSMutableString *ms;
NSString *uid, *x;
unsigned i, count;
um = [AgenorUserManager sharedUserManager];
uid = [[self container] login];
ms = [[[NSMutableString alloc] initWithCapacity:128] autorelease];
/* preamble */
[ms appendString:@"BEGIN:VCALENDAR\r\n"];
[ms appendString:@"BEGIN:VFREEBUSY\r\n"];
/* PRODID */
[ms appendString:@"PRODID:SOGo/0.9\r\n"];
/* VERSION */
[ms appendString:@"VERSION:2.0\r\n"];
/* DTSTAMP */
[ms appendString:@"DTSTAMP:"];
[ms appendString:[[NSCalendarDate calendarDate] icalString]];
[ms appendString:@"\r\n"];
/* ORGANIZER - strictly required but missing for now */
/* ATTENDEE */
[ms appendString:@"ATTENDEE"];
if ((x = [um getCNForUID:uid])) {
[ms appendString:@";CN=\""];
[ms appendString:[x iCalDQUOTESafeString]];
[ms appendString:@"\""];
}
if ((x = [um getEmailForUID:uid])) {
[ms appendString:@":"]; /* sic! */
[ms appendString:[x iCalSafeString]];
}
[ms appendString:@"\r\n"];
/* DTSTART */
[ms appendString:@"DTSTART:"];
[ms appendString:[_startDate icalString]];
[ms appendString:@"\r\n"];
/* DTEND */
[ms appendString:@"DTEND:"];
[ms appendString:[_endDate icalString]];
[ms appendString:@"\r\n"];
/* FREEBUSY */
count = [_infos count];
for (i = 0; i < count; i++) {
NSDictionary *info;
NSCalendarDate *startDate, *endDate;
info = [_infos objectAtIndex:i];
startDate = [info objectForKey:@"startDate"];
endDate = [info objectForKey:@"endDate"];
/* NOTE: currently we cannot differentiate between all the types defined
* in RFC2445, Section 4.2.9.
* These are: FREE" / "BUSY" / "BUSY-UNAVAILABLE" / "BUSY-TENTATIVE"
*/
[ms appendString:@"FREEBUSY;FBTYPE=BUSY:"];
[ms appendString:[startDate icalString]];
[ms appendString:@"/"];
[ms appendString:[endDate icalString]];
[ms appendString:@"\r\n"];
}
/* postamble */
[ms appendString:@"END:VFREEBUSY\r\n"];
[ms appendString:@"END:VCALENDAR\r\n"];
return ms;
}
/* deliver content without need for view method */
- (id)GETAction:(id)_ctx {
WOResponse *r;
NSData *contentData;
contentData = [[self contentAsString] dataUsingEncoding:NSUTF8StringEncoding];
r = [(WOContext *)_ctx response];
[r setHeader:@"text/calendar" forKey:@"content-type"];
[r setContent:contentData];
[r setStatus:200];
return r;
}
@end
@@ -0,0 +1,52 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: SOGoAppointmentFolder.h 137 2004-07-02 17:42:14Z helge $
#ifndef __Appointments_SOGoGroupAppointmentFolder_H__
#define __Appointments_SOGoGroupAppointmentFolder_H__
#include "SOGoAppointmentFolder.h"
/*
SOGoGroupAppointmentFolder
Parent object: an SOGoGroupFolder (or subclass)
Child objects: SOGoAppointmentObject
Note: this is only a subclass of SOGoAppointmentFolder to inherit all the
SOPE methods (it provides the same API). It is not an ocsFolder but
rather looks up the "child" folders for aggregation using regular SOPE
techniques.
=> hm, do we need "aspects" in SOPE? ;-)
Note: this class retains appointment folders looked up, so watch out for
reference cycles!
*/
@class NSMutableDictionary;
@interface SOGoGroupAppointmentFolder : SOGoAppointmentFolder
{
NSMutableDictionary *uidToFolder;
}
@end
#endif /* __Appointments_SOGoGroupAppointmentFolder_H__ */
@@ -0,0 +1,233 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoGroupAppointmentFolder.h"
#include "common.h"
@implementation SOGoGroupAppointmentFolder
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
- (void)dealloc {
[self->uidToFolder release];
[super dealloc];
}
/* looking up shared objects */
- (SOGoGroupsFolder *)lookupGroupsFolder {
return [[self container] lookupGroupsFolder];
}
/* selection */
- (NSArray *)calendarUIDs {
return [[self container] valueForKey:@"uids"];
}
/* folders */
- (void)resetFolderCaches {
[self->uidToFolder release]; self->uidToFolder = nil;
}
- (SOGoAppointmentFolder *)folderForUID:(NSString *)_uid {
if (self->uidToFolder == nil) {
// TODO: can we trigger a fetch?
[self errorWithFormat:
@"called -folderForUID method before fetchCoreInfos .."];
return nil;
}
return [self->uidToFolder objectForKey:_uid];
}
/* merging */
- (BOOL)doesRecord:(NSDictionary *)_rec conflictWith:(NSDictionary *)_other {
if (_rec == _other)
return NO;
if ([_rec isEqual:_other])
return NO;
return YES;
}
- (NSDictionary *)_registerConflictingRecord:(NSDictionary *)_other
inRecord:(NSDictionary *)_record
{
NSMutableArray *conflicts;
if (_record == nil) return _other;
if (_other == nil) return _record;
if ((conflicts = [_record objectForKey:@"conflicts"]) == nil) {
NSMutableDictionary *md;
md = [[_record mutableCopy] autorelease];
conflicts = [NSMutableArray arrayWithCapacity:4];
[md setObject:conflicts forKey:@"conflicts"];
_record = md;
}
[conflicts addObject:_other];
return _record;
}
/* functionality */
- (SOGoAppointmentFolder *)calendarFolderForMemberFolder:(id)_folder {
SOGoAppointmentFolder *aptFolder;
if (![_folder isNotNull])
return nil;
aptFolder = [_folder lookupName:@"Calendar" inContext:nil acquire:NO];
if (![aptFolder isNotNull])
return nil;
if (![aptFolder respondsToSelector:@selector(fetchCoreInfosFrom:to:)]) {
[self errorWithFormat:@"folder does not implemented required API: %@",
_folder];
return nil;
}
return aptFolder;
}
/* overridden */
- (NSArray *)fetchFields:(NSArray *)_fields
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
NSArray *folders;
NSMutableArray *result;
NSMutableDictionary *uidToRecord;
unsigned i, count;
if ((folders = [[self container] valueForKey:@"memberFolders"]) == nil) {
[self errorWithFormat:@"calendar container has no 'memberFolders'?!"];
return nil;
}
[self resetFolderCaches];
if ((count = [folders count]) == 0)
return [NSArray array];
if (self->uidToFolder == nil)
self->uidToFolder = [[NSMutableDictionary alloc] initWithCapacity:7*count];
else
[self->uidToFolder removeAllObjects];
uidToRecord = [NSMutableDictionary dictionaryWithCapacity:(7 * count)];
result = [NSMutableArray arrayWithCapacity:(7 * count)];
for (i = 0; i < count; i++) {
SOGoAppointmentFolder *aptFolder;
id results;
NSDictionary *record;
aptFolder = [self calendarFolderForMemberFolder:
[folders objectAtIndex:i]];
if (![aptFolder isNotNull]) {
[self debugWithFormat:@"did not find a Calendar folder in folder: %@",
[folders objectAtIndex:i]];
continue;
}
results = [aptFolder fetchFields:_fields
from:_startDate
to:_endDate];
if (![results isNotNull]) continue;
results = [results objectEnumerator];
while ((record = [results nextObject])) {
NSString *uid;
NSDictionary *existingRecord;
uid = [record objectForKey:@"uid"];
if (![uid isNotNull]) {
[self warnWithFormat:@"record without uid: %@", result];
[result addObject:record];
continue;
}
if ((existingRecord = [uidToRecord objectForKey:uid]) == nil) {
/* record not yet in result set */
[uidToRecord setObject:record forKey:uid];
[result addObject:record];
[self->uidToFolder setObject:aptFolder forKey:uid];
}
else if ([self doesRecord:existingRecord conflictWith:record]) {
/* record already registered and it conflicts (diff values) */
NSDictionary *newRecord;
int idx;
newRecord = [self _registerConflictingRecord:record
inRecord:existingRecord];
[uidToRecord setObject:newRecord forKey:uid];
if ((idx = [result indexOfObject:existingRecord]) != NSNotFound)
[result replaceObjectAtIndex:idx withObject:newRecord];
}
else {
/* record already registered, but values in sync, nothing to do */
}
}
}
return result;
}
/* URL generation */
- (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx {
/* Note: fetchCore must have been called before this works */
SOGoAppointmentFolder *folder;
if ([_uid length] == 0) {
[self errorWithFormat:@"got invalid UID."];
return nil;
}
if ((folder = [self folderForUID:_uid]) == nil) {
[self errorWithFormat:@"did not find a folder containing UID: '%@'",
_uid];
return nil;
}
if (![folder respondsToSelector:_cmd]) {
[self errorWithFormat:@"found folder cannot construct UID URLs: %@",
folder];
return nil;
}
[self debugWithFormat:@"found ID %@ in folder: %@", _uid, folder];
return [folder baseURLForAptWithUID:_uid inContext:_ctx];
}
@end /* SOGoGroupAppointmentFolder */
+10
View File
@@ -0,0 +1,10 @@
# Version file
SUBMINOR_VERSION:=50
# v0.9.42 requires libSOGo v0.9.54
# v0.9.32 requires libGDLContentStore v4.5.26
# v0.9.28 requires libNGiCal v4.5.47
# v0.9.26 requires libSOGo v0.9.30
# v0.9.19 requires libNGiCal v4.5.36
# v0.9.13 requires libSOGo v0.9.26
+2
View File
@@ -0,0 +1,2 @@
{
}
+31
View File
@@ -0,0 +1,31 @@
/*
Copyright (C) 2002-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
# include <NGExtensions/NGObjectMacros.h>
# include <NGExtensions/NSString+Ext.h>
#endif
#include <NGExtensions/NGExtensions.h>
#include <NGObjWeb/NGObjWeb.h>
#include <NGObjWeb/SoObjects.h>
+26
View File
@@ -0,0 +1,26 @@
{
requires = ( MAIN );
publicResources = (
);
factories = {
};
classes = {
SOGoAppointmentFolder = {
superclass = "SOGoFolder";
};
SOGoGroupAppointmentFolder = {
superclass = "SOGoAppointmentFolder";
};
SOGoAppointmentObject = {
superclass = "SOGoContentObject";
};
SOGoFreeBusyObject = {
superclass = "SOGoContentObject";
};
};
}
+102
View File
@@ -0,0 +1,102 @@
2005-07-15 Helge Hess <helge.hess@opengroupware.org>
* common.make: fixed for OSX compilation
2005-07-12 Marcus Mueller <znek@mulle-kybernetik.com>
* common.make: provide default install dir and bundle extension to
WO bundles
2005-03-20 Helge Hess <helge.hess@opengroupware.org>
* common.make: link against GDLContentStore instead of OGoContentStore
2005-02-24 Helge Hess <helge.hess@opengroupware.org>
* common.make (ADDITIONAL_LIB_DIRS): removed SOGoLogic from the linking
path
2005-02-17 Marcus Mueller <znek@mulle-kybernetik.com>
* common.make: removed dependency to removed libSOGoLogic
2005-02-15 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.25
* GNUmakefile.preamble: added libNGiCal as a dependency
* Appointments/SOGoAppointmentObject.[hm]: added -event as a
convenience to retrieve a complete iCalEvent from the stored
representation. This is used by SOGoAppointmentFolder to perform
necessary range calculations on recurrent events.
* Appointments/SOGoAppointmentFolder.[hm]: extended -fixupRecord: and
-fixupRecords: to accept a fetchRange: as second parameter. This
is used for cyclic (recurrent) events to determine the required
'amount' of fixup that needs to be done. During -fixupRecords: its
correctly determined now whether the record describes a recurrent
event - if that's the case, the whole event is retrieved and the
record gets duplicated (memory efficiently) as necessary to resemble
the appropriate fetch information in the desired range.
2004-12-22 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.24
* Appointments/SOGoAppointmentFolder.[hm]: added -fetchFreebusy...
Removed all -fetchXXXInfosFromFolder: methods. There's a single
method to override now, making the job in SOGoGroupAppointmentFolder
much easier (to understand).
* Appointments/SOGoGroupAppointmentFolder.m: added necessary fetch
abstraction.
2004-12-17 Marcus Mueller <znek@mulle-kybernetik.com>
* Appointments/SOGoAppointmentFolder.[hm]: added "partstates" to
coreInfos for fetching. Also added new API to fetch a stripped down
version of coreInfos, suitable for overviews. (v0.9.23)
2004-12-15 Marcus Mueller <znek@mulle-kybernetik.com>
* Appointments/SOGoAppointmentFolder.m: default redirect is now
"schedule" instead of "weekoverview" (0.9.22)
* Appointments/SOGoAppointmentFolder.m: cleanup (v0.9.21)
* Appointments/SOGoAppointmentFolder.m: compile fix (v0.9.20)
2004-12-13 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.19
* Appointments/SOGoAppointmentFolder.[hm]: new method to fetch generic
fields (better suited for freebusy), fixed some "mailto:" prefix
madness
* Appointments/SOGoAppointmentFolder.m: fixed bug in
-uidsFromICalPersons: (v0.9.18)
2004-12-10 Marcus Mueller <znek@mulle-kybernetik.com>
* Appointments/SOGoAppointmentFolder.m: added new quickinfo attributes
for fetching (v0.9.17)
2004-12-08 Marcus Mueller <znek@mulle-kybernetik.com>
* GNUmakefile: added Sieve product
* Appointments/SOGoGroupAppointmentFolder.m,
Appointments/SOGoAppointmentFolder.m, SOGo/SOGoContentObject.m,
SOGo/SOGoUserFolder.m: changed to NGLogging.
2004-08-26 Marcus Mueller <znek@mulle-kybernetik.com>
* added "Mailer" product
2004-08-25 Marcus Mueller <znek@mulle-kybernetik.com>
* added "Contacts" product
* ChangeLog: created
+73
View File
@@ -0,0 +1,73 @@
2005-07-21 Helge Hess <helge.hess@opengroupware.org>
* product.plist: make addressbook only available to owner (v0.9.15)
2005-07-18 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactObject.m: print an error log if a vCard could not be
parsed, cached failed parsing attempts (v0.9.14)
2005-07-15 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactFolder.m: fixed a gcc 4.0 warning (v0.9.13)
2005-07-14 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactFolder.m: use +globallyUniqueObjectId method from
SOGoFolder.m baseclass (v0.9.12)
2005-07-13 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactObject.m: hardened against vCard content (v0.9.11)
* SOGoContactObject.m: removed special GET action, the redirect to
'view' seems to be handled by SOPE now. This also makes WebDAV GET
work (v0.9.10)
2005-03-20 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactFolder.m: changed to use GDLContentStore (v0.9.9)
2005-02-24 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactObject.m: fixed a MacOSX warning (v0.9.8)
2005-02-18 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactObject.m: added a REST method to patch a single field of
the contact (v0.9.7)
2005-02-06 Helge Hess <helge.hess@opengroupware.org>
* SOGoContactObject.m, SOGoContactFolder.m: added proper
message/folder class (v0.9.6)
2004-12-08 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoContactFolder.m: changed to NGLogging (v0.9.5)
2004-09-25 Helge Hess <helge.hess@opengroupware.org>
* fixed compilation on MacOSX (v0.9.4)
2004-08-27 Helge Hess <helge.hess@skyrix.com>
* v0.9.3
* SOGoContactObject.m: use 'cn' as davDisplayName
* SOGoContactObject.[hm]: can now decode the property list stored in
the store (v0.9.2)
* started implementation of contact folders (v0.9.1)
2004-08-26 Marcus Mueller <znek@mulle-kybernetik.com>
* Product.m: typo in comment fixed
* SOGoContactFolder.h, SOGoContactObject.h: changed _Appointments_ to
_Contacts_
2004-08-25 Marcus Mueller <znek@mulle-kybernetik.com>
* ChangeLog: created
+22
View File
@@ -0,0 +1,22 @@
# GNUstep makefile
include ../common.make
BUNDLE_NAME = Contacts
Contacts_PRINCIPAL_CLASS = SOGoContactsProduct
Contacts_OBJC_FILES = \
Product.m \
\
SOGoContactObject.m \
SOGoContactFolder.m \
Contacts_RESOURCE_FILES += \
Version \
product.plist \
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
-include GNUmakefile.postamble
include ../../fhsbundle.make
+1
View File
@@ -0,0 +1 @@
# compilation settings
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: Product.m 274 2004-08-26 13:10:49Z znek $
#import <Foundation/NSObject.h>
@interface SOGoContactsProduct : NSObject
{
}
@end
@implementation SOGoContactsProduct
@end /* SOGoContactsProduct */
+58
View File
@@ -0,0 +1,58 @@
We can basically store anything we want in the property list. But we should
watch inetOrgPerson and vCard person compatibility in case we want to move
to vCard storage later on.
inetOrgPerson fields
====================
businessCategory
carLicense
departmentNumber
destinationIndicator
employeeNumber
employeeType
facsimileTelephoneNumber
givenName
homePostalAddress
initials
jpegPhoto
l
mail
manager
mobile
o
objectClass
ou
pager
physicalDeliveryOfficeName
postOfficeBox
postalCode
preferredLanguage
registeredAddress
roomNumber
secretary
seeAlso
sn
street
telephoneNumber
teletexTerminalIdentifier
title
uid
userCertificate
userPKCS12
userPassword
x121Address
audio
cn
description
displayName
homePhone
internationaliSDNNumber
labeledURI
photo
postalAddress
preferredDeliveryMethod
st
telexNumber
userSMIMECertificate
x500uniqueIdentifier
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Contacts_SOGoContactFolder_H__
#define __Contacts_SOGoContactFolder_H__
#include <SOGo/SOGoFolder.h>
/*
SOGoContactFolder
Parent object: the SOGoUserFolder
Child objects: SOGoContactObject
The SOGoContactFolder maps to an GCS folder of type 'contact', that
is, a content folder containing vcal?? files (and a proper quicktable).
*/
@class NSString, NSArray, NSCalendarDate, NSException;
@class GCSFolder;
@interface SOGoContactFolder : SOGoFolder
{
}
/* fetching */
- (NSArray *)fetchCoreInfos;
@end
#endif /* __Contacts_SOGoContactFolder_H__ */
+123
View File
@@ -0,0 +1,123 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoContactFolder.h"
#include <SOGo/SOGoCustomGroupFolder.h>
#include <SOGo/AgenorUserManager.h>
#include <GDLContentStore/GCSFolder.h>
#include <NGiCal/NGiCal.h>
#include "common.h"
#include <unistd.h>
#include <stdlib.h>
@implementation SOGoContactFolder
/* name lookup */
- (BOOL)isValidContactName:(NSString *)_key {
if ([_key length] == 0)
return NO;
return YES;
}
- (id)contactWithName:(NSString *)_key inContext:(id)_ctx {
static Class ctClass = Nil;
id ct;
if (ctClass == Nil)
ctClass = NSClassFromString(@"SOGoContactObject");
if (ctClass == Nil) {
[self errorWithFormat:@"missing SOGoContactObject class!"];
return nil;
}
ct = [[ctClass alloc] initWithName:_key inContainer:self];
return [ct autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
return obj;
if ([self isValidContactName:_key]) {
#if 0
if ([[self ocsFolder] versionOfContentWithName:_key])
#endif
return [self contactWithName:_key inContext:_ctx];
}
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* fetching */
- (NSArray *)fixupRecords:(NSArray *)_records {
return _records;
}
- (NSArray *)fetchCoreInfos {
NSArray *fields, *records;
fields = [NSArray arrayWithObjects:
@"c_name", @"cn",
@"sn", @"givenname", @"l",
@"mail", @"telephonenumber",
nil];
records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
if (records == nil) {
[self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
records = [self fixupRecords:records];
//[self debugWithFormat:@"fetched %i records.", [records count]];
return records;
}
/* GET */
- (id)GETAction:(id)_ctx {
// TODO: I guess this should really be done by SOPE (redirect to
// default method)
WOResponse *r;
NSString *uri;
uri = [[_ctx request] uri];
if (![uri hasSuffix:@"/"]) uri = [uri stringByAppendingString:@"/"];
uri = [uri stringByAppendingString:@"view"];
r = [_ctx response];
[r setStatus:302 /* moved */];
[r setHeader:uri forKey:@"location"];
return r;
}
/* folder type */
- (NSString *)outlookFolderClass {
return @"IPF.Contact";
}
@end /* SOGoContactFolder */
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Contacts_SOGoContactObject_H__
#define __Contacts_SOGoContactObject_H__
#include <SOGo/SOGoContentObject.h>
/*
SOGoContactObject
Represents a single contact. This SOPE controller object manages all the
attendee storages (that is, it might store into multiple folders for meeting
appointments!).
Note: SOGoContactObject do not need to exist yet. They can also be "new"
appointments with an externally generated unique key.
*/
@class NSDictionary;
@class NGVCard;
@interface SOGoContactObject : SOGoContentObject
{
id record; /* either an NSMutableDictionary or a NGVCard */
}
/* accessors */
- (id)record;
- (BOOL)isVCardRecord;
- (NGVCard *)vCard;
@end
#endif /* __Contacts_SOGoContactObject_H__ */
+162
View File
@@ -0,0 +1,162 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoContactObject.h"
#include <NGiCal/NGVCard.h>
#include "common.h"
@implementation SOGoContactObject
- (void)dealloc {
[self->record release];
[super dealloc];
}
/* content */
- (id)record {
if (self->record == nil) {
NSString *s;
s = [self contentAsString];
if ([s hasPrefix:@"BEGIN:VCARD"]) {
NSArray *v;
v = [NGVCard parseVCardsFromSource:s];
if ([v count] == 0) {
[self errorWithFormat:@"Could not parse vCards from content!"];
return nil;
}
self->record = [[v objectAtIndex:0] retain];
}
else
self->record = [[s propertyList] copy];
if (self->record == nil)
self->record = [[NSNull null] retain];
}
return [self->record isNotNull] ? self->record : nil;
}
- (BOOL)isVCardRecord {
return [[self record] isKindOfClass:[NGVCard class]];
}
- (NGVCard *)vCard {
return [[self record] isKindOfClass:[NGVCard class]]
? [self record]
: nil;
}
/* key value coding */
- (id)valueForKey:(NSString *)_key {
id value;
if ((value = [[self record] valueForKey:_key]) != nil)
return value;
return [super valueForKey:_key];
}
/* DAV */
- (NSString *)davDisplayName {
NSString *n;
if ((n = [self valueForKey:@"cn"]))
return n;
return [self nameInContainer];
}
/* specialized actions */
- (NSException *)saveRecord:(id)_record {
if ([_record isKindOfClass:[NGVCard class]]) {
// TODO: implement a vCard generator
return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
reason:@"Saving vCards is not supported yet."];
}
return [self saveContentString:[_record description]];
}
- (NSException *)patchField:(NSString *)_fname value:(NSString *)_value
inContext:(id)_ctx
{
NSMutableDictionary *md;
if ([self isVCardRecord]) {
return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
reason:@"Changing vCards is not supported yet."];
}
if ([_fname length] == 0) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"missing form field name"];
}
if ((md = [[[self record] mutableCopy] autorelease]) == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"did not find contact record"];
}
[md setObject:[_value isNotNull] ? _value : @"" forKey:_fname];
return [self saveRecord:md];
}
- (id)patchOneFieldAction:(id)_ctx {
/*
expects: fieldname\nfieldvalue
TODO: should be extended to properly parse XML.
*/
NSArray *fields;
NSException *error;
fields = [[[(WOContext *)_ctx request] contentAsString]
componentsSeparatedByString:@"\n"];
if ([fields count] < 2) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"missing form fields"];
}
error = [self patchField:[fields objectAtIndex:0]
value:[fields objectAtIndex:1]
inContext:_ctx];
if (error != nil)
return error;
return [(WOContext *)_ctx response];
}
/* message type */
- (NSString *)outlookMessageClass {
return @"IPM.Contact";
}
@end /* SOGoContactObject */
+7
View File
@@ -0,0 +1,7 @@
# version file
SUBMINOR_VERSION:=15
# v0.9.12 requires libSOGo v0.9.54
# v0.9.9 requires libGDLContentStore v4.5.26
# v0.9.5 requires libNGExtensions v4.5.136
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2002-2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: common.h 577 2005-02-17 14:38:14Z helge $
#import <Foundation/Foundation.h>
#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
# include <NGExtensions/NGObjectMacros.h>
# include <NGExtensions/NSString+Ext.h>
#endif
#include <NGExtensions/NGExtensions.h>
#include <NGObjWeb/NGObjWeb.h>
#include <NGObjWeb/SoObjects.h>
+35
View File
@@ -0,0 +1,35 @@
{
requires = ( MAIN );
publicResources = (
);
factories = {
};
classes = {
SOGoContactFolder = {
superclass = "SOGoFolder";
protectedBy = "View";
defaultAccess = "allow"; /* I think this affects key lookup */
defaultRoles = {
"View" = "Owner";
"WebDAV Access" = "Owner";
};
};
SOGoContactObject = {
superclass = "SOGoContentObject";
protectedBy = "View";
defaultAccess = "allow"; /* I think this affects key lookup */
defaultRoles = {
"View" = "Owner";
"WebDAV Access" = "Owner";
};
methods = {
};
};
};
}
+13
View File
@@ -0,0 +1,13 @@
# GNUstep makefile
-include ../config.make
include $(GNUSTEP_MAKEFILES)/common.make
SUBPROJECTS = \
SOGo \
Appointments \
Contacts \
Mailer \
Sieve \
include $(GNUSTEP_MAKEFILES)/aggregate.make
+718
View File
@@ -0,0 +1,718 @@
2005-07-30 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: fetch Kolab parts for inline display (v0.9.126)
* SOGoMailObject.m: added 'outlookMessageClass' property with some
mail tagging for Kolab entities (v0.9.125)
* SOGoMailBodyPart.m, SOGoMailObject.m: added default to disable etag
delivery/checks for mail objects (SOGoMailDisableETag) (v0.9.124)
2005-07-22 Helge Hess <helge.hess@opengroupware.org>
* v0.9.123
* SOGoMailAccount.m: added -preferredIdentity method
* SOGoUser+Mail.m: added a method to retrieve the primary identity of
an account
* SOGoSharedMailAccount.m: do not show the drafts folder in share
mailboxes (v0.9.122)
* SOGoUser+Mail.m: properly set email field of share account identities
(v0.9.121)
* v0.9.120
* SOGoMailBaseObject.m: added a method to locate the mail accounts
folderby traversing the container hierarchy
* SOGoMailIdentity.m: include sent folder in -description
* SOGoMailAccounts.m: added method to fetch just the identities with
emitter permissions
* SOGoUser+Mail.m: properly create mail identity objects
2005-07-21 Helge Hess <helge.hess@opengroupware.org>
* v0.9.119
* SOGoMailAccounts.m: fetch identities from SoUser
* added SOGoUser+Mail category for mail specific SoUser fields
* SOGoMailIdentity.m: added ivars/accessors/description
* v0.9.118
* SOGoMailAccounts.m: reject access to the folder in case the name of
the home folder doesn't match the logged in account (a user tries to
access another users Mail application)
* started work on account based identities
* SOGoMailObject.m: added a -davEntityTag (v0.9.117)
2005-07-20 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: improved -doesMailExist check (do not refetch if
the coreinfo is available anyways) (v0.9.116)
* v0.9.115
* SOGoMailObject.m: do not use clientObject when fetching the
coreinfos, added a -doesMailExist method to cheaply check for
mail existance
* SOGoMailBodyPart.m, SOGoMailObject.m: added a constant etag for
content (possible for IMAP4 content), check etag prior delivering
content (return a 304 in case the constant etag is supplied)
* SOGoMailObject.m: added handling for some specific headers (v0.9.114)
2005-07-19 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: changed to fetch and parse the full mail header,
can be disabled using the 'SOGoDoNotFetchMailHeader' default. The
overhead is about 10ms on the dev-system (v0.9.113)
* SOGoMailFolder.m: report NO in -isCreateAllowed when the folder is
the INBOX and SOGoSpecialFoldersInRoot (altnamespace) is enabled
(#1472) (v0.9.112)
* SOGoDraftObject.m: made 'SOGoInternetMailSuffix' a pattern which can
access request values (v0.9.111)
* SOGoDraftObject.m: added support for adding a marker to the textpart
of the message in case the client is accessing the server from the
Internet. The text can be specified using the
'SOGoInternetMailSuffix' default (v0.9.110)
* v0.9.109
* added fragile base class version checks to most classes
* SOGoMailBaseObject.m: cache failed login attempts (+ connection) to
improve failed-login behaviour
2005-07-18 Helge Hess <helge.hess@opengroupware.org>
* SOGoDraftObject.m: send text/* attachments with disposition 'attach'
instead of 'inline' (inline can still be enabled with the
'SOGoShowTextAttachmentsInline' default, but be aware of the charset
issues with text attachments!) (v0.9.108)
2005-07-15 Helge Hess <helge.hess@opengroupware.org>
* v0.9.107
* SOGoMailBodyPart.m: check lookup key path extension to determine the
part subclass to instantiate (note: the link generator in the UI
must generate proper links including the extension to make this work)
* added special part subclasses SOGoVCardMailBodyPart and
SOGoCalendarMailBodyPart
* SOGoMailObject.m: map vcf extension to vcard mime type, added some
debug logging
* SOGoMailFolder.m: fixed a stupid bug in permission parsing (v0.9.106)
* SOGoMailFolder.m: added support for more Cyrus permission flags
(v0.9.105)
* SOGoMailObject.m, SOGoMailBodyPart.m, SOGoDraftObject.m: fixed gcc4.0
warnings (v0.9.104)
* SOGoMailManager.h: fixed a gcc 4.0 warning (v0.9.103)
2005-07-14 Helge Hess <helge.hess@opengroupware.org>
* v0.9.102
* SOGoSharedMailAccount.m: create an object of class
SOGoSharedInboxFolder in case the lookup key is INBOX
* added SOGoSharedInboxFolder.m for inboxes of shared mail accounts
(which have a different location than standard inboxes and never
contain subfolders)
* SOGoSharedMailAccount.m: prepared code to support a shared INBOX
folder (v0.9.101)
* v0.9.100
* SOGoMailAccounts.m: create an object of class SOGoSharedMailAccount
in case the lookup-key contains an ".-."
* SOGoMailAccount.[hm]: added some functionality to deal with shared
mail accounts
* added SOGoSharedMailAccount class for shared mailboxes
2005-07-12 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailAccounts.m: use new SoUser API instead of AgenorUserManager
(v0.9.99)
2005-07-11 Helge Hess <helge.hess@opengroupware.org>
* v0.9.98
* use new -imap4Connection API everywhere
* SOGoMailBaseObject.m: deprecated -mailManager method, added
-imap4Connection method
* SOGoMailBaseObject.[hm]: removed unused -imap4Client method (v0.9.97)
* moved SOGoMailManager related API to NGImap4 (NGImap4Connection,
NGImap4ConnectionManager, etc) (v0.9.96)
2005-07-11 Helge Hess <helge.hess@opengroupware.org>
* v0.9.95
* SOGoMailBaseObject.m: removed -imapFolderName method
* SOGoMailManager.m, SOGoMailConnectionEntry.m: moved implementations
of operations to SOGoMailConnectionEntry
2005-07-08 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailAccounts.m: use WOContext method from libSOGo to detect
Internet access (v0.9.94)
* SOGoMailAccounts.m: only deliver share-names as subobjects if the
request is not coming from the Internet. Ensure that limitation in
-lookupName:. Can be enabled using SOGoInternetDetectQualifier just
like in UIxMailEditor (v0.9.93)
* v0.9.92
* SOGoMailManager.m, SOGoMailConnectionEntry.m: added caching of
per-folder permissions to avoid an IMAP4 query on each folder click
* SOGoMailFolder.m: fixed an inconsistency in the naming of flags
2005-07-07 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailManager.m, SOGoMailFolder.m: added methods to "bulk add"
flags to all messages contained in a folder
(-addFlags:toAllMessagesInURL:password: for the manager,
-addFlagsToAllMessages: for the SOGoFolder) (v0.9.91)
* added specific SOGoTrashFolder.m class (subclass of SOGoMailFolder)
to allow hooking up a different UI (v0.9.90)
* SOGoMailAccounts.m: expose shared mailboxes retrieved via
AgenorUserManager (aka LDAP) in -toManyRelationshipKeys (v0.9.89)
* SOGoMailObject.m: implemented -trashInContext: method (v0.9.88)
2005-07-06 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: prefetch pgp-signature parts (v0.9.87)
* SOGoMailObject.m: started -trash action (v0.9.86)
* v0.9.85
* SOGoMailFolder.m: added ability to check certain ACL permissions (and
cache them for one invocation)
* SOGoMailObject.m: implemented -isDeletionAllowed by asking the folder
for deletion
* SOGoMailManager.m: added methods to retrieve the ACL and the myrights
from an IMAP4 mailbox
* SOGoMailManager.m: detect Cyrus 'permission denied' when changing
flags and deliver a http-code 403 exception (v0.9.84)
2005-03-25 Helge Hess <helge.hess@opengroupware.org>
* v0.9.83
* SOGoMailManager.m: properly select folder prior changing flags
* SOGoMailObject.m: added a special DELETEAction: for mails, it marks
a mail deleted and expunges the folder to ensure the mail is deleted
(this is different to the -delete method!)
* SOGoMailFolder.m: implemented content datasource for efficient WebDAV
listings (currently uses hardcoded parts and has inefficient DASL
support) (v0.9.82)
* v0.9.81
* SOGoMailFolder.m: implemented mailbox renaming using WebDAV MOVE
* SOGoMailManager.m: added support for renaming mailboxes
* v0.9.80
* SOGoMailFolder.m: now ensures during lookup that mailboxes actually
exist, this may slow down the lookup if the hierarchy was not fetched
yet
* SOGoMailObject.m, SOGoMailFolder.m: prepared for WebDAV move and copy
operations
* v0.9.79
* SOGoMailFolder.m: ensure that mailbox exists if a DAV depth:0 query
is run on the folder (by selecting the mailbox)
* SOGoMailManager.m: added method to retrieve mailbox select info
* SOGoMailAccount.m: added ability to create mailboxes at the root
(account) level (v0.9.78)
2005-03-03 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailManager.m: improved compatibility with Cocoa Foundation
(some weird difference in NGImap4 result processing) (v0.9.77)
2005-02-21 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: generate proper .ics extension for text/calendar
message parts, fetch text/calendar parts as plain content (v0.9.76)
2005-02-15 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailAccount.m: added 'SOGoSpecialFoldersInRoot' default to
make SOGo lookup Sent/Trash in the IMAP4 root instead of under
INBOX (that is, set SOGoSpecialFoldersInRoot to YES if you have
'altnamespace' in Cyrus set to yes) (v0.9.75)
2005-02-14 Helge Hess <helge.hess@opengroupware.org>
* v0.9.74
* SOGoMailAccount.m: fetch all root folders from IMAP4, do not assume
"INBOX"
* SOGoMailManager.m: properly support rootfolders in IMAP4
2005-02-13 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailAccount.m: changed short title creation to use login@host
(v0.9.73)
* SOGoMailFolder.m: do not fetch message ids on folder traversal
(v0.9.72)
2005-02-09 Helge Hess <helge.hess@opengroupware.org>
* v0.9.71
* SOGoMailObject.m: -lookupInfoForBodyPart: can now deal with string
parameters (eg 1.2.3) in addition to arrays and returns the full
body structure for empty strings ("")
added support for fetching parts as NSString's (by applying the
charset in the body structure)
2005-02-08 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailObject.m: added method to fetch individual plain/text parts
* v0.9.70
* SOGoMailManager.m: only perform IMAP4 select if the folder changes
(gives a major speed boost), can be disabled with the
'SOGoAlwaysSelectIMAP4Folder' default
* SOGoMailAccount.m: added code to take the IMAP4 login from the
HTTP request
* v0.9.69
* reworked for new NGImap4 v4.5.210 API
* SOGoMailObject.m: removed fromEnvelopeAddress in favor of
fromEnvelopeAddresses to match the new NGImap4 v4.5.210 API
2005-02-07 Helge Hess <helge.hess@opengroupware.org>
* v0.9.68
* SOGoDraftObject.m: added ability to specify additional headers
* v0.9.67
* SOGoMailFolder.m: detect special folder names and return proper
folder type
* SOGoMailAccount.m: made Sent/Trash folder name configurable using the
'SOGoSentFolderName'/'SOGoTrashFolderName' defaults
2005-02-06 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailFolder.m, SOGoDraftsFolder.m: added proper folder classes
(v0.9.66)
2005-01-31 Helge Hess <helge.hess@opengroupware.org>
* v0.9.65
* SOGoMailManager.m, SOGoMailFolder.m: added expunge method
* SOGoMailObject.m: improve bulk key collector
* SOGoMailObject.m: moved more bulk fetching to SoObject (v0.9.64)
* SOGoMailObject.m: moved in plain/text bulk fetch method from mail
viewer (since it is general purpose) (v0.9.63)
* SOGoMailObject.m: properly handle part lookup in the context of
embedded message/rfc822 content (v0.9.62)
2005-01-30 Helge Hess <helge.hess@opengroupware.org>
* v0.9.61
* SOGoMailObject.m, SOGoMailBodyPart.m, product.plist: added type
specific SOGoMailBodyPart controller objects, reduced debug logs
* v0.9.60
* SOGoDraftObject.m: added -content and -contentAsString methods (Note:
those are expensive operations!), added GETAction: to retrieve the
MIME representation of a draft
* SOGoDraftsFolder.m: added -toOneRelationshipKeys to support SOPE
WebDAV access
* v0.9.59
* SOGoDraftObject.m: improved attachment/info API to return exceptions,
minor improvement to attachment name check, properly generate
message/rfc822 mime type for mail attachments, properly generate
"text/plain; utf8" header
* SOGoMailAccount.m: use a constant for INBOX folder name, disabled
'Filters' folder unless the 'SOGoEnableSieveFolder' is set (since
Sieve support is incomplete)
2005-01-28 Helge Hess <helge.hess@skyrix.com>
* v0.9.58
* SOGoMailObject.m: implemented a simple delete by setting the IMAP4
delete flag
* SOGoMailManager.m: added method to mark a message deleted
* SOGoMailFolder.m: just override -delete instead of implementing
-DELETAction: directrly
* SOGoMailAccount.m: added ability to retrieve Sent/Trash and INBOX
special mail folders
* v0.9.57
* SOGoMailBodyPart.m: enhanced lookup to allow for arbitary filenames
being attached to a body-part name. Improves download behaviour.
* SOGoMailBaseObject.m, SOGoMailObject.m: moved
-isBodyPartKey:inContext: method to base object for reuse in
SOGoMailBodyPart
* SOGoMailBodyPart.m: minor code cleanup
2005-01-26 Helge Hess <helge.hess@skyrix.com>
* v0.9.56
* SOGoMailManager.m, SOGoMailFolder.m: added method to save new
content in an SOGoMailFolder (to support #1054)
* SOGoMailAccount.m: minor code cleanup
2004-12-08 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoMailAccount.m, SOGoMailBaseObject.m, SOGoMailObject.m,
SOGoMailManager.m, SOGoDraftsFolder.m, SOGoMailAccounts.m,
SOGoMailBodyPart.m, SOGoDraftObject.m: changed to use NGLogging
(v0.9.55)
2004-11-27 Helge Hess <helge.hess@skyrix.com>
* SOGoMailAccount.m: added 'Filters' key and lookup of Sieve filters
(v0.9.54)
2004-11-11 Helge Hess <helge.hess@skyrix.com>
* v0.9.53
* SOGoMailAccounts.m: added -allFolderPathes methods to retrieve the
names of all folders associated with the account
* SOGoMailManager.m: added -allFoldersForURL:password: method
2004-11-09 Helge Hess <helge.hess@skyrix.com>
* added support for toggling mail flags (v0.9.52)
* moved all tree navigation code to UIxMailTree (v0.9.51)
* SOGoMailBaseObject.m, SOGoMailAccounts.m: moved tree code to separate
file (v0.9.50)
* SOGoMailBaseObject.m, SOGoMailManager.m, SOGoMailConnectionEntry.m:
added cache flush operations for getmail (v0.9.49)
2004-11-08 Helge Hess <helge.hess@skyrix.com>
* SOGoMailManager.m: improved a log message (v0.9.48)
2004-10-30 Helge Hess <helge.hess@skyrix.com>
* v0.9.47
* SOGoMailFolder.m: added ability to create and delete subfolders using
WebDAV
* SOGoMailConnectionEntry.m: added method to flush folder hierarchy
cache
* product.plist: added default role for adding folders (owner)
* SOGoMailObject.m: added -content and -contentAsString method to
retrieve raw IMAP4 message content, added GETAction to query a
message (v0.9.46)
2004-10-29 Helge Hess <helge.hess@skyrix.com>
* SOGoDraftObject.m: add 'name' parameter to content-type, properly
encode binary attachments (v0.9.45)
2004-10-28 Helge Hess <helge.hess@skyrix.com>
* SOGoDraftObject.m: do not patch NGImap4Envelope ivars and use the new
constructor method (v0.9.44)
2004-10-27 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailManager.m: added 'SOGoIMAP4StringSeparator' default to
configure to folder separator. Default is '/' instead of '.'
(v0.9.43)
* v0.9.42
* SOGoDraftObject.m: implements some SOGoMailObject methods
* SOGoDraftsFolder.m: implemented some draft folder listing
* SOGoDraftObject.m: added mail send functionality (v0.9.41)
* SOGoDraftObject.m: added ability to create NGMime objects from draft
(v0.9.40)
2004-10-26 Helge Hess <helge.hess@opengroupware.org>
* SOGoDraftObject.[hm]: added method to delete attachments (v0.9.39)
2004-10-26 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoDraftObject.h: lF compile fix (v0.9.38)
2004-10-25 Helge Hess <helge.hess@opengroupware.org>
* SOGoDraftObject.m: added ability to manage a draft object folder
(v0.9.37)
2004-10-21 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailBaseObject.m: extract IMAP4 password from HTTP basic
authentication (v0.9.36)
2004-10-20 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoMailAccounts.m: changed methods according to new API of
AgenorUserManager (v0.9.35)
2004-10-19 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoMailAccounts.m: values for toManyRelationshipKeys are constructed
from information retrieved by the AgenorUserManager now. (v0.9.34)
2004-10-11 Helge Hess <helge.hess@opengroupware.org>
* SOGoDraftsFolder.m: added methods to deal with new draft objects
(v0.9.33)
* started SOGoDraftObject (v0.9.32)
2004-10-10 Helge Hess <helge.hess@opengroupware.org>
* SOGoDraftsFolder.m: added methods to be compatible with the mail
folder (v0.9.31)
2004-10-08 Helge Hess <helge.hess@opengroupware.org>
* v0.9.30
* SOGoMailObject.m: added support for davContentLength, added
RFC822.SIZE to coreinfo attributes
* SOGoMailFolder.m: added .mail path extensions to generated
toOneRelationshipKeys
* SOGoMailObject.m: fixed body part id (starts with 1, not with 0)
* v0.9.29
* SOGoMailObject.m: added toOneRelationshipKeys/toManyRelationshipKeys
(return the contained body parts)
* SOGoMailFolder.m: added toOneRelationshipKeys (returns the message
uids)
* marked collections as WebDAV collections (v0.9.28)
2004-10-06 Helge Hess <helge.hess@opengroupware.org>
* started SOGoDraftsFolder (v0.9.27)
* v0.9.26
* SOGoMailConnectionEntry.m: added caching of sorted UIDs sets
* SOGoMailManager.m: moved SOGoMailConnectionEntry class to own file
2004-10-05 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailManager: removed range argument from -fetchUIDsInURL:..,
since IMAP4 doesn't support ranges anyway ... (v0.9.25)
2004-10-04 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailBodyPart.m: improved MIME type generation (v0.9.24)
* SOGoMailBodyPart.m, SOGoMailObject.m: finished first part fetching
support (v0.9.23)
* SOGoMailObject.m: added method to fetch core infos of a mail, added
various methods to retrieve core info data (like subject or date)
(v0.9.22)
2004-10-03 Helge Hess <helge.hess@opengroupware.org>
* v0.9.21
* SOGoMailObject.m: lookup 'number' names as part child objects
* added SOGoMailBodyPart as a child object of SOGoMailObject
* SOGoMailAccounts.m: changed link generation for active folder
* v0.9.20
* SOGoMailObject.m: added method to fetch parts
* SOGoMailManager.m: properly select folder prior fetch, added method
to fetch parts of a single (message) URL
2004-10-02 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailFolder.[hm]: removed ability to restrict UID fetch range,
need to fetch all (qualifier matching!) UIDs anyway (v0.9.19)
2004-10-01 Helge Hess <helge.hess@opengroupware.org>
* more work on fetching mails (v0.9.18)
* v0.9.17
* SOGoMailFolder.m: -fetchUIDsMatchingQualifier:sortOrdering:range: was
added to retrieve message UIDs
* SOGoMailManager.m: added method for fetching message ids with server
side sorting
2004-09-30 Helge Hess <helge.hess@skyrix.com>
* v0.9.16
* SOGoMailBaseObject.m: improved debug logging
* SOGoMailManager.m: fixed an Apple/libFoundation incompatibility
2004-09-29 Helge Hess <helge.hess@skyrix.com>
* SOGoMailManager.m: minor improvement to error logging (v0.9.15)
2004-09-29 Helge Hess <helge.hess@opengroupware.org>
* v0.9.14
* SOGoMailAccounts.m: added tree navigation support
* SOGoMailBaseObject.m: moved fetchSubfolders method to SOGoObject
* SOGoMailBaseObject.m: fixed WebDAV include (v0.9.13)
* v0.9.12
* SOGoMailAccount.m: added -shortTitle and use that as the
-davDisplayName
* SOGoMailBaseObject.m, SOGoMailAccounts.m: added path tree navigation
support
2004-09-28 Helge Hess <helge.hess@opengroupware.org>
* SOGoMailManager.m: added caching of folder hierarchy (v0.9.11)
* SOGoMailManager.m: added connection pooling (v0.9.10)
* v0.9.9
* SOGoMailBaseObject.m: added -mailManager method to retrieve the
mail manager object for a folder/mail/account
* added SOGoMailManager class, will probably be moved to SOGoLogic
in the long run
* SOGoMailBaseObject.m, SOGoMailAccount.m: added -mailAccountFolder
method to find the active root folder along the SOPE chain (v0.9.8)
* SOGoMailAccount.m: be graceful if the IMAP4 login failed (v0.9.7)
2004-09-27 Helge Hess <helge.hess@opengroupware.org>
* v0.9.6
* added basic folder listing
* SOGoMailAccount.m: list "INBOX" as the sole account subfolder
2004-09-26 Helge Hess <helge.hess@opengroupware.org>
* v0.9.5
* added SOGoMailBaseObject as a base object for the other mailer
SoObjects
* added -imapURL method to the mail SoObjects (the imapURL is used to
locate the object in the IMAP4 server, just like in Thunderbird)
2004-09-25 Helge Hess <helge.hess@opengroupware.org>
* fixed compilation on MacOSX (v0.9.4)
2004-09-20 Helge Hess <helge.hess@skyrix.com>
* SOGoMailAccounts.m: added hardcoded mail-account key (v0.9.3)
2004-09-08 Helge Hess <helge.hess@skyrix.com>
* added missing account objects (v0.9.2)
2004-08-26 Marcus Mueller <znek@mulle-kybernetik.com>
* ChangeLog: created
+43
View File
@@ -0,0 +1,43 @@
# GNUstep makefile
include ../common.make
BUNDLE_NAME = Mailer
Mailer_PRINCIPAL_CLASS = SOGoMailerProduct
Mailer_OBJC_FILES += \
Product.m \
\
SOGoMailManager.m \
\
SOGoMailBaseObject.m \
SOGoMailAccounts.m \
SOGoMailAccount.m \
SOGoSharedMailAccount.m \
SOGoMailFolder.m \
SOGoSharedInboxFolder.m \
SOGoMailObject.m \
SOGoMailFolderDataSource.m \
SOGoTrashFolder.m \
\
SOGoMailBodyPart.m \
SOGoImageMailBodyPart.m \
SOGoMessageMailBodyPart.m \
SOGoCalendarMailBodyPart.m \
SOGoVCardMailBodyPart.m \
\
SOGoDraftsFolder.m \
SOGoDraftObject.m \
\
SOGoMailIdentity.m \
SOGoUser+Mail.m \
Mailer_RESOURCE_FILES += \
Version \
product.plist \
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
-include GNUmakefile.postamble
include ../../fhsbundle.make
+7
View File
@@ -0,0 +1,7 @@
# compilation settings
ADDITIONAL_CPPFLAGS += \
-Wall -DCOMPILE_FOR_GSTEP_MAKE=1 \
-DUIX_MAILER_MAJOR_VERSION=$(MAJOR_VERSION) \
-DUIX_MAILER_MINOR_VERSION=$(MINOR_VERSION) \
-DUIX_MAILER_SUBMINOR_VERSION=$(SUBMINOR_VERSION)
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: Product.m 274 2004-08-26 13:10:49Z znek $
#import <Foundation/NSObject.h>
@interface SOGoMailerProduct : NSObject
{
}
@end
@implementation SOGoMailerProduct
@end /* SOGoMailerProduct */
+60
View File
@@ -0,0 +1,60 @@
# README for Mailer SoObjects
Class Overview
==============
SOGoMailManager
- backend class connecting to NGImap4, will probably move to SOGoLogic
Class Hierarchy
===============
[NSObject]
SOGoMailerProduct
<SOGoObject>
SOGoMailAccounts
SOGoMailBaseObject
SOGoDraftObject
SOGoDraftsFolder
SOGoMailAccount
SOGoSharedMailAccount
SOGoMailBodyPart
SOGoImageMailBodyPart
SOGoMessageMailBodyPart
SOGoMailFolder
SOGoSharedInboxFolder
SOGoMailObject
[EODataSource]
SOGoMailFolderDataSource
Defaults
========
SOGoEnableIMAP4Debug YES|NO - enable/disable debugging in SOGoMailManager
SOGoDisableIMAP4Pooling YES|NO - disable IMAP4 connection pooling
SOGoMailSpoolPath path - FS path where mail drafts are stored
SOGoNoDraftDeleteAfterSend YES|NO - makes the draft's -delete method a NOOP
SOGoSpecialFoldersInRoot YES|NO - do special folders live under INBOX?
- corresponds to the Cyrus setting: "altnamespace: yes"
SOGoEnableSieveFolder YES|NO - should the Sieve folder be visible?
SOGoSharedFolderName IMAP4-Name
- corresponds to the Cyrus setting: sharedprefix
SOGoOtherUsersFolderName IMAP4-Name
- corresponds to the Cyrus setting: userprefix
SOGoInternetMailSuffix String-Pattern
- suffix to add to mails sent via Internet
- eg: -SOGoInternetMailSuffix \
'"*** This email was composed using SOGo on the public Internet ***"'
you can access request values inside the pattern, eg:
"$headers.host$"
SOGoDoNotFetchMailHeader YES|NO - whether or not to fetch the mail header
- the mail header is ~4KB to fetch, a slowdown of ~13ms on the dev system
- the header gives much more information about the mail
- eg: spam status
- parsing the mail header takes time
SOGoMailDisableETag YES|NO - whether or not to check/deliver an etag
@@ -0,0 +1,53 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include <Mailer/SOGoMailBodyPart.h>
/*
SOGoCalendarMailBodyPart
A specialized SOGoMailBodyPart subclass for text/calendar attachments. Can
be used to attach special SoMethods.
See the superclass for more information on part objects.
*/
@interface SOGoCalendarMailBodyPart : SOGoMailBodyPart
{
}
@end
#include "common.h"
@implementation SOGoCalendarMailBodyPart
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
@end /* SOGoCalendarMailBodyPart */
+81
View File
@@ -0,0 +1,81 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoDraftObject_H__
#define __Mailer_SOGoDraftObject_H__
#include <Mailer/SOGoMailBaseObject.h>
/*
SOGoDraftsFolder
Parent object: SOGoDraftsFolder
Child objects: draft attachments?
The SOGoDraftObject is used for composing new messages. It is necessary
because we can't cache objects in a session. So the contents of the drafts
folder are some kind of "mail creation transaction".
TODO: store-info should be an own object, not NSDictionary.
*/
@class NSString, NSArray, NSDictionary, NSData, NSException;
@class NGMimeMessage, NGImap4Envelope;
@interface SOGoDraftObject : SOGoMailBaseObject
{
NSString *path;
NSDictionary *info; /* stores the envelope information */
NGImap4Envelope *envelope;
}
/* contents */
- (NSDictionary *)fetchInfo;
- (NSException *)storeInfo:(NSDictionary *)_info;
/* attachments */
- (NSArray *)fetchAttachmentNames;
- (BOOL)isValidAttachmentName:(NSString *)_name;
- (NSException *)saveAttachment:(NSData *)_attach withName:(NSString *)_name;
- (NSException *)deleteAttachmentWithName:(NSString *)_name;
/* NGMime representations */
- (NGMimeMessage *)mimeMessage;
- (NSString *)saveMimeMessageToTemporaryFile;
- (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_addh;
- (NSException *)sendMimeMessageAtPath:(NSString *)_path;
- (NSException *)sendMail;
/* operations */
- (NSException *)delete;
/* fake being a SOGoMailObject */
- (id)fetchParts:(NSArray *)_parts;
@end
#endif /* __Mailer_SOGoDraftObject_H__ */
+985
View File
@@ -0,0 +1,985 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoDraftObject.h"
#include <SoObjects/SOGo/WOContext+Agenor.h>
#include <NGMail/NGMimeMessage.h>
#include <NGMail/NGMimeMessageGenerator.h>
#include <NGMail/NGSendMail.h>
#include <NGMime/NGMimeBodyPart.h>
#include <NGMime/NGMimeFileData.h>
#include <NGMime/NGMimeMultipartBody.h>
#include <NGMime/NGMimeType.h>
#include <NGImap4/NGImap4Envelope.h>
#include <NGImap4/NGImap4EnvelopeAddress.h>
#include <NGExtensions/NSFileManager+Extensions.h>
#include "common.h"
@implementation SOGoDraftObject
static NGMimeType *TextPlainType = nil;
static NGMimeType *MultiMixedType = nil;
static NSString *userAgent = @"SOGoMail 1.0";
static BOOL draftDeleteDisabled = NO; // for debugging
static BOOL debugOn = NO;
static BOOL showTextAttachmentsInline = NO;
static NSString *fromInternetSuffixPattern = nil;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
/* Note: be aware of the charset issues before enabling this! */
showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"];
if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"]))
NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)");
fromInternetSuffixPattern = [ud stringForKey:@"SOGoInternetMailSuffix"];
if ([fromInternetSuffixPattern length] == 0)
NSLog(@"Note: no 'SOGoInternetMailSuffix' is configured.");
else {
fromInternetSuffixPattern =
[@"\n" stringByAppendingString:fromInternetSuffixPattern];
}
TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy];
MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy];
}
- (void)dealloc {
[self->envelope release];
[self->info release];
[self->path release];
[super dealloc];
}
/* draft folder functionality */
- (NSFileManager *)spoolFileManager {
return [[self container] spoolFileManager];
}
- (NSString *)userSpoolFolderPath {
return [[self container] userSpoolFolderPath];
}
- (BOOL)_ensureUserSpoolFolderPath {
return [[self container] _ensureUserSpoolFolderPath];
}
/* draft object functionality */
- (NSString *)draftFolderPath {
if (self->path != nil)
return self->path;
self->path = [[[self userSpoolFolderPath] stringByAppendingPathComponent:
[self nameInContainer]] copy];
return self->path;
}
- (BOOL)_ensureDraftFolderPath {
NSFileManager *fm;
if (![self _ensureUserSpoolFolderPath])
return NO;
if ((fm = [self spoolFileManager]) == nil) {
[self errorWithFormat:@"missing spool file manager!"];
return NO;
}
return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil];
}
- (NSString *)infoPath {
return [[self draftFolderPath]
stringByAppendingPathComponent:@".info.plist"];
}
/* contents */
- (NSException *)storeInfo:(NSDictionary *)_info {
if (_info == nil) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"got no info to write for draft!"];
}
if (![self _ensureDraftFolderPath]) {
[self errorWithFormat:@"could not create folder for draft: '%@'",
[self draftFolderPath]];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"could not create folder for draft!"];
}
if (![_info writeToFile:[self infoPath] atomically:YES]) {
[self errorWithFormat:@"could not write info: '%@'", [self infoPath]];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"could not write draft info!"];
}
/* reset info cache */
[self->info release]; self->info = nil;
return nil /* everything is excellent */;
}
- (NSDictionary *)fetchInfo {
NSString *p;
if (self->info != nil)
return self->info;
p = [self infoPath];
if (![[self spoolFileManager] fileExistsAtPath:p]) {
[self debugWithFormat:@"Note: info object does not yet exist: %@", p];
return nil;
}
self->info = [[NSDictionary alloc] initWithContentsOfFile:p];
if (self->info == nil)
[self errorWithFormat:@"draft info dictionary broken at path: %@", p];
return self->info;
}
/* accessors */
- (NSString *)sender {
id tmp;
if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil)
return nil;
if ([tmp isKindOfClass:[NSArray class]])
return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil;
return tmp;
}
/* attachments */
- (NSArray *)fetchAttachmentNames {
NSMutableArray *ma;
NSFileManager *fm;
NSArray *files;
unsigned i, count;
fm = [self spoolFileManager];
if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil)
return nil;
count = [files count];
ma = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
NSString *filename;
filename = [files objectAtIndex:i];
if ([filename hasPrefix:@"."])
continue;
[ma addObject:filename];
}
return ma;
}
- (BOOL)isValidAttachmentName:(NSString *)_name {
static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
unsigned i;
NSRange r;
if (![_name isNotNull]) return NO;
if ([_name length] == 0) return NO;
if ([_name hasPrefix:@"."]) return NO;
for (i = 0; sescape[i] != nil; i++) {
r = [_name rangeOfString:sescape[i]];
if (r.length > 0) return NO;
}
return YES;
}
- (NSString *)pathToAttachmentWithName:(NSString *)_name {
if ([_name length] == 0)
return nil;
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
}
- (NSException *)invalidAttachmentNameError:(NSString *)_name {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"Invalid attachment name!"];
}
- (NSException *)saveAttachment:(NSData *)_attach withName:(NSString *)_name {
NSString *p;
if (![_attach isNotNull]) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason:@"Missing attachment content!"];
}
if (![self _ensureDraftFolderPath]) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"Could not create folder for draft!"];
}
if (![self isValidAttachmentName:_name])
return [self invalidAttachmentNameError:_name];
p = [self pathToAttachmentWithName:_name];
if (![_attach writeToFile:p atomically:YES]) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"Could not write attachment to draft!"];
}
return nil; /* everything OK */
}
- (NSException *)deleteAttachmentWithName:(NSString *)_name {
NSFileManager *fm;
NSString *p;
if (![self isValidAttachmentName:_name])
return [self invalidAttachmentNameError:_name];
fm = [self spoolFileManager];
p = [self pathToAttachmentWithName:_name];
if (![fm fileExistsAtPath:p])
return nil; /* well, doesn't exist, so its deleted ;-) */
if (![fm removeFileAtPath:p handler:nil]) {
[self logWithFormat:@"ERROR: failed to delete file: %@", p];
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"Could not delete attachment from draft!"];
}
return nil; /* everything OK */
}
/* NGMime representations */
- (NGMimeBodyPart *)bodyPartForText {
/*
This add the text typed by the user (the primary plain/text part).
*/
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
NSDictionary *lInfo;
id body;
if ((lInfo = [self fetchInfo]) == nil)
return nil;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
// TODO: set charset in header!
[map setObject:@"text/plain" forKey:@"content-type"];
if ((body = [lInfo objectForKey:@"text"]) != nil) {
if ([body isKindOfClass:[NSString class]]) {
[map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
body = [body dataUsingEncoding:NSUTF8StringEncoding];
}
}
/* prepare body content */
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody:body];
return bodyPart;
}
- (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map {
NSDictionary *lInfo;
NGMimeMessage *message;
WOContext *ctx;
NSString *fromInternetSuffix;
BOOL addSuffix;
id body;
if ((lInfo = [self fetchInfo]) == nil)
return nil;
ctx = [[WOApplication application] context];
addSuffix = [ctx isAccessFromIntranet] ? NO : YES;
if (addSuffix) {
fromInternetSuffix =
[fromInternetSuffixPattern stringByReplacingVariablesWithBindings:
[ctx request]
stringForUnknownBindings:@""];
addSuffix = [fromInternetSuffix length] > 0 ? YES : NO;
}
[map setObject:@"text/plain" forKey:@"content-type"];
if ((body = [lInfo objectForKey:@"text"]) != nil) {
if ([body isKindOfClass:[NSString class]]) {
if (addSuffix)
body = [body stringByAppendingString:fromInternetSuffix];
/* Note: just 'utf8' is displayed wrong in Mail.app */
[map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
body = [body dataUsingEncoding:NSUTF8StringEncoding];
}
else if ([body isKindOfClass:[NSData class]] && addSuffix) {
body = [[body mutableCopy] autorelease];
[(NSMutableData *)body appendData:
[fromInternetSuffix dataUsingEncoding:
NSUTF8StringEncoding]];
}
else if (addSuffix) {
[self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
NSStringFromClass([body class])];
}
}
else if (addSuffix)
body = [fromInternetSuffix dataUsingEncoding:NSUTF8StringEncoding];
message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
[message setBody:body];
return message;
}
- (NSString *)mimeTypeForExtension:(NSString *)_ext {
// TODO: make configurable
// TODO: use /etc/mime-types
if ([_ext isEqualToString:@"txt"]) return @"text/plain";
if ([_ext isEqualToString:@"html"]) return @"text/html";
if ([_ext isEqualToString:@"htm"]) return @"text/html";
if ([_ext isEqualToString:@"gif"]) return @"image/gif";
if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg";
if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
if ([_ext isEqualToString:@"mail"]) return @"message/rfc822";
return @"application/octet-stream";
}
- (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
NSString *s;
s = [self mimeTypeForExtension:[_name pathExtension]];
if ([_name length] > 0) {
s = [s stringByAppendingString:@"; name=\""];
s = [s stringByAppendingString:_name];
s = [s stringByAppendingString:@"\""];
}
return s;
}
- (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
NSString *type;
NSString *cdtype;
NSString *cd;
type = [self contentTypeForAttachmentWithName:_name];
if ([type hasPrefix:@"text/"])
cdtype = showTextAttachmentsInline ? @"inline" : @"attachment";
else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"])
cdtype = @"inline";
else
cdtype = @"attachment";
cd = [cdtype stringByAppendingString:@"; filename=\""];
cd = [cd stringByAppendingString:_name];
cd = [cd stringByAppendingString:@"\""];
// TODO: add size parameter (useful addition, RFC 2183)
return cd;
}
- (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
NSFileManager *fm;
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
NSString *s;
NSData *content;
BOOL attachAsString, is7bit;
NSString *p;
id body;
if (_name == nil) return nil;
/* check attachment */
fm = [self spoolFileManager];
p = [self pathToAttachmentWithName:_name];
if (![fm isReadableFileAtPath:p]) {
[self errorWithFormat:@"did not find attachment: '%@'", _name];
return nil;
}
attachAsString = NO;
is7bit = NO;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) {
[map setObject:s forKey:@"content-type"];
if ([s hasPrefix:@"text/"])
attachAsString = YES;
else if ([s hasPrefix:@"message/rfc822"])
is7bit = YES;
}
if ((s = [self contentDispositionForAttachmentWithName:_name]))
[map setObject:s forKey:@"content-disposition"];
/* prepare body content */
if (attachAsString) { // TODO: is this really necessary?
NSString *s;
content = [[NSData alloc] initWithContentsOfMappedFile:p];
s = [[NSString alloc] initWithData:content
encoding:[NSString defaultCStringEncoding]];
if (s != nil) {
body = s;
[content release]; content = nil;
}
else {
[self warnWithFormat:
@"could not get text attachment as string: '%@'", _name];
body = content;
content = nil;
}
}
else if (is7bit) {
/*
Note: Apparently NGMimeFileData objects are not processed by the MIME
generator!
*/
body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
[map setObject:@"7bit" forKey:@"content-transfer-encoding"];
[map setObject:[NSNumber numberWithInt:[body length]]
forKey:@"content-length"];
}
else {
/*
Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
NGMimeFileData objects are not processed by the MIME generator!
*/
NSData *encoded;
content = [[NSData alloc] initWithContentsOfMappedFile:p];
encoded = [content dataByEncodingBase64];
[content release]; content = nil;
[map setObject:@"base64" forKey:@"content-transfer-encoding"];
[map setObject:[NSNumber numberWithInt:[encoded length]]
forKey:@"content-length"];
/* Note: the -init method will create a temporary file! */
body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes]
length:[encoded length]];
}
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody:body];
[body release]; body = nil;
return bodyPart;
}
- (NSArray *)bodyPartsForAllAttachments {
/* returns nil on error */
NSMutableArray *bodyParts;
NSArray *names;
unsigned i, count;
names = [self fetchAttachmentNames];
if ((count = [names count]) == 0)
return [NSArray array];
bodyParts = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
NGMimeBodyPart *bodyPart;
bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
if (bodyPart == nil)
return nil;
[bodyParts addObject:bodyPart];
}
return bodyParts;
}
- (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
andBodyParts:(NSArray *)_bodyParts
{
NGMimeMessage *message;
NGMimeMultipartBody *mBody;
NGMimeBodyPart *part;
NSEnumerator *e;
[map addObject:MultiMixedType forKey:@"content-type"];
message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
mBody = [[NGMimeMultipartBody alloc] initWithPart:message];
part = [self bodyPartForText];
[mBody addBodyPart:part];
e = [_bodyParts objectEnumerator];
while ((part = [e nextObject]) != nil)
[mBody addBodyPart:part];
[message setBody:mBody];
[mBody release]; mBody = nil;
return message;
}
- (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map {
NSEnumerator *names;
NSString *name;
if ([_h count] == 0)
return;
names = [_h keyEnumerator];
while ((name = [names nextObject]) != nil) {
id value;
value = [_h objectForKey:name];
[_map addObject:value forKey:name];
}
}
- (BOOL)isEmptyValue:(id)_value {
if (![_value isNotNull])
return YES;
if ([_value isKindOfClass:[NSArray class]])
return [_value count] == 0 ? YES : NO;
if ([_value isKindOfClass:[NSString class]])
return [_value length] == 0 ? YES : NO;
return NO;
}
- (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers {
NGMutableHashMap *map;
NSDictionary *lInfo; // TODO: this should be some kind of object?
NSArray *emails;
NSString *s;
id from, replyTo;
if ((lInfo = [self fetchInfo]) == nil)
return nil;
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
/* add recipients */
if ((emails = [lInfo objectForKey:@"to"]) != nil) {
if ([emails count] == 0) {
[self errorWithFormat:@"missing 'to' recipient in email!"];
return nil;
}
[map setObjects:emails forKey:@"to"];
}
if ((emails = [lInfo objectForKey:@"cc"]) != nil)
[map setObjects:emails forKey:@"cc"];
if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
[map setObjects:emails forKey:@"bcc"];
/* add senders */
from = [lInfo objectForKey:@"from"];
replyTo = [lInfo objectForKey:@"replyTo"];
if (![self isEmptyValue:from]) {
if ([from isKindOfClass:[NSArray class]])
[map setObjects:from forKey:@"from"];
else
[map setObject:from forKey:@"from"];
}
if (![self isEmptyValue:replyTo]) {
if ([from isKindOfClass:[NSArray class]])
[map setObjects:from forKey:@"reply-to"];
else
[map setObject:from forKey:@"reply-to"];
}
else if (![self isEmptyValue:from])
[map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"];
/* add subject */
if ([(s = [lInfo objectForKey:@"subject"]) length] > 0)
[map setObject:s forKey:@"subject"];
/* add standard headers */
[map addObject:[NSCalendarDate date] forKey:@"date"];
[map addObject:@"1.0" forKey:@"MIME-Version"];
[map addObject:userAgent forKey:@"X-Mailer"];
/* add custom headers */
[self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map];
[self _addHeaders:_headers toHeaderMap:map];
return map;
}
- (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers {
NSAutoreleasePool *pool;
NGMutableHashMap *map;
NSArray *bodyParts;
NGMimeMessage *message;
pool = [[NSAutoreleasePool alloc] init];
if ([self fetchInfo] == nil) {
[self errorWithFormat:@"could not locate draft fetch info!"];
return nil;
}
if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil)
return nil;
[self debugWithFormat:@"MIME Envelope: %@", map];
if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
[self errorWithFormat:
@"could not create body parts for attachments!"];
return nil; // TODO: improve error handling, return exception
}
[self debugWithFormat:@"attachments: %@", bodyParts];
if ([bodyParts count] == 0) {
/* no attachments */
message = [self mimeMessageForContentWithHeaderMap:map];
}
else {
/* attachments, create multipart/mixed */
message = [self mimeMultiPartMessageWithHeaderMap:map
andBodyParts:bodyParts];
}
[self debugWithFormat:@"message: %@", message];
message = [message retain];
[pool release];
return [message autorelease];
}
- (NGMimeMessage *)mimeMessage {
return [self mimeMessageWithHeaders:nil];
}
- (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
NGMimeMessageGenerator *gen;
NSAutoreleasePool *pool;
NGMimeMessage *message;
NSString *tmpPath;
pool = [[NSAutoreleasePool alloc] init];
message = [self mimeMessageWithHeaders:_h];
if (![message isNotNull])
return nil;
if ([message isKindOfClass:[NSException class]]) {
[self errorWithFormat:@"error: %@", message];
return nil;
}
gen = [[NGMimeMessageGenerator alloc] init];
tmpPath = [[gen generateMimeFromPartToFile:message] copy];
[gen release]; gen = nil;
[pool release];
return [tmpPath autorelease];
}
- (NSString *)saveMimeMessageToTemporaryFile {
return [self saveMimeMessageToTemporaryFileWithHeaders:nil];
}
- (void)deleteTemporaryMessageFile:(NSString *)_path {
NSFileManager *fm;
if (![_path isNotNull])
return;
fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:_path])
return;
[fm removeFileAtPath:_path handler:nil];
}
- (NSArray *)allRecipients {
NSDictionary *lInfo;
NSMutableArray *ma;
NSArray *tmp;
if ((lInfo = [self fetchInfo]) == nil)
return nil;
ma = [NSMutableArray arrayWithCapacity:16];
if ((tmp = [lInfo objectForKey:@"to"]) != nil)
[ma addObjectsFromArray:tmp];
if ((tmp = [lInfo objectForKey:@"cc"]) != nil)
[ma addObjectsFromArray:tmp];
if ((tmp = [lInfo objectForKey:@"bcc"]) != nil)
[ma addObjectsFromArray:tmp];
return ma;
}
- (NSException *)sendMimeMessageAtPath:(NSString *)_path {
static NGSendMail *mailer = nil;
NSArray *recipients;
NSString *from;
/* validate */
recipients = [self allRecipients];
from = [self sender];
if ([recipients count] == 0) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"draft has no recipients set!"];
}
if ([from length] == 0) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"draft has no sender (from) set!"];
}
/* setup mailer object */
if (mailer == nil)
mailer = [[NGSendMail sharedSendMail] retain];
if (![mailer isSendMailAvailable]) {
[self errorWithFormat:@"missing sendmail binary!"];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"did not find sendmail binary!"];
}
/* send mail */
return [mailer sendMailAtPath:_path toRecipients:recipients sender:from];
}
- (NSException *)sendMail {
NSException *error;
NSString *tmpPath;
/* save MIME mail to file */
tmpPath = [self saveMimeMessageToTemporaryFile];
if (![tmpPath isNotNull]) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"could not save MIME message for draft!"];
}
/* send mail */
error = [self sendMimeMessageAtPath:tmpPath];
/* delete temporary file */
[self deleteTemporaryMessageFile:tmpPath];
return error;
}
/* operations */
- (NSException *)delete {
NSFileManager *fm;
NSString *p, *sp;
NSEnumerator *e;
if ((fm = [self spoolFileManager]) == nil) {
[self errorWithFormat:@"missing spool file manager!"];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"missing spool file manager!"];
}
p = [self draftFolderPath];
if (![fm fileExistsAtPath:p]) {
return [NSException exceptionWithHTTPStatus:404 /* not found */
reason:@"did not find draft!"];
}
e = [[fm directoryContentsAtPath:p] objectEnumerator];
while ((sp = [e nextObject])) {
sp = [p stringByAppendingPathComponent:sp];
if (draftDeleteDisabled) {
[self logWithFormat:@"should delete draft file %@ ...", sp];
continue;
}
if (![fm removeFileAtPath:sp handler:nil]) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"failed to delete draft!"];
}
}
if (draftDeleteDisabled) {
[self logWithFormat:@"should delete draft directory: %@", p];
}
else {
if (![fm removeFileAtPath:p handler:nil]) {
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"failed to delete draft directory!"];
}
}
return nil;
}
- (NSData *)content {
/* Note: does not cache, expensive operation */
NSData *data;
NSString *p;
if ((p = [self saveMimeMessageToTemporaryFile]) == nil)
return nil;
data = [NSData dataWithContentsOfMappedFile:p];
/* delete temporary file */
[self deleteTemporaryMessageFile:p];
return data;
}
- (NSString *)contentAsString {
NSString *str;
NSData *data;
if ((data = [self content]) == nil)
return nil;
str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
if (str == nil) {
[self errorWithFormat:@"could not load draft as ASCII (data size=%d)",
[data length]];
return nil;
}
return [str autorelease];
}
/* actions */
- (id)DELETEAction:(id)_ctx {
NSException *error;
if ((error = [self delete]) != nil)
return error;
return [NSNumber numberWithBool:YES]; /* delete worked out ... */
}
- (id)GETAction:(id)_ctx {
/*
Override, because SOGoObject's GETAction uses the less efficient
-contentAsString method.
*/
WORequest *rq;
rq = [_ctx request];
if ([rq isSoWebDAVRequest]) {
WOResponse *r;
NSData *content;
if ((content = [self content]) == nil) {
return [NSException exceptionWithHTTPStatus:500
reason:@"Could not generate MIME content!"];
}
r = [_ctx response];
[r setHeader:@"message/rfc822" forKey:@"content-type"];
[r setContent:content];
return r;
}
return [super GETAction:_ctx];
}
/* fake being a SOGoMailObject */
- (id)fetchParts:(NSArray *)_parts {
return [NSDictionary dictionaryWithObject:self forKey:@"fetch"];
}
- (NSString *)uid {
return [self nameInContainer];
}
- (NSArray *)flags {
static NSArray *seenFlags = nil;
seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil];
return seenFlags;
}
- (unsigned)size {
// TODO: size, hard to support, we would need to generate MIME?
return 0;
}
- (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails {
NSMutableArray *ma;
unsigned i, count;
if (_emails == nil)
return nil;
if ((count = [_emails count]) == 0)
return [NSArray array];
ma = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
NGImap4EnvelopeAddress *envaddr;
envaddr = [[NGImap4EnvelopeAddress alloc]
initWithString:[_emails objectAtIndex:i]];
if ([envaddr isNotNull])
[ma addObject:envaddr];
[envaddr release];
}
return ma;
}
- (NGImap4Envelope *)envelope {
NSDictionary *lInfo;
id from, replyTo;
if (self->envelope != nil)
return self->envelope;
if ((lInfo = [self fetchInfo]) == nil)
return nil;
if ((from = [self sender]) != nil)
from = [NSArray arrayWithObjects:&from count:1];
if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) {
if (![replyTo isKindOfClass:[NSArray class]])
replyTo = [NSArray arrayWithObjects:&replyTo count:1];
}
self->envelope =
[[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer]
subject:[lInfo objectForKey:@"subject"]
from:from replyTo:replyTo
to:[lInfo objectForKey:@"to"]
cc:[lInfo objectForKey:@"cc"]
bcc:[lInfo objectForKey:@"bcc"]];
return self->envelope;
}
/* debugging */
- (BOOL)isDebuggingEnabled {
return debugOn;
}
@end /* SOGoDraftObject */
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoDraftsFolder_H__
#define __Mailer_SOGoDraftsFolder_H__
#include <SoObjects/Mailer/SOGoMailBaseObject.h>
/*
SOGoDraftsFolder
Parent object: SOGoMailAccount
Child objects: SOGoDraftObject's
The SOGoDraftsFolder is used for composing new messages. It is necessary
because we can't cache objects in a session. So the contents of the drafts
folder are some kind of "mail creation transaction".
*/
@interface SOGoDraftsFolder : SOGoMailBaseObject
{
}
/* new objects */
- (NSString *)makeNewObjectNameInContext:(id)_ctx;
- (NSString *)newObjectBaseURLInContext:(id)_ctx;
- (id)newObjectInContext:(id)_ctx;
@end
#endif /* __Mailer_SOGoDraftsFolder_H__ */
+193
View File
@@ -0,0 +1,193 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoDraftsFolder.h"
#include "SOGoDraftObject.h"
#include <SOGo/SOGoUserFolder.h>
#include <NGExtensions/NSFileManager+Extensions.h>
#include "common.h"
#include <unistd.h>
@implementation SOGoDraftsFolder
static NSString *spoolFolder = nil;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
spoolFolder = [[ud stringForKey:@"SOGoMailSpoolPath"] copy];
if ([spoolFolder length] < 3)
spoolFolder = @"/tmp/";
NSLog(@"Note: using SOGo mail spool folder: %@", spoolFolder);
}
/* new objects */
- (NSString *)makeNewObjectNameInContext:(id)_ctx {
static int counter = 1; // THREAD
return [NSString stringWithFormat:@"draft_%08d_%08x", getpid(), counter++];
}
- (NSString *)newObjectBaseURLInContext:(id)_ctx {
NSString *s, *n;
n = [self makeNewObjectNameInContext:_ctx];
if (![n isNotNull]) return nil;
s = [self baseURLInContext:_ctx];
if (![s isNotNull]) return nil;
if (![s hasSuffix:@"/"]) s = [s stringByAppendingString:@"/"];
return [s stringByAppendingString:n];
}
- (id)newObjectInContext:(id)_ctx {
return [self lookupName:[self makeNewObjectNameInContext:_ctx]
inContext:_ctx acquire:NO];
}
/* draft folder functionality */
- (NSFileManager *)spoolFileManager {
return [NSFileManager defaultManager];
}
- (NSString *)spoolFolderPath {
return spoolFolder;
}
- (NSString *)userSpoolFolderPath {
NSString *p, *n;
p = [self spoolFolderPath];
n = [[self lookupUserFolder] nameInContainer];
return [p stringByAppendingPathComponent:n];
}
- (BOOL)_ensureUserSpoolFolderPath {
NSFileManager *fm;
if ((fm = [self spoolFileManager]) == nil) {
[self errorWithFormat:@"missing spool file manager!"];
return NO;
}
return [fm createDirectoriesAtPath:[self userSpoolFolderPath]
attributes:nil];
}
- (NSArray *)fetchMailNames {
NSString *p;
if ((p = [self userSpoolFolderPath]) == nil)
return nil;
return [[self spoolFileManager] directoryContentsAtPath:p];
}
/* folder methods (used by template) */
- (NSArray *)fetchUIDsMatchingQualifier:(id)_q sortOrdering:(id)_so {
// TODO: retrieve contained objects
NSArray *allUids;
allUids = [self fetchMailNames];
if (![allUids isNotNull]) {
[self logWithFormat:@"Note: no uids in drafts folder: %@",
[self userSpoolFolderPath]];
return [NSArray array];
}
// TODO: should sort uids (q=%@,so=%@): %@", _q, _so, allUids];
return allUids;
}
- (NSArray *)fetchUIDs:(NSArray *)_uids parts:(NSArray *)_parts {
/* FLAGS, ENVELOPE, RFC822.SIZE */
NSMutableArray *drafts;
unsigned i, count;
if (_uids == nil)
return nil;
if ((count = [_uids count]) == 0)
return [NSArray array];
drafts = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
SOGoDraftObject *draft;
id parts;
draft = [self lookupName:[_uids objectAtIndex:i] inContext:nil acquire:NO];
if (![draft isNotNull] || [draft isKindOfClass:[NSException class]])
continue;
parts = [draft fetchParts:_parts];
if ([parts isNotNull])
[drafts addObject:parts];
}
return drafts;
}
/* name lookup */
- (id)lookupDraftMessage:(NSString *)_key inContext:(id)_ctx {
// TODO: we might want to check for existence prior controller creation
return [[[SOGoDraftObject alloc] initWithName:_key
inContainer:self] autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
if ((obj = [self lookupDraftMessage:_key inContext:_ctx]) != nil)
return obj;
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* WebDAV */
- (BOOL)davIsCollection {
return YES;
}
- (NSArray *)toOneRelationshipKeys {
return [self fetchMailNames];
}
/* folder type */
- (NSString *)outlookFolderClass {
return @"IPF.Drafts";
}
@end /* SOGoDraftsFolder */
+53
View File
@@ -0,0 +1,53 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include <Mailer/SOGoMailBodyPart.h>
/*
SOGoImageMailBodyPart
A specialized SOGoMailBodyPart subclass for image attachments. Can be used
to attach special SoMethods.
See the superclass for more information on part objects.
*/
@interface SOGoImageMailBodyPart : SOGoMailBodyPart
{
}
@end
#include "common.h"
@implementation SOGoImageMailBodyPart
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
@end /* SOGoImageMailBodyPart */
+74
View File
@@ -0,0 +1,74 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailAccount_H__
#define __Mailer_SOGoMailAccount_H__
#include <SoObjects/Mailer/SOGoMailBaseObject.h>
/*
SOGoMailAccount
Parent object: SOGoMailAccounts
Child objects: SOGoMailFolder
The SOGoMailAccount represents a single IMAP4 mail account (host, login,
password, etc)
*/
@class NSString, NSArray;
@class SOGoMailFolder, SOGoMailIdentity;
@interface SOGoMailAccount : SOGoMailBaseObject
{
SOGoMailFolder *inboxFolder;
SOGoMailFolder *sentFolder;
SOGoMailFolder *trashFolder;
}
/* folder pathes */
- (NSArray *)allFolderPathes;
- (NSArray *)additionalRootFolderNames; /* stuff like filters and drafts */
/* shared accounts */
- (BOOL)isSharedAccount;
- (NSString *)sharedAccountName;
/* identity */
- (SOGoMailIdentity *)preferredIdentity;
/* special folders */
- (NSString *)inboxFolderNameInContext:(id)_ctx;
- (NSString *)draftsFolderNameInContext:(id)_ctx;
- (NSString *)sieveFolderNameInContext:(id)_ctx;
- (NSString *)sentFolderNameInContext:(id)_ctx;
- (NSString *)trashFolderNameInContext:(id)_ctx;
- (SOGoMailFolder *)inboxFolderInContext:(id)_ctx;
- (SOGoMailFolder *)sentFolderInContext:(id)_ctx;
- (SOGoMailFolder *)trashFolderInContext:(id)_ctx;
@end
#endif /* __Mailer_SOGoMailAccount_H__ */
+410
View File
@@ -0,0 +1,410 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailAccount.h"
#include "SOGoMailFolder.h"
#include "SOGoMailManager.h"
#include "SOGoDraftsFolder.h"
#include "SOGoUser+Mail.h"
#include <NGObjWeb/SoHTTPAuthenticator.h>
#include "common.h"
@implementation SOGoMailAccount
static NSArray *rootFolderNames = nil;
static NSString *inboxFolderName = @"INBOX";
static NSString *draftsFolderName = @"Drafts";
static NSString *sieveFolderName = @"Filters";
static NSString *sharedFolderName = @""; // TODO: add English default
static NSString *otherUsersFolderName = @""; // TODO: add English default
static BOOL useAltNamespace = NO;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
sharedFolderName = [ud stringForKey:@"SOGoSharedFolderName"];
otherUsersFolderName = [ud stringForKey:@"SOGoOtherUsersFolderName"];
NSLog(@"Note: using shared-folders name: '%@'", sharedFolderName);
NSLog(@"Note: using other-users-folders name: '%@'", otherUsersFolderName);
if ([ud boolForKey:@"SOGoEnableSieveFolder"]) {
rootFolderNames = [[NSArray alloc] initWithObjects:
draftsFolderName,
sieveFolderName,
nil];
}
else {
rootFolderNames = [[NSArray alloc] initWithObjects:
draftsFolderName,
nil];
}
}
/* shared accounts */
- (BOOL)isSharedAccount {
NSString *s;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"@"];
if (r.length == 0) /* regular HTTP logins are never a shared mailbox */
return NO;
s = [s substringToIndex:r.location];
return [s rangeOfString:@".-."].length > 0 ? YES : NO;
}
- (NSString *)sharedAccountName {
return nil;
}
/* listing the available folders */
- (NSArray *)additionalRootFolderNames {
return rootFolderNames;
}
- (NSArray *)toManyRelationshipKeys {
NSArray *a, *b;
a = [self additionalRootFolderNames];
b = [[self imap4Connection] subfoldersForURL:[self imap4URL]];
return [b count] > 0 ? [b arrayByAddingObjectsFromArray:a] : a;
}
/* identity */
- (SOGoMailIdentity *)preferredIdentity {
WOContext *ctx;
if ((ctx = [[WOApplication application] context]) == nil) {
[self logWithFormat:@"ERROR(%s): cannot procede without context!",
__PRETTY_FUNCTION__];
return nil;
}
return [[ctx activeUser] primaryMailIdentityForAccount:
[self nameInContainer]];
}
/* hierarchy */
- (SOGoMailAccount *)mailAccountFolder {
return self;
}
- (NSArray *)allFolderPathes {
NSArray *pathes;
pathes = [[self imap4Connection] allFoldersForURL:[self imap4URL] ];
pathes = [pathes sortedArrayUsingSelector:@selector(compare:)];
return pathes;
}
/* IMAP4 */
- (BOOL)useSSL {
return NO;
}
- (NSString *)imap4LoginFromHTTP {
WORequest *rq;
NSString *s;
NSArray *creds;
rq = [[(WOApplication *)[WOApplication application] context] request];
s = [rq headerForKey:@"x-webobjects-remote-user"];
if ([s length] > 0)
return s;
if ((s = [rq headerForKey:@"authorization"]) == nil) {
/* no basic auth */
return nil;
}
creds = [SoHTTPAuthenticator parseCredentials:s];
if ([creds count] < 2)
/* somehow invalid */
return nil;
return [creds objectAtIndex:0]; /* the user */
}
- (NSString *)imap4URLString {
/* private, overridden by SOGoSharedMailAccount */
NSString *s;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"@"];
if (r.length == 0) {
NSString *u;
u = [self imap4LoginFromHTTP];
if ([u length] == 0) {
[self errorWithFormat:@"missing login in account folder name: %@", s];
return nil;
}
s = [[u stringByAppendingString:@"@"] stringByAppendingString:s];
}
if ([s hasSuffix:@":80"]) { // HACK
[self logWithFormat:@"WARNING: incorrect value for IMAP4 URL: '%@'", s];
s = [s substringToIndex:([s length] - 3)];
}
s = [([self useSSL] ? @"imaps://" : @"imap://") stringByAppendingString:s];
s = [s stringByAppendingString:@"/"];
return s;
}
- (NSURL *)imap4URL {
/* imap://agenortest@mail.opengroupware.org/ */
NSString *s;
if (self->imap4URL != nil)
return self->imap4URL;
if ((s = [self imap4URLString]) == nil)
return nil;
self->imap4URL = [[NSURL alloc] initWithString:s];
return self->imap4URL;
}
- (NSString *)imap4Login {
return [[self imap4URL] user];
}
/* name lookup */
- (id)lookupFolder:(NSString *)_key ofClassNamed:(NSString *)_cn
inContext:(id)_cx
{
Class clazz;
if ((clazz = NSClassFromString(_cn)) == Nil) {
[self logWithFormat:@"ERROR: did not find class '%@' for key: '%@'",
_cn, _key];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"did not find mail folder class!"];
}
return [[[clazz alloc] initWithName:_key inContainer:self] autorelease];
}
- (id)lookupImap4Folder:(NSString *)_key inContext:(id)_cx {
NSString *s;
s = [_key isEqualToString:[self trashFolderNameInContext:_cx]]
? @"SOGoTrashFolder" : @"SOGoMailFolder";
return [self lookupFolder:_key ofClassNamed:s inContext:_cx];
}
- (id)lookupDraftsFolder:(NSString *)_key inContext:(id)_ctx {
return [self lookupFolder:_key ofClassNamed:@"SOGoDraftsFolder"
inContext:_ctx];
}
- (id)lookupFiltersFolder:(NSString *)_key inContext:(id)_ctx {
return [self lookupFolder:_key ofClassNamed:@"SOGoSieveScriptsFolder"
inContext:_ctx];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
// TODO: those should be product.plist bindings? (can't be class bindings
// though because they are 'per-account')
if ([_key isEqualToString:draftsFolderName]) {
if ((obj = [self lookupDraftsFolder:_key inContext:_ctx]) != nil)
return obj;
}
if ([_key isEqualToString:sieveFolderName]) {
if ((obj = [self lookupFiltersFolder:_key inContext:_ctx]) != nil)
return obj;
}
if ((obj = [self lookupImap4Folder:_key inContext:_ctx]) != nil)
return obj;
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* special folders */
- (NSString *)inboxFolderNameInContext:(id)_ctx {
return inboxFolderName; /* cannot be changed in Cyrus ? */
}
- (NSString *)draftsFolderNameInContext:(id)_ctx {
return draftsFolderName; /* SOGo managed folder */
}
- (NSString *)sieveFolderNameInContext:(id)_ctx {
return sieveFolderName; /* SOGo managed folder */
}
- (NSString *)sentFolderNameInContext:(id)_ctx {
/* OGo issue #1225 */
static NSString *s = nil;
if (s == nil) {
NSUserDefaults *ud;
ud = [NSUserDefaults standardUserDefaults];
s = [[ud stringForKey:@"SOGoSentFolderName"] copy];
if ([s length] == 0) s = @"Sent";
[self logWithFormat:@"Note: using SOGoSentFolderName: '%@'", s];
}
return s;
}
- (NSString *)trashFolderNameInContext:(id)_ctx {
/* OGo issue #1225 */
static NSString *s = nil;
if (s == nil) {
NSUserDefaults *ud;
ud = [NSUserDefaults standardUserDefaults];
s = [[ud stringForKey:@"SOGoTrashFolderName"] copy];
if ([s length] == 0) s = @"Trash";
NSLog(@"Note: using SOGoTrashFolderName: '%@'", s);
}
return s;
}
- (SOGoMailFolder *)inboxFolderInContext:(id)_ctx {
// TODO: use some profile to determine real location, use a -traverse lookup
SOGoMailFolder *folder;
if (self->inboxFolder != nil)
return self->inboxFolder;
folder = [self lookupName:[self inboxFolderNameInContext:_ctx]
inContext:_ctx acquire:NO];
if ([folder isKindOfClass:[NSException class]]) return folder;
return ((self->inboxFolder = [folder retain]));
}
- (SOGoMailFolder *)sentFolderInContext:(id)_ctx {
// TODO: use some profile to determine real location, use a -traverse lookup
SOGoMailFolder *folder;
if (self->sentFolder != nil)
return self->sentFolder;
folder = useAltNamespace ? (id)self : [self inboxFolderInContext:_ctx];
if ([folder isKindOfClass:[NSException class]]) return folder;
folder = [folder lookupName:[self sentFolderNameInContext:_ctx]
inContext:_ctx acquire:NO];
if ([folder isKindOfClass:[NSException class]]) return folder;
if (![folder isNotNull]) {
return [NSException exceptionWithHTTPStatus:404 /* not found */
reason:@"did not find Sent folder!"];
}
return ((self->sentFolder = [folder retain]));
}
- (SOGoMailFolder *)trashFolderInContext:(id)_ctx {
// TODO: use some profile to determine real location
SOGoMailFolder *folder;
if (self->trashFolder != nil)
return self->trashFolder;
folder = useAltNamespace ? (id)self : [self inboxFolderInContext:_ctx];
if ([folder isKindOfClass:[NSException class]]) return folder;
folder = [folder lookupName:[self trashFolderNameInContext:_ctx]
inContext:_ctx acquire:NO];
if ([folder isKindOfClass:[NSException class]]) return folder;
if (![folder isNotNull]) {
return [NSException exceptionWithHTTPStatus:404 /* not found */
reason:@"did not find Trash folder!"];
}
return ((self->trashFolder = [folder retain]));
}
/* WebDAV */
- (BOOL)davIsCollection {
return YES;
}
- (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx {
return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
}
- (NSString *)shortTitle {
NSString *s, *login, *host;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"@"];
if (r.length > 0) {
login = [s substringToIndex:r.location];
host = [s substringFromIndex:(r.location + r.length)];
}
else {
login = nil;
host = s;
}
r = [host rangeOfString:@"."];
if (r.length > 0)
host = [host substringToIndex:r.location];
if ([login length] == 0)
return host;
r = [login rangeOfString:@"."];
if (r.length > 0)
login = [login substringToIndex:r.location];
return [NSString stringWithFormat:@"%@@%@", login, host];
}
- (NSString *)davDisplayName {
return [self shortTitle];
}
@end /* SOGoMailAccount */
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailAccounts_H__
#define __Mailer_SOGoMailAccounts_H__
#include <SOGo/SOGoObject.h>
/*
SOGoMailAccounts
Parent object: SOGoUserFolder
Child objects: SOGoMailAccount
The SOGoMailAccounts folder contains the mail accounts which are associated
with a user.
The list of accounts is currently retrieved from the context -activeUser.
Note: this is not yet an IMAP4 object. Its a wrapper which contains the root
IMAP4 objects.
*/
@class NSArray;
@interface SOGoMailAccounts : SOGoObject
{
}
- (NSArray *)fetchAllIdentities;
- (NSArray *)fetchIdentitiesWithEmitterPermissions;
@end
#endif /* __Mailer_SOGoMailAccounts_H__ */
+205
View File
@@ -0,0 +1,205 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailAccounts.h"
#include "SOGoUser+Mail.h"
#include "common.h"
#include <NGObjWeb/SoObject+SoDAV.h>
#include <SOGo/WOContext+Agenor.h>
@implementation SOGoMailAccounts
static NSString *AgenorShareLoginMarker = @".-.";
/* detect webmail being accessed from the outside */
- (BOOL)isInternetRequest {
return [[(WOApplication *)[WOApplication application] context]
isAccessFromIntranet] ? NO : YES;
}
/* listing the available mailboxes */
- (BOOL)isInHomeFolderBranchOfLoggedInAccount:(id)_ctx {
id user;
if (_ctx == nil) _ctx = [[WOApplication application] context];
if (_ctx == nil) {
[self errorWithFormat:@"Missing context!"];
return NO;
}
user = [_ctx activeUser];
return [[[self container] nameInContainer] isEqualToString:[user login]];
}
- (NSArray *)toManyRelationshipKeys {
WOContext *ctx;
id user;
id account;
NSArray *shares;
if ((ctx = [[WOApplication application] context]) == nil) {
[self logWithFormat:@"ERROR(%s): cannot procede without context!",
__PRETTY_FUNCTION__];
return nil;
}
/*
Note: this is not strictly correct. The accounts being retrieved should be
the accounts based on the container object of this folder. Given
sufficient rights (eg delegation rights!), this would allow you to
browse the hierarchies of other users.
But then, the home-folder would need to know about mail
functionality which isn't perfect either.
=> TODO
*/
user = [ctx activeUser];
/* for now: return nothing if the home-folder does not belong to the login */
if (![self isInHomeFolderBranchOfLoggedInAccount:ctx]) {
[self warnWithFormat:@"User %@ tried to access mail hierarchy of %@",
[user login], [[self container] nameInContainer]];
return nil;
}
account = [user valueForKey:@"primaryIMAP4AccountString"];
if ([account isNotNull]) account = [NSArray arrayWithObject:account];
if ([self isInternetRequest]) /* only show primary mailbox in Internet */
return account;
shares = [user valueForKey:@"additionalIMAP4AccountStrings"];
return ([shares count] == 0)
? account
: [account arrayByAddingObjectsFromArray:shares];
}
- (NSArray *)fetchIdentitiesWithOnlyEmitterAccess:(BOOL)_flag {
WOContext *ctx;
if ((ctx = [[WOApplication application] context]) == nil) {
[self logWithFormat:@"ERROR(%s): cannot procede without context!",
__PRETTY_FUNCTION__];
return nil;
}
if ([self isInternetRequest]) { /* only show primary mailbox in Internet */
// just return the primary identity
id identity;
identity = [[ctx activeUser] primaryMailIdentity];
return [identity isNotNull] ? [NSArray arrayWithObject:identity] : nil;
}
return [[ctx activeUser] fetchAllMailIdentitiesWithOnlyEmitterAccess:_flag];
}
- (NSArray *)fetchAllIdentities {
return [self fetchIdentitiesWithOnlyEmitterAccess:NO];
}
- (NSArray *)fetchIdentitiesWithEmitterPermissions {
return [self fetchIdentitiesWithOnlyEmitterAccess:YES];
}
/* name lookup */
- (BOOL)isValidMailAccountName:(NSString *)_key {
if ([_key length] == 0)
return NO;
return YES;
}
- (id)mailAccountWithName:(NSString *)_key inContext:(id)_ctx {
static Class ctClass = Nil;
id ct;
if (ctClass == Nil)
ctClass = NSClassFromString(@"SOGoMailAccount");
if (ctClass == Nil) {
[self errorWithFormat:@"missing SOGoMailAccount class!"];
return nil;
}
ct = [[ctClass alloc] initWithName:_key inContainer:self];
return [ct autorelease];
}
- (id)sharedMailAccountWithName:(NSString *)_key inContext:(id)_ctx {
static Class ctClass = Nil;
id ct;
if (ctClass == Nil)
ctClass = NSClassFromString(@"SOGoSharedMailAccount");
if (ctClass == Nil) {
[self errorWithFormat:@"missing SOGoSharedMailAccount class!"];
return nil;
}
ct = [[ctClass alloc] initWithName:_key inContainer:self];
return [ct autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
return obj;
if (![self isInHomeFolderBranchOfLoggedInAccount:_ctx]) {
[self warnWithFormat:@"User %@ tried to access mail hierarchy of %@",
[[_ctx activeUser] login], [[self container] nameInContainer]];
return [NSException exceptionWithHTTPStatus:403 /* Forbidden */
reason:@"Tried to access the mail of another user"];
}
if ([self isValidMailAccountName:_key]) {
/* forbid shares for requests coming from the Internet */
BOOL isSharedKey;
isSharedKey = [_key rangeOfString:AgenorShareLoginMarker].length > 0;
if ([self isInternetRequest]) {
if (isSharedKey) {
return [NSException exceptionWithHTTPStatus:403 /* Forbidden */
reason:
@"Access to shares forbidden from the Internet"];
}
}
return isSharedKey
? [self sharedMailAccountWithName:_key inContext:_ctx]
: [self mailAccountWithName:_key inContext:_ctx];
}
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* WebDAV */
- (BOOL)davIsCollection {
return YES;
}
@end /* SOGoMailAccounts */
+74
View File
@@ -0,0 +1,74 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailBaseObject_H__
#define __Mailer_SOGoMailBaseObject_H__
#include <SOGo/SOGoObject.h>
/*
SOGoMailBaseObject
Common base class for mailer SoObjects.
Subclasses:
SOGoDraftObject
SOGoDraftsFolder
SOGoMailAccount
SOGoMailBodyPart
SOGoMailFolder
SOGoMailObject
*/
@class NSString, NSArray, NSURL;
@class NGImap4ConnectionManager, NGImap4Connection;
@class SOGoMailAccount, SOGoMailAccounts;
@interface SOGoMailBaseObject : SOGoObject
{
NSURL *imap4URL;
NGImap4Connection *imap4;
}
- (id)initWithImap4URL:(NSURL *)_url inContainer:(id)_container;
/* hierarchy */
- (SOGoMailAccount *)mailAccountFolder;
- (SOGoMailAccounts *)mailAccountsFolder;
/* IMAP4 */
- (NGImap4Connection *)imap4Connection;
- (NGImap4ConnectionManager *)mailManager;
- (NSURL *)imap4URL;
- (NSString *)imap4Login;
- (NSString *)imap4Password;
- (void)flushMailCaches;
/* IMAP4 names */
- (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx;
@end
#endif /* __Mailer_SOGoMailBaseObject_H__ */
+194
View File
@@ -0,0 +1,194 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailBaseObject.h"
#include "SOGoMailManager.h"
#include "common.h"
#include <NGObjWeb/SoObject+SoDAV.h>
#include <NGObjWeb/SoHTTPAuthenticator.h>
#include <NGExtensions/NSURL+misc.h>
@implementation SOGoMailBaseObject
+ (int)version {
return [super version] + 1 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 0,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
#if 0
static BOOL debugOn = YES;
#endif
- (id)initWithImap4URL:(NSURL *)_url inContainer:(id)_container {
NSString *n;
n = [[_url path] lastPathComponent];
if ((self = [self initWithName:n inContainer:_container])) {
self->imap4URL = [_url retain];
}
return self;
}
- (void)dealloc {
[self->imap4URL release];
[super dealloc];
}
/* hierarchy */
- (SOGoMailAccount *)mailAccountFolder {
if (![[self container] respondsToSelector:_cmd]) {
[self warnWithFormat:@"weird container of mailfolder: %@",
[self container]];
return nil;
}
return [[self container] mailAccountFolder];
}
- (SOGoMailAccounts *)mailAccountsFolder {
id o;
for (o = [self container]; [o isNotNull]; o = [o container]) {
if ([o isKindOfClass:NSClassFromString(@"SOGoMailAccounts")])
return o;;
}
return nil;
}
/* IMAP4 */
- (NGImap4ConnectionManager *)mailManager {
return [NGImap4ConnectionManager defaultConnectionManager];
}
- (NGImap4Connection *)imap4Connection {
if (self->imap4 == nil) {
self->imap4 = [[[self mailManager] connectionForURL:[self imap4URL]
password:[self imap4Password]] retain];
if (self->imap4 == nil) {
[self errorWithFormat:@"Could not connect IMAP4."];
self->imap4 = [[NSNull null] retain];
}
}
return [self->imap4 isNotNull] ? self->imap4 : nil;
}
- (NSString *)relativeImap4Name {
[self warnWithFormat:@"subclass should override %@",
NSStringFromSelector(_cmd)];
return nil;
}
- (NSURL *)baseImap4URL {
if (![[self container] respondsToSelector:@selector(imap4URL)]) {
[self warnWithFormat:@"container does not implement -imap4URL!"];
return nil;
}
return [[self container] imap4URL];
}
- (NSURL *)imap4URL {
NSString *sn;
NSURL *base;
if (self->imap4URL != nil)
return self->imap4URL;
if ((sn = [self relativeImap4Name]) == nil)
return nil;
if (![[self container] respondsToSelector:_cmd]) {
[self warnWithFormat:@"container does not implement -imap4URL!"];
return nil;
}
if ((base = [self baseImap4URL]) == nil)
return nil;
sn = [[base path] stringByAppendingPathComponent:sn];
self->imap4URL = [[NSURL alloc] initWithString:sn relativeToURL:base];
return self->imap4URL;
}
- (NSString *)imap4Login {
if (![[self container] respondsToSelector:_cmd])
return nil;
return [[self container] imap4Login];
}
- (NSString *)imap4Password {
/*
Extract password from basic authentication.
TODO: we might want to
a) move the primary code to SOGoMailAccount
b) cache the password
*/
WORequest *rq;
NSString *auth;
NSArray *creds;
rq = [[(WOApplication *)[WOApplication application] context] request];
if ((auth = [rq headerForKey:@"authorization"]) == nil) {
/* no basic auth */
return nil;
}
creds = [SoHTTPAuthenticator parseCredentials:auth];
if ([creds count] < 2)
/* somehow invalid */
return nil;
return [creds objectAtIndex:1]; /* the password */
}
- (void)flushMailCaches {
[[self mailManager] flushCachesForURL:[self imap4URL]];
}
/* IMAP4 names */
- (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx {
/*
Every key starting with a digit is consider an IMAP4 mime part key, used in
SOGoMailObject and SOGoMailBodyPart.
*/
if ([_key length] == 0)
return NO;
if (isdigit([_key characterAtIndex:0]))
return YES;
return NO;
}
/* debugging */
- (NSString *)loggingPrefix {
/* improve perf ... */
return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
self, NSStringFromClass([self class]),
[self nameInContainer]];
}
@end /* SOGoMailBaseObject */
+66
View File
@@ -0,0 +1,66 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailBodyPart_H__
#define __Mailer_SOGoMailBodyPart_H__
#include <Mailer/SOGoMailBaseObject.h>
/*
SOGoMailBodyPart
Parent object: SOGoMailObject or SOGoMailBodyPart
Child objects: SOGoMailBodyPart's
Represents a MIME part of a mail as retrieved using IMAP4 body structure
commands in NGImap4.
*/
@class NSString, NSArray;
@class SOGoMailObject;
@interface SOGoMailBodyPart : SOGoMailBaseObject
{
NSArray *pathToPart;
NSString *identifier;
id partInfo;
}
/* hierarchy */
- (SOGoMailObject *)mailObject;
/* IMAP4 */
- (NSString *)bodyPartName;
- (NSArray *)bodyPartPath;
- (NSString *)bodyPartIdentifier;
/* part info */
- (id)partInfo;
/* factory */
+ (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx;
@end
#endif /* __Mailer_SOGoMailBodyPart_H__ */
+336
View File
@@ -0,0 +1,336 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailBodyPart.h"
#include "SOGoMailObject.h"
#include "SOGoMailManager.h"
#include "common.h"
@implementation SOGoMailBodyPart
static NSString *mailETag = nil;
static BOOL debugOn = NO;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
if (![[ud objectForKey:@"SOGoMailDisableETag"] boolValue]) {
mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"",
UIX_MAILER_MAJOR_VERSION,
UIX_MAILER_MINOR_VERSION,
UIX_MAILER_SUBMINOR_VERSION];
NSLog(@"Note(SOGoMailBodyPart): using constant etag for mail parts: '%@'",
mailETag);
}
else
NSLog(@"Note(SOGoMailBodyPart): etag caching disabled!");
}
- (void)dealloc {
[self->partInfo release];
[self->identifier release];
[self->pathToPart release];
[super dealloc];
}
/* hierarchy */
- (SOGoMailObject *)mailObject {
return [[self container] mailObject];
}
/* IMAP4 */
- (NSString *)bodyPartName {
NSString *s;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"."]; /* strip extensions */
if (r.length == 0)
return s;
return [s substringToIndex:r.location];
}
- (NSArray *)bodyPartPath {
NSMutableArray *p;
id obj;
if (self->pathToPart != nil)
return [self->pathToPart isNotNull] ? self->pathToPart : nil;
p = [[NSMutableArray alloc] initWithCapacity:8];
for (obj = self; [obj isKindOfClass:[SOGoMailBodyPart class]];
obj = [obj container]) {
[p insertObject:[obj bodyPartName] atIndex:0];
}
self->pathToPart = [p copy];
[p release];
return self->pathToPart;
}
- (NSString *)bodyPartIdentifier {
if (self->identifier != nil)
return [self->identifier isNotNull] ? self->identifier : nil;
self->identifier =
[[[self bodyPartPath] componentsJoinedByString:@"."] copy];
return self->identifier;
}
- (NSURL *)imap4URL {
/* reuse URL of message */
return [[self mailObject] imap4URL];
}
/* part info */
- (id)partInfo {
if (self->partInfo != nil)
return [self->partInfo isNotNull] ? self->partInfo : nil;
self->partInfo =
[[[self mailObject] lookupInfoForBodyPart:[self bodyPartPath]] retain];
return self->partInfo;
}
/* name lookup */
- (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx {
// TODO: we might want to check for existence prior controller creation
Class clazz;
clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
return [[[clazz alloc] initWithName:_key inContainer:self] autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
/* lookup body part */
if ([self isBodyPartKey:_key inContext:_ctx]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil)
return obj;
}
/*
Treat other keys which have a path-extension as 'virtual' noops to allow
addition of path names to the attachment path, eg:
http://.../login@server/INBOX/1/2/3/MyDocument.pdf
*/
if ([[_key pathExtension] length] > 0)
return self;
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"Did not find a subpart for the given name!"];
}
/* fetch */
- (NSData *)fetchBLOB {
// HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, TEXT
NSString *enc;
NSData *data;
data = [[self imap4Connection] fetchContentOfBodyPart:
[self bodyPartIdentifier]
atURL:[self imap4URL]];
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];
}
return data;
}
/* WebDAV */
- (NSString *)contentTypeForBodyPartInfo:(id)_info {
NSMutableString *type;
NSString *mt, *st;
NSDictionary *parameters;
NSEnumerator *ke;
NSString *pn;
if (![_info isNotNull])
return nil;
mt = [_info valueForKey:@"type"]; if (![mt isNotNull]) return nil;
st = [_info valueForKey:@"subtype"]; if (![st isNotNull]) return nil;
type = [NSMutableString stringWithCapacity:16];
[type appendString:[mt lowercaseString]];
[type appendString:@"/"];
[type appendString:[st lowercaseString]];
parameters = [_info valueForKey:@"parameterList"];
ke = [parameters keyEnumerator];
while ((pn = [ke nextObject]) != nil) {
[type appendString:@"; "];
[type appendString:pn];
[type appendString:@"=\""];
[type appendString:[[parameters objectForKey:pn] stringValue]];
[type appendString:@"\""];
}
return type;
}
- (NSString *)contentTypeForPathExtension:(NSString *)pe {
if ([pe length] == 0)
return @"application/octet-stream";
/* TODO: add some map */
if ([pe isEqualToString:@"gif"]) return @"image/gif";
if ([pe isEqualToString:@"png"]) return @"image/png";
if ([pe isEqualToString:@"jpg"]) return @"image/jpeg";
if ([pe isEqualToString:@"txt"]) return @"text/plain";
return @"application/octet-stream";
}
- (NSString *)davContentType {
// TODO: what about the content-type and other headers?
// => we could pass them in as the extension? (eg generate 1.gif!)
NSString *pe;
/* try type from body structure info */
if ((pe = [self contentTypeForBodyPartInfo:[self partInfo]]) != nil)
return pe;
/* construct type */
pe = [[self nameInContainer] pathExtension];
return [self contentTypeForPathExtension:pe];
}
/* actions */
- (id)GETAction:(id)_ctx {
NSException *error;
WOResponse *r;
NSData *data;
NSString *etag;
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
// TODO: currently we fetch the body structure to get here - check this!
/* check whether the mail still exists */
if (![[self mailObject] doesMailExist]) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"mail was deleted"];
}
return error; /* return 304 or 416 */
}
[self debugWithFormat:@"should fetch body part: %@",
[self bodyPartIdentifier]];
if ((data = [self fetchBLOB]) == nil) {
return [NSException exceptionWithHTTPStatus:404 /* not found */
reason:@"did not find body part"];
}
[self debugWithFormat:@" fetched %d bytes: %@", [data length],
[self partInfo]];
// TODO: wrong, could be encoded
r = [(WOContext *)_ctx response];
[r setHeader:[self davContentType] forKey:@"content-type"];
[r setHeader:[NSString stringWithFormat:@"%d", [data length]]
forKey:@"content-length"];
if ((etag = [self davEntityTag]) != nil)
[r setHeader:etag forKey:@"etag"];
[r setContent:data];
return r;
}
/* factory */
+ (Class)bodyPartClassForKey:(NSString *)_key inContext:(id)_ctx {
NSString *pe;
pe = [_key pathExtension];
if (![pe isNotNull] || [pe length] == 0)
return self;
/* hard coded for now */
switch ([pe length]) {
case 3:
if ([pe isEqualToString:@"gif"] ||
[pe isEqualToString:@"png"] ||
[pe isEqualToString:@"jpg"])
return NSClassFromString(@"SOGoImageMailBodyPart");
if ([pe isEqualToString:@"ics"])
return NSClassFromString(@"SOGoCalendarMailBodyPart");
if ([pe isEqualToString:@"vcf"])
return NSClassFromString(@"SOGoVCardMailBodyPart");
break;
case 4:
if ([pe isEqualToString:@"mail"])
return NSClassFromString(@"SOGoMessageMailBodyPart");
break;
default:
return self;
}
return self;
}
/* etag support */
- (id)davEntityTag {
return mailETag;
}
/* debugging */
- (BOOL)isDebuggingEnabled {
return debugOn;
}
@end /* SOGoMailBodyPart */
+86
View File
@@ -0,0 +1,86 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailFolder_H__
#define __Mailer_SOGoMailFolder_H__
#include <SoObjects/Mailer/SOGoMailBaseObject.h>
#import <Foundation/NSRange.h>
/*
SOGoMailFolder
Parent object: the SOGoMailAccount
Child objects: SOGoMailObject or SOGoMailFolder
The SOGoMailFolder maps to an IMAP4 folder from NGImap4.
*/
@class NSData, NSArray, NSException;
@class NGImap4MailboxInfo;
@interface SOGoMailFolder : SOGoMailBaseObject
{
NSArray *filenames;
NSString *folderType;
NGImap4MailboxInfo *selectInfo;
struct {
int didCheckMyRights:1;
int isDeleteAndExpungeAllowed:1;
int isReadAllowed:1;
int isWriteAllowed:1;
int isInsertAllowed:1;
int isPostAllowed:1;
int isCreateAllowed:1;
int hasAdminAccess:1;
int reserved:24;
} somfFlags;
}
/* messages */
- (NSArray *)fetchUIDsMatchingQualifier:(id)_q sortOrdering:(id)_so;
- (NSArray *)fetchUIDs:(NSArray *)_uids parts:(NSArray *)_parts;
- (NSException *)postData:(NSData *)_data flags:(id)_flags;
- (NSException *)expunge;
/* permissions */
- (BOOL)isDeleteAndExpungeAllowed;
- (BOOL)isReadAllowed;
- (BOOL)isWriteAllowed;
- (BOOL)isInsertAllowed;
- (BOOL)isPostAllowed;
- (BOOL)isCreateAllowed;
- (BOOL)hasAdminAccess;
/* flags */
- (NSException *)addFlagsToAllMessages:(id)_f;
/* folder type */
- (NSString *)outlookFolderClass;
@end
#endif /* __Mailer_SOGoMailFolder_H__ */
+389
View File
@@ -0,0 +1,389 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailFolder.h"
#include "SOGoMailObject.h"
#include "SOGoMailAccount.h"
#include "SOGoMailManager.h"
#include <NGImap4/NGImap4MailboxInfo.h>
#include "SOGoMailFolderDataSource.h"
#include "common.h"
@implementation SOGoMailFolder
static BOOL useAltNamespace = NO;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
}
- (void)dealloc {
[self->selectInfo release];
[self->filenames release];
[self->folderType release];
[super dealloc];
}
/* IMAP4 */
- (NSString *)relativeImap4Name {
return [self nameInContainer];
}
/* listing the available folders */
- (NSArray *)toManyRelationshipKeys {
return [[self imap4Connection] subfoldersForURL:[self imap4URL]];
}
- (NSArray *)toOneRelationshipKeys {
NSArray *uids;
unsigned count;
if (self->filenames != nil)
return [self->filenames isNotNull] ? self->filenames : nil;
uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
if ([uids isKindOfClass:[NSException class]])
return nil;
if ((count = [uids count]) == 0) {
self->filenames = [[NSArray alloc] init];
}
else {
NSMutableArray *keys;
unsigned i;
keys = [[NSMutableArray alloc] initWithCapacity:count];
for (i = 0; i < count; i++) {
NSString *k;
k = [[uids objectAtIndex:i] stringValue];
k = [k stringByAppendingString:@".mail"];
[keys addObject:k];
}
self->filenames = [keys copy];
[keys release];
}
return self->filenames;
}
- (EODataSource *)contentDataSourceInContext:(id)_ctx {
SOGoMailFolderDataSource *ds;
ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
imap4Password:[self imap4Password]];
return [ds autorelease];
}
/* mailbox raw ops */
- (NSException *)primaryFetchMailboxInfo {
/* returns nil if fetch was successful */
id info;
if (self->selectInfo != nil)
return nil; /* select info exists, => no error */
info = [[self imap4Connection] infoForMailboxAtURL:[self imap4URL]];
if ([info isKindOfClass:[NSException class]])
return info;
self->selectInfo = [info retain];
return nil; /* no error */
}
/* permissions */
- (void)_loadACLPermissionFlags {
NSString *rights;
unsigned i, len;
if (self->somfFlags.didCheckMyRights)
return;
rights = [[self imap4Connection] myRightsForMailboxAtURL:[self imap4URL]];
if ([rights isKindOfClass:[NSException class]]) {
[self logWithFormat:@"ERROR: could not retrieve ACL: %@", rights];
return;
}
// [self logWithFormat:@"GOT PERM: %@", rights];
self->somfFlags.didCheckMyRights = 1;
/* reset flags */
self->somfFlags.isDeleteAndExpungeAllowed = 0;
self->somfFlags.isReadAllowed = 0;
self->somfFlags.isWriteAllowed = 0;
self->somfFlags.isInsertAllowed = 0;
self->somfFlags.isPostAllowed = 0;
self->somfFlags.isCreateAllowed = 0;
self->somfFlags.hasAdminAccess = 0;
for (i = 0, len = [rights length]; i < len; i++) {
switch ([rights characterAtIndex:i]) {
case 'd': self->somfFlags.isDeleteAndExpungeAllowed = 1; break;
case 'r': self->somfFlags.isReadAllowed = 1; break;
case 'w': self->somfFlags.isWriteAllowed = 1; break;
case 'i': self->somfFlags.isInsertAllowed = 1; break;
case 'p': self->somfFlags.isPostAllowed = 1; break;
case 'c': self->somfFlags.isCreateAllowed = 1; break;
case 'a': self->somfFlags.hasAdminAccess = 1; break;
}
}
}
- (BOOL)isDeleteAndExpungeAllowed {
[self _loadACLPermissionFlags];
return self->somfFlags.isDeleteAndExpungeAllowed ? YES : NO;
}
- (BOOL)isReadAllowed {
[self _loadACLPermissionFlags];
return self->somfFlags.isReadAllowed ? YES : NO;
}
- (BOOL)isWriteAllowed {
[self _loadACLPermissionFlags];
return self->somfFlags.isWriteAllowed ? YES : NO;
}
- (BOOL)isInsertAllowed {
[self _loadACLPermissionFlags];
return self->somfFlags.isInsertAllowed ? YES : NO;
}
- (BOOL)isPostAllowed {
[self _loadACLPermissionFlags];
return self->somfFlags.isPostAllowed ? YES : NO;
}
- (BOOL)isCreateAllowedInACL {
/* we call this directly from UIxMailAccountView */
[self _loadACLPermissionFlags];
return self->somfFlags.isCreateAllowed ? YES : NO;
}
- (BOOL)isCreateAllowed {
if (useAltNamespace) {
/* with altnamespace, Cyrus doesn't allow mailboxes under INBOX */
if ([[self outlookFolderClass] isEqualToString:@"IPF.Inbox"])
return NO;
}
return [self isCreateAllowedInACL];
}
- (BOOL)hasAdminAccess {
[self _loadACLPermissionFlags];
return self->somfFlags.hasAdminAccess ? YES : NO;
}
/* messages */
- (NSArray *)fetchUIDsMatchingQualifier:(id)_q sortOrdering:(id)_so {
/* seems to return an NSArray of NSNumber's */
return [[self imap4Connection] fetchUIDsInURL:[self imap4URL]
qualifier:_q sortOrdering:_so];
}
- (NSArray *)fetchUIDs:(NSArray *)_uids parts:(NSArray *)_parts {
return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
parts:_parts];
}
- (NSException *)postData:(NSData *)_data flags:(id)_flags {
return [[self imap4Connection] postData:_data flags:_flags
toFolderURL:[self imap4URL]];
}
- (NSException *)expunge {
return [[self imap4Connection] expungeAtURL:[self imap4URL]];
}
/* flags */
- (NSException *)addFlagsToAllMessages:(id)_f {
return [[self imap4Connection] addFlags:_f
toAllMessagesInURL:[self imap4URL]];
}
/* name lookup */
- (BOOL)isMessageKey:(NSString *)_key inContext:(id)_ctx {
/*
Every key starting with a digit is consider an IMAP4 message key. This is
not entirely correct since folders could also start with a number.
If we want to support folders beginning with numbers, we would need to
scan the folder list for the _key, which would make everything quite a bit
slower.
TODO: support this mode using a default.
*/
if ([_key length] == 0)
return NO;
if (isdigit([_key characterAtIndex:0]))
return YES;
return NO;
}
- (id)lookupImap4Folder:(NSString *)_key inContext:(id)_ctx {
// TODO: we might want to check for existence prior controller creation
NSURL *sf;
/* check whether URL exists */
sf = [self imap4URL];
sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
relativeToURL:sf];
if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
/*
We may not return 404, confuses path traversal - but we still do in the
calling method. Probably the traversal process should be fixed to
support 404 exceptions (as stop traversal _and_ acquisition).
*/
return nil;
}
/* create object */
return [[[SOGoMailFolder alloc] initWithName:_key
inContainer:self] autorelease];
}
- (id)lookupImap4Message:(NSString *)_key inContext:(id)_ctx {
// TODO: we might want to check for existence prior controller creation
return [[[SOGoMailObject alloc] initWithName:_key
inContainer:self] autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_acquire {
id obj;
if ([self isMessageKey:_key inContext:_ctx]) {
/*
We assume here that _key is a number and methods are not and this is
moved above the super lookup since the super checks the
-toOneRelationshipKeys which in turn loads the message ids.
*/
return [self lookupImap4Message:_key inContext:_ctx];
}
/* check attributes directly bound to the app */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
return obj;
obj = [self lookupImap4Folder:_key inContext:_ctx];
if (obj != nil)
return obj;
/* return 404 to stop acquisition */
return _acquire
? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
: nil; /* hack to work with WebDAV move */
}
/* WebDAV */
- (BOOL)davIsCollection {
return YES;
}
- (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx {
return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
}
- (NSException *)delete {
/* Note: overrides SOGoObject -delete */
return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
}
- (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
inContext:(id)_ctx
{
NSURL *destImapURL;
if ([_name length] == 0) { /* target already exists! */
// TODO: check the overwrite request field (should be done by dispatcher)
return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
reason:@"target already exists"];
}
if (![_target respondsToSelector:@selector(imap4URL)]) {
return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
reason:@"target is not an IMAP4 folder"];
}
/* build IMAP4 URL for target */
destImapURL = [_target imap4URL];
destImapURL = [NSURL URLWithString:[[destImapURL path]
stringByAppendingPathComponent:_name]
relativeToURL:destImapURL];
[self logWithFormat:@"TODO: should move collection as '%@' to: %@",
[[self imap4URL] absoluteString],
[destImapURL absoluteString]];
return [[self imap4Connection] moveMailboxAtURL:[self imap4URL]
toURL:destImapURL];
}
- (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
inContext:(id)_ctx
{
[self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
_name, _target];
return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
reason:@"not implemented"];
}
/* folder type */
- (NSString *)outlookFolderClass {
// TODO: detect Trash/Sent/Drafts folders
SOGoMailAccount *account;
NSString *n;
if (self->folderType != nil)
return self->folderType;
account = [self mailAccountFolder];
n = [self nameInContainer];
if ([n isEqualToString:[account trashFolderNameInContext:nil]])
self->folderType = @"IPF.Trash";
else if ([n isEqualToString:[account inboxFolderNameInContext:nil]])
self->folderType = @"IPF.Inbox";
else if ([n isEqualToString:[account sentFolderNameInContext:nil]])
self->folderType = @"IPF.Sent";
else
self->folderType = @"IPF.Folder";
return self->folderType;
}
@end /* SOGoMailFolder */
@@ -0,0 +1,59 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailFolderDataSource_H__
#define __Mailer_SOGoMailFolderDataSource_H__
#include <EOControl/EODataSource.h>
/*
SOGoMailFolderDataSource
This is used as the contentDataSource in the SOGoMailFolder, that is, as the
object to retrieve WebDAV listings of an IMAP4 folder.
*/
@class NSString, NSURL, NSArray;
@class EOFetchSpecification;
@interface SOGoMailFolderDataSource : EODataSource
{
EOFetchSpecification *fetchSpecification;
NSURL *imap4URL;
NSString *imap4Password;
}
- (id)initWithImap4URL:(NSURL *)_imap4URL imap4Password:(NSString *)_pwd;
/* accessors */
- (void)setFetchSpecification:(EOFetchSpecification *)_fetchSpec;
- (EOFetchSpecification *)fetchSpecification;
- (NSURL *)imap4URL;
/* operations */
- (NSArray *)fetchObjects;
@end
#endif /* SOGoMailFolderDataSource */
+217
View File
@@ -0,0 +1,217 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailFolderDataSource.h"
#include "SOGoMailManager.h"
#include <NGObjWeb/EOFetchSpecification+SoDAV.h>
#include <NGImap4/NGImap4Envelope.h>
#include "common.h"
@implementation SOGoMailFolderDataSource
static BOOL debugOn = NO;
- (id)initWithImap4URL:(NSURL *)_imap4URL imap4Password:(NSString *)_pwd {
if (_imap4URL == nil) {
[self release];
return nil;
}
if ((self = [super init])) {
self->imap4URL = [_imap4URL copy];
self->imap4Password = [_pwd copy];
}
return self;
}
- (id)init {
return [self initWithImap4URL:nil imap4Password:nil];
}
- (void)dealloc {
[self->imap4Password release];
[self->imap4URL release];
[self->fetchSpecification release];
[super dealloc];
}
/* accessors */
- (void)setFetchSpecification:(EOFetchSpecification *)_fetchSpec {
if ([_fetchSpec isEqual:self->fetchSpecification]) return;
ASSIGN(self->fetchSpecification, _fetchSpec);
[self postDataSourceChangedNotification];
}
- (EOFetchSpecification *)fetchSpecification {
return self->fetchSpecification;
}
- (NSURL *)imap4URL {
return self->imap4URL;
}
- (NGImap4ConnectionManager *)mailManager {
static NGImap4ConnectionManager *mm = nil;
if (mm == nil)
mm = [[NGImap4ConnectionManager defaultConnectionManager] retain];
return mm;
}
/* fetches */
- (NSArray *)partsForWebDAVPropertyNames:(NSArray *)_names {
// TODO: implement
static NSArray *parts = nil;
// [self logWithFormat:@"props: %@", _names];
if (parts == nil) {
parts = [[NSArray alloc] initWithObjects:
@"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
}
return parts;
}
- (void)addRecordsForFolderNames:(NSArray *)_n toArray:(NSMutableArray *)_r {
unsigned i, count;
for (i = 0, count = [_n count]; i < count; i++) {
NSDictionary *rec;
NSString *keys[2], *values[2];
keys[0] = @"{DAV:}href"; values[0] = [_n objectAtIndex:i];
keys[1] = @"davResourceType"; values[1] = @"collection";
rec = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:2];
[_r addObject:rec];
[rec release];
}
}
- (void)addRecordsForUIDs:(NSArray *)_uids toArray:(NSMutableArray *)_r {
NSAutoreleasePool *pool;
NSArray *partNames, *results;
unsigned i, count;
if ([_uids count] == 0)
return;
pool = [[NSAutoreleasePool alloc] init];
partNames = [self partsForWebDAVPropertyNames:
[[self fetchSpecification] selectedWebDAVPropertyNames]];
results = [[self mailManager] fetchUIDs:_uids inURL:self->imap4URL
parts:partNames password:self->imap4Password];
results = [results valueForKey:@"fetch"];
for (i = 0, count = [results count]; i < count; i++) {
NGImap4Envelope *envelope;
NSDictionary *result;
NSDictionary *rec;
NSString *keys[6];
id values[6];
result = [results objectAtIndex:i];
envelope = [result valueForKey:@"envelope"];
// NSLog(@"RES: %@", result);
keys[0] = @"{DAV:}href";
values[0] = [[[result objectForKey:@"uid"] stringValue]
stringByAppendingString:@".mail"];
keys[1] = @"davResourceType";
values[1] = @"";
keys[2] = @"davContentLength";
values[2] = [result objectForKey:@"size"];
keys[3] = @"davDisplayName";
values[3] = [envelope subject];
keys[4] = @"davLastModified";
values[4] = [envelope date];
rec = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:5];
[_r addObject:rec];
[rec release];
}
[pool release];
}
/* operations */
- (NSArray *)fetchObjects {
NSMutableArray *results;
EOQualifier *qualifier;
NSArray *sortOrderings;
NSArray *uids, *folderNames;
unsigned total;
// TODO: support [fs davBulkTargetKeys]
if ([[self fetchSpecification] davBulkTargetKeys] != nil) {
[self logWithFormat:@"unsupported fetch specification"];
return nil;
}
/* fetch message uids */
// TODO: translate WebDAV qualifier and sort-ordering into IMAP4 one
uids = [[self mailManager] fetchUIDsInURL:self->imap4URL
qualifier:nil sortOrdering:@"DATE"
password:self->imap4Password];
/* fetch folders */
folderNames = [[self mailManager] subfoldersForURL:self->imap4URL
password:self->imap4Password];
/* builds results */
if ((total = ([uids count] + [folderNames count])) == 0)
return [NSArray array];
results = [NSMutableArray arrayWithCapacity:total];
[self addRecordsForFolderNames:folderNames toArray:results];
[self addRecordsForUIDs:uids toArray:results];
/* filter and sort results */
if ((qualifier = [[self fetchSpecification] qualifier]) != nil)
results = (id)[results filteredArrayUsingQualifier:qualifier];
if ((sortOrderings = [[self fetchSpecification] sortOrderings]) != nil) {
if (qualifier != nil)
results = (id)[results sortedArrayUsingKeyOrderArray:sortOrderings];
else
[results sortUsingKeyOrderArray:sortOrderings];
}
return results;
}
/* logging */
- (NSString *)loggingPrefix {
return @"[mailfolder-ds]";
}
- (BOOL)isDebuggingEnabled {
return debugOn;
}
@end /* SOGoMailFolderDataSource */
+111
View File
@@ -0,0 +1,111 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailIdentity_H__
#define __Mailer_SOGoMailIdentity_H__
#import <Foundation/NSObject.h>
/*
SOGoMailIdentity
A user identity bound to an account.
Note: currently this is not a SoObject. This might change later on.
In Thunderbird you have a set of accounts which in turn have a set of
identities. There is one default identity.
The identities then have:
- settings
- a name
- a from-email
- a reply-to
- an organization
- a signature
- a vcard (to be attached)
- folder settings
- Sent-Folder and bcc
- Drafts + Templates
- composition
- whether to use HTML
- whether to quote the source message (reply below, above the quote or
select the quote)
*/
@class NSString;
@interface SOGoMailIdentity : NSObject
{
NSString *name;
NSString *email;
NSString *replyTo;
NSString *organization;
NSString *signature;
NSString *vCard;
NSString *sentFolderName;
NSString *sentBCC;
NSString *draftsFolderName;
NSString *templatesFolderName;
struct {
int composeHTML:1;
int reserved:31;
} idFlags;
}
/* accessors */
- (void)setName:(NSString *)_value;
- (NSString *)name;
- (void)setEmail:(NSString *)_value;
- (NSString *)email;
- (void)setReplyTo:(NSString *)_value;
- (NSString *)replyTo;
- (void)setOrganization:(NSString *)_value;
- (NSString *)organization;
- (void)setSignature:(NSString *)_value;
- (NSString *)signature;
- (BOOL)hasSignature;
- (void)setVCard:(NSString *)_value;
- (NSString *)vCard;
- (BOOL)hasVCard;
- (void)setSentFolderName:(NSString *)_value;
- (NSString *)sentFolderName;
- (void)setSentBCC:(NSString *)_value;
- (NSString *)sentBCC;
- (void)setDraftsFolderName:(NSString *)_value;
- (NSString *)draftsFolderName;
- (void)setTemplatesFolderName:(NSString *)_value;
- (NSString *)templatesFolderName;
@end
#endif /* __Mailer_SOGoMailIdentity_H__ */
+140
View File
@@ -0,0 +1,140 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailIdentity.h"
#include "common.h"
@implementation SOGoMailIdentity
- (void)dealloc {
[self->name release];
[self->email release];
[self->replyTo release];
[self->organization release];
[self->signature release];
[self->vCard release];
[self->sentFolderName release];
[self->sentBCC release];
[self->draftsFolderName release];
[self->templatesFolderName release];
[super dealloc];
}
/* accessors */
- (void)setName:(NSString *)_value {
ASSIGNCOPY(self->name, _value);
}
- (NSString *)name {
return self->name;
}
- (void)setEmail:(NSString *)_value {
ASSIGNCOPY(self->email, _value);
}
- (NSString *)email {
return self->email;
}
- (void)setReplyTo:(NSString *)_value {
ASSIGNCOPY(self->replyTo, _value);
}
- (NSString *)replyTo {
return self->replyTo;
}
- (void)setOrganization:(NSString *)_value {
ASSIGNCOPY(self->organization, _value);
}
- (NSString *)organization {
return self->organization;
}
- (void)setSignature:(NSString *)_value {
ASSIGNCOPY(self->signature, _value);
}
- (NSString *)signature {
return self->signature;
}
- (BOOL)hasSignature {
return [[self signature] isNotEmpty];
}
- (void)setVCard:(NSString *)_value {
ASSIGNCOPY(self->vCard, _value);
}
- (NSString *)vCard {
return self->vCard;
}
- (BOOL)hasVCard {
return [[self vCard] isNotEmpty];
}
- (void)setSentFolderName:(NSString *)_value {
ASSIGNCOPY(self->sentFolderName, _value);
}
- (NSString *)sentFolderName {
return self->sentFolderName;
}
- (void)setSentBCC:(NSString *)_value {
ASSIGNCOPY(self->sentBCC, _value);
}
- (NSString *)sentBCC {
return self->sentBCC;
}
- (void)setDraftsFolderName:(NSString *)_value {
ASSIGNCOPY(self->draftsFolderName, _value);
}
- (NSString *)draftsFolderName {
return self->draftsFolderName;
}
- (void)setTemplatesFolderName:(NSString *)_value {
ASSIGNCOPY(self->templatesFolderName, _value);
}
- (NSString *)templatesFolderName {
return self->templatesFolderName;
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:128];
[ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
if (self->name != nil) [ms appendFormat:@" name='%@'", self->name];
if (self->email != nil) [ms appendFormat:@" email='%@'", self->email];
if (self->sentFolderName != nil)
[ms appendFormat:@" sent='%@'", self->sentFolderName];
if ([self->sentBCC length] > 0) [ms appendString:@" sent-bcc"];
if ([self->vCard length] > 0) [ms appendString:@" vcard"];
[ms appendString:@">"];
return ms;
}
@end /* SOGoMailIdentity */
+93
View File
@@ -0,0 +1,93 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailManager_H__
#define __Mailer_SOGoMailManager_H__
#import <Foundation/NSObject.h>
#import <Foundation/NSRange.h>
#include <NGImap4/NGImap4ConnectionManager.h>
/*
NGImap4ConnectionManager(SOGoMailManager)
Legacy methods, the methods were used prior the move to NGImap4.
*/
@class NSString, NSData, NSURL, NSArray, NSMutableDictionary, NSTimer;
@class NSDictionary;
@class NSException;
@class NGImap4Client;
@interface NGImap4ConnectionManager(SOGoMailManager)
/* folder hierarchy */
- (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd;
/* messages */
- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_q
sortOrdering:(id)_so password:(NSString *)_pwd;
- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
parts:(NSArray *)_parts password:(NSString *)_pwd;
- (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd;
/* individual message */
- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd;
- (NSData *)fetchContentOfBodyPart:(NSString *)_partId
atURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p;
- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p;
- (NSException *)markURLDeleted:(NSURL *)_u password:(NSString *)_p;
- (NSException *)postData:(NSData *)_data flags:(id)_flags
toFolderURL:(NSURL *)_url password:(NSString *)_p;
- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl
password:(NSString *)_pwd;
/* managing folders */
- (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd;
- (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url
password:(NSString *)_pwd;
- (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl
password:(NSString *)_pwd;
- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd;
- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_u
password:(NSString *)_p;
@end
#endif /* __Mailer_SOGoMailManager_H__ */
+321
View File
@@ -0,0 +1,321 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailManager.h"
#include <NGImap4/NGImap4Connection.h>
#include <NGImap4/NGImap4ConnectionManager.h>
#include "common.h"
/*
Could check read-write state:
dict = [[self->context client] select:[self absoluteName]];
self->isReadOnly =
[[dict objectForKey:@"access"] isEqualToString:@"READ-WRITE"]
? NoNumber : YesNumber;
TODO: to implement copy, use "uid copy" instead of "copy" as used by
NGImap4Client.
*/
@implementation NGImap4ConnectionManager(SOGoMailManager)
+ (id)defaultMailManager {
return [self defaultConnectionManager];
}
- (NSException *)errorForMissingEntryAtURL:(NSURL *)_url {
// TODO: improve
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"Did not find mail URL"];
}
/* client object */
/* folder hierarchy */
- (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return nil;
return [entry subfoldersForURL:_url];
}
- (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return nil;
return [entry allFoldersForURL:_url];
}
/* messages */
- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier
sortOrdering:(id)_so password:(NSString *)_pwd
{
/*
sortOrdering can be an NSString, an EOSortOrdering or an array of EOS.
*/
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return nil;
return [entry fetchUIDsInURL:_url qualifier:_qualifier sortOrdering:_so];
}
- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
parts:(NSArray *)_parts password:(NSString *)_pwd
{
// currently returns a dict?!
/*
Allowed fetch keys:
UID
BODY.PEEK[<section>]<<partial>>
BODY [this is the bodystructure, supported]
BODYSTRUCTURE [not supported yet!]
ENVELOPE [this is a parsed header, but does not include type]
FLAGS
INTERNALDATE
RFC822
RFC822.HEADER
RFC822.SIZE
RFC822.TEXT
*/
NGImap4Connection *entry;
if (_uids == nil)
return nil;
if ([_uids count] == 0)
return nil; // TODO: might break empty folders?! return a dict!
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return nil;
return [entry fetchUIDs:_uids inURL:_url parts:_parts];
}
- (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry expungeAtURL:_url];
}
- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd{
NGImap4Connection *entry;
if (![_url isNotNull]) return nil;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry fetchURL:_url parts:_parts];
}
- (NSData *)fetchContentOfBodyPart:(NSString *)_partId
atURL:(NSURL *)_url password:(NSString *)_pwd
{
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return nil; // TODO: improve?
return [entry fetchContentOfBodyPart:_partId atURL:_url];
}
- (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f
toURL:(NSURL *)_url password:(NSString *)_p
{
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_p]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry addOrRemove:_flag flags:_f toURL:_url];
}
- (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
return [self addOrRemove:YES flags:_f toURL:_u password:_p];
}
- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
return [self addOrRemove:NO flags:_f toURL:_u password:_p];
}
- (NSException *)markURLDeleted:(NSURL *)_url password:(NSString *)_p {
return [self addOrRemove:YES flags:@"Deleted" toURL:_url password:_p];
}
- (NSException *)postData:(NSData *)_data flags:(id)_f
toFolderURL:(NSURL *)_url password:(NSString *)_p
{
NGImap4Connection *entry;
if (![_url isNotNull]) return nil;
if ((entry = [self connectionForURL:_url password:_p]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry postData:_data flags:_f toFolderURL:_url];
}
- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl
password:(NSString *)_pwd
{
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_srcurl password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_srcurl];
/* check whether URLs are on different servers */
if ([self connectionForURL:_desturl password:_pwd] != entry) {
// TODO: find a better error code
return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
reason:@"source and destination on different servers"];
}
return [entry copyMailURL:_srcurl toFolderURL:_desturl];
}
/* managing folders */
- (BOOL)isPermissionDeniedResult:(id)_result {
if ([[_result valueForKey:@"result"] intValue] != 0)
return NO;
return [[_result valueForKey:@"reason"]
isEqualToString:@"Permission denied"];
}
- (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return NO;
return [entry doesMailboxExistAtURL:_url];
}
- (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry infoForMailboxAtURL:_url];
}
- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url
password:(NSString *)_pwd
{
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry createMailbox:_mailbox atURL:_url];
}
- (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry deleteMailboxAtURL:_url];
}
- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl
password:(NSString *)_pwd
{
NGImap4Connection *entry;
/* check connection cache */
if ((entry = [self connectionForURL:_srcurl password:_pwd]) == nil)
return [self errorForMissingEntryAtURL:_srcurl];
/* check whether URLs are on different servers */
if ([self connectionForURL:_desturl password:_pwd] != entry) {
// TODO: find a better error code
return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
reason:@"source and destination on different servers"];
}
return [entry moveMailboxAtURL:_srcurl toURL:_desturl];
}
- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
/*
Returns a mapping of uid => permission strings, eg:
guizmo.g = lrs;
root = lrswipcda;
*/
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return (id)[self errorForMissingEntryAtURL:_url];
return [entry aclForMailboxAtURL:_url];
}
- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
NGImap4Connection *entry;
if ((entry = [self connectionForURL:_url password:_pwd]) == nil)
return (id)[self errorForMissingEntryAtURL:_url];
return [entry myRightsForMailboxAtURL:_url];
}
/* bulk flag adding (eg used for empty/trash) */
- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url
password:(NSString *)_p
{
NGImap4Connection *entry;
if (![_url isNotNull]) return nil;
if (![_f isNotNull]) return nil;
if ((entry = [self connectionForURL:_url password:_p]) == nil)
return [self errorForMissingEntryAtURL:_url];
return [entry addFlags:_f toAllMessagesInURL:_url];
}
@end /* NGImap4ConnectionManager(SOGoMailManager) */
+93
View File
@@ -0,0 +1,93 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoMailObject_H__
#define __Mailer_SOGoMailObject_H__
#include <SoObjects/Mailer/SOGoMailBaseObject.h>
/*
SOGoMailObject
Parent object: the SOGoMailFolder
Child objects: SOGoMailBodyPart's
Represents a single mail as retrieved using NGImap4. Since IMAP4 can parse
MIME structures on the server side, which we map into child objects of the
message.
The child objects are accessed using integer IDs, eg:
/INBOX/12345/1/2/3
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;
@interface SOGoMailObject : SOGoMailBaseObject
{
id coreInfos;
id headerPart;
NSDictionary *headers;
}
/* message */
- (id)fetchParts:(NSArray *)_parts; /* Note: 'parts' are fetch keys here */
/* core infos */
- (BOOL)doesMailExist;
- (id)fetchCoreInfos; // TODO: what does it do?
- (NGImap4Envelope *)envelope;
- (NSString *)subject;
- (NSCalendarDate *)date;
- (NSArray *)fromEnvelopeAddresses;
- (NSArray *)toEnvelopeAddresses;
- (NSArray *)ccEnvelopeAddresses;
- (id)bodyStructure;
- (id)lookupInfoForBodyPart:(id)_path;
/* content */
- (NSData *)content;
- (NSString *)contentAsString;
/* bulk fetching of plain/text content */
- (NSArray *)plainTextContentFetchKeys;
- (NSDictionary *)fetchPlainTextParts:(NSArray *)_fetchKeys;
- (NSDictionary *)fetchPlainTextParts;
- (NSDictionary *)fetchPlainTextStrings:(NSArray *)_fetchKeys;
/* flags */
- (NSException *)addFlags:(id)_f;
- (NSException *)removeFlags:(id)_f;
/* deletion */
- (BOOL)isDeletionAllowed;
- (NSException *)trashInContext:(id)_ctx;
@end
#endif /* __Mailer_SOGoMailObject_H__ */
+970
View File
@@ -0,0 +1,970 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoMailObject.h"
#include "SOGoMailFolder.h"
#include "SOGoMailAccount.h"
#include "SOGoMailManager.h"
#include "SOGoMailBodyPart.h"
#include <NGImap4/NGImap4Envelope.h>
#include <NGImap4/NGImap4EnvelopeAddress.h>
#include <NGMail/NGMimeMessageParser.h>
#include "common.h"
@implementation SOGoMailObject
static NSArray *coreInfoKeys = nil;
static NSString *mailETag = nil;
static BOOL heavyDebug = NO;
static BOOL fetchHeader = YES;
static BOOL debugOn = NO;
static BOOL debugBodyStructure = NO;
static BOOL debugSoParts = NO;
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
if ((fetchHeader = ([ud boolForKey:@"SOGoDoNotFetchMailHeader"] ? NO : YES)))
NSLog(@"Note: fetching full mail header.");
else
NSLog(@"Note: not fetching full mail header: 'SOGoDoNotFetchMailHeader'");
/* Note: see SOGoMailManager.m for allowed IMAP4 keys */
/* Note: "BODY" actually returns the structure! */
if (fetchHeader) {
coreInfoKeys = [[NSArray alloc] initWithObjects:
@"FLAGS", @"ENVELOPE", @"BODY",
@"RFC822.SIZE",
@"RFC822.HEADER",
// not yet supported: @"INTERNALDATE",
nil];
}
else {
coreInfoKeys = [[NSArray alloc] initWithObjects:
@"FLAGS", @"ENVELOPE", @"BODY",
@"RFC822.SIZE",
// not yet supported: @"INTERNALDATE",
nil];
}
if (![[ud objectForKey:@"SOGoMailDisableETag"] boolValue]) {
mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"",
UIX_MAILER_MAJOR_VERSION,
UIX_MAILER_MINOR_VERSION,
UIX_MAILER_SUBMINOR_VERSION];
NSLog(@"Note(SOGoMailObject): using constant etag for mail parts: '%@'",
mailETag);
}
else
NSLog(@"Note(SOGoMailObject): etag caching disabled!");
}
- (void)dealloc {
[self->headers release];
[self->headerPart release];
[self->coreInfos release];
[super dealloc];
}
/* IMAP4 */
- (NSString *)relativeImap4Name {
return [[self nameInContainer] stringByDeletingPathExtension];
}
/* hierarchy */
- (SOGoMailObject *)mailObject {
return self;
}
/* part hierarchy */
- (NSString *)keyExtensionForPart:(id)_partInfo {
NSString *mt, *st;
if (_partInfo == nil)
return nil;
mt = [_partInfo valueForKey:@"type"];
st = [[_partInfo valueForKey:@"subtype"] lowercaseString];
if ([mt isEqualToString:@"text"]) {
if ([st isEqualToString:@"plain"]) return @".txt";
if ([st isEqualToString:@"html"]) return @".html";
if ([st isEqualToString:@"calendar"]) return @".ics";
if ([st isEqualToString:@"x-vcard"]) return @".vcf";
}
else if ([mt isEqualToString:@"image"])
return [@"." stringByAppendingString:st];
else if ([mt isEqualToString:@"application"]) {
if ([st isEqualToString:@"pgp-signature"])
return @".asc";
}
return nil;
}
- (NSArray *)relationshipKeysWithParts:(BOOL)_withParts {
/* should return non-multipart children */
NSMutableArray *ma;
NSArray *parts;
unsigned i, count;
parts = [[self bodyStructure] valueForKey:@"parts"];
if (![parts isNotNull])
return nil;
if ((count = [parts count]) == 0)
return nil;
for (i = 0, ma = nil; i < count; i++) {
NSString *key, *ext;
id part;
BOOL hasParts;
part = [parts objectAtIndex:i];
hasParts = [part valueForKey:@"parts"] != nil ? YES:NO;
if ((hasParts && !_withParts) || (_withParts && !hasParts))
continue;
if (ma == nil)
ma = [NSMutableArray arrayWithCapacity:count - i];
ext = [self keyExtensionForPart:part];
key = [[NSString alloc] initWithFormat:@"%d%@", i + 1, ext?ext:@""];
[ma addObject:key];
[key release];
}
return ma;
}
- (NSArray *)toOneRelationshipKeys {
return [self relationshipKeysWithParts:NO];
}
- (NSArray *)toManyRelationshipKeys {
return [self relationshipKeysWithParts:YES];
}
/* message */
- (id)fetchParts:(NSArray *)_parts {
// TODO: explain what it does
/*
Called by -fetchPlainTextParts:
*/
return [[self imap4Connection] fetchURL:[self imap4URL] parts:_parts];
}
/* core infos */
- (BOOL)doesMailExist {
static NSArray *existsKey = nil;
id msgs;
if (self->coreInfos != nil) /* if we have coreinfos, we can use them */
return [self->coreInfos isNotNull];
/* otherwise fetch something really simple */
if (existsKey == nil) /* we use size, other suggestions? */
existsKey = [[NSArray alloc] initWithObjects:@"RFC822.SIZE", nil];
msgs = [self fetchParts:existsKey]; // returns dict
msgs = [msgs valueForKey:@"fetch"];
return [msgs count] > 0 ? YES : NO;
}
- (id)fetchCoreInfos {
id msgs;
if (self->coreInfos != nil)
return [self->coreInfos isNotNull] ? self->coreInfos : nil;
#if 0 // TODO: old code, why was it using clientObject??
msgs = [[self clientObject] fetchParts:coreInfoKeys]; // returns dict
#else
msgs = [self fetchParts:coreInfoKeys]; // returns dict
#endif
if (heavyDebug) [self logWithFormat:@"M: %@", msgs];
msgs = [msgs valueForKey:@"fetch"];
if ([msgs count] == 0)
return nil;
self->coreInfos = [[msgs objectAtIndex:0] retain];
return self->coreInfos;
}
- (id)bodyStructure {
id body;
body = [[self fetchCoreInfos] valueForKey:@"body"];
if (debugBodyStructure)
[self logWithFormat:@"BODY: %@", body];
return body;
}
- (NGImap4Envelope *)envelope {
return [[self fetchCoreInfos] valueForKey:@"envelope"];
}
- (NSString *)subject {
return [[self envelope] subject];
}
- (NSCalendarDate *)date {
return [[self envelope] date];
}
- (NSArray *)fromEnvelopeAddresses {
return [[self envelope] from];
}
- (NSArray *)toEnvelopeAddresses {
return [[self envelope] to];
}
- (NSArray *)ccEnvelopeAddresses {
return [[self envelope] cc];
}
- (NSData *)mailHeaderData {
return [[self fetchCoreInfos] valueForKey:@"header"];
}
- (BOOL)hasMailHeaderInCoreInfos {
return [[self mailHeaderData] length] > 0 ? YES : NO;
}
- (id)mailHeaderPart {
NGMimeMessageParser *parser;
NSData *data;
if (self->headerPart != nil)
return [self->headerPart isNotNull] ? self->headerPart : nil;
if ([(data = [self mailHeaderData]) length] == 0)
return nil;
// TODO: do we need to set some delegate method which stops parsing the body?
parser = [[NGMimeMessageParser alloc] init];
self->headerPart = [[parser parsePartFromData:data] retain];
[parser release]; parser = nil;
if (self->headerPart == nil) {
self->headerPart = [[NSNull null] retain];
return nil;
}
return self->headerPart;
}
- (NSDictionary *)mailHeaders {
if (self->headers == nil)
self->headers = [[[self mailHeaderPart] headers] copy];
return self->headers;
}
- (id)lookupInfoForBodyPart:(id)_path {
NSEnumerator *pe;
NSString *p;
id info;
if (![_path isNotNull])
return nil;
if ((info = [self bodyStructure]) == nil) {
[self errorWithFormat:@"got no body part structure!"];
return nil;
}
/* ensure array argument */
if ([_path isKindOfClass:[NSString class]]) {
if ([_path length] == 0)
return info;
_path = [_path componentsSeparatedByString:@"."];
}
/*
For each path component, eg 1,1,3
Remember that we need special processing for message/rfc822 which maps the
namespace of multiparts directly into the main namespace.
TODO(hh): no I don't remember, please explain in more detail!
*/
pe = [_path objectEnumerator];
while ((p = [pe nextObject]) != nil && [info isNotNull]) {
unsigned idx;
NSArray *parts;
NSString *mt;
[self debugWithFormat:@"check PATH: %@", p];
idx = [p intValue] - 1;
parts = [info valueForKey:@"parts"];
mt = [[info valueForKey:@"type"] lowercaseString];
if ([mt isEqualToString:@"message"]) {
/* we have special behaviour for message types */
id body;
if ((body = [info valueForKey:@"body"]) != nil) {
mt = [body valueForKey:@"type"];
if ([mt isEqualToString:@"multipart"])
parts = [body valueForKey:@"parts"];
else
parts = [NSArray arrayWithObject:body];
}
}
if (idx >= [parts count]) {
[self errorWithFormat:
@"body part index out of bounds(idx=%d vs count=%d): %@",
(idx + 1), [parts count], info];
return nil;
}
info = [parts objectAtIndex:idx];
}
return [info isNotNull] ? info : nil;
}
/* content */
- (NSData *)content {
NSData *content;
id result, fullResult;
fullResult = [self fetchParts:[NSArray arrayWithObject:@"RFC822"]];
if (fullResult == nil)
return nil;
if ([fullResult isKindOfClass:[NSException class]])
return fullResult;
/* extract fetch result */
result = [fullResult valueForKey:@"fetch"];
if (![result isKindOfClass:[NSArray class]]) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
fullResult];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"unexpected IMAP4 result"];
}
if ([result count] == 0)
return nil;
result = [result objectAtIndex:0];
/* extract message */
if ((content = [result valueForKey:@"message"]) == nil) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
result];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason:@"unexpected IMAP4 result"];
}
return [[content copy] autorelease];
}
- (NSString *)contentAsString {
NSString *s;
NSData *content;
if ((content = [self content]) == nil)
return nil;
if ([content isKindOfClass:[NSException class]])
return (id)content;
s = [[NSString alloc] initWithData:content
encoding:NSISOLatin1StringEncoding];
if (s == nil) {
[self logWithFormat:
@"ERROR: could not convert data of length %d to string",
[content length]];
return nil;
}
return [s autorelease];
}
/* bulk fetching of plain/text content */
- (BOOL)shouldFetchPartOfType:(NSString *)_type subtype:(NSString *)_subtype {
/*
This method decides which parts are 'prefetched' for display. Those are
usually text parts (the set is currently hardcoded in this method ...).
*/
_type = [_type lowercaseString];
_subtype = [_subtype lowercaseString];
if ([_type isEqualToString:@"text"]) {
if ([_subtype isEqualToString:@"plain"])
return YES;
if ([_subtype isEqualToString:@"calendar"]) /* we also fetch calendars */
return YES;
}
if ([_type isEqualToString:@"application"]) {
if ([_subtype isEqualToString:@"pgp-signature"])
return YES;
if ([_subtype hasPrefix:@"x-vnd.kolab."])
return YES;
}
return NO;
}
- (void)addRequiredKeysOfStructure:(id)_info path:(NSString *)_p
toArray:(NSMutableArray *)_keys
recurse:(BOOL)_recurse
{
/*
This is used to collect the set of IMAP4 fetch-keys required to fetch
the basic parts of the body structure. That is, to fetch all parts which
are displayed 'inline' in a single IMAP4 fetch.
The method calls itself recursively to walk the body structure.
*/
NSArray *parts;
unsigned i, count;
BOOL fetchPart;
id body;
/* Note: if the part itself doesn't qualify, we still check subparts */
fetchPart = [self shouldFetchPartOfType:[_info valueForKey:@"type"]
subtype:[_info valueForKey:@"subtype"]];
if (fetchPart) {
NSString *k;
if ([_p length] > 0) {
k = [[@"body[" stringByAppendingString:_p] stringByAppendingString:@"]"];
}
else {
/*
for some reason we need to add ".TEXT" for plain text stuff on root
entities?
TODO: check with HTML
*/
k = @"body[text]";
}
[_keys addObject:k];
}
if (!_recurse)
return;
/* recurse */
parts = [(NSDictionary *)_info objectForKey:@"parts"];
for (i = 0, count = [parts count]; i < count; i++) {
NSString *sp;
id childInfo;
sp = ([_p length] > 0)
? [_p stringByAppendingFormat:@".%d", i + 1]
: [NSString stringWithFormat:@"%d", i + 1];
childInfo = [parts objectAtIndex:i];
[self addRequiredKeysOfStructure:childInfo path:sp toArray:_keys
recurse:YES];
}
/* check body */
if ((body = [(NSDictionary *)_info objectForKey:@"body"]) != nil) {
NSString *sp;
sp = [[body valueForKey:@"type"] lowercaseString];
if ([sp isEqualToString:@"multipart"])
sp = _p;
else
sp = [_p length] > 0 ? [_p stringByAppendingString:@".1"] : @"1";
[self addRequiredKeysOfStructure:body path:sp toArray:_keys
recurse:YES];
}
}
- (NSArray *)plainTextContentFetchKeys {
/*
The name is not 100% correct. The method returns all body structure fetch
keys which are marked by the -shouldFetchPartOfType:subtype: method.
*/
NSMutableArray *ma;
ma = [NSMutableArray arrayWithCapacity:4];
[self addRequiredKeysOfStructure:[[self clientObject] bodyStructure]
path:@"" toArray:ma recurse:YES];
return ma;
}
- (NSDictionary *)fetchPlainTextParts:(NSArray *)_fetchKeys {
// TODO: is the name correct or does it also fetch other parts?
NSMutableDictionary *flatContents;
unsigned i, count;
id result;
[self debugWithFormat:@"fetch keys: %@", _fetchKeys];
result = [self fetchParts:_fetchKeys];
result = [result valueForKey:@"RawResponse"]; // hackish
// Note: -valueForKey: doesn't work!
result = [(NSDictionary *)result objectForKey:@"fetch"];
count = [_fetchKeys count];
flatContents = [NSMutableDictionary dictionaryWithCapacity:count];
for (i = 0; i < count; i++) {
NSString *key;
NSData *data;
key = [_fetchKeys objectAtIndex:i];
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
objectForKey:@"data"];
if (![data isNotNull]) {
[self errorWithFormat:@"got no data for key: %@", key];
continue;
}
if ([key isEqualToString:@"body[text]"])
key = @""; // see key collector for explanation (TODO: where?)
else if ([key hasPrefix:@"body["]) {
NSRange r;
key = [key substringFromIndex:5];
r = [key rangeOfString:@"]"];
if (r.length > 0)
key = [key substringToIndex:r.location];
}
[flatContents setObject:data forKey:key];
}
return flatContents;
}
- (NSDictionary *)fetchPlainTextParts {
return [self fetchPlainTextParts:[self plainTextContentFetchKeys]];
}
/* convert parts to strings */
- (NSString *)stringForData:(NSData *)_data partInfo:(NSDictionary *)_info {
NSString *charset;
NSString *s;
if (![_data isNotNull])
return nil;
s = nil;
charset = [[_info valueForKey:@"parameterList"] valueForKey:@"charset"];
if ([charset isNotNull] && [charset length] > 0)
s = [NSString stringWithData:_data usingEncodingNamed:charset];
if (s == nil) { /* no charset provided, fall back to UTF-8 */
s = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
s = [s autorelease];
}
return s;
}
- (NSDictionary *)stringifyTextParts:(NSDictionary *)_datas {
NSMutableDictionary *md;
NSEnumerator *keys;
NSString *key;
md = [NSMutableDictionary dictionaryWithCapacity:4];
keys = [_datas keyEnumerator];
while ((key = [keys nextObject]) != nil) {
NSDictionary *info;
NSString *s;
info = [self lookupInfoForBodyPart:key];
if ((s = [self stringForData:[_datas objectForKey:key] partInfo:info]))
[md setObject:s forKey:key];
}
return md;
}
- (NSDictionary *)fetchPlainTextStrings:(NSArray *)_fetchKeys {
/*
The fetched parts are NSData objects, this method converts them into
NSString objects based on the information inside the bodystructure.
The fetch-keys are body fetch-keys like: body[text] or body[1.2.3].
The keys in the result dictionary are "" for 'text' and 1.2.3 for parts.
*/
NSDictionary *datas;
if ((datas = [self fetchPlainTextParts:_fetchKeys]) == nil)
return nil;
if ([datas isKindOfClass:[NSException class]])
return datas;
return [self stringifyTextParts:datas];
}
/* flags */
- (NSException *)addFlags:(id)_flags {
return [[self imap4Connection] addFlags:_flags toURL:[self imap4URL]];
}
- (NSException *)removeFlags:(id)_flags {
return [[self imap4Connection] removeFlags:_flags toURL:[self imap4URL]];
}
/* permissions */
- (BOOL)isDeletionAllowed {
return [[self container] isDeleteAndExpungeAllowed];
}
/* name lookup */
- (id)lookupImap4BodyPartKey:(NSString *)_key inContext:(id)_ctx {
// TODO: we might want to check for existence prior controller creation
Class clazz;
clazz = [SOGoMailBodyPart bodyPartClassForKey:_key inContext:_ctx];
return [[[clazz alloc] initWithName:_key inContainer:self] autorelease];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
/* lookup body part */
if ([self isBodyPartKey:_key inContext:_ctx]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
if (debugSoParts)
[self logWithFormat:@"mail looked up part %@: %@", _key, obj];
return obj;
}
}
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"Did not find mail method or part-reference!"];
}
/* WebDAV */
- (BOOL)davIsCollection {
/* while a mail has child objects, it should appear as a file in WebDAV */
return NO;
}
- (id)davContentLength {
return [[self fetchCoreInfos] valueForKey:@"size"];
}
- (NSDate *)davCreationDate {
// TODO: use INTERNALDATE once NGImap4 supports that
return nil;
}
- (NSDate *)davLastModified {
return [self davCreationDate];
}
- (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
inContext:(id)_ctx
{
[self logWithFormat:@"TODO: should move mail as '%@' to: %@",
_name, _target];
return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
reason:@"not implemented"];
}
- (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
inContext:(id)_ctx
{
/*
Note: this is special because we create SOGoMailObject's even if they do
not exist (for performance reasons).
Also: we cannot really take a target resource, the ID will be assigned by
the IMAP4 server.
We even cannot return a 'location' header instead because IMAP4
doesn't tell us the new ID.
*/
NSURL *destImap4URL;
destImap4URL = ([_name length] == 0)
? [[_target container] imap4URL]
: [_target imap4URL];
return [[self mailManager] copyMailURL:[self imap4URL]
toFolderURL:destImap4URL
password:[self imap4Password]];
}
/* actions */
- (id)GETAction:(id)_ctx {
NSException *error;
WOResponse *r;
NSData *content;
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
/* check whether the mail still exists */
if (![self doesMailExist]) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"mail was deleted"];
}
return error; /* return 304 or 416 */
}
content = [self content];
if ([content isKindOfClass:[NSException class]])
return content;
if (content == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"did not find IMAP4 message"];
}
r = [(WOContext *)_ctx response];
[r setHeader:@"message/rfc822" forKey:@"content-type"];
[r setContent:content];
return r;
}
/* operations */
- (NSException *)trashInContext:(id)_ctx {
/*
Trashing is three actions:
a) copy to trash folder
b) mark mail as deleted
c) expunge folder
In case b) or c) fails, we can't do anything because IMAP4 doesn't tell us
the ID used in the trash folder.
*/
SOGoMailFolder *trashFolder;
NSException *error;
// TODO: check for safe HTTP method
trashFolder = [[self mailAccountFolder] trashFolderInContext:_ctx];
if ([trashFolder isKindOfClass:[NSException class]])
return (NSException *)trashFolder;
if (![trashFolder isNotNull]) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason:@"Did not find Trash folder!"];
}
[trashFolder flushMailCaches];
/* a) copy */
error = [self davCopyToTargetObject:trashFolder
newName:@"fakeNewUnusedByIMAP4" /* autoassigned */
inContext:_ctx];
if (error != nil) return error;
/* b) mark deleted */
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
if (error != nil) return error;
/* c) expunge */
error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]];
if (error != nil) return error; // TODO: unflag as deleted?
[self flushMailCaches];
return nil;
}
- (NSException *)delete {
/*
Note: delete is different to DELETEAction: for mails! The 'delete' runs
either flags a message as deleted or moves it to the Trash while
the DELETEAction: really deletes a message (by flagging it as
deleted _AND_ performing an expunge).
*/
// TODO: copy to Trash folder
NSException *error;
// TODO: check for safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
return error;
}
- (id)DELETEAction:(id)_ctx {
NSException *error;
// TODO: ensure safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
if (error != nil) return error;
error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]];
if (error != nil) return error; // TODO: unflag as deleted?
return [NSNumber numberWithBool:YES]; /* delete was successful */
}
/* some mail classification */
- (BOOL)isKolabObject {
NSDictionary *h;
if ((h = [self mailHeaders]) != nil)
return [[h objectForKey:@"x-kolab-type"] isNotEmpty];
// TODO: we could check the body structure?
return NO;
}
- (BOOL)isMailingListMail {
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
return [[h objectForKey:@"list-id"] isNotEmpty];
}
- (BOOL)isVirusScanned {
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
if (![[h objectForKey:@"x-virus-status"] isNotEmpty]) return NO;
if (![[h objectForKey:@"x-virus-scanned"] isNotEmpty]) return NO;
return YES;
}
- (NSString *)scanListHeaderValue:(id)_value
forFieldWithPrefix:(NSString *)_prefix
{
/* Note: not very tolerant on embedded commands and <> */
// TODO: does not really belong here, should be a header-field-parser
NSRange r;
if (![_value isNotEmpty])
return nil;
if ([_value isKindOfClass:[NSArray class]]) {
NSEnumerator *e;
id value;
e = [_value objectEnumerator];
while ((value = [e nextObject]) != nil) {
value = [self scanListHeaderValue:value forFieldWithPrefix:_prefix];
if (value != nil) return value;
}
return nil;
}
if (![_value isKindOfClass:[NSString class]])
return nil;
/* check for commas in string values */
r = [_value rangeOfString:@","];
if (r.length > 0) {
return [self scanListHeaderValue:[_value componentsSeparatedByString:@","]
forFieldWithPrefix:_prefix];
}
/* value qualifies */
if (![(NSString *)_value hasPrefix:_prefix])
return nil;
/* unquote */
if ([_value characterAtIndex:0] == '<') {
r = [_value rangeOfString:@">"];
_value = (r.length == 0)
? [_value substringFromIndex:1]
: [_value substringWithRange:NSMakeRange(1, r.location - 2)];
}
return _value;
}
- (NSString *)mailingListArchiveURL {
return [self scanListHeaderValue:
[[self mailHeaders] objectForKey:@"list-archive"]
forFieldWithPrefix:@"<http://"];
}
- (NSString *)mailingListSubscribeURL {
return [self scanListHeaderValue:
[[self mailHeaders] objectForKey:@"list-subscribe"]
forFieldWithPrefix:@"<http://"];
}
- (NSString *)mailingListUnsubscribeURL {
return [self scanListHeaderValue:
[[self mailHeaders] objectForKey:@"list-unsubscribe"]
forFieldWithPrefix:@"<http://"];
}
/* etag support */
- (id)davEntityTag {
/*
Note: There is one thing which *can* change for an existing message,
those are the IMAP4 flags (and annotations, which we do not use).
Since we don't render the flags, it should be OK, if this changes
we must embed the flagging into the etag.
*/
return mailETag;
}
- (int)zlGenerationCount {
return 0; /* mails never change */
}
/* Outlook mail tagging */
- (NSString *)outlookMessageClass {
NSString *type;
if ((type = [[self mailHeaders] objectForKey:@"x-kolab-type"]) != nil) {
if ([type isEqualToString:@"application/x-vnd.kolab.contact"])
return @"IPM.Contact";
if ([type isEqualToString:@"application/x-vnd.kolab.task"])
return @"IPM.Task";
if ([type isEqualToString:@"application/x-vnd.kolab.event"])
return @"IPM.Appointment";
if ([type isEqualToString:@"application/x-vnd.kolab.note"])
return @"IPM.Note";
if ([type isEqualToString:@"application/x-vnd.kolab.journal"])
return @"IPM.Journal";
}
return @"IPM.Message"; /* email, default class */
}
/* debugging */
- (BOOL)isDebuggingEnabled {
return debugOn;
}
@end /* SOGoMailObject */
@@ -0,0 +1,55 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include <Mailer/SOGoMailBodyPart.h>
/*
SOGoMessageMailBodyPart
A specialized SOGoMailBodyPart subclass for message attachments. Can be used
to attach special SoMethods.
This object should allow the user to peek inside the message contents.
See the superclass for more information on part objects.
*/
@interface SOGoMessageMailBodyPart : SOGoMailBodyPart
{
}
@end
#include "common.h"
@implementation SOGoMessageMailBodyPart
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
@end /* SOGoMessageMailBodyPart */
+44
View File
@@ -0,0 +1,44 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoSharedInboxFolder_H__
#define __Mailer_SOGoSharedInboxFolder_H__
#include <SoObjects/Mailer/SOGoMailFolder.h>
/*
SOGoSharedInboxFolder
Parent object: the SOGoSharedMailAccount
Child objects: SOGoMailObject or SOGoMailFolder
The SOGoSharedInboxFolder is a special SOGoMailFolder for use as the INBOX
og SOGoSharedMailAccounts. This is necessary because the INBOX location in
the IMAP4 server is different for shared boxes (its the shared user folder,
not a mapped subfolder).
*/
@interface SOGoSharedInboxFolder : SOGoMailFolder
{
}
@end
#endif /* __Mailer_SOGoSharedInboxFolder_H__ */
+53
View File
@@ -0,0 +1,53 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoSharedInboxFolder.h"
#include "common.h"
@implementation SOGoSharedInboxFolder
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
/* listing available folders */
- (NSArray *)toManyRelationshipKeys {
/*
A shared INBOX has no subfolders because those are already shown as
subfolders of the account.
*/
return nil;
}
/* IMAP4 locator */
- (NSURL *)imap4URL {
return [[self container] imap4URL];
}
@end /* SOGoSharedInboxFolder */
+56
View File
@@ -0,0 +1,56 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoSharedMailAccount_H__
#define __Mailer_SOGoSharedMailAccount_H__
#include <SoObjects/Mailer/SOGoMailAccount.h>
/*
SOGoSharedMailAccount
Parent object: SOGoMailAccounts
Child objects: SOGoMailFolder
The SOGoSharedMailAccount represents an IMAP4 mail account which is shared
by multiple users using the ".-." login trick.
Eg:
beatrix.b.-.evariste.e@amelie-01.ac.melanie2.i2
The mailbox of 'evariste.e' will be accessed using as user 'beatrix.b'. The
Cyrus server will deliver a special kind of mailbox hierarchy in this case.
An advantage is that either Cyrus or LDAP seems to know about the ".-."
separator.
*/
@class NSString;
@interface SOGoSharedMailAccount : SOGoMailAccount
{
}
- (BOOL)isSharedAccount;
- (NSString *)sharedAccountName;
@end
#endif /* __Mailer_SOGoSharedMailAccount_H__ */
+141
View File
@@ -0,0 +1,141 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoSharedMailAccount.h"
#include "common.h"
@interface SOGoMailAccount(UsedPrivates)
- (NSString *)imap4URLString;
- (id)lookupFolder:(NSString *)_key ofClassNamed:(NSString *)_cn
inContext:(id)_cx;
@end
@implementation SOGoSharedMailAccount
static NSString *otherUsersFolderName = @""; // TODO: add English default
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
otherUsersFolderName = [ud stringForKey:@"SOGoOtherUsersFolderName"];
NSLog(@"Note: using other-users-folders name: '%@'", otherUsersFolderName);
}
/* shared accounts */
- (BOOL)isSharedAccount {
return YES;
}
- (NSString *)sharedAccountName {
NSString *s;
NSRange r;
s = [self nameInContainer];
r = [s rangeOfString:@"@"];
if (r.length == 0) /* regular HTTP logins are never a shared mailbox */
return nil;
s = [s substringToIndex:r.location];
r = [s rangeOfString:@".-."];
if (r.length == 0) /* no shared mailbox marker */
return nil;
return [s substringFromIndex:(r.location + r.length)];
}
/* listing the available folders */
- (NSArray *)additionalRootFolderNames {
/* do not show Drafts folder in shared mailboxes (#1452) */
/* Note: this also disables the Sieve folder */
return nil;
}
- (NSArray *)toManyRelationshipKeys {
NSMutableArray *m;
NSArray *b;
m = [NSMutableArray arrayWithCapacity:16];
[m addObject:@"INBOX"]; /* special for shared, see -lookupInboxFolder:.. */
if ((b = [[self imap4Connection] subfoldersForURL:[self imap4URL]]) != nil)
[m addObjectsFromArray:b];
if ((b = [self additionalRootFolderNames]) != nil)
[m addObjectsFromArray:b];
return m;
}
/* IMAP4 locator */
- (NSString *)imap4URLString {
/* we jump in the hierarchy directly to the shared user folder */
NSString *s;
s = [super imap4URLString];
s = [s stringByAppendingString:otherUsersFolderName];
s = [s stringByAppendingString:@"/"];
s = [s stringByAppendingString:[self sharedAccountName]];
return s;
}
/* lookup */
- (id)lookupInboxFolder:(NSString *)_key inContext:(id)_ctx {
/*
Note: We have a choice: Cyrus does both, it gives the user an own
INBOX folder, but it also provides access to the mailbox INBOX
folder.
However the mailbox INBOX folder is NOT returned as an "INBOX"
subcollection as usual, but is the primary mailbox name.
Eg:
2 list "*" "*"
* LIST (\Noinferiors) "/" "INBOX"
* LIST (\HasChildren) "/" "Boite partag&AOk-e/alienor.a"
The first INBOX is auto-assigned for the user by Cyrus, the second entry
is actually the INBOX of the mailbox itself (alienor.a).
*/
return [self lookupFolder:_key ofClassNamed:@"SOGoSharedInboxFolder"
inContext:_ctx];
}
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
if ([_key isEqualToString:@"INBOX"])
return [self lookupInboxFolder:_key inContext:_ctx];
return [super lookupName:_key inContext:_ctx acquire:_flag];
}
@end /* SOGoSharedMailAccount */
+30
View File
@@ -0,0 +1,30 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoTrashFolder_H__
#define __Mailer_SOGoTrashFolder_H__
#include <SoObjects/Mailer/SOGoMailFolder.h>
@interface SOGoTrashFolder : SOGoMailFolder
@end
#endif /* __Mailer_SOGoTrashFolder_H__ */
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoTrashFolder.h"
#include "common.h"
@implementation SOGoTrashFolder
/* folder type */
- (NSString *)outlookFolderClass {
return @"IPF.Trash";
}
@end /* SOGoTrashFolder */
+48
View File
@@ -0,0 +1,48 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoUser_Mail_H__
#define __Mailer_SOGoUser_Mail_H__
#include <SoObjects/Mailer/SOGoMailBaseObject.h>
/*
SOGoUser(Mail)
TODO: document
This category adds mail related stuff to the SOGo user class.
*/
#include <SOGo/SOGoUser.h>
@class NSArray;
@class SOGoMailIdentity;
@interface SOGoUser(Mail)
- (SOGoMailIdentity *)primaryMailIdentity;
- (NSArray *)fetchAllMailIdentitiesWithOnlyEmitterAccess:(BOOL)_onlyGC;
- (SOGoMailIdentity *)primaryMailIdentityForAccount:(NSString *)_account;
@end
#endif /* __Mailer_SOGoUser_Mail_H__ */
+139
View File
@@ -0,0 +1,139 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "SOGoUser+Mail.h"
#include "SOGoMailIdentity.h"
#include "common.h"
@implementation SOGoUser(Mail)
- (NSString *)agenorSentFolderName {
/* Note: specialty: the Sent folder is called the same in all accounts */
static NSString *s = nil;
if (s == nil) {
NSUserDefaults *ud;
ud = [NSUserDefaults standardUserDefaults];
s = [[ud stringForKey:@"SOGoSentFolderName"] copy];
if (![s isNotEmpty]) s = @"Sent";
[self logWithFormat:@"Note: using SOGoSentFolderName: '%@'", s];
}
return s;
}
- (NSString *)agenorSentFolderForAccount:(NSString *)_account {
// TODO: support different locations for shares!
NSString *p;
if (![_account isNotEmpty])
return nil;
// if ([_account rangeOfString:@".-."].length == 0)
// TODO: check whether we need special handling for shares!
p = [_account stringByAppendingString:@"/"];
p = [p stringByAppendingString:[self agenorSentFolderName]];
return p;
}
- (SOGoMailIdentity *)primaryMailIdentity {
SOGoMailIdentity *identity;
NSString *account;
account = [self valueForKey:@"primaryIMAP4AccountString"];
identity = [[[SOGoMailIdentity alloc] init] autorelease];
[identity setName:[self cn]];
[identity setEmail:[self email]];
[identity setSentFolderName:[self agenorSentFolderForAccount:account]];
return identity;
}
- (SOGoMailIdentity *)mailIdentityForAccount:(NSString *)_account
emitter:(NSString *)_em
{
SOGoMailIdentity *identity;
identity = [[[SOGoMailIdentity alloc] init] autorelease];
[identity setName:[self cn]]; // TODO: should we use something else?
if ([_em isNotEmpty]) [identity setEmail:_em];
[identity setSentFolderName:[self agenorSentFolderForAccount:_account]];
return identity;
}
- (NSArray *)fetchAllMailIdentitiesWithOnlyEmitterAccess:(BOOL)_onlyGC {
NSMutableArray *identities;
NSEnumerator *accounts;
NSDictionary *shares;
NSString *account;
id identity;
identity = [self primaryMailIdentity];
shares = [self valueForKey:@"additionalIMAP4AccountsAndEMails"];
if ([shares count] == 0)
return identity;
identities = [NSMutableArray arrayWithCapacity:[shares count] + 1];
if (identity != nil) [identities addObject:identity];
accounts = [shares keyEnumerator];
while ((account = [accounts nextObject]) != nil) {
NSString *emitter;
emitter = [shares objectForKey:account];
if (_onlyGC && ![emitter isNotNull]) continue;
identity = [self mailIdentityForAccount:account emitter:emitter];
if (identity != nil)
[identities addObject:identity];
}
return identities;
}
- (SOGoMailIdentity *)primaryMailIdentityForAccount:(NSString *)_account {
NSEnumerator *accounts;
NSDictionary *shares;
NSString *account;
id identity;
identity = [self primaryMailIdentity];
shares = [self valueForKey:@"additionalIMAP4AccountsAndEMails"];
if ([shares count] == 0)
return identity;
/* scan shares for ID */
accounts = [shares keyEnumerator];
while ((account = [accounts nextObject]) != nil) {
NSString *emitter;
if (![account isEqualToString:_account])
continue;
emitter = [shares objectForKey:account];
identity = [self mailIdentityForAccount:_account emitter:emitter];
if ([identity isNotNull])
return identity;
}
return identity;
}
@end /* SOGoUser(Mail) */
+53
View File
@@ -0,0 +1,53 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include <Mailer/SOGoMailBodyPart.h>
/*
SOGoVCardMailBodyPart
A specialized SOGoMailBodyPart subclass for text/x-vcard attachments. Can
be used to attach special SoMethods.
See the superclass for more information on part objects.
*/
@interface SOGoVCardMailBodyPart : SOGoMailBodyPart
{
}
@end
#include "common.h"
@implementation SOGoVCardMailBodyPart
+ (int)version {
return [super version] + 0 /* v1 */;
}
+ (void)initialize {
NSAssert2([super version] == 1,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
}
@end /* SOGoVCardMailBodyPart */
+15
View File
@@ -0,0 +1,15 @@
# Version file
SUBMINOR_VERSION:=126
# v0.9.114 requires libNGMime v4.5.229
# v0.9.114 requires libNGExtensions v4.5.165
# v0.9.111 requires libNGExtensions v4.5.163
# v0.9.96 requires libNGMime v4.5.223
# v0.9.91 requires libNGMime v4.5.222
# v0.9.69 requires libNGMime v4.5.210
# v0.9.55 requires libNGExtensions v4.5.136
# v0.9.44 requires libNGMime v4.3.194
# v0.9.41 requires libNGMime v4.3.190
# v0.9.35 requires libSOGoLogic v0.9.24
# v0.9.34 requires libSOGoLogic v0.9.22
+36
View File
@@ -0,0 +1,36 @@
/*
Copyright (C) 2002-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#import <Foundation/NSObjCRuntime.h>
#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
# include <NGExtensions/NGObjectMacros.h>
# include <NGExtensions/NSString+Ext.h>
#endif
#include <NGExtensions/NGExtensions.h>
#include <NGObjWeb/NGObjWeb.h>
#include <NGObjWeb/SoObjects.h>
#include <NGObjWeb/SoObject+SoDAV.h>
#include <NGImap4/NGImap4Connection.h>
+70
View File
@@ -0,0 +1,70 @@
{
requires = ( MAIN );
publicResources = (
);
factories = {
};
classes = {
SOGoMailBaseObject = {
superclass = "SOGoObject";
};
SOGoMailAccounts = {
superclass = "SOGoMailBaseObject";
};
SOGoMailAccount = {
superclass = "SOGoMailBaseObject";
defaultRoles = {
"Add Folders" = "Owner";
};
};
SOGoSharedMailAccount = {
superclass = "SOGoMailAccount";
};
SOGoMailFolder = {
superclass = "SOGoMailBaseObject";
defaultRoles = {
"Add Folders" = "Owner";
};
};
SOGoSharedInboxFolder = {
superclass = "SOGoMailFolder";
};
SOGoTrashFolder = {
superclass = "SOGoMailFolder";
};
SOGoMailObject = {
superclass = "SOGoMailBaseObject";
};
SOGoMailBodyPart = {
superclass = "SOGoMailBaseObject";
};
SOGoImageMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoMessageMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoCalendarMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoVCardMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoDraftsFolder = {
superclass = "SOGoMailBaseObject";
};
SOGoDraftObject = {
superclass = "SOGoMailBaseObject";
};
};
}
+9
View File
@@ -0,0 +1,9 @@
# $Id$
SoObjects
=========
The SoObjects directory contains SOPE "path controller" objects, those objects
which are bound to the URL traversal path.
Also contained is a common library for such objects (libSOGo).
+84
View File
@@ -0,0 +1,84 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __AgenorUserDefaults_H_
#define __AgenorUserDefaults_H_
#import <Foundation/NSObject.h>
/*
AgenorUserDefaults
An object with the same API like NSUserDefaults which retrieves profile
information for users in the database.
*/
@class NSString, NSURL, NSUserDefaults, NSArray, NSDictionary, NSData;
@class NSCalendarDate, NSMutableDictionary;
@interface AgenorUserDefaults : NSObject
{
NSUserDefaults *parent;
NSURL *url;
NSString *uid;
NSArray *fieldNames;
NSDictionary *attributes;
NSMutableDictionary *values;
NSCalendarDate *lastFetch;
struct {
int modified:1;
int isNew:1;
int reserved:30;
} defFlags;
}
- (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid;
/* value access */
- (void)setObject:(id)_value forKey:(NSString *)_key;
- (id)objectForKey:(NSString *)_key;
- (void)removeObjectForKey:(NSString *)_key;
/* typed accessors */
- (NSArray *)arrayForKey:(NSString *)_key;
- (NSDictionary *)dictionaryForKey:(NSString *)_key;
- (NSData *)dataForKey:(NSString *)_key;
- (NSArray *)stringArrayForKey:(NSString *)_key;
- (NSString *)stringForKey:(NSString *)_key;
- (BOOL)boolForKey:(NSString *)_key;
- (float)floatForKey:(NSString *)_key;
- (int)integerForKey:(NSString *)_key;
- (void)setBool:(BOOL)value forKey:(NSString *)_key;
- (void)setFloat:(float)value forKey:(NSString *)_key;
- (void)setInteger:(int)value forKey:(NSString *)_key;
/* saving changes */
- (BOOL)synchronize;
@end
#endif /* __AgenorUserDefaults_H_ */
+472
View File
@@ -0,0 +1,472 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "AgenorUserDefaults.h"
#include <GDLContentStore/GCSChannelManager.h>
#include <GDLContentStore/NSURL+GCS.h>
#include <GDLAccess/EOAdaptorChannel.h>
#include <GDLAccess/EOAdaptorContext.h>
#include <GDLAccess/EOAttribute.h>
#include "common.h"
@implementation AgenorUserDefaults
static NSString *uidColumnName = @"uid";
- (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid {
if ((self = [super init])) {
if (_url == nil || [_uid length] < 1) {
[self errorWithFormat:@"tried to create AgenorUserDefaults w/o args!"];
[self release];
return nil;
}
self->parent = [[NSUserDefaults standardUserDefaults] retain];
self->url = [_url copy];
self->uid = [_uid copy];
}
return self;
}
- (id)init {
return [self initWithTableURL:nil uid:nil];
}
- (void)dealloc {
[self->attributes release];
[self->lastFetch release];
[self->parent release];
[self->url release];
[self->uid release];
[super dealloc];
}
/* accessors */
- (NSURL *)tableURL {
return self->url;
}
- (NSString *)uid {
return self->uid;
}
- (NSUserDefaults *)parentDefaults {
return self->parent;
}
/* operation */
- (void)_loadAttributes:(NSArray *)_attrs {
NSMutableArray *fields;
NSMutableDictionary *attrmap;
unsigned i, count;
fields = [[NSMutableArray alloc] initWithCapacity:16];
attrmap = [[NSMutableDictionary alloc] initWithCapacity:16];
for (i = 0, count = [_attrs count]; i < count; i++) {
EOAttribute *attr;
NSString *name;
attr = [_attrs objectAtIndex:i];
name = [attr valueForKey:@"name"];
[attrmap setObject:attr forKey:name];
if (![name isEqual:uidColumnName])
[fields addObject:name];
}
ASSIGNCOPY(self->fieldNames, fields);
ASSIGNCOPY(self->attributes, attrmap);
[attrmap release];
[fields release];
}
- (BOOL)primaryFetchProfile {
GCSChannelManager *cm;
EOAdaptorChannel *channel;
NSDictionary *row;
NSException *ex;
NSString *sql;
NSArray *attrs;
cm = [GCSChannelManager defaultChannelManager];
if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[self tableURL]];
return NO;
}
/* generate SQL */
sql = [[self tableURL] gcsTableName];
sql = [@"SELECT * FROM " stringByAppendingString:sql];
sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'",
uidColumnName, [self uid]];
/* run SQL */
if ((ex = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
[cm releaseChannel:channel];
return NO;
}
/* fetch schema */
attrs = [channel describeResults:NO /* don't beautify */];
[self _loadAttributes:attrs];
/* fetch values */
row = [channel fetchAttributes:attrs withZone:NULL];
self->defFlags.isNew = (row != nil) ? 0 : 1;
[channel cancelFetch];
/* remember values */
[self->values release]; self->values = nil;
self->values = (row != nil)
? [row mutableCopy]
: [[NSMutableDictionary alloc] initWithCapacity:8];
[self->values removeObjectForKey:uidColumnName];
ASSIGN(self->lastFetch, [NSCalendarDate date]);
self->defFlags.modified = 0;
[cm releaseChannel:channel];
return YES;
}
- (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute {
NSString *s;
if (![_value isNotNull])
return @"NULL";
if ([[_attribute externalType] hasPrefix:@"int"])
return [_value stringValue];
s = [_value stringValue];
s = [s stringByReplacingString:@"'" withString:@"''"];
s = [[@"'" stringByAppendingString:s] stringByAppendingString:@"'"];
return s;
}
- (NSString *)generateSQLForInsert {
NSMutableString *sql;
unsigned i, count;
if ([self->values count] == 0)
return nil;
sql = [NSMutableString stringWithCapacity:2048];
[sql appendString:@"INSERT INTO "];
[sql appendString:[[self tableURL] gcsTableName]];
[sql appendString:@" ( uid"];
for (i = 0, count = [self->fieldNames count]; i < count; i++) {
EOAttribute *attr;
attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
[sql appendString:@", "];
[sql appendString:[attr columnName]];
}
[sql appendString:@") VALUES ("];
[sql appendString:@"'"];
[sql appendString:[self uid]]; // TODO: escaping necessary?
[sql appendString:@"'"];
for (i = 0, count = [self->fieldNames count]; i < count; i++) {
EOAttribute *attr;
id value;
attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
value = [self->values objectForKey:[self->fieldNames objectAtIndex:i]];
[sql appendString:@", "];
[sql appendString:[self formatValue:value forAttribute:attr]];
}
[sql appendString:@")"];
return sql;
}
- (NSString *)generateSQLForUpdate {
NSMutableString *sql;
unsigned i, count;
if ([self->values count] == 0)
return nil;
sql = [NSMutableString stringWithCapacity:2048];
[sql appendString:@"UPDATE "];
[sql appendString:[[self tableURL] gcsTableName]];
[sql appendString:@" SET "];
for (i = 0, count = [self->fieldNames count]; i < count; i++) {
EOAttribute *attr;
NSString *name;
id value;
name = [self->fieldNames objectAtIndex:i];
value = [self->values objectForKey:name];
attr = [self->attributes objectForKey:name];
if (i != 0) [sql appendString:@", "];
[sql appendString:[attr columnName]];
[sql appendString:@" = "];
[sql appendString:[self formatValue:value forAttribute:attr]];
}
[sql appendString:@" WHERE "];
[sql appendString:uidColumnName];
[sql appendString:@" = '"];
[sql appendString:[self uid]];
[sql appendString:@"'"];
return sql;
}
- (BOOL)primaryStoreProfile {
GCSChannelManager *cm;
EOAdaptorChannel *channel;
NSException *ex;
NSString *sql;
cm = [GCSChannelManager defaultChannelManager];
if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[self tableURL]];
return NO;
}
/* run SQL */
sql = self->defFlags.isNew
? [self generateSQLForInsert]
: [self generateSQLForUpdate];
if ((ex = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
[cm releaseChannel:channel];
return NO;
}
/* commit */
ex = nil;
if ([[channel adaptorContext] hasOpenTransaction])
ex = [channel evaluateExpressionX:@"COMMIT TRANSACTION"];
[cm releaseChannel:channel];
if (ex != nil) {
[self errorWithFormat:@"could not commit transaction for update: %@", ex];
return NO;
}
self->defFlags.modified = 0;
self->defFlags.isNew = 0;
return YES;
}
- (BOOL)fetchProfile {
if (self->values != nil)
return YES;
return [self primaryFetchProfile];
}
- (NSArray *)primaryDefaultNames {
if (![self fetchProfile])
return nil;
return self->fieldNames;
}
/* value access */
- (void)setObject:(id)_value forKey:(NSString *)_key {
if (![self fetchProfile])
return;
if (![self->fieldNames containsObject:_key]) {
[self errorWithFormat:@"tried to write key: '%@'", _key];
return;
}
/* check whether the value is actually modified */
if (!self->defFlags.modified) {
id old;
old = [self->values objectForKey:_key];
if (old == _value || [old isEqual:_value]) /* value didn't change */
return;
/* we need to this because our typed accessors convert to strings */
// TODO: especially problematic with bools
if ([_value isKindOfClass:[NSString class]]) {
if (![old isKindOfClass:[NSString class]])
if ([[old description] isEqualToString:_value])
return;
}
}
/* set in hash and mark as modified */
[self->values setObject:(_value ? _value : [NSNull null]) forKey:_key];
self->defFlags.modified = 1;
}
- (id)objectForKey:(NSString *)_key {
id value;
if (![self fetchProfile])
return nil;
if (![self->fieldNames containsObject:_key])
return [self->parent objectForKey:_key];
value = [self->values objectForKey:_key];
return [value isNotNull] ? value : nil;
}
- (void)removeObjectForKey:(NSString *)_key {
[self setObject:nil forKey:_key];
}
/* saving changes */
- (BOOL)synchronize {
if (!self->defFlags.modified) /* was not modified */
return YES;
/* ensure fetched data (more or less guaranteed by modified!=0) */
if (![self fetchProfile])
return NO;
/* store */
if (![self primaryStoreProfile]) {
[self primaryFetchProfile];
return NO;
}
/* refetch */
return [self primaryFetchProfile];
}
- (void)flush {
[self->values release]; self->values = nil;
[self->fieldNames release]; self->fieldNames = nil;
[self->attributes release]; self->attributes = nil;
[self->lastFetch release]; self->lastFetch = nil;
self->defFlags.modified = 0;
self->defFlags.isNew = 0;
}
/* typed accessors */
- (NSArray *)arrayForKey:(NSString *)_key {
id obj = [self objectForKey:_key];
return [obj isKindOfClass:[NSArray class]] ? obj : nil;
}
- (NSDictionary *)dictionaryForKey:(NSString *)_key {
id obj = [self objectForKey:_key];
return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
}
- (NSData *)dataForKey:(NSString *)_key {
id obj = [self objectForKey:_key];
return [obj isKindOfClass:[NSData class]] ? obj : nil;
}
- (NSArray *)stringArrayForKey:(NSString *)_key {
id obj = [self objectForKey:_key];
int n;
Class strClass = [NSString class];
if (![obj isKindOfClass:[NSArray class]])
return nil;
for (n = [obj count]-1; n >= 0; n--) {
if (![[obj objectAtIndex:n] isKindOfClass:strClass])
return nil;
}
return obj;
}
- (NSString *)stringForKey:(NSString *)_key {
id obj = [self objectForKey:_key];
return [obj isKindOfClass:[NSString class]] ? obj : nil;
}
- (BOOL)boolForKey:(NSString *)_key {
// TODO: need special support here for int-DB fields
id obj;
if ((obj = [self objectForKey:_key]) == nil)
return NO;
if ([obj isKindOfClass:[NSString class]]) {
if ([obj compare:@"YES" options:NSCaseInsensitiveSearch] == NSOrderedSame)
return YES;
}
if ([obj respondsToSelector:@selector(intValue)])
return [obj intValue] ? YES : NO;
return NO;
}
- (float)floatForKey:(NSString *)_key {
id obj = [self stringForKey:_key];
return (obj != nil) ? [obj floatValue] : 0.0;
}
- (int)integerForKey:(NSString *)_key {
id obj = [self stringForKey:_key];
return (obj != nil) ? [obj intValue] : 0;
}
- (void)setBool:(BOOL)value forKey:(NSString *)_key {
// TODO: need special support here for int-DB fields
[self setObject:(value ? @"YES" : @"NO") forKey:_key];
}
- (void)setFloat:(float)value forKey:(NSString *)_key {
[self setObject:[NSString stringWithFormat:@"%f", value] forKey:_key];
}
- (void)setInteger:(int)value forKey:(NSString *)_key {
[self setObject:[NSString stringWithFormat:@"%d", value] forKey:_key];
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:16];
[ms appendFormat:@"<0x%08X[%@]>", self, NSStringFromClass([self class])];
[ms appendFormat:@" uid=%@", self->uid];
[ms appendFormat:@" url=%@", [self->url absoluteString]];
[ms appendFormat:@" parent=0x%08X", self->parent];
[ms appendString:@">"];
return ms;
}
@end /* AgenorUserDefaults */
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __AgenorUserManager_H_
#define __AgenorUserManager_H_
#import <Foundation/NSObject.h>
/*
AgenorUserManager
TODO: document
*/
@class NSString, NSArray, NSURL, NSUserDefaults, NSMutableDictionary, NSTimer;
@class NSDictionary;
@class iCalPerson;
@interface AgenorUserManager : NSObject
{
NSMutableDictionary *cnCache;
NSMutableDictionary *serverCache;
NSMutableDictionary *uidCache;
NSMutableDictionary *emailCache;
NSMutableDictionary *shareStoreCache;
NSMutableDictionary *shareEMailCache;
NSMutableDictionary *changeInternetAccessCache;
NSMutableDictionary *internetAutoresponderFlagCache;
NSMutableDictionary *intranetAutoresponderFlagCache;
NSTimer *gcTimer;
}
+ (id)sharedUserManager;
- (NSString *)getUIDForEmail:(NSString *)_email;
- (NSString *)getEmailForUID:(NSString *)_uid;
- (NSString *)getUIDForICalPerson:(iCalPerson *)_person;
/* may insert NSNulls into returned array if _mapStrictly -> YES */
- (NSArray *)getUIDsForICalPersons:(NSArray *)_persons
applyStrictMapping:(BOOL)_mapStrictly;
/* i.e. BUTTO Hercule, CETE Lyon/DI/ET/TEST */
- (NSString *)getCNForUID:(NSString *)_uid;
/* i.e. hercule.butto@amelie-ida01.melanie2.i2 */
- (NSString *)getIMAPAccountStringForUID:(NSString *)_uid;
/* i.e. amelie-ida01.melanie2.i2 */
- (NSString *)getServerForUID:(NSString *)_uid;
- (NSArray *)getSharedMailboxAccountStringsForUID:(NSString *)_uid;
- (NSArray *)getSharedMailboxEMailsForUID:(NSString *)_uid;
- (NSDictionary *)getSharedMailboxesAndEMailsForUID:(NSString *)_uid;
- (NSURL *)getFreeBusyURLForUID:(NSString *)_uid;
- (NSUserDefaults *)getUserDefaultsForUID:(NSString *)_uid;
- (BOOL)isUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid;
- (BOOL)isInternetAutoresponderEnabledForUser:(NSString *)_uid;
- (BOOL)isIntranetAutoresponderEnabledForUser:(NSString *)_uid;
@end
#endif /* __AgenorUserManager_H_ */
File diff suppressed because it is too large Load Diff
+374
View File
@@ -0,0 +1,374 @@
2005-08-01 Helge Hess <helge.hess@skyrix.com>
* added FHS installation (v0.9.70)
* SOGoObject.m: added class security declarations (previously in
product.plist of MainUI) (v0.9.69)
2005-07-21 Helge Hess <helge.hess@opengroupware.org>
* SOGoUser.m: added a method to fetch share/emitter address pairs from
the AgenorUserManager (v0.9.68)
* AgenorUserManager.m: rewrote to use NSMutableDictionary instead of
SOGoLRUCache. Flush caches every hour (configurable using
'AgenorCacheCheckInterval' default). Added method to retrieve the
shares and the emitter emails in one step. (v0.9.67)
2005-07-20 Helge Hess <helge.hess@opengroupware.org>
* v0.9.66
* SOGoContentObject.m: properly quote etag
* SOGoObject.m: changed to check etag when the content is accessed in
WebDAV mode and return a 304 if the tag stayed the same.
Do not strip quotes from etags in if-*match headers.
Properly implement if-none-match for GET requests.
* SOGoContentObject.m: moved generic etag checking to SOGoObject (to
share implementation with Mailer) (v0.9.65)
2005-07-19 Marcus Mueller <znek@mulle-kybernetik.com>
* AgenorUserManager.m: properly implemented internet/intranet vacation
message status detection (v0.9.64)
2005-07-14 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.63
* SOGoAppointment.[hm]: new API to cancel appointments, NSCopying
support
2005-07-15 Helge Hess <helge.hess@opengroupware.org>
* v0.9.62
* SOGoGroupFolder.m, SOGoUser.m: fixed a gcc 4.0 warning
* GNUmakefile.preamble: properly use SYSTEM_LIB_DIR
* v0.9.61
* SOGoContentObject.m: fixed a Cocoa warning
* GNUmakefile.preamble: added proper linking flags for OSX
2005-07-14 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.60
* SOGoAppointment.[hm]: added 'method' and 'userComment' accessors
* SOGoAppointmentICalRenderer.m: properly render 'method' of
appointment if it's set - otherwise assume 'REQUEST'
2005-07-14 Helge Hess <helge.hess@opengroupware.org>
* SOGoUser.m: added -homeFolderInContext: and
-schedulingCalendarInContext: methods (v0.9.59)
* moved in SOGoUser and SOGoAuthenticator from Main (v0.9.58)
2005-07-14 Marcus Mueller <znek@mulle-kybernetik.com>
* AgenorUserManager.[hm]: added accessors and cache for the
'mineqMelReponse' flag. Please note that the implementation isn't
fully fleshed out because the technical specification isn't correct.
(v0.9.57)
* AgenorUserManager.[hm]: added accessors and cache for the
'mineqOgoAccesInternet' flag (v0.9.56)
2005-07-14 Helge Hess <helge.hess@opengroupware.org>
* SOGoContentObject.m: added empty davCopy/davMove methods (v0.9.55)
* v0.9.54
* SOGoContentObject.m: added support for special 'new' key (server will
assign a name and add the new location in a special response header)
* SOGoFolder.m: added +globallyUniqueObjectId (previously the method
was duplicated in each subclass)
* SOGoContentObject.m: added transactionally save etag-checks in PUT
(use the etag value as the baseVersion in the content store)
(v0.9.53)
2005-07-13 Helge Hess <helge.hess@opengroupware.org>
* v0.9.52
* SOGoObject.m: properly add etag during a GET (if available)
* SOGoContentObject.m: generate etag from content object version, added
methods to check request preconditions, check preconditions prior
running a PUT, added new etag after running a PUT
* SOGoObject.m, SOGoFolder.m: added +version methods to detect fragile
base class issues (v0.9.51)
* SOGoFolder.m: changed to use plain column names (v0.9.50)
2005-07-12 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoUserFolder.m: forbid access (403) to SOGoGroupFolders if access
is not from the Intranet (v0.9.49)
2005-07-12 Helge Hess <helge.hess@opengroupware.org>
* AgenorUserDefaults.m: added automagic profile row creation (v0.9.48)
* AgenorUserDefaults.m: implemented saving of changed profiles
(v0.9.47)
2005-07-12 Marcus Mueller <znek@mulle-kybernetik.com>
* AgenorUserManager.[hm]: new API for extracting UIDs from iCalPersons
(v0.9.46)
2005-07-12 Helge Hess <helge.hess@opengroupware.org>
* v0.9.45
* AgenorUserDefaults.m: properly fetch profile contents
* agenor_defaults.m: read operation can now return all defined keys
* v0.9.44
* AgenorUserDefaults.m: added typed value accessors and proper
initializers
* AgenorUserManager.m: added 'AgenorProfileURL' default to configure
the database location of the user profile table
* finished agenor_defaults tool
2005-07-12 Helge Hess <helge.hess@opengroupware.org>
* v0.9.43
* added agenor_defaults tool to test defaults functionality
* AgenorUserManager.[hm]: added -getUserDefaultsForUID: method to
retrieve the profile of a user (incomplete)
* added AgenorUserDefaults class (incomplete) as a wrapper for the
profile data of Agenor users
2005-07-08 Helge Hess <helge.hess@opengroupware.org>
* v0.9.42
* added agenor_emails4uid tool to check whether the
uid=>allowed-from-mails discovery in AgenorUserManager works
* AgenorUserManager.m: added -getSharedMailboxEMailsForUID: method to
discover the shared emails the user is allowed to post from,
added caching of shared emails and Cyrus-logins
* WOContext+Agenor.m: use SOGoInternetDetectQualifier for detecting
Internet access level (v0.9.41)
2005-07-08 Marcus Mueller <znek@mulle-kybernetik.com>
* WOContext+Agenor.[hm]: new category for discovering if the current
context is via access from the intranet. (v0.9.40)
2005-07-07 Helge Hess <helge.hess@opengroupware.org>
* added agenor_shares4uid tool to check whether the uid=>shared mailbox
discovery in AgenorUserManager works
* AgenorUserManager.m: fixed a major string scanning bug in
_serverCandidatesForMineqMelRoutage: method,
added -getSharedMailboxAccountStringsForUID: method to discover
shared IMAP4 accounts (v0.9.39)
2005-07-07 Helge Hess <helge.hess@opengroupware.org>
* AgenorUserManager.m: added a simple -description method (v0.9.38)
* added agenor_email2uid.m tool to check whether the email=>uid mapping
in AgenorUserManager works
2005-07-06 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.37
* SOGoUserFolder.m: added 'freebusy.ifb' as an object to the
collection for proper display via DAV.
* SOGoAppointmentICalRenderer.m: fixed header inclusion
* AgenorUserManager.[hm]: added proposed future API for discovering
URLs for free/busy information (implementation currently returns
nil)
2005-07-05 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointment.m: fixed a wrong -release (v0.9.36)
2005-03-25 Helge Hess <helge.hess@opengroupware.org>
* SOGoObject.m: fixed DELETEAction to return a boolean if the delete
was successful (required by SoObjectRequestHandler) (v0.9.35)
2005-03-20 Helge Hess <helge.hess@opengroupware.org>
* changed to use GDLContentStore (v0.9.34)
2005-03-03 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoGroupFolder.m: switched logging to NGLogging (v0.9.33)
2005-03-02 Marcus Mueller <znek@mulle-kybernetik.com>
* NSObject+AptComparison.[hm]: new comparison method. This is used
in SOGoAppointmentFolder currently. (v0.9.32)
2005-02-20 Helge Hess <helge.hess@opengroupware.org>
* AgenorUserManager.m: refactoring of the LDAP fetch code, added the
'SOGoFallbackIMAP4Server' default to configure the IMAP4 server when
LDAP is disabled (v0.9.31)
2005-02-17 Helge Hess <helge.hess@opengroupware.org>
* moved in code from libSOGoLogic (unnecessarily a separate library)
(v0.9.30)
2005-02-10 Helge Hess <helge.hess@opengroupware.org>
* SOGoObject.m: fixed a warning on MacOSX (v0.9.29)
2005-02-07 Helge Hess <helge.hess@opengroupware.org>
* SOGoObject.h: added prototype for GETAction (v0.9.28)
2005-02-06 Helge Hess <helge.hess@opengroupware.org>
* added -outlookMessageClass / -outlookFolderClass (v0.9.27)
2004-10-19 Helge Hess <helge.hess@opengroupware.org>
* SOGoFolder: added method -fetchContentStringsAndNamesOfAllObjects
which fetches the contents of all folders objects (avoid to use this
high overhead method!). Required for iCalendar file generation.
(v0.9.26)
2004-10-08 Helge Hess <helge.hess@opengroupware.org>
* SOGoUserFolder.m: enhanced object lookup so that when a Calendar
is opened with an extensions (eg "Calendar.ics") (v0.9.25)
* SOGoFolder.m: added facility to define default extensions (v0.9.24)
* SOGoObject.m: fixed not implemented return status (501, not 502)
(v0.9.23)
* SOGoUserFolder.m: do not try to fetch file names in this folder
(v0.9.22)
* v0.9.21
* SOGoContentObject.m: added PUTAction:
* SOGoObject.m: implemented special WebDAV support in GETAction:
* SOGoFolder.m: added -toOneRelationshipKeys method (enables listing of
contained objects in WebDAV), added container name to logging prefix,
explicitly mark as WebDAV collection
* SOGoContentObject.m: mark as WebDAV non-collection
2004-10-07 Helge Hess <helge.hess@opengroupware.org>
* SOGoUserFolder.m: added toManyRelationshipKeys, marked as WebDAV
collection (v0.9.20)
2004-09-29 Helge Hess <helge.hess@opengroupware.org>
* SOGoObject.m: added -fetchSubfolders method to resolve all
toManyRelationshipKeys to SOPE objects (v0.9.19)
2004-09-20 Helge Hess <helge.hess@skyrix.com>
* SOGoObject.m: added a default GET method which redirects to
url + "/view" (v0.9.18)
* SOGoObject.m(-description): added name of container (v0.9.17)
2004-09-08 Helge Hess <helge.hess@skyrix.com>
* SOGoGroupFolder.m: separate -resetFolderCaches method from -sleep
(v0.9.16)
2004-09-08 Helge Hess <helge.hess@skyrix.com>
* SOGoUserFolder.m: map "Mail" key to SOGoMailAccounts object (v0.9.15)
2004-09-01 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.14
* GNUmakefile.preamble: fixed for gsmake 1.9.2 build
* GNUmakefile: include ../../Version also
2004-08-26 Helge Hess <helge.hess@skyrix.com>
* SOGoUserFolder.m: added ability to create Contacts folder (v0.9.13)
2004-08-24 Maxime Wacker <mwacker@linagora.com>
* GNUmakefile.preamble: fixes for the build process (v0.9.12)
2004-08-16 Helge Hess <helge.hess@skyrix.com>
* SOGoCustomGroupFolder.m: added -initWithUIDs:inContainer: for using
the groups folder for internal group fetches (v0.9.11)
* SOGoContentObject.m: updated multi-save things (which belong into the
SOGoAppointmentObject class), implement -sleep to release the content
(v0.9.10)
2004-08-15 Helge Hess <helge.hess@skyrix.com>
* SOGoContentObject.m: implement first version of -delete (v0.9.9)
* SOGoObject.[hm], SOGoUserFolder.[hm]: implemented SOPE
-ownerInContext: (v0.9.8)
2004-08-14 Helge Hess <helge.hess@skyrix.com>
* v0.9.7
* SOGoGroupFolder.m: renamed -reset method to -sleep (called by SOPE)
* SOGoObject.m: added SOPE -sleep method (resets container and can be
called by subclasses)
* SOGoGroupFolder.m: made the folder found note log a debug log
2004-08-11 Helge Hess <helge.hess@skyrix.com>
* v0.9.6
* SOGoUserFolder.m: added "Groups" folder name and lookup
* added: SOGoGroupsFolder, SOGoGroupFolder, SOGoCustomGroupFolder
2004-07-02 Helge Hess <helge.hess@opengroupware.org>
* SOGoObject.m: added -delete method (but not yet implemented)
2004-06-30 Helge Hess <helge.hess@opengroupware.org>
* SOGoContentObject.m: added -contentAsString method
* created ChangeLog
+78
View File
@@ -0,0 +1,78 @@
# GNUstep makefile
-include ../../config.make
include $(GNUSTEP_MAKEFILES)/common.make
-include ../../Version
-include ./Version
LIBRARY_NAME = libSOGo
TOOL_NAME = \
agenor_email2uid \
agenor_shares4uid \
agenor_emails4uid \
agenor_defaults
libSOGo_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION)
libSOGo_HEADER_FILES_DIR = .
libSOGo_HEADER_FILES_INSTALL_DIR = /SOGo
FHS_HEADER_DIRS = SOGo
libSOGo_HEADER_FILES = \
SOGoObject.h \
SOGoFolder.h \
SOGoContentObject.h \
SOGoUserFolder.h \
SOGoGroupsFolder.h \
SOGoGroupFolder.h \
SOGoCustomGroupFolder.h \
\
SOGoAppointment.h \
AgenorUserManager.h \
SOGoLRUCache.h \
NSString+iCal.h \
NSObject+AptComparison.h \
WOContext+Agenor.h \
\
SOGoAuthenticator.h \
SOGoUser.h \
libSOGo_OBJC_FILES = \
SOGoObject.m \
SOGoFolder.m \
SOGoContentObject.m \
SOGoUserFolder.m \
SOGoGroupsFolder.m \
SOGoGroupFolder.m \
SOGoCustomGroupFolder.m \
\
SOGoAppointment.m \
SOGoAppointmentICalRenderer.m \
SOGoLRUCache.m \
AgenorUserManager.m \
NSObject+AptComparison.m \
WOContext+Agenor.m \
AgenorUserDefaults.m \
\
SOGoAuthenticator.m \
SOGoUser.m \
# tools
COMMON_TOOL_FILES = \
AgenorUserManager.m \
AgenorUserDefaults.m \
SOGoLRUCache.m \
agenor_email2uid_OBJC_FILES += agenor_email2uid.m $(COMMON_TOOL_FILES)
agenor_shares4uid_OBJC_FILES += agenor_shares4uid.m $(COMMON_TOOL_FILES)
agenor_emails4uid_OBJC_FILES += agenor_emails4uid.m $(COMMON_TOOL_FILES)
agenor_defaults_OBJC_FILES += agenor_defaults.m $(COMMON_TOOL_FILES)
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/library.make
include $(GNUSTEP_MAKEFILES)/tool.make
-include GNUmakefile.postamble
-include ../../fhslib.make
-include ../../fhstools.make

Some files were not shown because too many files have changed in this diff Show More