mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-08-25 16:33:22 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
+7
@@ -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>
|
||||
+34
@@ -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_ */
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
requires = ( MAIN );
|
||||
|
||||
publicResources = (
|
||||
);
|
||||
|
||||
factories = {
|
||||
};
|
||||
|
||||
classes = {
|
||||
SOGoAppointmentFolder = {
|
||||
superclass = "SOGoFolder";
|
||||
};
|
||||
SOGoGroupAppointmentFolder = {
|
||||
superclass = "SOGoAppointmentFolder";
|
||||
};
|
||||
|
||||
SOGoAppointmentObject = {
|
||||
superclass = "SOGoContentObject";
|
||||
};
|
||||
|
||||
SOGoFreeBusyObject = {
|
||||
superclass = "SOGoContentObject";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
# compilation settings
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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) */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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__ */
|
||||
@@ -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) */
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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_ */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user