from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
-- (NSArray *)fetchCoreInfosFromFolder:(OCSFolder *)_folder
+- (NSArray *)fetchFields:(NSArray *)_fields
from:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
-- (NSArray *)fetchOverviewInfosFromFolder:(OCSFolder *)_folder
- from:(NSCalendarDate *)_startDate
+- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
-- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate
+- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate;
/* URL generation */
return ma;
}
+
- (NSArray *)fetchFields:(NSArray *)_fields
fromFolder:(OCSFolder *)_folder
from:(NSCalendarDate *)_startDate
return records;
}
-- (NSArray *)fetchOverviewInfosFromFolder:(OCSFolder *)_folder
+/* override this in subclasses */
+- (NSArray *)fetchFields:(NSArray *)_fields
from:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate
+{
+ OCSFolder *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;
if(!infos) {
infos = [[NSArray arrayWithObjects:@"uid", @"startdate", @"enddate",
- @"title", @"location", @"orgmail",
- @"status", @"ispublic", @"iscycle",
- @"isallday",
nil] retain];
}
return [self fetchFields:infos
- fromFolder:_folder
from:_startDate
to:_endDate];
}
+
- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
- OCSFolder *folder;
-
- if ((folder = [self ocsFolder]) == nil) {
- [self errorWithFormat:@"(%s): missing folder for fetch!",
- __PRETTY_FUNCTION__];
- return nil;
+ static NSArray *infos = nil;
+ if(!infos) {
+ infos = [[NSArray arrayWithObjects:@"uid", @"startdate", @"enddate",
+ @"title", @"location", @"orgmail",
+ @"status", @"ispublic", @"iscycle",
+ @"isallday",
+ nil] retain];
}
- return [self fetchOverviewInfosFromFolder:folder from:_startDate to:_endDate];
+ return [self fetchFields:infos
+ from:_startDate
+ to:_endDate];
}
-- (NSArray *)fetchCoreInfosFromFolder:(OCSFolder *)_folder
- from:(NSCalendarDate *)_startDate
+- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
to:(NSCalendarDate *)_endDate
{
static NSArray *infos = nil;
nil] retain];
}
return [self fetchFields:infos
- fromFolder:_folder
from:_startDate
to:_endDate];
}
-- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
- to:(NSCalendarDate *)_endDate
-{
- /* this is the primary API */
- OCSFolder *folder;
-
- if ((folder = [self ocsFolder]) == nil) {
- [self errorWithFormat:@"(%s): missing folder for fetch!",
- __PRETTY_FUNCTION__];
- return nil;
- }
- return [self fetchCoreInfosFromFolder:folder from:_startDate to:_endDate];
-}
-
/* URL generation */
- (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx {
return aptFolder;
}
-- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
- to:(NSCalendarDate *)_endDate
- folder:(SOGoAppointmentFolder *)_folder
+/* overridden */
+- (NSArray *)fetchFields:(NSArray *)_fields
+ from:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate
{
- if (![_folder isNotNull])
- return nil;
- return [_folder fetchCoreInfosFrom:_startDate to:_endDate];
-}
-
-- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
- to:(NSCalendarDate *)_endDate
- memberFolders:(NSArray *)_folders
-{
- NSMutableArray *result;
+ NSArray *folders;
+ NSMutableArray *result;
NSMutableDictionary *uidToRecord;
- unsigned i, count;
+ 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)
+ if ((count = [folders count]) == 0)
return [NSArray array];
if (self->uidToFolder == nil)
result = [NSMutableArray arrayWithCapacity:(7 * count)];
for (i = 0; i < count; i++) {
SOGoAppointmentFolder *aptFolder;
- id results;
- NSDictionary *record;
-
+ id results;
+ NSDictionary *record;
+
aptFolder = [self calendarFolderForMemberFolder:
- [_folders objectAtIndex:i]];
+ [folders objectAtIndex:i]];
if (![aptFolder isNotNull]) {
[self debugWithFormat:@"did not find a Calendar folder in folder: %@",
- [_folders objectAtIndex:i]];
+ [folders objectAtIndex:i]];
continue;
}
- results = [self fetchCoreInfosFrom:_startDate to:_endDate
- folder:aptFolder];
+ results = [aptFolder fetchFields:_fields
+ from:_startDate
+ to:_endDate];
if (![results isNotNull]) continue;
results = [results objectEnumerator];
}
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];
+ /* 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];
+ /* 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 */
+ /* record already registered, but values in sync, nothing to do */
}
}
}
return result;
}
-- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate
- to:(NSCalendarDate *)_endDate
-{
- /* this is the main dispatcher method */
- NSArray *folders;
-
- if ((folders = [[self container] valueForKey:@"memberFolders"]) == nil) {
- [self errorWithFormat:@"calendar container has no 'memberFolders'?!"];
- return nil;
- }
-
- return [self fetchCoreInfosFrom:_startDate to:_endDate
- memberFolders:folders];
-}
/* URL generation */
# Version file
-SUBMINOR_VERSION:=23
+SUBMINOR_VERSION:=24
# v0.9.19 requires NGiCal v4.5.36
# v0.9.13 requires libSOGo v0.9.26
+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
+2004-12-22 Marcus Mueller <znek@mulle-kybernetik.com>
+
+ * v0.9.108
+
+ * UIxAppointmentEditor.m: added "check for conflict" checkbox and
+ appropriate logic. This closes SOGo bug #1069. Localized error
+ messages.
+
+ * UIxAppointmentProposal.m: changed fetchCoreInfos... to new
+ fetchFreebusy... method.
+
+ * English.lproj/default.strings: provided localized error messages
+
+ * UIxCalView.m: rewrite of _fetchCoreInfosForUIDs: to adapt to the
+ newer API of SoObjects (this was still some old OCS based code).
+
2004-12-21 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.107
"partStat_DELEGATED" = "Delegated";
"partStat_OTHER" = "???";
+/* Appointments (error messages) */
+
+"Conflicts found!" = "One or more conflicts were found.";
+"Invalid iCal data!" = "Invalid iCalendar data ...";
+"Could not create iCal data!" = "Could not create iCalendar data ...";
+
/* Searching */
NSString *title;
NSString *location;
NSString *comment;
- NSArray *participants; /* array of iCalPerson's */
- NSArray *resources; /* array of iCalPerson's */
+ NSArray *participants; /* array of iCalPerson's */
+ NSArray *resources; /* array of iCalPerson's */
NSString *priority;
NSArray *categories;
NSString *accessClass;
- BOOL isPrivate; /* default: NO */
+ BOOL isPrivate; /* default: NO */
+ BOOL checkForConflicts; /* default: NO */
}
- (NSString *)iCalStringTemplate;
- (void)setIsPrivate:(BOOL)_yn;
- (void)setAccessClass:(NSString *)_class;
+- (void)setCheckForConflicts:(BOOL)_checkForConflicts;
+- (BOOL)checkForConflicts;
+
- (NSString *)_completeURIForMethod:(NSString *)_method;
- (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
@end
#include "common.h"
+#include <NGiCal/NGiCal.h>
+#include <NGExtensions/NGCalendarDateRange.h>
#include <SOGoUI/SOGoDateFormatter.h>
#include <SOGoLogic/SOGoAppointment.h>
#include <Appointments/SOGoAppointmentFolder.h>
#include <Appointments/SOGoAppointmentObject.h>
-#include <NGiCal/NGiCal.h>
#include <SOGoLogic/AgenorUserManager.h>
#include "iCalPerson+UIx.h"
#include "UIxComponent+Agenor.h"
self = [super init];
if(self) {
[self setIsPrivate:NO];
+ [self setCheckForConflicts:NO];
}
return self;
}
[self setAccessClass:@"PUBLIC"];
self->isPrivate = _yn;
}
-
- (BOOL)isPrivate {
return self->isPrivate;
}
+- (void)setCheckForConflicts:(BOOL)_checkForConflicts {
+ self->checkForConflicts = _checkForConflicts;
+}
+- (BOOL)checkForConflicts {
+ return self->checkForConflicts;
+}
+
/* transparency */
}
+/* conflict management */
+
+- (BOOL)containsConflict:(SOGoAppointment *)_apt {
+ NSArray *attendees, *uids;
+ SOGoAppointmentFolder *groupCalendar;
+ NSArray *infos;
+ NSArray *ranges;
+ id folder;
+
+ [self logWithFormat:@"search from %@ to %@",
+ [_apt startDate], [_apt endDate]];
+
+ folder = [[self clientObject] container];
+ attendees = [_apt attendees];
+ uids = [folder uidsFromICalPersons:attendees];
+ if ([uids count] == 0) {
+ [self logWithFormat:@"Note: no UIDs selected."];
+ return NO;
+ }
+
+ groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
+ inContext:[self context]];
+ [self debugWithFormat:@"group calendar: %@", groupCalendar];
+
+ if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
+ [self errorWithFormat:@"invalid folder to run freebusy query on!"];
+ return NO;
+ }
+
+ infos = [groupCalendar fetchFreebusyInfosFrom:[_apt startDate]
+ to:[_apt endDate]];
+ [self debugWithFormat:@" process: %d events", [infos count]];
+
+ ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
+ andEndDateKey:@"endDate"];
+ ranges = [ranges arrayByCompactingContainedDateRanges];
+ [self debugWithFormat:@" blocked ranges: %@", ranges];
+
+ return [ranges count] != 0 ? YES : NO;
+}
+
+
/* actions */
- (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
if (apt == nil) {
- [self setErrorText:@"Invalid iCalendar data ..."]; // localize
+ NSString *s;
+
+ s = [self labelForKey:@"Invalid iCal data!"];
+ [self setErrorText:s];
return self;
}
[p setParticipationStatus:iCalPersonPartStatAccepted];
}
+ if ([self checkForConflicts]) {
+ if ([self containsConflict:apt]) {
+ NSString *s;
+
+ s = [self labelForKey:@"Conflicts found!"];
+ [self setErrorText:s];
+ [apt release];
+ return self;
+ }
+ }
content = [apt iCalString];
[apt release]; apt = nil;
if (content == nil) {
- [self setErrorText:@"Could not create iCalendar data ..."]; // localize
+ NSString *s;
+
+ s = [self labelForKey:@"Could not create iCal data!"];
+ [self setErrorText:s];
return self;
}
</span>
</td>
</tr>
+ <tr valign="top">
+ <td align="right" width="15%">
+ <span class="aptview_text">
+ <var:string label:value="Constraints" />:
+ </span>
+ </td>
+ <td align="left" bgcolor="#FFFFF0">
+ <span class="aptview_text">
+ <input type="checkbox"
+ var:selection="checkForConflicts"
+ var:checked="checkForConflicts"
+ /> <var:string label:value="check for conflicts" />
+ </span>
+ </td>
+ </tr>
</table>
</td>
</tr>
- (id)proposalSearchAction {
NSArray *attendees, *uids;
SOGoAppointmentFolder *groupCalendar;
- NSArray *coreInfos;
+ NSArray *infos;
NSArray *ranges;
[self logWithFormat:@"search from %@ to %@",
inContext:[self context]];
[self debugWithFormat:@"group calendar: %@", groupCalendar];
- if (![groupCalendar respondsToSelector:@selector(fetchCoreInfosFrom:to:)]) {
+ if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
reason:@"invalid folder to run proposal query on!"];
}
- // TODO: just query startdate and enddate ... (freebusy)
- coreInfos = [groupCalendar fetchCoreInfosFrom:[self startDate]
- to:[self endDate]];
- [self debugWithFormat:@" process: %d events", [coreInfos count]];
+ infos = [groupCalendar fetchFreebusyInfosFrom:[self startDate]
+ to:[self endDate]];
+ [self debugWithFormat:@" process: %d events", [infos count]];
- ranges = [coreInfos arrayByCreatingDateRangesFromObjectsWithStartDateKey:
+ ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:
@"startDate"
andEndDateKey:@"endDate"];
ranges = [ranges arrayByCompactingContainedDateRanges];
ma = [NSMutableArray arrayWithCapacity:count * 10];
for (i = 0; i < count; i++) {
- OCSFolder *folder;
- NSString *path;
- NSString *uid;
- NSArray *res;
-
- uid = [_uids objectAtIndex:i];
- if ([uid length] == 0) continue;
-
- path = [[@"/Users/" stringByAppendingString:uid]
- stringByAppendingString:@"/Calendar"];
-
- if ((folder = [[self clientObject] ocsFolderForPath:path]) == nil) {
- [self errorWithFormat:@"did not find user: %@", uid];
- continue;
- }
-
- res = [[self clientObject] fetchOverviewInfosFromFolder:folder
- from:[self startDate]
- to:[self endDate]];
- if (res == nil) {
- [self errorWithFormat:@"fetch failed for user: %@", uid];
- continue;
- }
+ SOGoAppointmentFolder *folder;
+ NSArray *res;
+ int j, rcount;
+
+ folder = [[self clientObject] lookupGroupCalendarFolderForUIDs:_uids
+ inContext:[self context]];
+ res = [folder fetchOverviewInfosFrom:[self startDate]
+ to:[self endDate]];
/* perform uniquing */
- {
- int j, rcount;
+ for (j = 0, rcount = [res count]; j < rcount; j++) {
+ NSDictionary *record;
- for (j = 0, rcount = [res count]; j < rcount; j++) {
- NSDictionary *record;
-
- record = [res objectAtIndex:j];
-
- if ([_uniquer containsObject:[record valueForKey:@"uid"]])
- continue;
-
- [ma addObject:record];
- [_uniquer addObject:[record valueForKey:@"uid"]];
- }
+ record = [res objectAtIndex:j];
+
+ if ([_uniquer containsObject:[record valueForKey:@"uid"]])
+ continue;
+
+ [ma addObject:record];
+ [_uniquer addObject:[record valueForKey:@"uid"]];
}
}
return ma;
self->appointments =
[[aptFolder fetchOverviewInfosFrom:[self startDate]
to:[self endDate]] retain];
-
+
+ /* ZNeK: this is dead code, isn't it? -> check */
uids = [[[[self context] request] formValueForKey:@"uids"] stringValue];
uids = [uids length] > 0 ? [uids componentsSeparatedByString:@","] : nil;
if ([uids count] > 0) {
NSMutableSet *availUIDs;
id tmp;
-
+
+ [self errorWithFormat:@"fetchCoreInfos called for 'uids' form value"];
+
/* make appointments unique, prefer the own "versions" */
tmp = [self->appointments valueForKey:@"uid"];
availUIDs = [[NSMutableSet alloc] initWithArray:tmp];
# $Id$
-SUBMINOR_VERSION:=107
+SUBMINOR_VERSION:=108
# v0.9.107 requires WOExtensions v4.5.21
# v0.9.105 requires NGObjWeb v4.5.102
+2004-12-22 Marcus Mueller <znek@mulle-kybernetik.com>
+
+ * SOGoAppointment.m: transparency is supported by NGiCal, thus fixed
+ the bogus implementation (v0.9.33)
+
2004-12-17 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.32
}
- (void)setTransparency:(NSString *)_value {
- [self debugWithFormat:@"transparency currently unsupported by iCalEvent!"];
+ [self->event setTransparency:_value];
}
- (NSString *)transparency {
- [self debugWithFormat:@"returning bogus transparency information!"];
- return @"TRANSPARENT";
+ return [self->event transparency];
}
- (BOOL)isTransparent {
return [[self transparency] isEqualToString:@"TRANSPARENT"];
# Version file
-SUBMINOR_VERSION:=32
+SUBMINOR_VERSION:=33
# v0.9.32 requires NGiCal v4.5.37
# v0.9.31 requires NGiCal v4.5.36