diff --git a/ChangeLog b/ChangeLog index 6a5addd2a..b7c3982eb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,446 @@ +2007-10-17 Wolfgang Sourdeau + + * UI/MailerUI/UIxMailActions.m ([UIxMailActions -copyAction]): + implemented new web method. + + * SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject + -copyToFolderNamed:folderNameinContext:]): new method with the + code cut/pasted from -moveToFolderNamed:inContext:. + ([SOGoMailObject -moveToFolderNamed:folderNameinContext:]): + modified to use the code from -copyToFolderNamed:inContext:, which + is common between the two actions. + + * SoObjects/Mailer/SOGoMailAccount.m ([SOGoMailAccount -draftsFolderNameInContext:_ctx]) + ([SOGoMailAccount -sentFolderNameInContext:]) + ([SOGoMailAccount -trashFolderNameInContext:]): modified to take + the user settings into account. + + * UI/MailerUI/UIxMailFolderActions.m ([UIxMailFolderActions -setAsDraftsFolderAction]) + ([UIxMailFolderActions -setAsSentFolderAction]) + ([UIxMailFolderActions -setAsTrashFolderAction]): new web methods + that change the purpose of the active folder to "Sent", "Drafts" + or "Trash". + + * UI/SOGoUI/SOGoACLAdvisory.m ([SOGoACLAdvisory -subject]): + returns the subject as quoted-printable. + + * UI/SOGoUI/SOGoACLAdvisory.[hm]: added two intermediary classes: + SOGoACLAdditionAdvisory and SOGoACLRemovalAdvisory implementing + the "aclMethod" method for the subsequent language-dependent + subclasses. + + * UI/SOGoUI/SOGoFolderAdvisory.m ([SOGoFolderAdvisory -subject]): + returns the subject as quoted-printable. + + * UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor + -dealloc]): release item, aptStartDate and aptEndDate. + +2007-10-16 Wolfgang Sourdeau + + * SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder + -initWithName:newNameinContainer:newContainer]): the owner of a + shared folder is set to "nobody" by default. + + * UI/Common/UIxAclEditor.m ([UIxAclEditor -hasOwner]): new method + that returns whether the object has an owner or not. + +2007-10-15 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoFolder.m ([SOGoFolder -ocsFolder]): create + the folder even if the current user is not its owner. + +2007-10-10 Ludovic Marcotte + + * We now send advisory emails when folders + are created / deleted. + + * Fixed the sending of advisory emails upon + ACL changes on folders. + +2007-10-10 Ludovic Marcotte + + * UI/Scheduler/UIxComponentEditor.m + Implemented event/task priority support. + + * SoObjects/Contacts/SOGoContactGCSFolder.m + Added CardDAV support. + + * SoObjects/SOGo/LDAPUserManager.m and SOGoUser.m + Implemented From: based on LDAP results based on + the MailFieldNames attribute (an array) specified + in every LDAP-based authentication sources. + + * UI/MailPartViewers/UIxMailPartTextViewer.m and + UI/WebServerResources/MailerUI.css + We avoid replacing "\r\n" and "\n" with
and + rather use CSS capabilities for proper formatting. + This is _WAY_ faster on very large mails. + +2007-10-10 Francis Lachapelle + + * UI/Scheduler/UIxComponentEditor.m + ([UIxComponentEditor -componentCalendar]): returns the calendar + object of the current event. + +2007-10-05 Ludovic Marcotte + + * UI/WebServerResources/MailerUI.js + We check if at least one message is selected + before performing a Reply/Reply All/Forward + + * SoObjects/Appointments/SOGoAppointmentFolder.m + and others - implemented support for recurring + events (with some known limitations right now, + all soon to be fixed). + +2007-10-04 Francis Lachapelle + + * Main/SOGo.m ([SOGo -isUserName:_keyinContext:_ctx]): removed + the constraint that a username can't start with a digit. + +2007-10-02 Francis Lachapelle + + * Moved SOPE/sope-gdl1/GDLContentStore from the default trunk + repository to Inverse's branch. + +2007-09-28 Francis Lachapelle + + * SoObjects/Mailer/SOGoDraftObject.m + ([SOGoDraftObject -isValidAttachmentName:_name]): removed + constraint on space in file name. + ([SOGoDraftObject -saveAttachment:_attachwithMetadata:metadata]): + now removes from file name all characters preceding a backslash. + This happens with IE7 because the complete attachment file path + is sent. + +2007-09-25 Francis Lachapelle + + * SoObjects/Appointments/SOGoAptMailNotification.m + ([SOGoAptMailNotification -appointmentURL]): set personal as the + default calendar where to add the event. + + * UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage +initialize]): + activate the SOGoUIxDefaultModule user defaults. + +2007-09-21 Francis Lachapelle + + * UI/SOGoUI/UIxComponent.m + ([UIxComponent -shortUserNameForDisplay]): returns the string + "wrongusernamepassword" when authentication failed. + +2007-09-17 Wolfgang Sourdeau + + * UI/MailPartViewers/UIxMailPartICalViewer.m + ([UIxMailPartICalViewer -calendarFolder]): returns the "personal" + entry of the Calendars parent folder. + + * UI/MailerUI/UIxMailListView.m ([UIxMailListView + -messageSubject]): new accessor method to work-around a problem + within SOPE where a subject could be returned as an NSData. + + * SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder + -appendPersonalSources]): make sure the value of the "c_path4" of + the returned rows are not NSNull, otherwise, discard them. + +2007-09-16 Wolfgang Sourdeau + + * SoObjects/Contacts/SOGoContactGCSFolder.m ([SOGoContactGCSFolder + -compare:otherFolder]): new overriden method that compares two + contact foldes based on their class and then transfer the control + to the super method in SOGoFolder. + + * SoObjects/Contacts/SOGoContactLDAPFolder.m + ([SOGoContactLDAPFolder -compare:otherFolder]): new method that + compare two contact folders based on their class and then their + display name. + + * SoObjects/SOGo/SOGoFolder.m ([SOGoFolder -compare:otherFolder]): + new method for sorting folders. The folders are compared based on + their ownership, whether they are a main folder and finally + depending on their display name. + + * SoObjects/SOGo/SOGoObject.m ([SOGoObject + -pathArrayToSOGoObject]): do not reorder the paths if the third + element is an instance of NSNull. + + * SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder + -subFolders]): returns a sorted array using the "compare:" + selector. + +2007-09-14 Wolfgang Sourdeau + + * UI/Scheduler/UIxCalendarSelector.m ([UIxCalendarSelector + -calendars]): also returns the owner of the listed folders. + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([-deleteEntriesWithIds:ids]): moved method into SOGoFolder. + + * UI/Scheduler/UIxCalMainView.m ([-batchDeleteAction]): moved + method into UIxFolderActions. + + * SoObjects/Appointments/SOGoFreeBusyObject.m ([SOGoFreeBusyObject + -fetchFreeBusyInfosFrom:_startDateto:_endDate]): fetch the + freebusy info from the "personal" calendar. + + * UI/Common/UIxParentFolderActions.m ([UIxParentFolderActions + -createFolderAction]): new standardized method for requesting + folder creations among gcs-based modules. + + * UI/Common/UIxParentFolderActions.[hm]: new action class module. + + * SoObjects/Appointments/SOGoAppointmentFolders.m: new class + module, equivalent to the SOGoParentFolder's child + SOGoContactFolders, but for calendars. + + * SoObjects/SOGo/SOGoObject.m ([SOGoObject -labelForKey:key]): new + method that returns translated strings for controller bundles + (same as what UIxComponent does for view bundles). + ([SOGoObject -pathArrayToSOGoObject]): new method that returns + the real path to a subscribed folder (if subscribed). + ([SOGoObject +globallyUniqueObjectId]): move method from SOGoFolder. + ([SOGoObject -globallyUniqueObjectId]): new instance method + calling its class equivalent. + +2007-09-12 Wolfgang Sourdeau + + * UI/MainUI/SOGoRootPage.m ([SOGoRootPage -defaultAction]): test + whether the user is logged in and if so, redirect to his/her + homepage. + ([SOGoRootPage -appendToResponse:inContext:]): removed useless + method. + +2007-09-11 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoFolder.m ([SOGoFolder + +folderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]): + new method. + ([SOGoFolder -displayName]): new method. + ([SOGoFolder -delete]): accept to proceed only if nameInContainer + != "personal". + + * SoObjects/Contacts/SOGoContactLDAPFolder.m + ([SOGoContactLDAPFolder + +folderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]): + renamed from "contactFolderWithName..." for compatibility with SOGoFolder. + + * SoObjects/Contacts/SOGoContactGCSFolder.m ([SOGoContactGCSFolder + +contactFolderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]): + removed method, reimplemented in SOGoFolder. + ([SOGoContactGCSFolder -displayName]): removed method, + reimplemented in SOGoFolder. + ([-delete]): removed method, modified in SOGoFolder. + + * SoObjects/Contacts/SOGoContactFolders.[hm]: modified class to be + a subclass of SOGoParentFolder. + + * SoObjects/SOGo/SOGoParentFolder.[hm]: new class module derived + from SOGoContactFolders and modified to be more content-independent. + + * UI/MailerUI/UIxMailActions.m ([UIxMailActions -markMessageUnreadAction]) + ([UIxMailActions -markMessageReadAction]): new methods moved from + UIxMailListView and adapted to invoke the client object directly, + since the previous versions had to to a lookup from the parent + SOGoMailFolder. + + * UI/MailerUI/UIxMailListView.m ([-markMessageUnreadAction]): move + method into UIxMailActions. + ([-markMessageReadAction]): same as above. + ([-viewAction]): removed useless method. + ([-javaScriptOK]): removed useless method. + ([-isJavaScriptRequest]): removed useless method. + ([-lookupActiveMessage]): removed useless method. + + * UI/Common/WODirectAction+SOGo.m ([WODirectAction + -responseWithStatus:status]): new method that returns a WOResponse + initialized with the specified status code. + ([WODirectAction -responseWith204]): new method that invokes the + above one with "204" as parameter. + ([WODirectAction -redirectToLocation:newLocation]): rewrote method + to make use of -responseWithStatus:. + + * UI/SOGoUI/UIxComponent.m ([UIxComponent -responseWith204]): new + method that returns a WOResponse initialized with the 204 status + code. + + * UI/MailerUI/UIxMailListView.m ([UIxMailListView -sortedUIDs]): + always use a "not deleted" search qualifier along with the user + qualifier (if present). + +2007-09-10 Wolfgang Sourdeau + + * UI/Contacts/UIxContactFoldersView.m ([UIxContactFoldersView + -contactSearchAction]): only return the records which have an + email set. + + * SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject + -trashInContext:_ctx]): no longer expunge the mailbox after + marking a message deleted. + ([SOGoMailObject -moveToFolderNamed:folderNameinContext:]): same + as above. + + * UI/MailerUI/UIxMailView.m ([-deleteAction]): removed method. + ([-trashAction]): moved method into UIxMailActions. + ([-moveAction]): moved method into UIxMailActions. + +2007-09-07 Wolfgang Sourdeau + + * UI/MailPartViewers/UIxMailPartHTMLViewer.m + ([_UIxHTMLMailContentHandler + -endElement:_localNamenamespace:_nsrawName:_rawName]): remove HTML + comments from the CSS code, do not add the CSS code to the body + content and remove references of body from the CSS declarations. + ([UIxMailPartHTMLViewer -cssContent]): new accessor method. + ([UIxMailPartHTMLViewer -flatContentAsString]): separated code + common with cssContent in a different method and invoke it only + once. + + * UI/MainUI/SOGoRootPage.[hm]: made a subclass of UIxComponent + instead of UIxPageFrame. + +2007-09-06 Wolfgang Sourdeau + + * UI/MainUI/SOGoRootPage.m ([-defaultAction]): commented out. + ([-appendToResponse:responseinContext:ctx]): commented out. + ([SOGoRootPage -connectURL]): new accessor that returns the full + url the the "connect" method. + ([-connectAction]): rewrote method to return a properly formatted + auth. cookie based on the username and password passed as + parameter. + + * UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]): + set the value of the cookie to "discard" and set its expiration + date to yesterday. + + * SoObjects/SOGo/SOGoWebAuthenticator.m ([SOGoWebAuthenticator + -preprocessCredentialsInContext:context]): consider the user + anonymous if the cookie value is "discard". + ([SOGoWebAuthenticator + -setupAuthFailResponse:responsewithReason:reasoninContext:context]): + set the expiration date of the cookie to yesterday. + + * UI/SOGoUI/UIxComponent.m ([UIxComponent -applicationPath]): + returns the path to the application if the clientObject is not a + SOGoObject. + + * SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder +initialize]): + moved the requirement of authentication from the SOGo application + class to here. + + * SoObjects/Appointments/SOGoAppointmentObject.m + ([SOGoAppointmentObject -saveContentString:_iCalbaseSequence:_v]): + check whether the new appointment object is still relevant before + sending a notification. + +2007-09-05 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoWebAuthenticator.m ([SOGoWebAuthenticator + -setupAuthFailResponse:responsewithReason:reasoninContext:context]): + render the login page through the SoDefaultRenderer. + + * UI/MainUI/SOGoRootPage.m ([SOGoRootPage + -isPublicInContext:localContext]): new overriden method that + returns YES. + + * UI/Scheduler/UIxCalendarSelector.m ([UIxCalendarSelector + -currentCalendarLogin]): replace css-unsafe characters with _. + + * UI/SOGoUI/UIxComponent.m ([UIxComponent + -shortUserNameForDisplay]): simplified method. + ([-user]): removed method since [context activeUser] is as useful. + +2007-09-04 Wolfgang Sourdeau + + * UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]): + set the cookie path to "/". + + * Main/SOGo.m ([SOGo -authenticatorInContext:_ctx]): choose the + authenticator based on the request handler key. "dav" returns the + SOGoDAVAuthenticator, anything else returns the Web authenticator. + + * SoObjects/SOGo/SOGoDAVAuthenticator.m: renamed module from + "SOGoAuthenticator". + + * SoObjects/SOGo/SOGoWebAuthenticator.m: new class module + implementing a subclass of SoCookieAuthenticator, designed for + web-based cookie authentication of users.m + + * UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]): + new method that resets the authentification cookie. + +2007-08-29 Wolfgang Sourdeau + + * SoObjects/SOGo/LDAPSource.m ([LDAPSource + -checkLogin:loginToCheckandPassword:passwordToCheck]): initialize + didBind to NO to make sure no false authentication is returned if + the bind operation is not executed. + +2007-08-28 Wolfgang Sourdeau + + * SoObjects/Mailer/SOGoDraftObject.m: added support for the + "In-Reply-To" header field when replying. + + * UI/MainUI/SOGoUserHomePage.m: add the "c_" prefix to the quick + table field names that are queried. + + * SoObjects/Appointments/SOGoFreeBusyObject.m ([SOGoFreeBusyObject + -iCalStringForFreeBusyInfos:_infosfrom:_startDateto:_endDate]): + add the "c_" prefix to the quick table field names that are + queried. + +2007-08-24 Wolfgang Sourdeau + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder -lookupCalendarFolderForUID:uid]): add + "personal" to the ocs path of the appointment folder. + + * UI/MailPartViewers/UIxMailPartViewer.m ([UIxMailPartViewer + -flatContentAsString]): use latin1 when the encoding is not + specified, and to reencode data chunk which were not correctly + decoded with the original charset. + + * SoObjects/Appointments/SOGoAppointmentFolder.m ([SOGoAppointmentFolder -aclUsersForObjectAtPath:objectPathArray]) + ([SOGoAppointmentFolder -aclsForUser:uidforObjectAtPath:objectPathArray]) + ([SOGoAppointmentFolder -setRoles:rolesforUser:uidforObjectAtPath:objectPathArray]) + ([SOGoAppointmentFolder + -removeAclsForUsers:usersforObjectAtPath:objectPathArray]): + override those methods to use the "personal" additional directory. + + * SoObjects/SOGo/SOGoUserFolder.m ([-ocsPrivateCalendarPath]): + append "/personal" to the calendar path to simulate a single + calendar in a choice of many. + + * SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder + -lookupName:_keyinContext:acquire:_acquire]): moved the lookup + methods back here. Moved the folder existence check here, and do + it on self only when the lookup happens for a non-folder object. + This permits to accept entries for folders with parents who + don't really exist. + +2007-08-23 Wolfgang Sourdeau + + * UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor + -shouldTakeValuesFromRequest:requestinContext:context]): same as + below. + + * UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor + -shouldTakeValuesFromRequest:requestinContext:context]): + redesigned method since any method called can be received from a + POST or a GET. Instead we check the method call itself and we + accept only if it has the "save" prefix. + + * SoObjects/Appointments/SOGoAptMailNotification.m + ([SOGoAptMailNotification -getSubject]): returns the subject an a + quoted-printable encoded string, if needed. + + * SoObjects/Mailer/SOGoDraftObject.m ([NSString + -asQPSubjectString:encoding]): moved method into + NSString+Utilities.m. + 2007-08-22 Wolfgang Sourdeau - * UI/PreferencesUI/UIxPreferences.m ([UIxPreferences -messageForwardingList]) - ([UIxPreferences -itemMessageForwardingText]) + * UI/PreferencesUI/UIxPreferences.m ([UIxPreferences + -messageForwardingList]) + ([UIxPreferences -itemMessageForwardingText])D ([UIxPreferences -userMessageForwarding]) ([UIxPreferences -setUserMessageForwarding:newMessageForwarding]): new template methods for manage the user preference regarding @@ -3289,7 +3728,7 @@ 2006-09-13 Wolfgang Sourdeau * UI/Contacts/UIxContactView.m: added many wrapper methods to - display blocks of data à la Thunderbird Addressbook. If data is + display blocks of data à la Thunderbird Addressbook. If data is available, those wrappers (around the NGVCard methods) will enclose the results in a proper HTML output with the correct label (if present), otherwise it will return an empty string. diff --git a/GNUmakefile b/GNUmakefile index fa15ad0d9..2877399d7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -5,6 +5,7 @@ include $(GNUSTEP_MAKEFILES)/common.make SUBPROJECTS = \ SOPE/NGCards \ + SOPE/sope-gdl1/GDLContentStore \ OGoContentStore \ SoObjects \ Main \ diff --git a/Main/GNUmakefile b/Main/GNUmakefile index 9526d647a..f8113691b 100644 --- a/Main/GNUmakefile +++ b/Main/GNUmakefile @@ -5,6 +5,9 @@ include $(GNUSTEP_MAKEFILES)/common.make include ../Version include ./Version +ADDITIONAL_INCLUDE_DIRS += -I../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../SOPE/sope-gdl1/GDLContentStore/obj/ + SOGOD = sogod-$(MAJOR_VERSION).$(MINOR_VERSION) TOOL_NAME = $(SOGOD) diff --git a/Main/SOGo.m b/Main/SOGo.m index 702297498..15b568dd5 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -41,10 +41,11 @@ #import -#import +#import +#import #import #import -#import +#import #import "build.h" #import "SOGoProductLoader.h" @@ -103,7 +104,7 @@ static BOOL debugObjectAllocation = NO; /* SoClass security declarations */ sInfo = [self soClassSecurityInfo]; /* require View permission to access the root (bound to authenticated ...) */ - [sInfo declareObjectProtected: SoPerm_View]; +// [sInfo declareObjectProtected: SoPerm_View]; /* to allow public access to all contained objects (subkeys) */ [sInfo setDefaultAccess: @"allow"]; @@ -231,9 +232,18 @@ static BOOL debugObjectAllocation = NO; /* authenticator */ -- (id) authenticatorInContext: (id) _ctx +- (id) authenticatorInContext: (WOContext *) context { - return [$(@"SOGoAuthenticator") sharedSOGoAuthenticator]; + id authenticator; + NSString *key; + + key = [[context request] requestHandlerKey]; + if ([key isEqualToString: @"dav"]) + authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator]; + else + authenticator = [SOGoWebAuthenticator sharedSOGoWebAuthenticator]; + + return authenticator; } /* name lookup */ @@ -244,9 +254,6 @@ static BOOL debugObjectAllocation = NO; if ([_key length] < 1) return NO; - if (isdigit([_key characterAtIndex:0])) - return NO; - return YES; } diff --git a/NEWS b/NEWS index db619da5a..95e1bff63 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,27 @@ -0.9.0-20070xxx +0.9.0-200710XX +-------------- +- the user can now configure his folders as drafts, trash or sent folder; +- added the ability the move and copy message across mail folders; + +0.9.0-200709XX +-------------- +- implemented cookie-based identification in the web interface; +- fixed a bug where a false positive happening whenever a wrong user login was + given during an indirect bind; +- remove the constraint that a username can't begin with a digit; +- deleting a message no longer expunges its parent folder; +- implemented support for multiple calendars; +- it is now possible to rename folders; +- fixed search in message content; +- added tooltips for toolbar buttons (English and French); +- added checkmarks in live search options popup menus; +- added browser detection with recommanded alternatives; +- support for resizable columns in tables; +- improved support for multiple selection in tables and lists; +- improved IE7 and Safari support: attendees selector, email file attachments; +- countless bugfixes; + +0.9.0-20070824 -------------- - added the ability to choose the default module from the application settings: "Calendars", "Contacts" or "Mail"; diff --git a/OGoContentStore/GNUmakefile b/OGoContentStore/GNUmakefile index d66c95f27..d74e26469 100644 --- a/OGoContentStore/GNUmakefile +++ b/OGoContentStore/GNUmakefile @@ -13,6 +13,9 @@ libOGoContentStore_HEADER_FILES_DIR = . libOGoContentStore_HEADER_FILES_INSTALL_DIR = /OGoContentStore # no headers, commented out: FHS_HEADER_DIRS = OGoContentStore +ADDITIONAL_INCLUDE_DIRS += -I../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../SOPE/sope-gdl1/GDLContentStore/obj/ + libOGoContentStore_OBJC_FILES += \ iCalEntityObject+OCS.m \ iCalRepeatableEntityObject+OCS.m \ diff --git a/SOPE/NGCards/NGCardsSaxHandler.m b/SOPE/NGCards/NGCardsSaxHandler.m index e91cb36c9..dd939c179 100644 --- a/SOPE/NGCards/NGCardsSaxHandler.m +++ b/SOPE/NGCards/NGCardsSaxHandler.m @@ -60,7 +60,7 @@ /* state */ -- (void)resetExceptResult +- (void) resetExceptResult { if (content) { @@ -130,7 +130,7 @@ } } -- (void)endValueTag +- (void) endValueTag { [types removeAllObjects]; [args removeAllObjects]; @@ -151,14 +151,14 @@ currentGroup = nil; } -- (void)startVCardSet +- (void) startVCardSet { currentCardGroup = nil; currentGroup = nil; vcs.isInVCardSet = 1; } -- (void)endVCardSet +- (void) endVCardSet { vcs.isInVCardSet = 0; } diff --git a/SOPE/NGCards/iCalMonthlyRecurrenceCalculator.m b/SOPE/NGCards/iCalMonthlyRecurrenceCalculator.m index 2516534a9..e8a236312 100644 --- a/SOPE/NGCards/iCalMonthlyRecurrenceCalculator.m +++ b/SOPE/NGCards/iCalMonthlyRecurrenceCalculator.m @@ -262,10 +262,9 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, interval = [self->rrule repeatInterval]; until = [self lastInstanceStartDate]; // TODO: maybe replace byMonthDay = [self->rrule byMonthDay]; - - /* check whether the range to be processed is beyond the 'until' date */ - + + /* check whether the range to be processed is beyond the 'until' date */ if (until != nil) { if ([until compare:rStart] == NSOrderedAscending) /* until before start */ return nil; @@ -314,7 +313,7 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet, continue; /* first check whether we are in the interval */ - + if ((monthIdxInRecurrence % interval) != 0) continue; diff --git a/SOPE/NGCards/iCalRecurrenceCalculator.m b/SOPE/NGCards/iCalRecurrenceCalculator.m index a56eaef38..4923639cb 100644 --- a/SOPE/NGCards/iCalRecurrenceCalculator.m +++ b/SOPE/NGCards/iCalRecurrenceCalculator.m @@ -100,9 +100,10 @@ static Class yearlyCalcClass = Nil; rule = [_rRules objectAtIndex:i]; if (![rule isKindOfClass:iCalRecurrenceRuleClass]) rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule]; - + calc = [self recurrenceCalculatorForRecurrenceRule:rule withFirstInstanceCalendarDateRange:_fir]; + rs = [calc recurrenceRangesWithinCalendarDateRange:_r]; [ranges addObjectsFromArray:rs]; } @@ -158,6 +159,7 @@ static Class yearlyCalcClass = Nil; unsigned k; exDate = [exDates objectAtIndex:i]; + for (k = 0; k < rCount; k++) { unsigned rIdx; diff --git a/SOPE/NGCards/versitCardsSaxDriver/ChangeLog b/SOPE/NGCards/versitCardsSaxDriver/ChangeLog index 454e8397c..a39b14061 100644 --- a/SOPE/NGCards/versitCardsSaxDriver/ChangeLog +++ b/SOPE/NGCards/versitCardsSaxDriver/ChangeLog @@ -1,3 +1,9 @@ +2007-08-27 Wolfgang Sourdeau + + * VSSaxDriver.m ([VSSaxDriver + -parseFromSource:_sourcesystemId:_sysId]): report only tags for + which the content is not empty. + 2007-05-03 Wolfgang Sourdeau * VSSaxDriver.m ([VSSaxDriver +initialize]): removed the space diff --git a/SOPE/NGCards/versitCardsSaxDriver/VSSaxDriver.m b/SOPE/NGCards/versitCardsSaxDriver/VSSaxDriver.m index 138df5e4a..98c3037a8 100644 --- a/SOPE/NGCards/versitCardsSaxDriver/VSSaxDriver.m +++ b/SOPE/NGCards/versitCardsSaxDriver/VSSaxDriver.m @@ -471,20 +471,17 @@ static VSStringFormatter *stringFormatter = nil; withAttrs: (SaxAttributes *) _attrs andContent: (NSString *) _content { - /* - This is called for all non-BEGIN|END types. - */ + VSSaxTag *a; + NSString *testContent; -// _content = [stringFormatter stringByUnescapingRFC2445Text: _content]; + /* This is called for all non-BEGIN|END types. */ + testContent = [[_content unescapedFromCard] stringByReplacingString: @";" + withString: @""]; - /* check whether type should be reported as an attribute in XML */ - - [self _beginTag: _tagName group: _group withAttrs: _attrs]; - - if ([_content length] > 0) + if ([[testContent stringByTrimmingSpaces] length] > 0) { - VSSaxTag *a; - + [self _beginTag: _tagName group: _group withAttrs: _attrs]; + a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString: [_content unescapedFromCard]]; if (a) @@ -492,9 +489,9 @@ static VSStringFormatter *stringFormatter = nil; [elementList addObject: a]; [a release]; } - } - [self _endTag: _tagName]; + [self _endTag: _tagName]; + } } /* report events for collected elements */ diff --git a/SOPE/sope-gdl1/GDLContentStore/COPYING b/SOPE/sope-gdl1/GDLContentStore/COPYING new file mode 100644 index 000000000..161a3d1d4 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/COPYING @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/SOPE/sope-gdl1/GDLContentStore/COPYRIGHT b/SOPE/sope-gdl1/GDLContentStore/COPYRIGHT new file mode 100644 index 000000000..1053b2aa3 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/COPYRIGHT @@ -0,0 +1,4 @@ +Copyright (C) 2004 SKYRIX Software AG + + +Contact: info@skyrix.com diff --git a/SOPE/sope-gdl1/GDLContentStore/ChangeLog b/SOPE/sope-gdl1/GDLContentStore/ChangeLog new file mode 100644 index 000000000..bdd4c43b7 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/ChangeLog @@ -0,0 +1,329 @@ +2007-08-29 Wolfgang Sourdeau + + * EOQualifier+GCS.m: rewrote comparison code, now uses UPPER instead of + the PostgreSQL specific ILIKE. Fixes OGo bug #1906 (v4.7.49) + +2007-07-20 Helge Hess + + * GCSFolderManager.m: added 'some' rollback after an error (v4.7.48) + +2007-07-20 Wolfgang Sourdeau + + * GCSFolderManager.m: fixed a bug in last check, DROP TABLE is allowed + to fail in the given context (bug #1883) (v4.7.47) + +2007-07-11 Helge Hess + + * GCSFolderManager.m: added some error checking, plenty of open ends + pending (eg folder creation not wrapped in a transaction) (v4.7.46) + +2007-06-29 Wolfgang Sourdeau + + * GCSFolderManager.m: fixed folder creation to populate empty path + fields with NULLs (OGo bug #1883) (v4.7.45) + +2007-04-25 Wolfgang Sourdeau + + * GCSFolder.[hm]: added methods to delete ACL records (OGo bug #1866) + (v4.7.44) + +2007-04-22 Helge Hess + + * GCSChannelManager.m: improved error log (v4.7.43) + +2007-04-17 Helge Hess + + * fixed a few GNUstep compilation warnings (v4.7.42) + +2007-03-21 Wolfgang Sourdeau + + * GCSFolder.[hm], GCSFolderManager.[hm]: added ability to create and + delete GCS folders programmatically (OGo bug #1850) (v4.7.41) + +2007-02-12 Helge Hess + + * GCSFolder.m: fixed a gnustep-base compilation warning (v4.7.40) + +2007-02-09 Helge Hess + + * use -errorWithFormat:, fixed a few logging crashes (incomplete format + strings) (v4.5.39) + +2007-02-08 Wolfgang Sourdeau + + * GCSFolder.m: added a gnustep-base hack to properly format bool + numbers for SQL. Base returns YES or NO in -stringValue while + libFoundation/NGExt returns 0 or 1 (v4.5.39) + +2007-01-15 Wolfgang Sourdeau + + * GCSFolder.[hm], GCSFolderManager.m: added support for content table + ACLs (v4.5.38) + +2006-08-31 Wolfgang Sourdeau + + * EOQualifier+GCS.m: added support for OR qualifiers and for case + insensitive-like qualifiers on PostgreSQL (v4.5.37) + +2006-07-04 Helge Hess + + * use %p for pointer formats, fixed gcc 4.1 warnings (v4.5.36) + +2005-08-16 Helge Hess + + * GNUmakefile, GNUmakefile.preamble: added OSX framework compilation + (v4.5.35) + +2005-07-23 Sebastian Reitenbach + + * GNUmakefile.preamble: added OpenBSD linking flags (v4.5.34) + +2005-07-13 Helge Hess + + * GCSFolder.h: added -versionOfContentWithName: method to header file + (v4.5.33) + + * GCSFolder.m: return a proper exception if the extractor was unable to + create a quickrow for a given content object (v4.5.32) + + * GCSFolder.m: added -writeContent:toName:baseVersion: to support + consistent update operations (eg using etags), properly increase + content object version on update operations (v4.5.31) + + * GCSFolderManager.m, GCSFolder.m: changed not to use EOF + attribute-name 'beautification', eg 'c_name' will stay 'c_name' + instead of being transformed into 'cName' (v4.5.30) + +2005-07-11 Helge Hess + + * GCSFolderManager.m: added automatic discovery of folder types by + scanning for .ocs files (v4.5.29) + +2005-04-25 Helge Hess + + * fixed gcc 4.0 warnings (v4.5.28) + +2005-03-21 Helge Hess + + * GNUmakefile: added FHS support (v4.5.27) + +2005-03-20 Helge Hess + + * moved OGoContentStore as GDLContentStore into sope-gdl1, removed + dependencies on NGiCal and removed some SOGo specific things + (v4.5.26) + +2005-03-07 Helge Hess + + * appointment.ocs: added missing 'partstates' field (v0.9.25) + +2005-03-04 Helge Hess + + * v0.9.24 + + * ocs_gensql.m: started tool to create SQL CREATE from ocs model file + + * OCSFolderType.m: small change to the factory API, changed to use + NGResourceLocator + +2005-03-03 Helge Hess + + * OCSFolderManager.m: fixed a bug in subfolder listing (v0.9.23) + +2005-03-01 Marcus Mueller + + * v0.9.22 + + * appointment.ocs: added 'cycleenddate' and 'cycleinfo' to address + previous performance issues + + * OCSiCalFieldExtractor.m: set 'cycleenddate' and 'cycleinfo' for + recurrent events. Reverted setting of 'enddate' to the previous + behaviour since 'cycleenddate' is dedicated to the task now + + * iCalRepeatableEntityObject+OCS.[hm]: new category used by the + OCSiCalFieldExtractor to extract cycleInfo in an appropriate format + + * sql/generate-folderinfo-sql-for-users.sh, + sql/foldertablecreate-helge-privcal.psql, + sql/foldertablecreate-helge-privcal.sqlite, + sql/generate-folderinfo-sql-for-users-sqlite.sh: adjusted to new + schema + +2005-03-01 Helge Hess + + * OCSFolder.m: added support for storing content and quick info in + the same table (untested) (v0.9.21) + +2005-02-21 Helge Hess + + * v0.9.20 + + * OCSFolderManager.m: removed quoting of SQL table and column names + (breaks with SQLite and isn't necessary for PG), fixed URL pooling + for SQLite + + * NSURL+OCS.m: use tablename for last path component + +2005-02-12 Marcus Mueller + + * OCSiCalFieldExtractor.m: uses new iCalEvent API to determine correct + 'enddate' for recurrent events. This is an optimization which can + save quite some time for complex rules. (v0.9.19) + +2004-12-17 Marcus Mueller + + * v0.9.18 + + * OCSiCalFieldExtractor.m: extract participants' state + + * sql/generate-folderinfo-sql-for-user.sh, sql/appointment-create.psql, + sql/foldertablecreate-helge-privcal.psql: updated with new schema. + +2004-12-15 Marcus Mueller + + * OCSiCalFieldExtractor.m: partmails + cn's are concatenated by '\n' + now - this directly eliminates any ambiguities. Also, instead of + using 'email' for partmails and orgmail, the extractor uses the + 'rfc822Email' value which strips away any preceeding 'mailto:' + prefix, compacting the representation and speeding up comparison. + Also, "iscycle", "isallday" and "isopaque" are now provided by + NGiCal and thus always extracted (v0.9.17) + +2004-12-13 Marcus Mueller + + * sql/generate-folderinfo-sql-for-user.sh: fixed critical error in + Contacts folder_info, type was 'Appointment' but MUST be 'Contact' + (v0.9.16) + +2004-12-10 Marcus Mueller + + * sql: updated all generation scripts to the latest version (v0.9.15) + +2004-12-09 Marcus Mueller + + * v0.9.14 + + * appointment.ocs: added "ispublic", "isopaque", "status" and + "orgmail". + + * OCSiCalFieldExtractor.m: updated to extract new fields (see above) + + * sql: updated generate-folderinfo-sql-for-users.sh + +2004-10-19 Helge Hess + + * OCSFolder.m: added new method -fetchContentsOfAllFiles method which + fetches the contents of all files stored in the folder (required for + iCal generation, such bulk fetches should be avoided if possible!) + (v0.9.13) + +2004-10-15 Marcus Mueller + + * OCSStringFormatter.[hm]: minor cleanup (v0.9.12) + + * v0.9.11 + + * OCSStringFormatter.[hm]: new class to format strings according to + Database requirements (escaping etc.). + + * OCSFolder.m: uses new OCSStringFormatter now. + +2004-09-25 Helge Hess + + * fixed compilation on MacOSX (v0.9.10) + +2004-09-10 Helge Hess + + * v0.9.9 + + * fixed some gcc warnings + + * GNUmakefile.preamble: added pathes to compile against an FHS SOPE + + * OCSiCalFieldExtractor.m: fixed type of sequence iCalEvent field + +2004-09-01 Marcus Mueller + + * GNUmakefile: install type models into $(GNUSTEP_USER_ROOT) (v0.9.8) + +2004-08-27 Helge Hess + + * v0.9.7 + + * OCSChannelManager.m: use PostgreSQL as adaptor, not PostgreSQL72 + + * OCSFolder.m: added support for doing folder sorting in SQL + +2004-08-26 Helge Hess + + * v0.9.6 + + * added OCSContactFieldExtractor + + * sql: added sample contact folder create scripts + + * OCSFolderType.m: read extractor class name from type model + + * OCSFolderManager.m: added contact type model per default (v0.9.5) + +2004-08-25 Helge Hess + + * GNUmakefile: automatically install OCSTypeModels (v0.9.4) + +2004-08-15 Helge Hess + + * OCSFolder.m: added content deletion (v0.9.3) + + * OCSFolder.m: added sanity check to store method (v0.9.2) + +2004-08-14 Helge Hess + + * v0.9.1 + + * OCSiCalFieldExtractor.m: extract new quick fields: location, + partmails, sequence (does not yet handle allday and cycle due to + NGiCal restrictions) + + * appointment.ocs, sql/foldertablecreate-helge-privcal.psql, + sql/testapt-agenor-helge-privcal.psql, sql/appointment-create.psql: + added quick fields: isallday, iscycle, location, partmails, sequence + + * started ocs_recreatequick tool intended for recreating a quick table + based on the content table of a folder + +2004-07-20 Helge Hess + + * OCSChannelManager.m: fixed a bug in the channel GC which resulted + in an exception during the GC NSTimer + +2004-07-16 Helge Hess + + * improved error handling in various files + +2004-07-02 Helge Hess + + * OCSChannelManager.m: added garbage collector for channel pools + +2004-06-30 Helge Hess + + * OCSChannelManager.m: implemented pooling + + * OCSFolder.m: added quick fetches + + * GNUmakefile.preamble: fix link path + + * GNUmakefile (libOGoContentStore_HEADER_FILES_INSTALL_DIR): install + headers in OGoContentStore + + * GNUmakefile.preamble (ocs_ls_TOOL_LIBS): added static dependencies + for OSX + +2004-06-30 Marcus Mueller + + * ocs_cat.m, ocs_ls.m, ocs_mkdir.m: fixed for gnustep compile. + +2004-06-29 Helge Hess + + * created ChangeLog + diff --git a/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h new file mode 100644 index 000000000..da28e551a --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h @@ -0,0 +1,40 @@ +/* + 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 __GDLContentStore_EOAdaptorChannel_GCS_H__ +#define __GDLContentStore_EOAdaptorChannel_GCS_H__ + +#include + +@protocol GCSEOAdaptorChannel + +- (NSException *) createGCSFolderTableWithName: (NSString *) tableName; +- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName; + +@end + +@interface EOAdaptorChannel(GCS) + +- (BOOL)tableExistsWithName:(NSString *)_tableName; + +@end + +#endif /* __GDLContentStore_EOAdaptorChannel_GCS_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m new file mode 100644 index 000000000..23e156176 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m @@ -0,0 +1,50 @@ +/* + 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 "EOAdaptorChannel+GCS.h" +#include "common.h" + +@implementation EOAdaptorChannel(GCS) + +- (BOOL)tableExistsWithName:(NSString *)_tableName { + NSException *ex; + NSString *sql; + BOOL didOpen; + + didOpen = NO; + if (![self isOpen]) { + if (![self openChannel]) + return NO; + didOpen = YES; + } + + sql = @"SELECT COUNT(*) FROM "; + sql = [sql stringByAppendingString:_tableName]; + sql = [sql stringByAppendingString:@" WHERE 1 = 2"]; + + ex = [[[self evaluateExpressionX:sql] retain] autorelease]; + [self cancelFetch]; + + if (didOpen) [self closeChannel]; + return ex != nil ? NO : YES; +} + +@end /* EOAdaptorChannel(GCS) */ diff --git a/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h new file mode 100644 index 000000000..27fae8d72 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h @@ -0,0 +1,35 @@ +/* + 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 __GDLContentStore_EOQualifier_GCS_H__ +#define __GDLContentStore_EOQualifier_GCS_H__ + +#include + +@class NSMutableString; + +@interface EOQualifier(GCS) + +- (void)_gcsAppendToString:(NSMutableString *)_ms; + +@end + +#endif /* __GDLContentStore_EOQualifier_GCS_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m new file mode 100644 index 000000000..88343350e --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m @@ -0,0 +1,156 @@ +/* + Copyright (C) 2004-2007 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 "EOQualifier+GCS.h" +#include "common.h" + +@implementation EOQualifier(GCS) + +- (void)_appendAndQualifier:(EOAndQualifier *)_q + toString:(NSMutableString *)_ms +{ + // TODO: move to EOQualifier category + NSArray *qs; + unsigned i, count; + + qs = [_q qualifiers]; + if ((count = [qs count]) == 0) + return; + + for (i = 0; i < count; i++) { + if (i != 0) [_ms appendString:@" AND "]; + if (count > 1) [_ms appendString:@"("]; + [[qs objectAtIndex:i] _gcsAppendToString:_ms]; + if (count > 1) [_ms appendString:@")"]; + } +} +- (void)_appendOrQualifier:(EOAndQualifier *)_q + toString:(NSMutableString *)_ms +{ + // TODO: move to EOQualifier category + NSArray *qs; + unsigned i, count; + + qs = [_q qualifiers]; + if ((count = [qs count]) == 0) + return; + + for (i = 0; i < count; i++) { + if (i != 0) [_ms appendString:@" OR "]; + if (count > 1) [_ms appendString:@"("]; + [[qs objectAtIndex:i] _gcsAppendToString:_ms]; + if (count > 1) [_ms appendString:@")"]; + } +} +- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q + toString:(NSMutableString *)_ms +{ + id val; + NSString *qKey, *qOperator, *qValue, *qFormat; + BOOL isCI; + + qKey = [_q key]; + + if ((val = [_q value])) { + SEL op = [_q selector]; + + if ([val isNotNull]) { + isCI = NO; + + if (sel_eq(op, EOQualifierOperatorEqual)) + qOperator = @"="; + else if (sel_eq(op, EOQualifierOperatorNotEqual)) + qOperator = @"!="; + else if (sel_eq(op, EOQualifierOperatorLessThan)) + qOperator = @"<"; + else if (sel_eq(op, EOQualifierOperatorGreaterThan)) + qOperator = @">"; + else if (sel_eq(op, EOQualifierOperatorLessThanOrEqualTo)) + qOperator = @"<="; + else if (sel_eq(op, EOQualifierOperatorGreaterThanOrEqualTo)) + qOperator = @">="; + else if (sel_eq(op, EOQualifierOperatorLike)) + qOperator = @"LIKE"; + else if (sel_eq(op, EOQualifierOperatorCaseInsensitiveLike)) { + isCI = YES; + qOperator = @"LIKE"; + } + else { + [self errorWithFormat:@"%s: unsupported operation for null: %@", + __PRETTY_FUNCTION__, NSStringFromSelector(op)]; + } + + if ([val isKindOfClass:[NSNumber class]]) + qValue = [val stringValue]; + else if ([val isKindOfClass:[NSString class]]) { + qValue = [NSString stringWithFormat: @"'%@'", val]; + } + else { + [self errorWithFormat:@"%s: unsupported value class: %@", + __PRETTY_FUNCTION__, NSStringFromClass([val class])]; + } + } + else { + if (sel_eq(op, EOQualifierOperatorEqual)) { + qOperator = @"IS"; + qValue = @"NULL"; + } + else if (sel_eq(op, EOQualifierOperatorEqual)) { + qOperator = @"IS NOT"; + qValue = @"NULL"; + } + else { + [self errorWithFormat:@"%s: invalid operation for null: %@", + __PRETTY_FUNCTION__, NSStringFromSelector(op)]; + } + } + } + else { + qOperator = @"IS"; + qValue = @"NULL"; + } + + if (isCI) + qFormat = @"UPPER(%@) %@ UPPER(%@)"; + else + qFormat = @"%@ %@ %@"; + + [_ms appendFormat: qFormat, qKey, qOperator, qValue]; +} + +- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms { + if (_q == nil) return; + + if ([_q isKindOfClass:[EOAndQualifier class]]) + [self _appendAndQualifier:(id)_q toString:_ms]; + else if ([_q isKindOfClass:[EOOrQualifier class]]) + [self _appendOrQualifier:(id)_q toString:_ms]; + else if ([_q isKindOfClass:[EOKeyValueQualifier class]]) + [self _appendKeyValueQualifier:(id)_q toString:_ms]; + else + [self errorWithFormat:@"unknown qualifier: %@", _q]; +} + +- (void)_gcsAppendToString:(NSMutableString *)_ms { + [self _appendQualifier:self toString:_ms]; +} + +@end /* EOQualifier(GCS) */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h new file mode 100644 index 000000000..1e8bae600 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h @@ -0,0 +1,58 @@ +/* + 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 __GDLContentStore_GCSChannelManager_H__ +#define __GDLContentStore_GCSChannelManager_H__ + +#import + +/* + GCSChannelManager + + This object manages the connection pooling. +*/ + +@class NSURL, NSMutableDictionary, NSMutableArray, NSTimer; +@class EOAdaptorChannel, EOAdaptor; + +@interface GCSChannelManager : NSObject +{ + NSMutableDictionary *urlToAdaptor; + + NSMutableArray *availableChannels; + NSMutableArray *busyChannels; + NSTimer *gcTimer; +} + ++ (id)defaultChannelManager; + +/* channels */ + +- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url; +- (void)releaseChannel:(EOAdaptorChannel *)_channel; + +/* checking for tables */ + +- (BOOL)canConnect:(NSURL *)_url; + +@end + +#endif /* __GDLContentStore_GCSChannelManager_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m new file mode 100644 index 000000000..e1983909d --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m @@ -0,0 +1,516 @@ +/* + 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 "GCSChannelManager.h" +#include "NSURL+GCS.h" +#include "EOAdaptorChannel+GCS.h" +#include +#include +#include +#include "common.h" + +/* + TODO: + - implemented pooling + - auto-close channels which are very old?! + (eg missing release due to an exception) +*/ + +@interface GCSChannelHandle : NSObject +{ +@public + NSURL *url; + EOAdaptorChannel *channel; + NSDate *creationTime; + NSDate *lastReleaseTime; + NSDate *lastAcquireTime; +} + +- (EOAdaptorChannel *)channel; +- (BOOL)canHandleURL:(NSURL *)_url; +- (NSTimeInterval)age; + +@end + +@implementation GCSChannelManager + +static BOOL debugOn = NO; +static BOOL debugPools = NO; +static int ChannelExpireAge = 180; +static NSTimeInterval ChannelCollectionTimer = 5 * 60; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"GCSChannelManagerDebugEnabled"]; + debugPools = [ud boolForKey:@"GCSChannelManagerPoolDebugEnabled"]; + + ChannelExpireAge = [[ud objectForKey:@"GCSChannelExpireAge"] intValue]; + if (ChannelExpireAge < 1) + ChannelExpireAge = 180; + + ChannelCollectionTimer = + [[ud objectForKey:@"GCSChannelCollectionTimer"] intValue]; + if (ChannelCollectionTimer < 1) + ChannelCollectionTimer = 5*60; +} + ++ (NSString *)adaptorNameForURLScheme:(NSString *)_scheme { + // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL + return @"PostgreSQL"; +} + ++ (id)defaultChannelManager { + static GCSChannelManager *cm = nil; + if (cm == nil) + cm = [[self alloc] init]; + return cm; +} + +- (id)init { + if ((self = [super init])) { + self->urlToAdaptor = [[NSMutableDictionary alloc] initWithCapacity:4]; + self->availableChannels = [[NSMutableArray alloc] initWithCapacity:16]; + self->busyChannels = [[NSMutableArray alloc] initWithCapacity:16]; + + self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval: + ChannelCollectionTimer + target:self selector:@selector(_garbageCollect:) + userInfo:nil repeats:YES] retain]; + } + return self; +} + +- (void)dealloc { + if (self->gcTimer) [self->gcTimer invalidate]; + [self->gcTimer release]; + + [self->busyChannels release]; + [self->availableChannels release]; + [self->urlToAdaptor release]; + [super dealloc]; +} + +/* DB key */ + +- (NSString *)databaseKeyForURL:(NSURL *)_url { + /* + We need to build a proper key that omits passwords and URL path components + which are not required. + */ + NSString *key; + + key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@", + [_url host], [_url port], + [_url user], [_url gcsDatabaseName]]; + return key; +} + +/* adaptors */ + +- (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url { + NSMutableDictionary *md; + id tmp; + + md = [NSMutableDictionary dictionaryWithCapacity:4]; + + if ((tmp = [_url host]) != nil) + [md setObject:tmp forKey:@"hostName"]; + if ((tmp = [_url port]) != nil) + [md setObject:tmp forKey:@"port"]; + if ((tmp = [_url user]) != nil) + [md setObject:tmp forKey:@"userName"]; + if ((tmp = [_url password]) != nil) + [md setObject:tmp forKey:@"password"]; + + if ((tmp = [_url gcsDatabaseName]) != nil) + [md setObject:tmp forKey:@"databaseName"]; + + [self debugWithFormat:@"build connection dictionary for URL %@: %@", + [_url absoluteString], md]; + return md; +} + +- (EOAdaptor *)adaptorForURL:(NSURL *)_url { + EOAdaptor *adaptor; + NSString *key; + + if (_url == nil) + return nil; + if ((key = [self databaseKeyForURL:_url]) == nil) + return nil; + if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) { + [self debugWithFormat:@"using cached adaptor: %@", adaptor]; + return adaptor; /* cached :-) */ + } + + [self debugWithFormat:@"creating new adaptor for URL: %@", _url]; + + if ([EOAdaptor respondsToSelector:@selector(adaptorForURL:)]) { + adaptor = [EOAdaptor adaptorForURL:_url]; + } + else { + NSString *adaptorName; + NSDictionary *condict; + + adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]]; + if ([adaptorName length] == 0) { + [self errorWithFormat:@"cannot handle URL: %@", _url]; + return nil; + } + + condict = [self connectionDictionaryForURL:_url]; + + if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) { + [self errorWithFormat:@"did not find adaptor '%@' for URL: %@", + adaptorName, _url]; + return nil; + } + + [adaptor setConnectionDictionary:condict]; + } + + [self->urlToAdaptor setObject:adaptor forKey:key]; + return adaptor; +} + +/* channels */ + +- (GCSChannelHandle *)findBusyChannelHandleForChannel:(EOAdaptorChannel *)_ch { + NSEnumerator *e; + GCSChannelHandle *handle; + + e = [self->busyChannels objectEnumerator]; + while ((handle = [e nextObject])) { + if ([handle channel] == _ch) + return handle; + } + return nil; +} +- (GCSChannelHandle *)findAvailChannelHandleForURL:(NSURL *)_url { + NSEnumerator *e; + GCSChannelHandle *handle; + + e = [self->availableChannels objectEnumerator]; + while ((handle = [e nextObject])) { + if ([handle canHandleURL:_url]) + return handle; + + if (debugPools) { + [self logWithFormat:@"DBPOOL: cannot use handle (%@ vs %@)", + [_url absoluteString], [handle->url absoluteString]]; + } + } + return nil; +} + +- (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url { + EOAdaptor *adaptor; + EOAdaptorContext *adContext; + EOAdaptorChannel *adChannel; + + if ((adaptor = [self adaptorForURL:_url]) == nil) + return nil; + + if ((adContext = [adaptor createAdaptorContext]) == nil) { + [self errorWithFormat:@"could not create adaptor context!"]; + return nil; + } + if ((adChannel = [adContext createAdaptorChannel]) == nil) { + [self errorWithFormat:@"could not create adaptor channel!"]; + return nil; + } + return adChannel; +} + +- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url { + // TODO: naive implementation, add pooling! + EOAdaptorChannel *channel; + GCSChannelHandle *handle; + NSCalendarDate *now; + + now = [NSCalendarDate date]; + + /* look for cached handles */ + + if ((handle = [self findAvailChannelHandleForURL:_url]) != nil) { + // TODO: check age? + [self->busyChannels addObject:handle]; + [self->availableChannels removeObject:handle]; + ASSIGN(handle->lastAcquireTime, now); + + if (debugPools) + [self logWithFormat:@"DBPOOL: reused cached DB channel!"]; + return [[handle channel] retain]; + } + + if (debugPools) { + [self logWithFormat:@"DBPOOL: create new DB channel for URL: %@", + [_url absoluteString]]; + } + + /* create channel */ + + if ((channel = [self _createChannelForURL:_url]) == nil) + return nil; + + if ([channel isOpen]) + ; + else if (![channel openChannel]) { + [self errorWithFormat:@"could not open channel %@ for URL: %@", + channel, [_url absoluteString]]; + return nil; + } + + /* create handle for channel */ + + handle = [[GCSChannelHandle alloc] init]; + handle->url = [_url retain]; + handle->channel = [channel retain]; + handle->creationTime = [now retain]; + handle->lastAcquireTime = [now retain]; + + [self->busyChannels addObject:handle]; + [handle release]; + + return [channel retain]; +} +- (void)releaseChannel:(EOAdaptorChannel *)_channel { + GCSChannelHandle *handle; + + if ((handle = [self findBusyChannelHandleForChannel:_channel]) != nil) { + NSCalendarDate *now; + + now = [NSCalendarDate date]; + + handle = [handle retain]; + ASSIGN(handle->lastReleaseTime, now); + + [self->busyChannels removeObject:handle]; + + if ([[handle channel] isOpen] && [handle age] < ChannelExpireAge) { + // TODO: consider age + [self->availableChannels addObject:handle]; + if (debugPools) { + [self logWithFormat: + @"DBPOOL: keeping channel (age %ds, #%d): %@", + (int)[handle age], [self->availableChannels count], + [handle->url absoluteString]]; + } + [_channel release]; + [handle release]; + return; + } + + if (debugPools) { + [self logWithFormat: + @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]]; + } + + /* not reusing channel */ + [handle release]; handle = nil; + } + + if ([_channel isOpen]) + [_channel closeChannel]; + + [_channel release]; +} + +/* checking for tables */ + +- (BOOL)canConnect:(NSURL *)_url { + /* + this can check for DB connect as well as for table URLs (whether a table + exists) + */ + EOAdaptorChannel *channel; + NSString *table; + BOOL result; + + if ((channel = [self acquireOpenChannelForURL:_url]) == nil) { + if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url]; + return NO; + } + if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel]; + result = YES; /* could open channel */ + + /* check whether table exists */ + + table = [_url gcsTableName]; + if ([table length] > 0) + result = [channel tableExistsWithName:table]; + + /* release channel */ + + [self releaseChannel:channel]; channel = nil; + + return result; +} + +/* collect old channels */ + +- (void)_garbageCollect:(NSTimer *)_timer { + NSMutableArray *handlesToRemove; + unsigned i, count; + + if ((count = [self->availableChannels count]) == 0) + /* no available channels */ + return; + + /* collect channels to expire */ + + handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4]; + for (i = 0; i < count; i++) { + GCSChannelHandle *handle; + + handle = [self->availableChannels objectAtIndex:i]; + if (![[handle channel] isOpen]) { + [handlesToRemove addObject:handle]; + continue; + } + if ([handle age] > ChannelExpireAge) { + [handlesToRemove addObject:handle]; + continue; + } + } + + /* remove channels */ + count = [handlesToRemove count]; + if (debugPools) + [self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count]; + for (i = 0; i < count; i++) { + GCSChannelHandle *handle; + + handle = [[handlesToRemove objectAtIndex:i] retain]; + [self->availableChannels removeObject:handle]; + if ([[handle channel] isOpen]) + [[handle channel] closeChannel]; + [handle release]; + } + + [handlesToRemove release]; +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + + [ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]]; + + [ms appendString:@">"]; + return ms; +} + +@end /* GCSChannelManager */ + +@implementation GCSChannelHandle + +- (void)dealloc { + [self->channel release]; + [self->creationTime release]; + [self->lastReleaseTime release]; + [self->lastAcquireTime release]; + [super dealloc]; +} + +/* accessors */ + +- (EOAdaptorChannel *)channel { + return self->channel; +} + +- (BOOL)canHandleURL:(NSURL *)_url { + BOOL isSQLite; + + if (_url == nil) { + [self logWithFormat:@"MISMATCH: no url .."]; + return NO; + } + if (_url == self->url) + return YES; + + isSQLite = [[_url scheme] isEqualToString:@"sqlite"]; + + if (!isSQLite && ![[self->url host] isEqual:[_url host]]) { + [self logWithFormat:@"MISMATCH: different host (%@ vs %@)", + [self->url host], [_url host]]; + return NO; + } + if (![[self->url gcsDatabaseName] isEqualToString:[_url gcsDatabaseName]]) { + [self logWithFormat:@"MISMATCH: different db .."]; + return NO; + } + if (!isSQLite) { + if (![[self->url user] isEqual:[_url user]]) { + [self logWithFormat:@"MISMATCH: different user .."]; + return NO; + } + if ([[self->url port] intValue] != [[_url port] intValue]) { + [self logWithFormat:@"MISMATCH: different port (%@ vs %@) ..", + [self->url port], [_url port]]; + return NO; + } + } + return YES; +} + +- (NSTimeInterval)age { + return [[NSCalendarDate calendarDate] + timeIntervalSinceDate:self->creationTime]; +} + +/* NSCopying */ + +- (id)copyWithZone:(NSZone *)_zone { + return [self retain]; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + + [ms appendFormat:@" channel=0x%p", self->channel]; + if (self->creationTime) [ms appendFormat:@" created=%@", self->creationTime]; + if (self->lastReleaseTime) + [ms appendFormat:@" last-released=%@", self->lastReleaseTime]; + if (self->lastAcquireTime) + [ms appendFormat:@" last-acquired=%@", self->lastAcquireTime]; + + [ms appendString:@">"]; + return ms; +} + +@end /* GCSChannelHandle */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSContext.h b/SOPE/sope-gdl1/GDLContentStore/GCSContext.h new file mode 100644 index 000000000..f386e6587 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSContext.h @@ -0,0 +1,39 @@ +/* + 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 __GDLContentStore_GCSContext_H__ +#define __GDLContentStore_GCSContext_H__ + +#import + +/* + GCSContext + + Context passed to all operations. +*/ + +@interface GCSContext : NSObject +{ +} + +@end + +#endif /* __GDLContentStore_GCSContext_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSContext.m b/SOPE/sope-gdl1/GDLContentStore/GCSContext.m new file mode 100644 index 000000000..17dd48574 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSContext.m @@ -0,0 +1,26 @@ +/* + 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 "GCSContext.h" +#include "common.h" + +@implementation GCSContext +@end /* GCSContext */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h new file mode 100644 index 000000000..1b1e64af4 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h @@ -0,0 +1,37 @@ +/* + 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 __GDLContentStore_GCSFieldExtractor_H__ +#define __GDLContentStore_GCSFieldExtractor_H__ + +#import + +@class NSString, NSMutableDictionary; + +@interface GCSFieldExtractor : NSObject +{ +} + +- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content; + +@end + +#endif /* __GDLContentStore_GCSFieldExtractor_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m new file mode 100644 index 000000000..6ec8af66d --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m @@ -0,0 +1,31 @@ +/* + 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 "GCSFieldExtractor.h" +#include "common.h" + +@implementation GCSFieldExtractor + +- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content { + return nil; +} + +@end /* GCSFieldExtractor */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h new file mode 100644 index 000000000..4f87f0902 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h @@ -0,0 +1,61 @@ +/* + 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 __GDLContentStore_GCSFieldInfo_H__ +#define __GDLContentStore_GCSFieldInfo_H__ + +#import + +/* + GCSFieldInfo + + The field info inside an .ocs schema file. + + The field info objects are stored in an GCSFolderType. +*/ + +@class NSString, NSArray; + +@interface GCSFieldInfo : NSObject +{ + NSString *columnName; + NSString *sqlType; + BOOL allowsNull; + BOOL isPrimaryKey; +} + ++ (NSArray *)fieldsForPropertyList:(NSArray *)_plist; +- (id)initWithPropertyList:(id)_plist; + +/* accessors */ + +- (NSString *)columnName; +- (NSString *)sqlType; +- (BOOL)doesAllowNull; +- (BOOL)isPrimaryKey; + +/* generating SQL */ + +- (NSString *)sqlCreateSection; + +@end + +#endif /* __GDLContentStore_GCSFieldInfo_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m new file mode 100644 index 000000000..96a8578cd --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m @@ -0,0 +1,127 @@ +/* + 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 "GCSFieldInfo.h" +#include "common.h" + +@implementation GCSFieldInfo + ++ (NSArray *)fieldsForPropertyList:(NSArray *)_plist { + NSMutableArray *fields; + unsigned i, count; + + if (_plist == nil) + return nil; + + count = [_plist count]; + fields = [NSMutableArray arrayWithCapacity:count]; + for (i = 0; i < count; i++) { + GCSFieldInfo *field; + + field = [[GCSFieldInfo alloc] initWithPropertyList: + [_plist objectAtIndex:i]]; + if (field != nil) [fields addObject:field]; + [field release]; + } + return fields; +} + +- (id)initWithPropertyList:(id)_plist { + if ((self = [super init])) { + NSDictionary *plist = _plist; + + self->columnName = [[plist objectForKey:@"columnName"] copy]; + self->sqlType = [[plist objectForKey:@"sqlType"] copy]; + + self->allowsNull = [[plist objectForKey:@"allowsNull"] boolValue]; + self->isPrimaryKey = [[plist objectForKey:@"isPrimaryKey"] boolValue]; + + if (![self->columnName isNotNull] || ![self->sqlType isNotNull]) { + [self release]; + return nil; + } + } + return self; +} + +- (void)dealloc { + [self->columnName release]; + [self->sqlType release]; + [super dealloc]; +} + +/* accessors */ + +- (NSString *)columnName { + return self->columnName; +} +- (NSString *)sqlType { + return self->sqlType; +} + +- (BOOL)doesAllowNull { + return self->allowsNull; +} +- (BOOL)isPrimaryKey { + return self->isPrimaryKey; +} + +/* generating SQL */ + +- (NSString *)sqlCreateSection { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:32]; + [ms appendString:[self columnName]]; + [ms appendString:@" "]; + [ms appendString:[self sqlType]]; + + [ms appendString:@" "]; + if (![self doesAllowNull]) [ms appendString:@"NOT "]; + [ms appendString:@"NULL"]; + + if ([self isPrimaryKey]) [ms appendString:@" PRIMARY KEY"]; + return ms; +} + +/* description */ + +- (void)appendAttributesToDescription:(NSMutableString *)ms { + id tmp; + + if ((tmp = [self columnName]) != nil) [ms appendFormat:@" column=%@", tmp]; + if ((tmp = [self sqlType]) != nil) [ms appendFormat:@" sql=%@", tmp]; + + if ([self doesAllowNull]) [ms appendString:@" allows-null"]; + if ([self isPrimaryKey]) [ms appendString:@" pkey"]; +} + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + [self appendAttributesToDescription:ms]; + [ms appendString:@">"]; + return ms; +} + +@end /* GCSFieldInfo */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolder.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.h new file mode 100644 index 000000000..7209517ed --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.h @@ -0,0 +1,128 @@ +/* + Copyright (C) 2004-2007 SKYRIX Software AG + Copyright (C) 2007 Helge Hess + + 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 __GDLContentStore_GCSFolder_H__ +#define __GDLContentStore_GCSFolder_H__ + +#import + +/* + GCSFolder + + TODO: document + + Fixed Quick-Table SQL fields: + - "c_name" (name of the file in the folder) + + Fixed BLOB-Table SQL fields: + - "c_name" (name of the file in the folder) + - "c_content" (content of the file in the folder) + - "c_version" (update revision of the file in the folder) +*/ + +@class NSString, NSURL, NSNumber, NSArray, NSException, NSMutableString; +@class NSDictionary; +@class EOQualifier, EOFetchSpecification; +@class EOAdaptorChannel; +@class GCSFolderManager, GCSFolderType, GCSChannelManager; + +@interface GCSFolder : NSObject +{ + GCSFolderManager *folderManager; + GCSFolderType *folderInfo; + + NSNumber *folderId; + NSString *folderName; + NSString *path; + NSURL *location; + NSURL *quickLocation; + NSURL *aclLocation; + NSString *folderTypeName; + + struct { + int requiresFolderSelect:1; + int sameTableForQuick:1; + int reserved:30; + } ofFlags; +} + +- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId + folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype + location:(NSURL *)_loc quickLocation:(NSURL *)_qloc + aclLocation: (NSURL *)_aloc + folderManager:(GCSFolderManager *)_fm; + +/* accessors */ + +- (NSNumber *)folderId; +- (NSString *)folderName; +- (NSString *)path; +- (NSURL *)location; +- (NSURL *)quickLocation; +- (NSURL *)aclLocation; +- (NSString *)folderTypeName; + +- (GCSFolderManager *)folderManager; +- (GCSChannelManager *)channelManager; + +- (NSString *)storeTableName; +- (NSString *)quickTableName; +- (NSString *)aclTableName; +- (BOOL)isQuickInfoStoredInContentTable; + +/* connection */ + +- (EOAdaptorChannel *)acquireStoreChannel; +- (EOAdaptorChannel *)acquireQuickChannel; +- (EOAdaptorChannel *)acquireAclChannel; +- (void)releaseChannel:(EOAdaptorChannel *)_channel; + +- (BOOL)canConnectStore; +- (BOOL)canConnectQuick; + +/* operations */ + +- (NSArray *)subFolderNames; +- (NSArray *)allSubFolderNames; + +- (NSNumber *)versionOfContentWithName:(NSString *)_name; + +- (NSString *)fetchContentWithName:(NSString *)_name; +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name + baseVersion:(unsigned int)_baseVersion; +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name; +- (NSException *)deleteContentWithName:(NSString *)_name; + +- (NSException *)deleteFolder; + +- (NSDictionary *)fetchContentsOfAllFiles; + +- (NSArray *)fetchFields:(NSArray *)_flds + fetchSpecification:(EOFetchSpecification *)_fs; +- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q; +- (NSArray *)fetchAclMatchingQualifier:(EOQualifier *)_q; +- (void)deleteAclMatchingQualifier:(EOQualifier *)_q; +- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs; + +@end + +#endif /* __GDLContentStore_GCSFolder_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolder.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.m new file mode 100644 index 000000000..be7f22c5c --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.m @@ -0,0 +1,1101 @@ +/* + Copyright (C) 2004-2007 SKYRIX Software AG + Copyright (C) 2007 Helge Hess + + 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 "GCSFolder.h" +#include "GCSFolderManager.h" +#include "GCSFolderType.h" +#include "GCSChannelManager.h" +#include "GCSFieldExtractor.h" +#include "NSURL+GCS.h" +#include "EOAdaptorChannel+GCS.h" +#include "EOQualifier+GCS.h" +#include "GCSStringFormatter.h" +#include "common.h" + +#include +#include +#include +#include + +#define CHECKERROR() \ + if (error) { \ + [[storeChannel adaptorContext] rollbackTransaction]; \ + [[quickChannel adaptorContext] rollbackTransaction]; \ + [self logWithFormat:@"ERROR(%s): cannot %s content : %@", \ + __PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", error]; \ + return error; \ + } \ + +@implementation GCSFolder + +static BOOL debugOn = NO; +static BOOL doLogStore = NO; + +static Class NSStringClass = Nil; +static Class NSNumberClass = Nil; +static Class NSCalendarDateClass = Nil; + +static GCSStringFormatter *stringFormatter = nil; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"]; + doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"]; + + NSStringClass = [NSString class]; + NSNumberClass = [NSNumber class]; + NSCalendarDateClass = [NSCalendarDate class]; + + stringFormatter = [GCSStringFormatter sharedFormatter]; +} + +- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId + folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype + location:(NSURL *)_loc quickLocation:(NSURL *)_qloc + aclLocation:(NSURL *)_aloc + folderManager:(GCSFolderManager *)_fm +{ + if (![_loc isNotNull]) { + [self errorWithFormat:@"missing quicktable parameter!"]; + [self release]; + return nil; + } + + if ((self = [super init])) { + self->folderManager = [_fm retain]; + self->folderInfo = [_ftype retain]; + + self->folderId = [_folderId copy]; + self->folderName = [[_path lastPathComponent] copy]; + self->path = [_path copy]; + self->location = [_loc retain]; + self->quickLocation = _qloc ? [_qloc retain] : [_loc retain]; + self->aclLocation = [_aloc retain]; + self->folderTypeName = [_ftname copy]; + + self->ofFlags.requiresFolderSelect = 0; + self->ofFlags.sameTableForQuick = + [self->location isEqualTo:self->quickLocation] ? 1 : 0; + } + return self; +} +- (id)init { + return [self initWithPath:nil primaryKey:nil + folderTypeName:nil folderType:nil + location:nil quickLocation:nil + aclLocation:nil + folderManager:nil]; +} +- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId + folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype + location:(NSURL *)_loc quickLocation:(NSURL *)_qloc + folderManager:(GCSFolderManager *)_fm +{ + return [self initWithPath:_path primaryKey:_folderId folderTypeName:_ftname + folderType:_ftype location:_loc quickLocation:_qloc + aclLocation:nil + folderManager:_fm]; +} + +- (void)dealloc { + [self->folderManager release]; + [self->folderInfo release]; + [self->folderId release]; + [self->folderName release]; + [self->path release]; + [self->location release]; + [self->quickLocation release]; + [self->aclLocation release]; + [self->folderTypeName release]; + [super dealloc]; +} + +/* accessors */ + +- (NSNumber *)folderId { + return self->folderId; +} + +- (NSString *)folderName { + return self->folderName; +} +- (NSString *)path { + return self->path; +} + +- (NSURL *)location { + return self->location; +} +- (NSURL *)quickLocation { + return self->quickLocation; +} +- (NSURL *)aclLocation { + return self->aclLocation; +} + +- (NSString *)folderTypeName { + return self->folderTypeName; +} + +- (GCSFolderManager *)folderManager { + return self->folderManager; +} +- (GCSChannelManager *)channelManager { + return [[self folderManager] channelManager]; +} + +- (NSString *)storeTableName { + return [[self location] gcsTableName]; +} +- (NSString *)quickTableName { + return [[self quickLocation] gcsTableName]; +} +- (NSString *)aclTableName { + return [[self aclLocation] gcsTableName]; +} + +- (BOOL)isQuickInfoStoredInContentTable { + return self->ofFlags.sameTableForQuick ? YES : NO; +} + +/* channels */ + +- (EOAdaptorChannel *)acquireStoreChannel { + return [[self channelManager] acquireOpenChannelForURL:[self location]]; +} +- (EOAdaptorChannel *)acquireQuickChannel { + return [[self channelManager] acquireOpenChannelForURL:[self quickLocation]]; +} +- (EOAdaptorChannel *)acquireAclChannel { + return [[self channelManager] acquireOpenChannelForURL:[self aclLocation]]; +} + +- (void)releaseChannel:(EOAdaptorChannel *)_channel { + [[self channelManager] releaseChannel:_channel]; + if (debugOn) [self debugWithFormat:@"released channel: %@", _channel]; +} + +- (BOOL)canConnectStore { + return [[self channelManager] canConnect:[self location]]; +} +- (BOOL)canConnectQuick { + return [[self channelManager] canConnect:[self quickLocation]]; +} +- (BOOL)canConnectAcl { + return [[self channelManager] canConnect:[self quickLocation]]; +} + +/* errors */ + +- (NSException *)errorVersionMismatchBetweenStoredVersion:(unsigned int)_store + andExpectedVersion:(unsigned int)_base +{ + NSDictionary *ui; + + ui = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:_base], + @"GCSExpectedVersion", + [NSNumber numberWithUnsignedInt:_store], + @"GCSStoredVersion", + self, @"GCSFolder", + nil]; + + return [NSException exceptionWithName:@"GCSVersionMismatch" + reason:@"Transaction conflict during a GCS modification." + userInfo:ui]; +} + +- (NSException *)errorExtractorReturnedNoQuickRow:(id)_extractor + forContent:(NSString *)_content +{ + NSDictionary *ui; + + ui = [NSDictionary dictionaryWithObjectsAndKeys: + self, @"GCSFolder", + _extractor, @"GCSExtractor", + _content, @"GCSContent", + nil]; + return [NSException exceptionWithName:@"GCSExtractFailed" + reason:@"Quickfield extractor did not return a result!" + userInfo:ui]; +} + +/* operations */ + +- (NSArray *)subFolderNames { + return [[self folderManager] listSubFoldersAtPath:[self path] + recursive:NO]; +} +- (NSArray *)allSubFolderNames { + return [[self folderManager] listSubFoldersAtPath:[self path] + recursive:YES]; +} + +- (id)_fetchValueOfColumn:(NSString *)_col inContentWithName:(NSString *)_name{ + EOAdaptorChannel *channel; + NSException *error; + NSDictionary *row; + NSArray *attrs; + NSString *result; + NSString *sql; + + if ((channel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@"could not open storage channel!"]; + return nil; + } + + /* generate SQL */ + + sql = @"SELECT "; + sql = [sql stringByAppendingString:_col]; + sql = [sql stringByAppendingString:@" FROM "]; + sql = [sql stringByAppendingString:[self storeTableName]]; + sql = [sql stringByAppendingString:@" WHERE c_name = '"]; + sql = [sql stringByAppendingString:_name]; + sql = [sql stringByAppendingString:@"'"]; + + /* run SQL */ + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self errorWithFormat:@"%s: cannot execute SQL '%@': %@", + __PRETTY_FUNCTION__, sql, error]; + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + result = nil; + attrs = [channel describeResults:NO /* do not beautify names */]; + if ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) { + result = [[[row objectForKey:_col] copy] autorelease]; + if (![result isNotNull]) result = nil; + [channel cancelFetch]; + } + + /* release and return result */ + + [self releaseChannel:channel]; + return result; +} + +- (NSNumber *)versionOfContentWithName:(NSString *)_name { + return [self _fetchValueOfColumn:@"c_version" inContentWithName:_name]; +} + +- (NSString *)fetchContentWithName:(NSString *)_name { + return [self _fetchValueOfColumn:@"c_content" inContentWithName:_name]; +} + +- (NSDictionary *)fetchContentsOfAllFiles { + /* + Note: try to avoid the use of this method! The key of the dictionary + will be filename, the value the content. + */ + NSMutableDictionary *result; + EOAdaptorChannel *channel; + NSException *error; + NSDictionary *row; + NSArray *attrs; + NSString *sql; + + if ((channel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@"%s: could not open storage channel!", + __PRETTY_FUNCTION__]; + return nil; + } + + /* generate SQL */ + + sql = @"SELECT c_name, c_content FROM "; + sql = [sql stringByAppendingString:[self storeTableName]]; + + /* run SQL */ + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self logWithFormat:@"ERROR(%s): cannot execute SQL '%@': %@", + __PRETTY_FUNCTION__, sql, error]; + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + result = [NSMutableDictionary dictionaryWithCapacity:128]; + attrs = [channel describeResults:NO /* do not beautify names */]; + while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) { + NSString *cName, *cContent; + + cName = [row objectForKey:@"c_name"]; + cContent = [row objectForKey:@"c_content"]; + + if (![cName isNotNull]) { + [self errorWithFormat:@"missing c_name in row: %@", row]; + continue; + } + if (![cContent isNotNull]) { + [self errorWithFormat:@"missing c_content in row: %@", row]; + continue; + } + + [result setObject:cContent forKey:cName]; + } + + /* release and return result */ + + [self releaseChannel:channel]; + return result; +} + +/* writing content */ + +- (NSString *)_formatRowValue:(id)_value { + + if (![_value isNotNull]) + return @"NULL"; + + if ([_value isKindOfClass:NSStringClass]) + return [stringFormatter stringByFormattingString:_value]; + + if ([_value isKindOfClass:NSNumberClass]) { +#if GNUSTEP_BASE_LIBRARY + _value = [_value stringValue]; + return ([(NSString *)_value hasPrefix:@"Y"] || + [(NSString *)_value hasPrefix:@"N"]) + ? (id)([_value boolValue] ? @"1" : @"0") + : _value; +#endif + return [_value stringValue]; + } + + if ([_value isKindOfClass:NSCalendarDateClass]) { + /* be smart ... convert to timestamp. Note: we loose precision. */ + char buf[256]; + snprintf(buf, sizeof(buf), "%i", (int)[_value timeIntervalSince1970]); + return [NSString stringWithCString:buf]; + } + + [self errorWithFormat:@"cannot handle value class: %@", [_value class]]; + return nil; +} + +- (NSString *)_generateInsertStatementForRow:(NSDictionary *)_row + tableName:(NSString *)_table +{ + // TODO: move to NSDictionary category? + NSMutableString *sql; + NSArray *keys; + unsigned i, count; + + if (_row == nil || _table == nil) + return nil; + + keys = [_row allKeys]; + + sql = [NSMutableString stringWithCapacity:512]; + [sql appendString:@"INSERT INTO "]; + [sql appendString:_table]; + [sql appendString:@" ("]; + + for (i = 0, count = [keys count]; i < count; i++) { + if (i != 0) [sql appendString:@", "]; + [sql appendString:[keys objectAtIndex:i]]; + } + + [sql appendString:@") VALUES ("]; + + for (i = 0, count = [keys count]; i < count; i++) { + id value; + + if (i != 0) [sql appendString:@", "]; + value = [_row objectForKey:[keys objectAtIndex:i]]; + value = [self _formatRowValue:value]; + [sql appendString:value]; + } + + [sql appendString:@")"]; + return sql; +} + +- (NSString *)_generateUpdateStatementForRow:(NSDictionary *)_row + tableName:(NSString *)_table + whereColumn:(NSString *)_colname isEqualTo:(id)_value + andColumn:(NSString *)_colname2 isEqualTo:(id)_value2 +{ + // TODO: move to NSDictionary category? + NSMutableString *sql; + NSArray *keys; + unsigned i, count; + + if (_row == nil || _table == nil) + return nil; + + keys = [_row allKeys]; + + sql = [NSMutableString stringWithCapacity:512]; + [sql appendString:@"UPDATE "]; + [sql appendString:_table]; + + [sql appendString:@" SET "]; + for (i = 0, count = [keys count]; i < count; i++) { + id value; + + value = [_row objectForKey:[keys objectAtIndex:i]]; + value = [self _formatRowValue:value]; + + if (i != 0) [sql appendString:@", "]; + [sql appendString:[keys objectAtIndex:i]]; + [sql appendString:@" = "]; + [sql appendString:value]; + } + + [sql appendString:@" WHERE "]; + [sql appendString:_colname]; + [sql appendString:@" = "]; + [sql appendString:[self _formatRowValue:_value]]; + + if (_colname2 != nil) { + [sql appendString:@" AND "]; + [sql appendString:_colname2]; + [sql appendString:@" = "]; + [sql appendString:[self _formatRowValue:_value2]]; + } + + return sql; +} + + +- (EOEntity *) _entityWithName: (NSString *) _name +{ + EOAttribute *attribute; + EOEntity *entity; + + entity = AUTORELEASE([[EOEntity alloc] init]); + [entity setName: _name]; + [entity setExternalName: _name]; + + attribute = AUTORELEASE([[EOAttribute alloc] init]); + [attribute setName: @"c_name"]; + [attribute setColumnName: @"c_name"]; + [entity addAttribute: attribute]; + + return entity; +} + +- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname + isEqualTo:(id)_value + andColumn:(NSString *)_colname2 + isEqualTo:(id)_value2 + entity: (EOEntity *)_entity +{ + EOSQLQualifier *qualifier; + + if (_colname2 == nil) + { + qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity + qualifierFormat: @"%A = %@", _colname, [self _formatRowValue:_value]]; + } + else + { + qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity + qualifierFormat: @"%A = %@ AND %A = %@", _colname, [self _formatRowValue:_value], + _colname2, [self _formatRowValue:_value2]]; + } + + return AUTORELEASE(qualifier); +} + +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name + baseVersion:(unsigned int)_baseVersion +{ + EOAdaptorChannel *storeChannel, *quickChannel; + NSMutableDictionary *quickRow, *contentRow; + GCSFieldExtractor *extractor; + NSException *error; + NSNumber *storedVersion; + BOOL isNewRecord, hasInsertDelegate, hasUpdateDelegate; + NSCalendarDate *nowDate; + NSNumber *now; + + /* check preconditions */ + if (_name == nil) { + return [NSException exceptionWithName:@"GCSStoreException" + reason:@"no content filename was provided" + userInfo:nil]; + } + if (_content == nil) { + return [NSException exceptionWithName:@"GCSStoreException" + reason:@"no content was provided" + userInfo:nil]; + } + + /* run */ + error = nil; + nowDate = [NSCalendarDate date]; + now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]]; + + if (doLogStore) + [self logWithFormat:@"should store content: '%@'\n%@", _name, _content]; + + storedVersion = [self versionOfContentWithName:_name]; + if (doLogStore) + [self logWithFormat:@" version: %@", storedVersion]; + isNewRecord = [storedVersion isNotNull] ? NO : YES; + + /* check whether sequence matches */ + if (_baseVersion != 0 /* use 0 to override check */) { + if (_baseVersion != [storedVersion unsignedIntValue]) { + /* version mismatch (concurrent update) */ + return [self errorVersionMismatchBetweenStoredVersion: + [storedVersion unsignedIntValue] + andExpectedVersion:_baseVersion]; + } + } + + /* extract quick info */ + extractor = [self->folderInfo quickExtractor]; + if ((quickRow = [extractor extractQuickFieldsFromContent:_content]) == nil) { + return [self errorExtractorReturnedNoQuickRow:extractor + forContent:_content]; + } + + [quickRow setObject:_name forKey:@"c_name"]; + + if (doLogStore) + [self logWithFormat:@" store quick: %@", quickRow]; + + /* make content row */ + contentRow = [NSMutableDictionary dictionaryWithCapacity:16]; + + if (self->ofFlags.sameTableForQuick) + [contentRow addEntriesFromDictionary:quickRow]; + + [contentRow setObject:_name forKey:@"c_name"]; + if (isNewRecord) [contentRow setObject:now forKey:@"c_creationdate"]; + [contentRow setObject:now forKey:@"c_lastmodified"]; + if (isNewRecord) + [contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"]; + else { + // TODO: increase version? + [contentRow setObject: + [NSNumber numberWithInt:([storedVersion intValue] + 1)] + forKey:@"c_version"]; + } + [contentRow setObject:_content forKey:@"c_content"]; + + /* open channels */ + if ((storeChannel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@"%s: could not open storage channel!", + __PRETTY_FUNCTION__]; + return nil; + } + if (!self->ofFlags.sameTableForQuick) { + if ((quickChannel = [self acquireQuickChannel]) == nil) { + [self errorWithFormat:@"%s: could not open quick channel!", + __PRETTY_FUNCTION__]; + [self releaseChannel:storeChannel]; + return nil; + } + } + + /* we check if we can call directly methods on our adaptor + channel delegate. If not, we generate SQL ourself since it'll + be a little bit faster and less complex than using GDL to do so */ + hasInsertDelegate = [[storeChannel delegate] + respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)]; + hasUpdateDelegate = [[storeChannel delegate] + respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)]; + + [[storeChannel adaptorContext] beginTransaction]; + [[quickChannel adaptorContext] beginTransaction]; + + if (isNewRecord) { + if (!self->ofFlags.sameTableForQuick) { + error = (hasInsertDelegate ? [quickChannel insertRowX: quickRow + forEntity: [self _entityWithName: [self quickTableName]]] + : [quickChannel evaluateExpressionX: [self _generateInsertStatementForRow: quickRow + tableName: [self quickTableName]]]); + CHECKERROR(); + } + + error = (hasInsertDelegate ? [storeChannel insertRowX: contentRow + forEntity: [self _entityWithName: [self storeTableName]]] + : [storeChannel evaluateExpressionX: [self _generateInsertStatementForRow: contentRow + tableName: [self storeTableName]]]); + + CHECKERROR(); + } + else { + if (!self->ofFlags.sameTableForQuick) { + error = (hasUpdateDelegate ? [quickChannel updateRowX: quickRow + describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name" + isEqualTo: _name andColumn: nil isEqualTo: nil + entity: [self _entityWithName: [self quickTableName]]]] + : [quickChannel evaluateExpressionX: [self _generateUpdateStatementForRow: quickRow + tableName: [self quickTableName] + whereColumn: @"c_name" isEqualTo: _name + andColumn: nil isEqualTo: nil]]); + CHECKERROR(); + } + + error = (hasUpdateDelegate ? [storeChannel updateRowX: contentRow + describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name" isEqualTo: _name + andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil) + isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt:_baseVersion] : (NSNumber *)nil) + entity: [self _entityWithName: [self storeTableName]]]] + : [storeChannel evaluateExpressionX: [self _generateUpdateStatementForRow: contentRow tableName:[self storeTableName] + whereColumn: @"c_name" isEqualTo: _name + andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil) + isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: _baseVersion] : (NSNumber *)nil)]]); + CHECKERROR(); + } + + [[storeChannel adaptorContext] commitTransaction]; + [[quickChannel adaptorContext] commitTransaction]; + + [self releaseChannel: storeChannel]; + if (!self->ofFlags.sameTableForQuick) [self releaseChannel: quickChannel]; + + return error; +} + + +- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name { + /* this method does not check for concurrent writes */ + return [self writeContent:_content toName:_name baseVersion:0]; +} + +- (NSException *)deleteContentWithName:(NSString *)_name { + EOAdaptorChannel *storeChannel, *quickChannel; + NSException *error; + NSString *delsql; + NSCalendarDate *nowDate; + + /* check preconditions */ + + if (_name == nil) { + return [NSException exceptionWithName:@"GCSDeleteException" + reason:@"no content filename was provided" + userInfo:nil]; + } + + if (doLogStore) + [self logWithFormat:@"should delete content: '%@'", _name]; + + /* open channels */ + + if ((storeChannel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@"could not open storage channel!"]; + return nil; + } + if (!self->ofFlags.sameTableForQuick) { + if ((quickChannel = [self acquireQuickChannel]) == nil) { + [self errorWithFormat:@"could not open quick channel!"]; + [self releaseChannel:storeChannel]; + return nil; + } + } + + /* delete rows */ + nowDate = [NSCalendarDate calendarDate]; + + delsql = [@"UPDATE " stringByAppendingString:[self storeTableName]]; + delsql = [delsql stringByAppendingString:@" SET c_deleted = 1"]; + delsql = [delsql stringByAppendingFormat:@", c_lastmodified = %u", + (unsigned int) [nowDate timeIntervalSince1970]]; + delsql = [delsql stringByAppendingString:@" WHERE c_name="]; + delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]]; + if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) { + [self errorWithFormat: + @"%s: cannot delete content '%@': %@", + __PRETTY_FUNCTION__, delsql, error]; + } + else if (!self->ofFlags.sameTableForQuick) { + /* content row deleted, now delete the quick row */ + delsql = [@"DELETE FROM " stringByAppendingString:[self quickTableName]]; + delsql = [delsql stringByAppendingString:@" WHERE c_name="]; + delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]]; + if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) { + [self errorWithFormat: + @"%s: cannot delete quick row '%@': %@", + __PRETTY_FUNCTION__, delsql, error]; + /* + Note: we now have a "broken" record, needs to be periodically GCed by + a script! + */ + } + } + + /* release channels and return */ + + [self releaseChannel:storeChannel]; + if (!self->ofFlags.sameTableForQuick) + [self releaseChannel:quickChannel]; + return error; +} + +- (NSException *)deleteFolder { + EOAdaptorChannel *channel; + NSString *delsql; + NSString *table; + + /* open channels */ + + if ((channel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@"could not open channel!"]; + return nil; + } + + /* delete rows */ + + table = [self storeTableName]; + if ([table length] > 0) { + delsql = [@"DROP TABLE " stringByAppendingString: table]; + [channel evaluateExpressionX:delsql]; + } + table = [self quickTableName]; + if ([table length] > 0) { + delsql = [@"DROP TABLE " stringByAppendingString: table]; + [channel evaluateExpressionX:delsql]; + } + table = [self aclTableName]; + if ([table length] > 0) { + delsql = [@"DROP TABLE " stringByAppendingString: table]; + [channel evaluateExpressionX:delsql]; + } + + [self releaseChannel:channel]; + + return nil; +} + +- (NSString *)columnNameForFieldName:(NSString *)_fieldName { + return _fieldName; +} + +/* SQL generation */ + +- (NSString *)generateSQLForSortOrderings:(NSArray *)_so { + NSMutableString *sql; + unsigned i, count; + + if ((count = [_so count]) == 0) + return nil; + + sql = [NSMutableString stringWithCapacity:(count * 16)]; + for (i = 0; i < count; i++) { + EOSortOrdering *so; + NSString *column; + SEL sel; + + so = [_so objectAtIndex:i]; + sel = [so selector]; + column = [self columnNameForFieldName:[so key]]; + + if (i > 0) [sql appendString:@", "]; + + if (sel_eq(sel, EOCompareAscending)) { + [sql appendString:column]; + [sql appendString:@" ASC"]; + } + else if (sel_eq(sel, EOCompareDescending)) { + [sql appendString:column]; + [sql appendString:@" DESC"]; + } + else if (sel_eq(sel, EOCompareCaseInsensitiveAscending)) { + [sql appendString:@"UPPER("]; + [sql appendString:column]; + [sql appendString:@") ASC"]; + } + else if (sel_eq(sel, EOCompareCaseInsensitiveDescending)) { + [sql appendString:@"UPPER("]; + [sql appendString:column]; + [sql appendString:@") DESC"]; + } + else { + [self logWithFormat:@"cannot handle sort selector in store: %@", + NSStringFromSelector(sel)]; + } + } + return sql; +} + +- (NSString *)generateSQLForQualifier:(EOQualifier *)_q { + NSMutableString *ms; + + if (_q == nil) return nil; + ms = [NSMutableString stringWithCapacity:32]; + [_q _gcsAppendToString:ms]; + return ms; +} + +/* fetching */ + +- (NSArray *)fetchFields:(NSArray *)_flds + fetchSpecification:(EOFetchSpecification *)_fs +{ + EOQualifier *qualifier; + NSArray *sortOrderings; + EOAdaptorChannel *channel; + NSException *error; + NSMutableString *sql; + NSArray *attrs; + NSMutableArray *results; + NSDictionary *row; + + qualifier = [_fs qualifier]; + sortOrderings = [_fs sortOrderings]; + +#if 0 + [self logWithFormat:@"FETCH: %@", _flds]; + [self logWithFormat:@" MATCH: %@", _q]; +#endif + + /* generate SQL */ + + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"SELECT "]; + if (_flds == nil) + [sql appendString:@"*"]; + else { + unsigned i, count; + + count = [_flds count]; + for (i = 0; i < count; i++) { + if (i > 0) [sql appendString:@", "]; + [sql appendString:[self columnNameForFieldName:[_flds objectAtIndex:i]]]; + } + } + [sql appendString:@" FROM "]; + [sql appendString:[self quickTableName]]; + + if (qualifier != nil) { + [sql appendString:@" WHERE "]; + [sql appendString:[self generateSQLForQualifier:qualifier]]; + } + if ([sortOrderings count] > 0) { + [sql appendString:@" ORDER BY "]; + [sql appendString:[self generateSQLForSortOrderings:sortOrderings]]; + } +#if 0 + /* limit */ + [sql appendString:@" LIMIT "]; // count + [sql appendString:@" OFFSET "]; // index from 0 +#endif + + /* open channel */ + + if ((channel = [self acquireStoreChannel]) == nil) { + [self errorWithFormat:@" could not open storage channel!"]; + return nil; + } + + /* run SQL */ + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@", + __PRETTY_FUNCTION__, sql, error]; + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + results = [NSMutableArray arrayWithCapacity:64]; + attrs = [channel describeResults:NO /* do not beautify names */]; + while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) + [results addObject:row]; + + /* release channels */ + + [self releaseChannel:channel]; + + return results; +} +- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q { + EOFetchSpecification *fs; + + if (_q == nil) + fs = nil; + else { + fs = [EOFetchSpecification fetchSpecificationWithEntityName: + [self folderName] + qualifier:_q + sortOrderings:nil]; + } + return [self fetchFields:_flds fetchSpecification:fs]; +} + +- (NSArray *)fetchAclWithSpecification:(EOFetchSpecification *)_fs { + EOQualifier *qualifier; + NSArray *sortOrderings; + EOAdaptorChannel *channel; + NSException *error; + NSMutableString *sql; + NSArray *attrs; + NSMutableArray *results; + NSDictionary *row; + + qualifier = [_fs qualifier]; + sortOrderings = [_fs sortOrderings]; + +#if 0 + [self logWithFormat:@"FETCH: %@", _flds]; + [self logWithFormat:@" MATCH: %@", _q]; +#endif + + /* generate SQL */ + + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"SELECT c_uid, c_object, c_role"]; + [sql appendString:@" FROM "]; + [sql appendString:[self aclTableName]]; + + if (qualifier != nil) { + [sql appendString:@" WHERE "]; + [sql appendString:[self generateSQLForQualifier:qualifier]]; + } + if ([sortOrderings count] > 0) { + [sql appendString:@" ORDER BY "]; + [sql appendString:[self generateSQLForSortOrderings:sortOrderings]]; + } +#if 0 + /* limit */ + [sql appendString:@" LIMIT "]; // count + [sql appendString:@" OFFSET "]; // index from 0 +#endif + + /* open channel */ + + if ((channel = [self acquireAclChannel]) == nil) { + [self errorWithFormat:@"could not open acl channel!"]; + return nil; + } + + /* run SQL */ + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", + __PRETTY_FUNCTION__, sql, error]; + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + results = [NSMutableArray arrayWithCapacity:64]; + attrs = [channel describeResults:NO /* do not beautify names */]; + while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) + [results addObject:row]; + + /* release channels */ + + [self releaseChannel:channel]; + + return results; +} +- (NSArray *) fetchAclMatchingQualifier:(EOQualifier *)_q { + EOFetchSpecification *fs; + + if (_q == nil) + fs = nil; + else { + fs = [EOFetchSpecification fetchSpecificationWithEntityName: + [self folderName] + qualifier:_q + sortOrderings:nil]; + } + return [self fetchAclWithSpecification:fs]; +} + +- (void) deleteAclMatchingQualifier:(EOQualifier *)_q { + EOFetchSpecification *fs; + + if (_q != nil) { + fs = [EOFetchSpecification fetchSpecificationWithEntityName: + [self folderName] + qualifier:_q + sortOrderings:nil]; + [self deleteAclWithSpecification:fs]; + } +} + +- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs +{ + EOQualifier *qualifier; + EOAdaptorChannel *channel; + NSException *error; + NSMutableString *sql; + + qualifier = [_fs qualifier]; + if (qualifier != nil) { + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"DELETE FROM "]; + [sql appendString:[self aclTableName]]; + [sql appendString:@" WHERE "]; + [sql appendString:[self generateSQLForQualifier:qualifier]]; + } + + /* open channel */ + + if ((channel = [self acquireAclChannel]) == nil) { + [self errorWithFormat:@"could not open acl channel!"]; + return; + } + + /* run SQL */ + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", + __PRETTY_FUNCTION__, sql, error]; + [self releaseChannel:channel]; + return; + } + + [self releaseChannel:channel]; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + id tmp; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + + if (self->folderId) + [ms appendFormat:@" id=%@", self->folderId]; + else + [ms appendString:@" no-id"]; + + if ((tmp = [self path])) [ms appendFormat:@" path=%@", tmp]; + if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp]; + if ((tmp = [self location])) + [ms appendFormat:@" loc=%@", [tmp absoluteString]]; + + [ms appendString:@">"]; + return ms; +} + +@end /* GCSFolder */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h new file mode 100644 index 000000000..27aa09acc --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h @@ -0,0 +1,83 @@ +/* + 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 __GDLContentStore_GCSFolderManager_H__ +#define __GDLContentStore_GCSFolderManager_H__ + +#import + +/* + GCSFolderManager + + Objects of this class manage the "folder_info" table, they manage the + model and manage the tables required for a folder. +*/ + +@class NSString, NSArray, NSURL, NSDictionary, NSException; +@class GCSChannelManager, GCSFolder, GCSFolderType; + +@interface GCSFolderManager : NSObject +{ + GCSChannelManager *channelManager; + NSDictionary *nameToType; + NSURL *folderInfoLocation; +} + ++ (id)defaultFolderManager; +- (id)initWithFolderInfoLocation:(NSURL *)_url; + +/* accessors */ + +- (NSURL *)folderInfoLocation; +- (NSString *)folderInfoTableName; + +/* connection */ + +- (GCSChannelManager *)channelManager; +- (BOOL)canConnect; + +/* handling folder names */ + +- (NSString *)internalNameFromPath:(NSString *)_path; +- (NSArray *)internalNamesFromPath:(NSString *)_path; +- (NSString *)pathFromInternalName:(NSString *)_name; + +/* operations */ + +- (BOOL)folderExistsAtPath:(NSString *)_path; +- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag; + +- (GCSFolder *)folderAtPath:(NSString *)_path; + +- (NSException *)createFolderOfType:(NSString *)_type withName:(NSString *)_name atPath:(NSString *)_path; +- (NSException *)deleteFolderAtPath:(NSString *)_path; + +/* folder types */ + +- (GCSFolderType *)folderTypeWithName:(NSString *)_name; + +/* cache management */ + +- (void)reset; + +@end + +#endif /* __GDLContentStore_GCSFolderManager_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m new file mode 100644 index 000000000..3526fd16b --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m @@ -0,0 +1,853 @@ +/* + Copyright (C) 2004-2007 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 "GCSFolderManager.h" +#include "GCSChannelManager.h" +#include "GCSFolderType.h" +#include "GCSFolder.h" +#include "NSURL+GCS.h" +#include "EOAdaptorChannel+GCS.h" +#include "common.h" +#include +#include +#include + +/* + Required database schema: + + + c_path + c_path1, path2, path3... [quickPathCount times] + c_foldername + + TODO: + - add a local cache? +*/ + +@implementation GCSFolderManager + +static GCSFolderManager *fm = nil; +static BOOL debugOn = NO; +static BOOL debugSQLGen = NO; +static BOOL debugPathTraversal = NO; +static int quickPathCount = 4; +static NSArray *emptyArray = nil; +#if 0 +static NSString *GCSPathColumnName = @"c_path"; +static NSString *GCSTypeColumnName = @"c_folder_type"; +static NSString *GCSTypeRecordName = @"c_folder_type"; +#endif +static NSString *GCSPathRecordName = @"c_path"; +static NSString *GCSGenericFolderTypeName = @"Container"; +static const char *GCSPathColumnPattern = "c_path%i"; +static NSCharacterSet *asciiAlphaNumericCS = nil; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"GCSFolderManagerDebugEnabled"]; + debugSQLGen = [ud boolForKey:@"GCSFolderManagerSQLDebugEnabled"]; + emptyArray = [[NSArray alloc] init]; + if (!asciiAlphaNumericCS) + { + asciiAlphaNumericCS + = [NSCharacterSet characterSetWithCharactersInString: + @"0123456789" + @"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + @"abcdefghijklmnopqrstuvwxyz"]; + [asciiAlphaNumericCS retain]; + } +} + ++ (id)defaultFolderManager { + NSString *s; + NSURL *url; + if (fm) return fm; + + s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSFolderInfoURL"]; + if ([s length] == 0) { + NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not configured.", + __PRETTY_FUNCTION__); + return nil; + } + if ((url = [NSURL URLWithString:s]) == nil) { + NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not a valid URL: '%@'", + __PRETTY_FUNCTION__, s); + return nil; + } + if ((fm = [[self alloc] initWithFolderInfoLocation:url]) == nil) { + NSLog(@"ERROR(%s): could not create folder manager with URL: '%@'", + __PRETTY_FUNCTION__, [url absoluteString]); + return nil; + } + + NSLog(@"Note: setup default manager at: %@", url); + return fm; +} + +- (NSDictionary *)loadDefaultFolderTypes { + NSMutableDictionary *typeMap; + NSArray *types; + unsigned i, count; + + + types = [[GCSFolderType resourceLocator] lookupAllFilesWithExtension:@"ocs" + doReturnFullPath:NO]; + if ((count = [types count]) == 0) { + [self logWithFormat:@"Note: no GCS folder types found."]; + return nil; + } + + typeMap = [NSMutableDictionary dictionaryWithCapacity:count]; + + [self logWithFormat:@"Note: loading %d GCS folder types:", count]; + for (i = 0, count = [types count]; i < count; i++) { + NSString *type; + GCSFolderType *typeObject; + + type = [[types objectAtIndex:i] stringByDeletingPathExtension]; + typeObject = [[GCSFolderType alloc] initWithFolderTypeName:type]; + + [self logWithFormat:@" %@: %s", + type, [typeObject isNotNull] ? "OK" : "FAIL"]; + [typeMap setObject:typeObject forKey:type]; + [typeObject release]; + } + + return typeMap; +} + +- (id)initWithFolderInfoLocation:(NSURL *)_url { + if (_url == nil) { + [self logWithFormat:@"ERROR(%s): missing folder info url!", + __PRETTY_FUNCTION__]; + [self release]; + return nil; + } + if ((self = [super init])) { + self->channelManager = [[GCSChannelManager defaultChannelManager] retain]; + self->folderInfoLocation = [_url retain]; + + if ([[self folderInfoTableName] length] == 0) { + [self logWithFormat:@"ERROR(%s): missing tablename in URL: %@", + __PRETTY_FUNCTION__, [_url absoluteString]]; + [self release]; + return nil; + } + + /* register default folder types */ + self->nameToType = [[self loadDefaultFolderTypes] copy]; + } + return self; +} + +- (void)dealloc { + [self->nameToType release]; + [self->folderInfoLocation release]; + [self->channelManager release]; + [super dealloc]; +} + +/* accessors */ + +- (NSURL *)folderInfoLocation { + return self->folderInfoLocation; +} + +- (NSString *)folderInfoTableName { + return [[self folderInfoLocation] gcsTableName]; +} + +/* connection */ + +- (GCSChannelManager *)channelManager { + return self->channelManager; +} + +- (EOAdaptorChannel *)acquireOpenChannel { + EOAdaptorChannel *ch; + + ch = [[self channelManager] acquireOpenChannelForURL: + [self folderInfoLocation]]; + return ch; +} +- (void)releaseChannel:(EOAdaptorChannel *)_channel { + [[self channelManager] releaseChannel:_channel]; + if (debugOn) [self debugWithFormat:@"released channel: %@", _channel]; +} + +- (BOOL)canConnect { + return [[self channelManager] canConnect:[self folderInfoLocation]]; +} + +- (NSArray *)performSQL:(NSString *)_sql { + EOAdaptorChannel *channel; + NSException *ex; + NSMutableArray *rows; + NSDictionary *row; + NSArray *attrs; + + /* acquire channel */ + + if ((channel = [self acquireOpenChannel]) == nil) { + if (debugOn) [self debugWithFormat:@"could not acquire channel!"]; + return nil; + } + if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel]; + + /* run SQL */ + + if ((ex = [channel evaluateExpressionX:_sql]) != nil) { + [self logWithFormat:@"ERROR(%s): cannot execute\n SQL '%@':\n %@", + __PRETTY_FUNCTION__, _sql, ex]; + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + attrs = [channel describeResults:NO /* do not beautify names */]; + rows = [NSMutableArray arrayWithCapacity:16]; + while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) + [rows addObject:row]; + + [self releaseChannel:channel]; + return rows; +} + +/* row factory */ + +- (GCSFolder *)folderForRecord:(NSDictionary *)_record { + GCSFolder *folder; + GCSFolderType *folderType; + NSString *folderTypeName, *locationString, *folderName, *path; + NSNumber *folderId; + NSURL *location, *quickLocation, *aclLocation; + + if (_record == nil) return nil; + + folderTypeName = [_record objectForKey:@"c_folder_type"]; + if (![folderTypeName isNotNull]) { + [self logWithFormat:@"ERROR(%s): missing type in folder: %@", + __PRETTY_FUNCTION__, _record]; + return nil; + } + if ((folderType = [self folderTypeWithName:folderTypeName]) == nil) { + [self logWithFormat: + @"ERROR(%s): could not resolve type '%@' of folder: %@", + __PRETTY_FUNCTION__, + folderTypeName, [_record valueForKey:@"c_path"]]; + return nil; + } + + folderId = [_record objectForKey:@"c_folder_id"]; + folderName = [_record objectForKey:@"c_path"]; + path = [self pathFromInternalName:folderName]; + + locationString = [_record objectForKey:@"c_location"]; + location = [locationString isNotNull] + ? [NSURL URLWithString:locationString] + : nil; + if (location == nil) { + [self logWithFormat:@"ERROR(%s): missing folder location in record: %@", + __PRETTY_FUNCTION__, _record]; + return nil; + } + + locationString = [_record objectForKey:@"c_quick_location"]; + quickLocation = [locationString isNotNull] + ? [NSURL URLWithString:locationString] + : nil; + + if (quickLocation == nil) { + [self logWithFormat:@"WARNING(%s): missing quick location in record: %@", + __PRETTY_FUNCTION__, _record]; + } + + locationString = [_record objectForKey:@"c_acl_location"]; + aclLocation = [locationString isNotNull] + ? [NSURL URLWithString:locationString] + : nil; + + folder = [[GCSFolder alloc] initWithPath:path primaryKey:folderId + folderTypeName:folderTypeName + folderType:folderType + location:location quickLocation:quickLocation + aclLocation:aclLocation + folderManager:self]; + return [folder autorelease]; +} + +/* path SQL */ + +- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names + exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs +{ + /* generates a WHERE qualifier for matching the "quick" entries */ + NSMutableString *sql; + unsigned i, count; + + if ((count = [_names count]) == 0) { + [self debugWithFormat:@"WARNING(%s): passed in empty name array!", + __PRETTY_FUNCTION__]; + return @"1 = 2"; + } + + sql = [NSMutableString stringWithCapacity:(count * 8)]; + for (i = 0; i < quickPathCount; i++) { + NSString *pathColumn; + char buf[32]; + + sprintf(buf, GCSPathColumnPattern, (i + 1)); + pathColumn = [[NSString alloc] initWithCString:buf]; + + /* Note: the AND addition must be inside the if's for non-exact stuff */ + + if (i < count) { + /* exact match, regular column */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + [sql appendFormat:@" = '%@'", [_names objectAtIndex:i]]; + } + else if (_beExact) { + /* exact match, ensure that all additional quick-cols are NULL */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + [sql appendString:@" IS NULL"]; + if (debugPathTraversal) [self logWithFormat:@"BE EXACT, NULL columns"]; + } + else if (_directSubs) { + /* fetch immediate subfolders */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + if (i == count) { + /* if it is a direct subfolder, the next path cannot be empty */ + [sql appendString:@" IS NOT NULL"]; + if (debugPathTraversal) + [self logWithFormat:@"DIRECT SUBS, first level"]; + } + else { + /* but for 'direct' subfolders, all following things must be empty */ + [sql appendString:@" IS NULL"]; + if (debugPathTraversal) + [self logWithFormat:@"DIRECT SUBS, lower level"]; + } + } + + [pathColumn release]; + } + + if (_beExact && (count > quickPathCount)) { + [sql appendString:@" AND c_foldername = '"]; + [sql appendString:[_names lastObject]]; + [sql appendString:@"'"]; + } + + return sql; +} + +- (NSString *)generateSQLPathFetchForInternalNames:(NSArray *)_names + exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs +{ + /* fetches the 'path' subset for a given quick-names */ + NSMutableString *sql; + NSString *ws; + + ws = [self generateSQLWhereForInternalNames:_names + exactMatch:_beExact orDirectSubfolderMatch:_directSubs]; + if ([ws length] == 0) + return nil; + + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"SELECT c_path FROM "]; + [sql appendString:[self folderInfoTableName]]; + [sql appendString:@" WHERE "]; + [sql appendString:ws]; + if (debugSQLGen) [self logWithFormat:@"PathFetch-SQL: %@", sql]; + return sql; +} + +/* handling folder names */ + +- (BOOL)_isStandardizedPath:(NSString *)_path { + if (![_path isAbsolutePath]) return NO; + if ([_path rangeOfString:@".."].length > 0) return NO; + if ([_path rangeOfString:@"~"].length > 0) return NO; + if ([_path rangeOfString:@"//"].length > 0) return NO; + return YES; +} + +- (NSString *)internalNameFromPath:(NSString *)_path { + // TODO: ensure proper path and SQL escaping! + + if (![self _isStandardizedPath:_path]) { + [self debugWithFormat:@"%s: not a standardized path: '%@'", + __PRETTY_FUNCTION__, _path]; + return nil; + } + + if ([_path hasSuffix:@"/"] && [_path length] > 1) + _path = [_path substringToIndex:([_path length] - 1)]; + + return _path; +} +- (NSArray *)internalNamesFromPath:(NSString *)_path { + NSString *fname; + NSArray *fnames; + + if ((fname = [self internalNameFromPath:_path]) == nil) + return nil; + + if ([fname hasPrefix:@"/"]) + fname = [fname substringFromIndex:1]; + + fnames = [fname componentsSeparatedByString:@"/"]; + if ([fnames count] == 0) + return nil; + + return fnames; +} +- (NSString *)pathFromInternalName:(NSString *)_name { + /* for incomplete pathes, like '/Users/helge/' */ + return _name; +} +- (NSString *)pathPartFromInternalName:(NSString *)_name { + /* for incomplete pathes, like 'Users/' */ + return _name; +} + +- (NSDictionary *)filterRecords:(NSArray *)_records forPath:(NSString *)_path { + unsigned i, count; + NSString *name; + + if (_records == nil) return nil; + if ((name = [self internalNameFromPath:_path]) == nil) return nil; + + for (i = 0, count = [_records count]; i < count; i++) { + NSDictionary *record; + NSString *recName; + + record = [_records objectAtIndex:i]; + recName = [record objectForKey:GCSPathRecordName]; +#if 0 + [self logWithFormat:@"check '%@' vs '%@' (%@)...", + name, recName, [_records objectAtIndex:i]]; +#endif + + if ([name isEqualToString:recName]) + return [_records objectAtIndex:i]; + } + return nil; +} + +- (BOOL)folderExistsAtPath:(NSString *)_path { + NSString *fname; + NSArray *fnames, *records; + NSString *sql; + unsigned count; + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return NO; + } + + sql = [self generateSQLPathFetchForInternalNames:fnames + exactMatch:YES orDirectSubfolderMatch:NO]; + if ([sql length] == 0) { + [self debugWithFormat:@"got no SQL for names: %@", fnames]; + return NO; + } + + if ((records = [self performSQL:sql]) == nil) { + [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", + __PRETTY_FUNCTION__, sql]; + return NO; + } + + if ((count = [records count]) == 0) + return NO; + + fname = [self internalNameFromPath:_path]; + if (count == 1) { + NSDictionary *record; + NSString *sname; + + record = [records objectAtIndex:0]; + sname = [record objectForKey:GCSPathRecordName]; + return [fname isEqualToString:sname]; + } + + [self logWithFormat:@"records: %@", records]; + + return NO; +} + +- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_recursive{ + NSMutableArray *result; + NSString *fname; + NSArray *fnames, *records; + NSString *sql; + unsigned i, count; + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return nil; + } + + sql = [self generateSQLPathFetchForInternalNames:fnames + exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)]; + if ([sql length] == 0) { + [self debugWithFormat:@"got no SQL for names: %@", fnames]; + return nil; + } + + if ((records = [self performSQL:sql]) == nil) { + [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", + __PRETTY_FUNCTION__, sql]; + return nil; + } + + if ((count = [records count]) == 0) + return emptyArray; + + result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)]; + + fname = [self internalNameFromPath:_path]; + fname = [fname stringByAppendingString:@"/"]; /* add slash */ + for (i = 0; i < count; i++) { + NSDictionary *record; + NSString *sname, *spath; + + record = [records objectAtIndex:i]; + sname = [record objectForKey:GCSPathRecordName]; + if (![sname hasPrefix:fname]) /* does not match at all ... */ + continue; + + /* strip prefix and following slash */ + sname = [sname substringFromIndex:[fname length]]; + spath = [self pathPartFromInternalName:sname]; + + if (_recursive) { + if ([spath length] > 0) [result addObject:spath]; + } + else { + /* direct children only, so exclude everything with a slash */ + if ([sname rangeOfString:@"/"].length == 0 && [spath length] > 0) + [result addObject:spath]; + } + } + + return result; +} + +- (GCSFolder *)folderAtPath:(NSString *)_path { + NSMutableString *sql; + NSArray *fnames, *records; + NSString *ws; + NSDictionary *record; + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return nil; + } + + /* generate SQL to fetch folder attributes */ + + ws = [self generateSQLWhereForInternalNames:fnames + exactMatch:YES orDirectSubfolderMatch:NO]; + + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"SELECT "]; + [sql appendString:@"c_folder_id, "]; + [sql appendString:@"c_path, "]; + [sql appendString:@"c_location, c_quick_location, c_acl_location,"]; + [sql appendString:@" c_folder_type"]; + [sql appendString:@" FROM "]; + [sql appendString:[self folderInfoTableName]]; + [sql appendString:@" WHERE "]; + [sql appendString:ws]; + + if (debugSQLGen) [self logWithFormat:@"folderAtPath: %@", sql]; + + /* fetching */ + + if ((records = [self performSQL:sql]) == nil) { + [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", + __PRETTY_FUNCTION__, sql]; + return nil; + } + + // TODO: need to filter on path + // required when we start to have deeper hierarchies + // => isn't that already done below? + + if ([records count] != 1) { + if ([records count] == 0) { + [self debugWithFormat:@"found no records for path: '%@'", _path]; + return nil; + } + + [self logWithFormat:@"ERROR(%s): more than one row for path: '%@'", + __PRETTY_FUNCTION__, _path]; + return nil; + } + + if ((record = [self filterRecords:records forPath:_path]) == nil) { + [self debugWithFormat:@"found no record for path: '%@'", _path]; + return nil; + } + + return [self folderForRecord:record]; +} + +- (NSString *)baseTableNameWithUID:(NSString *)_uid { + NSDate *now; + unichar currentChar; + unsigned int count, max, done; + NSMutableString *newUID; + + newUID = [NSMutableString string]; + now = [NSDate date]; + + max = [_uid length]; + done = 0; + count = 0; + while (done < 8 && count < max) + { + currentChar = [_uid characterAtIndex: count]; + if ([asciiAlphaNumericCS characterIsMember: currentChar]) + { + [newUID appendFormat: @"%c", currentChar]; + done++; + } + count++; + } + + return [NSString stringWithFormat: @"%@%u", + newUID, [now timeIntervalSince1970]]; +} + +- (NSException *)createFolderOfType:(NSString *)_type + withName:(NSString*)_name atPath:(NSString *)_path +{ + // TBD: badly broken, needs to be wrapped in a transaction. + // TBD: would be best to perform all operations as a single SQL statement. + GCSFolderType *ftype; + NSString *tableName, *quickTableName, *aclTableName; + NSString *baseURL, *pathElement; + EOAdaptorChannel *channel; + NSEnumerator *pathElements; + NSMutableArray *paths; + NSException *error; + NSString *sql; + + paths = [[NSMutableArray alloc] initWithCapacity: 5]; + + pathElements = [[_path componentsSeparatedByString: @"/"] objectEnumerator]; + while ((pathElement = [pathElements nextObject]) != nil) { + NSString *p = [[NSString alloc] initWithFormat: @"'%@'", pathElement]; + [paths addObject: p]; + [p release]; p = nil; + } + while ([paths count] < 5) + [paths addObject: @"NULL"]; + + // TBD: fix SQL injection issue! + sql = [NSString stringWithFormat: @"SELECT * FROM %@ WHERE c_path = '%@'", + [self folderInfoTableName], _path]; + if ([[self performSQL: sql] isNotEmpty]) { + return [NSException exceptionWithName:@"GCSExitingFolder" + reason:@"a folder already exists at that path" + userInfo:nil]; + } + if ((ftype = [self folderTypeWithName:_type]) == nil) { + return [NSException exceptionWithName:@"GCSMissingFolderType" + reason:@"missing folder type"userInfo:nil]; + } + if ((channel = [self acquireOpenChannel]) == nil) { + return [NSException exceptionWithName:@"GCSNoChannel" + reason:@"could not open channel" + userInfo:nil]; + } + + tableName = [self baseTableNameWithUID: [paths objectAtIndex: 2]]; + quickTableName = [tableName stringByAppendingString: @"_quick"]; + aclTableName = [tableName stringByAppendingString: @"_acl"]; + + sql = [@"DROP TABLE " stringByAppendingString:quickTableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) + ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2) + + sql = [@"DROP TABLE " stringByAppendingString:tableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) + ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2) + + sql = [@"DROP TABLE " stringByAppendingString:aclTableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) + ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2) + + if ((error = [channel createGCSFolderTableWithName: tableName]) != nil) + return error; + + sql = [ftype sqlQuickCreateWithTableName: quickTableName]; + if (debugSQLGen) [self logWithFormat:@"quick-Create: %@", sql]; + + if ((error = [channel evaluateExpressionX:sql]) != nil) { + /* 'rollback' TBD: wrap in proper tx */ + sql = [@"DROP TABLE " stringByAppendingString:tableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self warnWithFormat:@"failed to drop freshly created table: %@", + tableName]; + } + + return error; + } + + if (debugSQLGen) [self logWithFormat:@"acl-Create: %@", sql]; + if ((error = [channel createGCSFolderACLTableWithName: aclTableName]) + != nil) { + /* 'rollback' TBD: wrap in proper tx */ + sql = [@"DROP TABLE " stringByAppendingString:quickTableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self warnWithFormat:@"failed to drop freshly created table: %@", + tableName]; + } + sql = [@"DROP TABLE " stringByAppendingString:tableName]; + if ((error = [channel evaluateExpressionX:sql]) != nil) { + [self warnWithFormat:@"failed to drop freshly created table: %@", + tableName]; + } + + return error; + } + + // TBD: fix SQL injection issues + baseURL + = [[folderInfoLocation absoluteString] stringByDeletingLastPathComponent]; + + sql = [NSString stringWithFormat: @"INSERT INTO %@" + @" (c_path, c_path1, c_path2, c_path3, c_path4," + @" c_foldername, c_location, c_quick_location," + @" c_acl_location, c_folder_type)" + @" VALUES ('%@', %@, %@, %@, %@, '%@', '%@/%@'," + @" '%@/%@', '%@/%@', '%@')", + [self folderInfoTableName], _path, + [paths objectAtIndex: 1], [paths objectAtIndex: 2], + [paths objectAtIndex: 3], [paths objectAtIndex: 4], + _name, + baseURL, tableName, + baseURL, quickTableName, + baseURL, aclTableName, + _type]; + if ((error = [channel evaluateExpressionX:sql]) != nil) + return error; + + [paths release]; paths = nil; + [self releaseChannel: channel]; + + return nil; +} + +- (NSException *)deleteFolderAtPath:(NSString *)_path { + GCSFolder *folder; + NSArray *fnames; + NSString *sql, *ws; + EOAdaptorChannel *channel; + NSException *ex; + + if ((folder = [self folderAtPath:_path]) == nil) { + return [NSException exceptionWithName:@"GCSMissingFolder" + reason:@"missing folder" + userInfo:nil]; + } + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return nil; + } + + ws = [self generateSQLWhereForInternalNames:fnames + exactMatch:YES orDirectSubfolderMatch:NO]; + + sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@", + [self folderInfoTableName], ws]; + if ((channel = [self acquireOpenChannel]) == nil) { + return [NSException exceptionWithName:@"GCSNoChannel" + reason:@"could not " + userInfo:nil]; + } + + if ((ex = [channel evaluateExpressionX:sql]) != nil) { + [self releaseChannel:channel]; + return ex; + } + + [self releaseChannel:channel]; + + return [folder deleteFolder]; +} + +/* folder types */ + +- (GCSFolderType *)folderTypeWithName:(NSString *)_name { + NSString *specificName; + GCSFolderType *type; + + if ([_name length] == 0) + _name = GCSGenericFolderTypeName; + + specificName = [NSString stringWithFormat: @"%@-%@", + _name, [folderInfoLocation scheme]]; + type = [self->nameToType objectForKey: [specificName lowercaseString]]; + if (!type) + type = [self->nameToType objectForKey:[_name lowercaseString]]; + + return type; +} + +/* cache management */ + +- (void)reset { + /* does nothing in the moment, but we need a way to signal refreshes */ +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + + [ms appendFormat:@" url=%@", [self->folderInfoLocation absoluteString]]; + [ms appendFormat:@" channel-manager=0x%p", [self channelManager]]; + + [ms appendString:@">"]; + return ms; +} + +@end /* GCSFolderManager */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h new file mode 100644 index 000000000..8c62e0521 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h @@ -0,0 +1,82 @@ +/* + 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 __GDLContentStore_GCSFolderType_H__ +#define __GDLContentStore_GCSFolderType_H__ + +/* + GCSFolderType + + This is the "model" of a folder, it specifies what quick attributes are + available, in which tables it is stored and how it is retrieved. + + For now, we only support one 'quick' table (we might want to have multiple + ones in the future). + + Note: using the 'folderQualifier' we are actually prepared for 'multiple + folders in a single table' setups. So in case we want to go that + route later, we can still do it :-) +*/ + +#import + +@class NSString, NSArray, NSDictionary; +@class EOQualifier; +@class NGResourceLocator; +@class GCSFolder, GCSFieldExtractor; + +@interface GCSFolderType : NSObject +{ + NSString *blobTablePattern; // eg 'SOGo_$folderId$_blob + NSString *quickTablePattern; // eg 'SOGo_$folderId$_quick + NSArray *fields; // GCSFieldInfo objects + NSDictionary *fieldDict; // maps a name to GCSFieldInfo + EOQualifier *folderQualifier; // to further limit the table set + NSString *extractorClassName; + GCSFieldExtractor *extractor; +} + ++ (id)folderTypeWithName:(NSString *)_type; + +- (id)initWithPropertyList:(id)_plist; +- (id)initWithContentsOfFile:(NSString *)_path; +- (id)initWithFolderTypeName:(NSString *)_typeName; + +/* operations */ + +- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder; +- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder; + +/* generating SQL */ + +- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName; + +/* quick support */ + +- (GCSFieldExtractor *)quickExtractor; + +/* locator used to find .ocs files */ + ++ (NGResourceLocator *)resourceLocator; + +@end + +#endif /* __GDLContentStore_GCSFolderType_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m new file mode 100644 index 000000000..76f2d36bb --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m @@ -0,0 +1,198 @@ +/* + 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 "GCSFolderType.h" +#include "GCSFolder.h" +#include "GCSFieldInfo.h" +#include "GCSFieldExtractor.h" +#include "common.h" +#include +#include + +@implementation GCSFolderType + +- (id)initWithPropertyList:(id)_plist { + if ((self = [super init])) { + NSDictionary *plist = _plist; + + self->blobTablePattern = [[plist objectForKey:@"blobTablePattern"] copy]; + self->quickTablePattern = [[plist objectForKey:@"quickTablePattern"]copy]; + + self->extractorClassName = + [[plist objectForKey:@"extractorClassName"] copy]; + // TODO: qualifier; + + self->fields = [[GCSFieldInfo fieldsForPropertyList: + [plist objectForKey:@"fields"]] retain]; + } + return self; +} + +- (id)initWithContentsOfFile:(NSString *)_path { + NSDictionary *plist; + + plist = [NSDictionary dictionaryWithContentsOfFile:_path]; + if (plist == nil) { + NSLog(@"ERROR(%s): could not read dictionary at path %@", + __PRETTY_FUNCTION__, _path); + [self release]; + return nil; + } + return [self initWithPropertyList:plist]; +} + ++ (NGResourceLocator *)resourceLocator { + NGResourceLocator *loc; + + // TODO: fix me, GCS instead of OCS + loc = [NGResourceLocator resourceLocatorForGNUstepPath: + @"Library/OCSTypeModels" + fhsPath:@"share/ocs"]; + return loc; +} + ++ (id)folderTypeWithName:(NSString *)_typeName { + NSString *filename, *path; + + // TODO: fix me, GCS instead of OCS + filename = [_typeName stringByAppendingPathExtension:@"ocs"]; + path = [[self resourceLocator] lookupFileWithName:filename]; + + if (path != nil) + return [[self alloc] initWithContentsOfFile:path]; + + NSLog(@"ERROR(%s): did not find model for type: '%@'", + __PRETTY_FUNCTION__, _typeName); + return nil; +} + +- (id)initWithFolderTypeName:(NSString *)_typeName { + // DEPRECATED + [self release]; + return [[GCSFolderType folderTypeWithName:_typeName] retain]; +} + +- (void)dealloc { + [self->extractor release]; + [self->extractorClassName release]; + [self->blobTablePattern release]; + [self->quickTablePattern release]; + [self->fields release]; + [self->fieldDict release]; + [self->folderQualifier release]; + [super dealloc]; +} + +/* operations */ + +- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder { + return [self->blobTablePattern + stringByReplacingVariablesWithBindings:_folder]; +} +- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder { + return [self->quickTablePattern + stringByReplacingVariablesWithBindings:_folder]; +} + +- (EOQualifier *)qualifierForFolder:(GCSFolder *)_folder { + NSArray *keys; + NSDictionary *bindings; + + keys = [[self->folderQualifier allQualifierKeys] allObjects]; + if ([keys count] == 0) + return self->folderQualifier; + + bindings = [_folder valuesForKeys:keys]; + return [self->folderQualifier qualifierWithBindings:bindings + requiresAllVariables:NO]; +} + +/* generating SQL */ + +- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName { + NSMutableString *sql; + unsigned i, count; + + sql = [NSMutableString stringWithCapacity:512]; + [sql appendString:@"CREATE TABLE "]; + [sql appendString:_tabName]; + [sql appendString:@" (\n"]; + + count = [self->fields count]; + for (i = 0; i < count; i++) { + if (i > 0) [sql appendString:@",\n"]; + + [sql appendString:@" "]; + [sql appendString:[[self->fields objectAtIndex:i] sqlCreateSection]]; + } + + [sql appendString:@"\n)"]; + + return sql; +} + +/* quick support */ + +- (GCSFieldExtractor *)quickExtractor { + Class clazz; + + if (self->extractor != nil) { + return [self->extractor isNotNull] + ? self->extractor : (GCSFieldExtractor *)nil; + } + + clazz = self->extractorClassName + ? NSClassFromString(self->extractorClassName) + : [GCSFieldExtractor class]; + if (clazz == Nil) { + [self logWithFormat:@"ERROR: did not find field extractor class!"]; + return nil; + } + + if ((self->extractor = [[clazz alloc] init]) == nil) { + [self logWithFormat:@"ERROR: could not create field extractor of class %@", + clazz]; + return nil; + } + return self->extractor; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:256]; + [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; + + [ms appendFormat:@" blobtable='%@'", self->blobTablePattern]; + [ms appendFormat:@" quicktable='%@'", self->quickTablePattern]; + [ms appendFormat:@" fields=%@", self->fields]; + [ms appendFormat:@" extractor=%@", self->extractorClassName]; + + if (self->folderQualifier) + [ms appendFormat:@" qualifier=%@", self->folderQualifier]; + + [ms appendString:@">"]; + return ms; +} + +@end /* GCSFolderType */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h new file mode 100644 index 000000000..cdd57c2aa --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h @@ -0,0 +1,37 @@ +/* + 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 __GDLContentStore_GCSStringFormatter_H__ +#define __GDLContentStore_GCSStringFormatter_H__ + +#include + +@interface GCSStringFormatter : NSObject < NGStringEscaping > +{ +} + ++ (id)sharedFormatter; + +- (NSString *)stringByFormattingString:(NSString *)_s; + +@end + +#endif /* __GDLContentStore_GCSStringFormatter_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m new file mode 100644 index 000000000..b08bab5e9 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m @@ -0,0 +1,63 @@ +/* + 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 "GCSStringFormatter.h" +#include "common.h" + +@implementation GCSStringFormatter + +static NSCharacterSet *escapeSet = nil; + ++ (void)initialize { + static BOOL didInit = NO; + + if(didInit) + return; + + didInit = YES; + escapeSet = + [[NSCharacterSet characterSetWithCharactersInString:@"\\'"] retain]; +} + ++ (id)sharedFormatter { + static id sharedInstance = nil; + if(!sharedInstance) { + sharedInstance = [[self alloc] init]; + } + return sharedInstance; +} + +- (NSString *)stringByFormattingString:(NSString *)_s { + NSString *s; + + s = [_s stringByEscapingCharactersFromSet:escapeSet + usingStringEscaping:self]; + return [NSString stringWithFormat:@"'%@'", s]; +} + +- (NSString *)stringByEscapingString:(NSString *)_s { + if([_s isEqualToString:@"\\"]) { + return @"\\\\"; /* easy ;-) */ + } + return @"\\'"; +} + +@end /* GCSStringFormatter */ diff --git a/SOPE/sope-gdl1/GDLContentStore/GNUmakefile b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile new file mode 100644 index 000000000..85b394e8c --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile @@ -0,0 +1,77 @@ +# GNUstep makefiles + +include ../../../config.make +include $(GNUSTEP_MAKEFILES)/common.make +include ./Version + +GNUSTEP_INSTALLATION_DIR = ${GNUSTEP_LOCAL_ROOT} + +ifneq ($(frameworks),yes) +LIBRARY_NAME = libGDLContentStore +else +FRAMEWORK_NAME = GDLContentStore +endif + +libGDLContentStore_PCH_FILE = common.h +libGDLContentStore_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION) +libGDLContentStore_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION) + +TOOL_NAME = gcs_ls gcs_mkdir gcs_cat gcs_recreatequick gcs_gensql + +libGDLContentStore_HEADER_FILES_DIR = . +libGDLContentStore_HEADER_FILES_INSTALL_DIR = /GDLContentStore +FHS_HEADER_FILES_INSTALL_DIR = $(libGDLContentStore_HEADER_FILES_INSTALL_DIR) + +libGDLContentStore_HEADER_FILES += \ + NSURL+GCS.h \ + EOAdaptorChannel+GCS.h \ + \ + GCSContext.h \ + GCSFieldInfo.h \ + GCSFolder.h \ + GCSFolderManager.h \ + GCSFolderType.h \ + GCSChannelManager.h \ + GCSFieldExtractor.h \ + GCSStringFormatter.h \ + +libGDLContentStore_OBJC_FILES += \ + NSURL+GCS.m \ + EOAdaptorChannel+GCS.m \ + EOQualifier+GCS.m \ + \ + GCSContext.m \ + GCSFieldInfo.m \ + GCSFolder.m \ + GCSFolderManager.m \ + GCSFolderType.m \ + GCSChannelManager.m \ + GCSFieldExtractor.m \ + GCSStringFormatter.m \ + +gcs_ls_OBJC_FILES += gcs_ls.m +gcs_mkdir_OBJC_FILES += gcs_mkdir.m +gcs_cat_OBJC_FILES += gcs_cat.m +gcs_gensql_OBJC_FILES += gcs_gensql.m +gcs_recreatequick_OBJC_FILES += gcs_recreatequick.m + + +# framework support + +GDLContentStore_PCH_FILE = $(libGDLContentStore_PCH_FILE) +GDLContentStore_HEADER_FILES = $(libGDLContentStore_HEADER_FILES) +GDLContentStore_OBJC_FILES = $(libGDLContentStore_OBJC_FILES) +GDLContentStore_SUBPROJECTS = $(libGDLContentStore_SUBPROJECTS) + + +# building + +-include GNUmakefile.preamble +ifneq ($(frameworks),yes) +include $(GNUSTEP_MAKEFILES)/library.make +else +include $(GNUSTEP_MAKEFILES)/framework.make +endif +include $(GNUSTEP_MAKEFILES)/tool.make +-include GNUmakefile.postamble +include fhs.make diff --git a/SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble new file mode 100644 index 000000000..ada5aa195 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble @@ -0,0 +1,78 @@ +# compilation settings + +SOPE_ROOT=../.. + +ADDITIONAL_CPPFLAGS += -Wall + +ADDITIONAL_INCLUDE_DIRS += -I. -I.. + + +# dependencies + +libGDLContentStore_LIBRARIES_DEPEND_UPON += \ + -lGDLAccess \ + -lNGExtensions -lEOControl \ + -lDOM -lSaxObjC + +GDLContentStore_LIBRARIES_DEPEND_UPON += \ + -framework GDLAccess \ + -framework NGExtensions -framework EOControl \ + -framework DOM -framework SaxObjC + +ifneq ($(frameworks),yes) +GCS_TOOL_LIBS += \ + -lGDLContentStore -lGDLAccess \ + -lNGExtensions -lEOControl \ + -lDOM -lSaxObjC +else +GCS_TOOL_LIBS += \ + -framework GDLContentStore -framework GDLAccess \ + -framework NGExtensions -framework EOControl \ + -framework DOM -framework SaxObjC +endif + +gcs_ls_TOOL_LIBS += $(GCS_TOOL_LIBS) +gcs_mkdir_TOOL_LIBS += $(GCS_TOOL_LIBS) +gcs_cat_TOOL_LIBS += $(GCS_TOOL_LIBS) +gcs_recreatequick_TOOL_LIBS += $(GCS_TOOL_LIBS) +gcs_gensql_TOOL_LIBS += $(GCS_TOOL_LIBS) + +gcs_ls_PCH_FILE += common.h +gcs_mkdir_PCH_FILE += common.h +gcs_cat_PCH_FILE += common.h +gcs_recreatequick_PCH_FILE += common.h +gcs_gensql_PCH_FILE += common.h + + +# library/framework search pathes + +DEP_DIRS = \ + . \ + ../GDLAccess \ + $(SOPE_ROOT)/sope-core/NGExtensions \ + $(SOPE_ROOT)/sope-core/EOControl \ + $(SOPE_ROOT)/sope-xml/DOM \ + $(SOPE_ROOT)/sope-xml/SaxObjC + +ifneq ($(frameworks),yes) +ADDITIONAL_LIB_DIRS += \ + $(foreach dir,$(DEP_DIRS),\ + -L$(GNUSTEP_BUILD_DIR)/$(dir)/$(GNUSTEP_OBJ_DIR_NAME)) +else +ADDITIONAL_LIB_DIRS += \ + $(foreach dir,$(DEP_DIRS),-F$(GNUSTEP_BUILD_DIR)/$(dir)) +endif + +SYSTEM_LIB_DIR += $(CONFIGURE_SYSTEM_LIB_DIR) + + +# platform specific settings + +ifeq ($(FOUNDATION_LIB),apple) +libGDLContentStore_PREBIND_ADDR="0xC7700000" +libGDLContentStore_LDFLAGS += -seg1addr $(libGDLContentStore_PREBIND_ADDR) +endif + +ifeq ($(findstring openbsd3, $(GNUSTEP_HOST_OS)), openbsd3) +GCS_TOOL_LIBS += -liconv +endif diff --git a/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h new file mode 100644 index 000000000..c03ea403c --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h @@ -0,0 +1,41 @@ +/* + 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 __GDLContentStore_NSURL_GCS_H__ +#define __GDLContentStore_NSURL_GCS_H__ + +#import + +/* + "Database URLs" + + We use the schema: + postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename] +*/ + +@interface NSURL(GCS) + +- (NSString *)gcsDatabaseName; +- (NSString *)gcsTableName; + +@end + +#endif /* __GDLContentStore_NSURL_GCS_H__ */ diff --git a/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m new file mode 100644 index 000000000..285aad651 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m @@ -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. +*/ + +#include "NSURL+GCS.h" +#include "common.h" + +@implementation NSURL(GCS) + +- (NSString *)gcsPathComponent:(unsigned)_idx { + NSString *p; + NSArray *pcs; + unsigned len; + + p = [self path]; + if ([p length] == 0) + return nil; + + pcs = [p componentsSeparatedByString:@"/"]; + if ((len = [pcs count]) == 0) + return nil; + if (len <= _idx) + return nil; + return [pcs objectAtIndex:_idx]; +} + +- (NSString *)gcsDatabaseName { + return [self gcsPathComponent:1]; +} +- (NSString *)gcsTableName { + return [[self path] lastPathComponent]; +} + +@end /* NSURL(GCS) */ diff --git a/SOPE/sope-gdl1/GDLContentStore/README b/SOPE/sope-gdl1/GDLContentStore/README new file mode 100644 index 000000000..981e3d945 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/README @@ -0,0 +1,117 @@ +Storage Backend +=============== + +The storage backend implements the "low level" folder abstraction, which is +basically an arbitary "BLOB" containing some document. The feature is that +we extract "quick access" / "searchable" attributes from the document content. + +Further it contains the "folder management" API, as named folders can be stored +in different databases. +Note: we need a way to tell where "new" folders should be created +Note: to sync with LDAP we need to periodically delete or archive old folders + +Folders have associated a type (like 'calendar') which defines the query +attributes and serialization format. + +TODO +==== +- fix some OCS naming + - defaults + - lookup directories +- hierarchies deeper than 4 (properly filter on path in OCS) + +Open Questions +============== + +System-meta-data in the blob-table or in the quick-table? +- master data belongs into the blob table +- could be regular 'NSxxx' keys to differentiate meta data from + +Class Hierarchy +=============== + + [NSObject] + OCSContext - tracking context + OCSFolder - represents a single folder + OCSFolderManager - manages folders + OCSFolderType - the mapping info for a specific folder-type + OCSFieldInfo - mapping info for one 'quick field' + OCSChannelManager - maintains EOAdaptorChannel objects + + TBD: + - field 'extractor' + - field 'value' (eg array values for participants?) + - BLOB archiver/unarchiver + +Defaults +======== + + OCSFolderInfoURL - the DB URL where the folder-info table is located + eg: http://OGo:OGo@localhost/test/folder_info + + OCSFolderManagerDebugEnabled - enable folder-manager debug logs + OCSFolderManagerSQLDebugEnabled - enable folder-manager SQL gen debug logs + + OCSChannelManagerDebugEnabled - enable channel debug pooling logs + OCSChannelManagerPoolDebugEnabled - debug pool handle allocation + + OCSChannelExpireAge - if that age in seconds is exceeded, a channel + will be removed from the pool + OCSChannelCollectionTimer - time in seconds. each n-seconds the pool will be + checked for channels too old + + [PGDebugEnabled] - enable PostgreSQL adaptor debugging + +URLs +==== + + "Database URLs" + + We use the schema: + postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename] + +Support Tools +============= + +- tools we need: + - one to recreate a quick table based on the blob table + +Notes +===== + +- need to use http:// URLs for connect info, until generic URLs in + libFoundation are fixed (the parses breaks on the login/password parts) + +QA +== + +Q: Why do we use two tables, we could store the quick columns in the blob? +== +They could be in the same table. We considered using separate tables since the +quick table is likely to be recreated now and then if BLOB indexing +requirements change. +Actually one could even use different _quick tables which share a common BLOB +table. +(a quick table is nothing more than a database index and like with DB indexes + multiple ones for different requirements can make sense). + +Further it might improve caching behaviour for row based caches (the quick +table is going to be queried much more often) - not sure whether this is +relevant with PostgreSQL, probably not? + +Q: Can we use a VARCHAR primary key? +== +We asked in the postgres IRC channel and apparently the performance penalty of +string primary keys isn't big. +We could also use an 'internal' int sequence in addition (might be useful for +supporting ZideLook) +Motivation: the 'iCalendar' ID is a string and usually looks like a GUID. + +Q: Why using VARCHAR instead of TEXT in the BLOB? +== +To quote PostgreSQL documentation: +"There are no performance differences between these three types, apart from + the increased storage size when using the blank-padded type." +So varchar(xx) is just a large TEXT. Since we intend to store mostly small +snippets of data (tiny XML fragments), we considered VARCHAR the more +appropriate type. diff --git a/SOPE/sope-gdl1/GDLContentStore/Version b/SOPE/sope-gdl1/GDLContentStore/Version new file mode 100644 index 000000000..eb3352816 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/Version @@ -0,0 +1,14 @@ +# Version file + +MAJOR_VERSION:=4 +MINOR_VERSION:=7 +SUBMINOR_VERSION:=49 + +# v4.5.29 requires libNGExtensions v4.5.161 +# v4.5.26 does not require libNGiCal anymore +# v0.9.19 requires libNGiCal v4.5.40 +# v0.9.18 requires libNGiCal v4.5.38 +# v0.9.17 requires libNGiCal v4.5.37 +# v0.9.11 requires libFoundation v1.0.63 +# v0.9.11 requires libNGExtensions v4.3.125 +# v0.9.7 requires libGDLAccess v1.1.35 diff --git a/SOPE/sope-gdl1/GDLContentStore/common.h b/SOPE/sope-gdl1/GDLContentStore/common.h new file mode 100644 index 000000000..fa68afc07 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/common.h @@ -0,0 +1,36 @@ +/* + 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: common.h 673 2005-03-20 19:09:53Z helge $ + +#import +#import + +#include + +#if NeXT_RUNTIME || APPLE_RUNTIME +# define objc_free(__mem__) free(__mem__) +# define objc_malloc(__size__) malloc(__size__) +# define objc_calloc(__cnt__, __size__) calloc(__cnt__, __size__) +# define objc_realloc(__ptr__, __size__) realloc(__ptr__, __size__) +# ifndef sel_eq +# define sel_eq(sela,selb) (sela==selb?YES:NO) +# endif +#endif diff --git a/SOPE/sope-gdl1/GDLContentStore/fhs.make b/SOPE/sope-gdl1/GDLContentStore/fhs.make new file mode 100644 index 000000000..24d1e43fc --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/fhs.make @@ -0,0 +1,44 @@ +# postprocessing + +# FHS support (this is a hack and is going to be done by gstep-make!) + +# NOTE: you need to define FHS_HEADER_FILES_INSTALL_DIR for one library + +ifneq ($(FHS_INSTALL_ROOT),) + +FHS_INCLUDE_DIR=$(FHS_INSTALL_ROOT)/include/ +FHS_BIN_DIR=$(FHS_INSTALL_ROOT)/bin +FHS_LIB_DIR=$(CONFIGURE_FHS_INSTALL_LIBDIR) + +NONFHS_LIBDIR="$(GNUSTEP_LIBRARIES)/$(GNUSTEP_TARGET_LDIR)/" +NONFHS_LIBNAME="$(LIBRARY_NAME)$(LIBRARY_NAME_SUFFIX)$(SHARED_LIBEXT)" +NONFHS_BINDIR="$(GNUSTEP_TOOLS)/$(GNUSTEP_TARGET_LDIR)" + + +fhs-header-dirs :: + $(MKDIRS) $(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR) + +fhs-bin-dirs :: + $(MKDIRS) $(FHS_BIN_DIR) + + +move-headers-to-fhs :: fhs-header-dirs + @echo "moving headers to $(FHS_INCLUDE_DIR) .." + mv $(GNUSTEP_HEADERS)$(FHS_HEADER_FILES_INSTALL_DIR)/*.h \ + $(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR)/ + +move-libs-to-fhs :: + @echo "moving libs to $(FHS_LIB_DIR) .." + mv $(NONFHS_LIBDIR)/$(NONFHS_LIBNAME)* $(FHS_LIB_DIR)/ + +move-tools-to-fhs :: fhs-bin-dirs + @echo "moving tools from $(NONFHS_BINDIR) to $(FHS_BIN_DIR) .." + for i in $(TOOL_NAME); do \ + mv "$(NONFHS_BINDIR)/$${i}" $(FHS_BIN_DIR); \ + done + +move-to-fhs :: move-headers-to-fhs move-libs-to-fhs move-tools-to-fhs + +after-install :: move-to-fhs + +endif diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_cat.m b/SOPE/sope-gdl1/GDLContentStore/gcs_cat.m new file mode 100644 index 000000000..7cf768c2e --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/gcs_cat.m @@ -0,0 +1,122 @@ +/* + 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. +*/ + +#import + +@class NSUserDefaults, NSArray; +@class GCSFolderManager; + +@interface Tool : NSObject +{ + NSUserDefaults *ud; + GCSFolderManager *folderManager; +} + ++ (int)runWithArgs:(NSArray *)_args; +- (int)run; + +@end + +#include +#include +#include "common.h" + +@implementation Tool + +- (id)init { + if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; + self->folderManager = [[GCSFolderManager defaultFolderManager] retain]; + } + return self; +} +- (void)dealloc { + [self->ud release]; + [self->folderManager release]; + [super dealloc]; +} + +/* operation */ + +- (int)runOnPath:(NSString *)_path { + GCSFolder *folder; + NSString *dirname, *filename; + NSString *content; + + dirname = [_path stringByDeletingLastPathComponent]; + filename = [_path lastPathComponent]; + + if ((folder = [self->folderManager folderAtPath:dirname]) == nil) { + [self logWithFormat:@"did not find folder for file: '%@'", dirname]; + return 1; + } + + if ((content = [folder fetchContentWithName:filename]) == nil) { + [self logWithFormat:@"did not find file: '%@'", _path]; + return 1; + } + + printf("%s\n", [content cString]); + + return 0; +} + +- (int)run { + NSEnumerator *e; + NSString *path; + + [self logWithFormat:@"manager: %@", self->folderManager]; + + if (![self->folderManager canConnect]) { + [self logWithFormat:@"cannot connect folder-info database!"]; + return 1; + } + + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + while ((path = [e nextObject])) + [self runOnPath:path]; + + return 0; +} ++ (int)runWithArgs:(NSArray *)_args { + return [(Tool *)[[[self alloc] init] autorelease] run]; +} + +@end /* Tool */ + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + rc = [Tool runWithArgs: + [[NSProcessInfo processInfo] argumentsWithoutDefaults]]; + + [pool release]; + return rc; +} diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m b/SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m new file mode 100644 index 000000000..13e0caeae --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m @@ -0,0 +1,114 @@ +/* + 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. +*/ + +#import + +@class NSUserDefaults, NSArray; +@class GCSFolderManager; + +@interface Tool : NSObject +{ + NSUserDefaults *ud; +} + ++ (int)runWithArgs:(NSArray *)_args; +- (int)run; + +@end + +#include +#include "common.h" + +@implementation Tool + +- (id)init { + if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; + } + return self; +} +- (void)dealloc { + [self->ud release]; + [super dealloc]; +} + +/* operation */ + +- (int)runOnTable:(NSString *)_tableName typeName:(NSString *)_typeName { + GCSFolderType *folderType; + + if ((folderType = [GCSFolderType folderTypeWithName:_typeName]) != nil) { + NSString *s; + + s = [folderType sqlQuickCreateWithTableName:_tableName]; + + fwrite([s cString], 1, [s cStringLength], stdout); + printf("\n"); + } + else { + fprintf(stderr, "ERROR: did not find GCS type: '%s'\n", + [_typeName cString]); + } + + return 0; +} + +- (int)run { + NSEnumerator *e; + NSString *tableName, *typeName; + + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + while ((tableName = [e nextObject]) != nil) { + typeName = [e nextObject]; + if (typeName == nil) { + [self logWithFormat:@"got tablename '%@' but no type?!", tableName]; + break; + } + + [self runOnTable:tableName typeName:typeName]; + } + + return 0; +} ++ (int)runWithArgs:(NSArray *)_args { + return [(Tool *)[[[self alloc] init] autorelease] run]; +} + +@end /* Tool */ + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + rc = [Tool runWithArgs: + [[NSProcessInfo processInfo] argumentsWithoutDefaults]]; + + [pool release]; + return rc; +} diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_ls.m b/SOPE/sope-gdl1/GDLContentStore/gcs_ls.m new file mode 100644 index 000000000..20de58e94 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/gcs_ls.m @@ -0,0 +1,140 @@ +/* + 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. +*/ + +#import + +@class NSUserDefaults, NSArray; +@class GCSFolderManager; + +@interface Tool : NSObject +{ + NSUserDefaults *ud; + GCSFolderManager *folderManager; +} + ++ (int)runWithArgs:(NSArray *)_args; +- (int)run; + +@end + +#include +#include +#include "common.h" + +@implementation Tool + +- (id)init { + if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; + self->folderManager = [[GCSFolderManager defaultFolderManager] retain]; + } + return self; +} +- (void)dealloc { + [self->ud release]; + [self->folderManager release]; + [super dealloc]; +} + +/* operation */ + +- (int)runOnPath:(NSString *)_path { + NSArray *subfolders; + unsigned i, count; + GCSFolder *folder; + + [self logWithFormat:@"ls path: '%@'", _path]; + +#if 0 // we do not necessarily need the whole hierarchy + if (![self->folderManager folderExistsAtPath:_path]) + [self logWithFormat:@"folder does not exist: '%@'", _path]; +#endif + + subfolders = [self->folderManager + listSubFoldersAtPath:_path + recursive:[ud boolForKey:@"r"]]; + if (subfolders == nil) { + [self logWithFormat:@"cannot list folder: '%@'", _path]; + return 1; + } + + for (i = 0, count = [subfolders count]; i < count; i++) { + printf("%s\n", [[subfolders objectAtIndex:i] cString]); + } + + folder = [self->folderManager folderAtPath:_path]; + + if ([folder isNotNull]) { + NSLog(@"folder: %@", folder); + + NSLog(@" can%s connect store: %@", [folder canConnectStore] ? "" : "not", + [[folder location] absoluteString]); + NSLog(@" can%s connect quick: %@", [folder canConnectQuick] ? "" : "not", + [[folder quickLocation] absoluteString]); + } + else { + NSLog(@"ERROR: could not create folder object for path: '%@'", _path); + } + + return 0; +} + +- (int)run { + NSEnumerator *e; + NSString *path; + + [self logWithFormat:@"manager: %@", self->folderManager]; + + if (![self->folderManager canConnect]) { + [self logWithFormat:@"cannot connect folder-info database!"]; + return 1; + } + + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + while ((path = [e nextObject]) != nil) + [self runOnPath:path]; + + return 0; +} ++ (int)runWithArgs:(NSArray *)_args { + return [(Tool *)[[[self alloc] init] autorelease] run]; +} + +@end /* Tool */ + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + rc = [Tool runWithArgs: + [[NSProcessInfo processInfo] argumentsWithoutDefaults]]; + + [pool release]; + return rc; +} diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m b/SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m new file mode 100644 index 000000000..fc3b35473 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m @@ -0,0 +1,126 @@ +/* + 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. +*/ + +#import + +@class NSUserDefaults, NSArray; +@class GCSFolderManager; + +@interface Tool : NSObject +{ + NSUserDefaults *ud; + GCSFolderManager *folderManager; +} + ++ (int)runWithArgs:(NSArray *)_args; +- (int)run; + +@end + +#include +#include +#include "common.h" + +@implementation Tool + +- (id)init { + if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; + self->folderManager = [[GCSFolderManager defaultFolderManager] retain]; + } + return self; +} +- (void)dealloc { + [self->ud release]; + [self->folderManager release]; + [super dealloc]; +} + +/* operation */ + +- (int)runOnPath:(NSString *)_path type:(NSString *)_type { + NSException *error; + + [self logWithFormat:@"mkdir %@ at path: '%@'", _type, _path]; + + if ([self->folderManager folderExistsAtPath:_path]) { + [self logWithFormat:@"folder already exist at path: '%@'", _path]; + return 1; + } + + if ((error = [self->folderManager createFolderOfType:_type atPath:_path])) { + [self logWithFormat:@"creation of folder %@ at %@ failed: %@", + _type, _path, error]; + return 1; + } + + if ([self->folderManager folderExistsAtPath:_path]) + [self logWithFormat:@"CREATED."]; + else + [self logWithFormat:@"cannot find fresh folder?"]; + + return 0; +} + +- (int)run { + NSEnumerator *e; + NSString *type; + NSString *path; + + [self logWithFormat:@"manager: %@", self->folderManager]; + + if (![self->folderManager canConnect]) { + [self logWithFormat:@"cannot connect folder-info database!"]; + return 1; + } + + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + type = [[[e nextObject] copy] autorelease]; + + while ((path = [e nextObject])) + [self runOnPath:path type:type]; + + return 0; +} ++ (int)runWithArgs:(NSArray *)_args { + return [(Tool *)[[[self alloc] init] autorelease] run]; +} + +@end /* Tool */ + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + rc = [Tool runWithArgs: + [[NSProcessInfo processInfo] argumentsWithoutDefaults]]; + + [pool release]; + return rc; +} diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m b/SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m new file mode 100644 index 000000000..d68355359 --- /dev/null +++ b/SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m @@ -0,0 +1,121 @@ +/* + 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. +*/ + +#import + +@class NSUserDefaults, NSArray; +@class GCSFolderManager; + +@interface Tool : NSObject +{ + NSUserDefaults *ud; + GCSFolderManager *folderManager; +} + ++ (int)runWithArgs:(NSArray *)_args; +- (int)run; + +@end + +#include +#include +#include "common.h" + +@implementation Tool + +- (id)init { + if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; + self->folderManager = [[GCSFolderManager defaultFolderManager] retain]; + } + return self; +} +- (void)dealloc { + [self->ud release]; + [self->folderManager release]; + [super dealloc]; +} + +/* operation */ + +- (int)runOnPath:(NSString *)_path { + GCSFolder *folder; + + [self logWithFormat:@"update quick from store at path: '%@'", _path]; + + if (![self->folderManager folderExistsAtPath:_path]) { + [self logWithFormat:@"no folder exist at path: '%@'", _path]; + return 1; + } + + if ((folder = [self->folderManager folderAtPath:_path]) == nil) { + [self logWithFormat:@"got no folder object for path: '%@'", _path]; + return 2; + } + [self logWithFormat:@" folder: %@", folder]; + + // TODO: + [self logWithFormat:@" should recreate folder .."]; + + return 0; +} + +- (int)run { + NSEnumerator *e; + NSString *path; + + [self logWithFormat:@"manager: %@", self->folderManager]; + + if (![self->folderManager canConnect]) { + [self logWithFormat:@"cannot connect folder-info database!"]; + return 1; + } + + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + while ((path = [e nextObject])) + [self runOnPath:path]; + + return 0; +} ++ (int)runWithArgs:(NSArray *)_args { + return [(Tool *)[[[self alloc] init] autorelease] run]; +} + +@end /* Tool */ + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + rc = [Tool runWithArgs: + [[NSProcessInfo processInfo] argumentsWithoutDefaults]]; + + [pool release]; + return rc; +} diff --git a/SOPE/sope-patchset-r1544.diff b/SOPE/sope-patchset-r1544.diff new file mode 100644 index 000000000..5909c448c --- /dev/null +++ b/SOPE/sope-patchset-r1544.diff @@ -0,0 +1,435 @@ +Index: sope-mime/NGImap4/NGImap4Connection.m +=================================================================== +--- sope-mime/NGImap4/NGImap4Connection.m (revision 1544) ++++ sope-mime/NGImap4/NGImap4Connection.m (working copy) +@@ -381,7 +381,7 @@ + + if (debugCache) [self logWithFormat:@" no folders cached yet .."]; + +- result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*") ++ result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"") + pattern:@"*"]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"Could not list mailbox hierarchy!"]; +Index: sope-mime/NGImap4/NGImap4ResponseParser.m +=================================================================== +--- sope-mime/NGImap4/NGImap4ResponseParser.m (revision 1544) ++++ sope-mime/NGImap4/NGImap4ResponseParser.m (working copy) +@@ -84,6 +84,8 @@ + static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, + BOOL isBodyStructure); + ++static NSArray *_parseLanguages(); ++ + static NSString *_parseBodyString(NGImap4ResponseParser *self, + BOOL _convertString); + static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self, +@@ -1627,6 +1629,29 @@ + return _parseBodyDecodeString(self, _convertString, NO /* no decode */); + } + ++static NSArray *_parseLanguages(NGImap4ResponseParser *self) { ++ NSMutableArray *languages; ++ NSString *language; ++ ++ languages = [NSMutableArray array]; ++ if (_la(self, 0) == '(') { ++ while (_la(self, 0) != ')') { ++ _consume(self,1); ++ language = _parseBodyString(self, YES); ++ if ([language length]) ++ [languages addObject: language]; ++ } ++ _consume(self,1); ++ } ++ else { ++ language = _parseBodyString(self, YES); ++ if ([language length]) ++ [languages addObject: language]; ++ } ++ ++ return languages; ++} ++ + static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self) + { + NSMutableDictionary *list; +@@ -1734,10 +1759,11 @@ + *encoding, *bodysize; + NSDictionary *parameterList; + NSMutableDictionary *dict; ++ NSArray *languages; + + type = [_parseBodyString(self, YES) lowercaseString]; + _consumeIfMatch(self, ' '); +- subtype = _parseBodyString(self, YES); ++ subtype = [_parseBodyString(self, YES) lowercaseString]; + _consumeIfMatch(self, ' '); + parameterList = _parseBodyParameterList(self); + _consumeIfMatch(self, ' '); +@@ -1762,7 +1788,8 @@ + _consumeIfMatch(self, ' '); + [dict setObject:_parseBodyString(self, YES) forKey:@"lines"]; + } +- else if ([type isEqualToString:@"message"]) { ++ else if ([type isEqualToString:@"message"] ++ && [subtype isEqualToString:@"rfc822"]) { + if (_la(self, 0) != ')') { + _consumeIfMatch(self, ' '); + _consumeIfMatch(self, '('); +@@ -1805,14 +1832,9 @@ + forKey: @"disposition"]; + if (_la(self, 0) != ')') { + _consume(self,1); +- if (_la(self, 0) == '(') { +- [dict setObject: _parseBodyParameterList(self) +- forKey: @"language"]; +- } +- else { +- [dict setObject: _parseBodyString(self, YES) +- forKey: @"language"]; +- } ++ languages = _parseLanguages(self); ++ if ([languages count]) ++ [dict setObject: languages forKey: @"languages"]; + if (_la(self, 0) != ')') { + _consume(self,1); + [dict setObject: _parseBodyString(self, YES) +@@ -1829,6 +1851,7 @@ + static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, + BOOL isBodyStructure) { + NSMutableArray *parts; ++ NSArray *languages; + NSString *kind; + NSMutableDictionary *dict; + +@@ -1854,14 +1877,9 @@ + forKey: @"disposition"]; + if (_la(self, 0) != ')') { + _consume(self,1); +- if (_la(self, 0) == '(') { +- [dict setObject: _parseBodyParameterList(self) +- forKey: @"language"]; +- } +- else { +- [dict setObject: _parseBodyString(self, YES) +- forKey: @"language"]; +- } ++ languages = _parseLanguages(self); ++ if ([languages count]) ++ [dict setObject: languages forKey: @"languages"]; + if (_la(self, 0) != ')') { + _consume(self,1); + [dict setObject: _parseBodyString(self, YES) +Index: sope-mime/NGMime/NGMimeBodyPart.m +=================================================================== +--- sope-mime/NGMime/NGMimeBodyPart.m (revision 1544) ++++ sope-mime/NGMime/NGMimeBodyPart.m (working copy) +@@ -31,18 +31,6 @@ + return 2; + } + +-static NGMimeType *defaultType = nil; +- +-+ (void)initialize { +- static BOOL isInitialized = NO; +- if (!isInitialized) { +- isInitialized = YES; +- +- defaultType = +- [[NGMimeType mimeType:@"text/plain; charset=us-ascii"] retain]; +- } +-} +- + + (id)bodyPartWithHeader:(NGHashMap *)_header { + return [[[self alloc] initWithHeader:_header] autorelease]; + } +@@ -156,13 +144,12 @@ + if (!Fields) + Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames]; + +- + type = [self->header objectForKey:Fields->contentType]; + + if (![type isKindOfClass:[NGMimeType class]]) + type = [NGMimeType mimeType:[type stringValue]]; + +- return (type != nil ? type : (id)defaultType); ++ return type; + } + + - (NSString *)contentId { +Index: sope-mime/NGMime/NGMimeBodyParser.m +=================================================================== +--- sope-mime/NGMime/NGMimeBodyParser.m (revision 1544) ++++ sope-mime/NGMime/NGMimeBodyParser.m (working copy) +@@ -67,7 +67,10 @@ + if (_data == nil) return nil; + + ctype = [_part contentType]; +- ++ if (!ctype ++ && [_d respondsToSelector: @selector(parser:contentTypeOfPart:)]) ++ ctype = [_d parser: self contentTypeOfPart: _part]; ++ + if (![ctype isKindOfClass:[NGMimeType class]]) + ctype = [NGMimeType mimeType:[ctype stringValue]]; + +Index: sope-mime/NGMime/NGMimePartParser.h +=================================================================== +--- sope-mime/NGMime/NGMimePartParser.h (revision 1544) ++++ sope-mime/NGMime/NGMimePartParser.h (working copy) +@@ -117,6 +117,7 @@ + BOOL parserParseRawBodyDataOfPart:1; + BOOL parserBodyParserForPart:1; + BOOL parserDecodeBodyOfPart:1; ++ BOOL parserContentTypeOfPart:1; + } delegateRespondsTo; + + +@@ -275,6 +276,9 @@ + - (id)parser:(NGMimePartParser *)_parser + bodyParserForPart:(id)_part; + ++- (NGMimeType *)parser:(id)_parser ++ contentTypeOfPart:(id)_part; ++ + @end /* NSObject(NGMimePartParserDelegate) */ + + @interface NSObject(NGMimePartParser) +Index: sope-mime/NGMime/NGMimePartParser.m +=================================================================== +--- sope-mime/NGMime/NGMimePartParser.m (revision 1544) ++++ sope-mime/NGMime/NGMimePartParser.m (working copy) +@@ -1091,7 +1091,10 @@ + id bodyParser = nil; + + ctype = [_p contentType]; +- ++ if (!ctype ++ && self->delegateRespondsTo.parserContentTypeOfPart) ++ ctype = [self->delegate parser: self contentTypeOfPart: _p]; ++ + contentType = ([ctype isKindOfClass:[NGMimeType class]]) + ? ctype + : [NGMimeType mimeType:[ctype stringValue]]; +Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.h +=================================================================== +--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (revision 1544) ++++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (working copy) +@@ -28,6 +28,7 @@ + #define ___PostgreSQL72_Channel_H___ + + #include ++#include + #include + + @class NSArray, NSString, NSMutableDictionary; +@@ -40,7 +41,7 @@ + int modification; + } PostgreSQL72FieldInfo; + +-@interface PostgreSQL72Channel : EOAdaptorChannel ++@interface PostgreSQL72Channel : EOAdaptorChannel + { + // connection is valid after an openChannel call + PGConnection *connection; +Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m +=================================================================== +--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1544) ++++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (working copy) +@@ -713,6 +713,39 @@ + return ms; + } + ++/* GCSEOAdaptorChannel protocol */ ++static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \ ++ @" c_name VARCHAR (256) NOT NULL,\n" ++ @" c_content VARCHAR (100000) NOT NULL,\n" ++ @" c_creationdate INT4 NOT NULL,\n" ++ @" c_lastmodified INT4 NOT NULL,\n" ++ @" c_version INT4 NOT NULL,\n" ++ @" c_deleted INT4 NULL\n" ++ @")"); ++static NSString *sqlFolderACLFormat = (@"CREATE TABLE %@ (\n" \ ++ @" c_uid VARCHAR (256) NOT NULL,\n" ++ @" c_object VARCHAR (256) NOT NULL,\n" ++ @" c_role VARCHAR (80) NOT NULL\n" ++ @")"); ++ ++- (NSException *) createGCSFolderTableWithName: (NSString *) tableName ++{ ++ NSString *sql; ++ ++ sql = [NSString stringWithFormat: sqlFolderFormat, tableName]; ++ ++ return [self evaluateExpressionX: sql]; ++} ++ ++- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName ++{ ++ NSString *sql; ++ ++ sql = [NSString stringWithFormat: sqlFolderACLFormat, tableName]; ++ ++ return [self evaluateExpressionX: sql]; ++} ++ + @end /* PostgreSQL72Channel */ + + @implementation PostgreSQL72Channel(PrimaryKeyGeneration) +Index: sope-appserver/NGObjWeb/GNUmakefile.postamble +=================================================================== +--- sope-appserver/NGObjWeb/GNUmakefile.postamble (revision 1544) ++++ sope-appserver/NGObjWeb/GNUmakefile.postamble (working copy) +@@ -23,14 +23,20 @@ + + # install makefiles + +-after-install :: ++after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make ++ ++ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0) ++after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make ++endif ++ ++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make: ngobjweb.make + $(MKDIRS) $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ + $(INSTALL_DATA) ngobjweb.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make + +-ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0) +-after-install :: ++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make: woapp-gs.make + $(INSTALL_DATA) woapp-gs.make \ + $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make ++ ++$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make: wobundle-gs.make + $(INSTALL_DATA) wobundle-gs.make \ + $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make +-endif +Index: sope-appserver/NGObjWeb/WOContext.m +=================================================================== +--- sope-appserver/NGObjWeb/WOContext.m (revision 1544) ++++ sope-appserver/NGObjWeb/WOContext.m (working copy) +@@ -64,11 +64,13 @@ + static BOOL testNSURLs = NO; + static BOOL newCURLStyle = NO; + static NSString *WOApplicationSuffix = nil; ++static NSURL *redirectURL = nil; + + + (void)initialize { + static BOOL didInit = NO; + NSUserDefaults *ud; + NSString *cn; ++ NSString *url; + + if (didInit) return; + +@@ -91,6 +93,9 @@ + debugCursor = [ud boolForKey:@"WODebugCursor"] ? 1 : 0; + debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"]; + WOApplicationSuffix = [[ud stringForKey:@"WOApplicationSuffix"] copy]; ++ url = [ud stringForKey:@"WOApplicationRedirectURL"]; ++ if (url != nil) ++ redirectURL = [NSURL URLWithString: url]; + } + + + (id)contextWithRequest:(WORequest *)_r { +@@ -503,6 +508,11 @@ + return nil; + } + ++ if (redirectURL) { ++ // Use URL from user defaults (WOApplicationRedirectURL) ++ return redirectURL; ++ } ++ + if ((serverURL = [rq headerForKey:@"x-webobjects-server-url"]) == nil) { + if ((host = [rq headerForKey:@"host"])) + serverURL = [@"http://" stringByAppendingString:host]; +Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m +=================================================================== +--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (revision 1544) ++++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (working copy) +@@ -216,6 +216,12 @@ + assocCount++; + } + } ++ if (count > 0) { ++ if ((self->isAbsolute = OWGetProperty(_config, @"absolute"))) { ++ count--; ++ assocCount++; ++ } ++ } + + self->rest = _config; + +Index: sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m +=================================================================== +--- sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (revision 1544) ++++ sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (working copy) +@@ -40,6 +40,7 @@ + WOAssociation *string; + WOAssociation *target; + WOAssociation *disabled; ++ WOAssociation *isAbsolute; + WOElement *template; + + /* new in WO4: */ +@@ -359,6 +360,7 @@ + { + if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) { + self->href = _info->href; ++ self->isAbsolute = _info->isAbsolute; + } + return self; + } +@@ -374,6 +376,9 @@ + // TODO: we need a binding to disable rewriting! + NSRange r; + ++ if ([[self->isAbsolute valueInContext:_ctx] boolValue] == YES) ++ return NO; ++ + r = [_s rangeOfString:@":"]; + if (r.length == 0) + return YES; +Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h +=================================================================== +--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (revision 1544) ++++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (working copy) +@@ -41,7 +41,8 @@ + WOAssociation *pageName; + WOAssociation *actionClass; + WOAssociation *directActionName; +- ++ WOAssociation *isAbsolute; ++ + BOOL sidInUrl; + + /* 'ivar' associations */ +Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m +=================================================================== +--- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (revision 1544) ++++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (working copy) +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + #include "common.h" + + #include +@@ -1016,6 +1017,12 @@ + - (void)parser:(NGMimePartParser *)_parser didParseHeader:(NGHashMap *)_header { + } + ++- (NGMimeType *)parser:(id)_parser ++ contentTypeOfPart:(id)_part ++{ ++ return [NGMimeType mimeType: @"text/plain; charset=utf-8"]; ++} ++ + @end /* WOHttpAdaptor */ + + @implementation WOCoreApplication(SimpleParserSelection) diff --git a/Scripts/sogo-init.d-redhat b/Scripts/sogo-init.d-redhat index 68ac60d78..a63ca447c 100755 --- a/Scripts/sogo-init.d-redhat +++ b/Scripts/sogo-init.d-redhat @@ -1,4 +1,10 @@ #!/bin/bash +# chkconfig: - 85 15 +# description: SOGo is a groupware server +# processname: sogod-0.9 +# config: /etc/sysconfig/sogo +# config: /etc/httpd/conf.d/SOGo.conf +# pidfile: /var/run/sogo/sogod.pid # SOGo init script for RedHat # @@ -22,18 +28,17 @@ # Boston, MA 02111-1307, USA. # sogod Scalable OpenGroupware.org (Inverse edition) -# -# chkconfig: - 85 15 -# description: SOGo is a groupware server -# processname: sogod-0.9 -# config: /etc/sysconfig/sogo -# config: /etc/httpd/conf.d/SOGo.conf -# pidfile: /var/run/sogo/sogod.pid PATH=/sbin:/bin:/usr/sbin:/usr/bin . /etc/rc.d/init.d/functions +if [ -z "$GNUSTEP_SYSTEM_ROOT" ] +then + . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh +fi + +REAL_DAEMON=$GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9 DAEMON=/usr/sbin/sogod NAME=sogo DESC="Scalable OpenGroupware.Org (Inverse edition)" @@ -52,26 +57,24 @@ test -x $DAEMON || exit 0 case "$1" in start) - echo -n $"Starting $DESC: " / + echo -n $"Starting $DESC: " daemon --user sogo --pidfile $PIDFILE $DAEMON echo "$NAME." ;; stop) echo -n $"Stopping $DESC: " - killproc --pidfile $PIDFILE $DAEMON - rm -f $PIDFILE + killproc -p $PIDFILE $REAL_DAEMON && rm -f $PIDFILE echo "$NAME." ;; restart|force-reload) echo -n $"Restarting $DESC: " - killproc --pidfile $PIDFILE $DAEMON - rm -f $PIDFILE + killproc -p $PIDFILE $REAL_DAEMON && rm -f $PIDFILE sleep 1 daemon --user sogo --pidfile $PIDFILE $DAEMON echo "$NAME." ;; status) - status $DAEMON + status $REAL_DAEMON ;; *) N=/etc/init.d/$NAME diff --git a/Scripts/sogo-init.d-rhel4 b/Scripts/sogo-init.d-rhel4 new file mode 100755 index 000000000..35d20f9cc --- /dev/null +++ b/Scripts/sogo-init.d-rhel4 @@ -0,0 +1,87 @@ +#!/bin/bash +# chkconfig: - 85 15 +# description: SOGo is a groupware server +# processname: sogod-0.9 +# config: /etc/sysconfig/sogo +# config: /etc/httpd/conf.d/SOGo.conf +# pidfile: /var/run/sogo/sogod.pid + +# SOGo init script for RedHat +# +# Copyright (C) 2007 Inverse groupe conseil +# +# Author: Wolfgang Sourdeau +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This file 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# sogod Scalable OpenGroupware.org (Inverse edition) + +PATH=/sbin:/bin:/usr/sbin:/usr/bin + +. /etc/rc.d/init.d/functions + +if [ -z "$GNUSTEP_SYSTEM_ROOT" ] +then + . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh +fi + +REAL_DAEMON=$GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9 +DAEMON=/usr/sbin/sogod +NAME=sogo +DESC="Scalable OpenGroupware.Org (Inverse edition)" + +PIDFILE=/var/run/sogo/sogod.pid + +SOGO_ARGS="" + +if [ -f /etc/sysconfig/sogo ]; then + . /etc/sysconfig/sogo +fi + +test -x $DAEMON || exit 0 + +#set -e + +case "$1" in + start) + echo -n $"Starting $DESC: " + daemon su - sogo -c $DAEMON + echo "$NAME." + ;; + stop) + echo -n $"Stopping $DESC: " + killproc `basename $REAL_DAEMON` && rm -f $PIDFILE + echo "$NAME." + ;; + restart|force-reload) + echo -n $"Restarting $DESC: " + killproc `basename $REAL_DAEMON` && rm -f $PIDFILE + sleep 1 + daemon su - sogo -c $DAEMON + echo "$NAME." + ;; + status) + status $REAL_DAEMON + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 + diff --git a/Scripts/sogod-redhat b/Scripts/sogod-redhat index 03cb492ab..dfab9a75a 100755 --- a/Scripts/sogod-redhat +++ b/Scripts/sogod-redhat @@ -1,8 +1,19 @@ #!/bin/sh PIDFILE=/var/run/sogo/sogod.pid +PROGRAM=sogod-0.9 +oldpid=`pgrep -u $USER sogod-0.9` + +if [ -n "$oldpid" ] +then + echo SOGo already launched. + exit 1 +fi . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh -echo $$ > $PIDFILE -exec $GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9 >& /var/log/sogo/sogod.log +$GNUSTEP_SYSTEM_ROOT/Tools/$PROGRAM >& /var/log/sogo/sogod.log & + +newpid=`pgrep -u $USER $PROGRAM | awk '{ print $1 }'` +echo $newpid > $PIDFILE + diff --git a/Scripts/sql-update-20070822.sh b/Scripts/sql-update-20070822.sh new file mode 100755 index 000000000..80a41a2bd --- /dev/null +++ b/Scripts/sql-update-20070822.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# this script only work with PostgreSQL + +defaultusername=$USER +defaulthostname=localhost +defaultdatabase=$USER +indextable=sogo_folder_info + +read -p "Username ($defaultusername): " username +read -p "Hostname ($defaulthostname): " hostname +read -p "Database ($defaultdatabase): " database + +if [ -z "$username" ] +then + username=$defaultusername +fi +if [ -z "$hostname" ] +then + hostname=$defaulthostname +fi +if [ -z "$database" ] +then + database=$defaultdatabase +fi + +echo "" +echo "You will now be requested your password twice..." +echo "After that, a list of SQL operations will scroll." +echo "" + +sqlscript="" + +function addField() { + oldIFS="$IFS" + IFS=" " + part="`echo -e \"ALTER TABLE $table ADD COLUMN c_deleted INTEGER;\\n\"`"; + sqlscript="$sqlscript$part" + IFS="$oldIFS" +} + +tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_location, '/', 5) from $indextable where c_folder_type != 'Container';"` + +for table in $tables; +do + addField +done + +sqlscript="$sqlscript;update $indextable set c_path4 = 'personal', c_path = '/Users/' || c_path2 || '/Calendar/personal' where c_path3 = 'Calendar' and c_path4 is null;" + +function updateCalendarLocation() { + oldIFS="$IFS" + IFS=" " + user="`echo $table | cut -f 1 -d :`" + tablename="`echo $table | cut -f 2 -d :`" + newstart="/$user/Calendar/personal"; + part="update $tablename set c_object = replace(c_object, '/$user/Calendar', '$newstart') where c_object not like '$newstart%';"; + sqlscript="$sqlscript$part" + IFS="$oldIFS" +} + +tables=`psql -t -U $username -h $hostname $database -c "select c_path2 || ':' || split_part(c_acl_location, '/', 5) from $indextable where c_folder_type = 'Appointment';"` +for table in $tables; +do + updateCalendarLocation +done + +echo "$sqlscript" | psql -q -e -U $username -h $hostname $database > /dev/null + +echo "Please ignore the errors above. They just mean that the migration was already done for the elements in question."; \ No newline at end of file diff --git a/SoObjects/Appointments/English.lproj/Localizable.strings b/SoObjects/Appointments/English.lproj/Localizable.strings new file mode 100644 index 000000000..95feda9fd --- /dev/null +++ b/SoObjects/Appointments/English.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Calendar" = "Personal Calendar"; diff --git a/SoObjects/Appointments/French.lproj/Localizable.strings b/SoObjects/Appointments/French.lproj/Localizable.strings new file mode 100644 index 000000000..11b333c2a --- /dev/null +++ b/SoObjects/Appointments/French.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Calendar" = "Agenda personnel"; diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index 4e3d3255d..6b51f6b46 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -6,7 +6,9 @@ WOBUNDLE_NAME = Appointments Appointments_PRINCIPAL_CLASS = SOGoAppointmentsProduct -# Appointments_LANGUAGES = English French +Appointments_LANGUAGES = English French German + +Appointments_LOCALIZED_RESOURCE_FILES=Localizable.strings Appointments_OBJC_FILES = \ Product.m \ @@ -17,6 +19,7 @@ Appointments_OBJC_FILES = \ SOGoAppointmentObject.m \ SOGoTaskObject.m \ SOGoAppointmentFolder.m \ + SOGoAppointmentFolders.m \ SOGoGroupAppointmentFolder.m \ SOGoFreeBusyObject.m \ \ @@ -32,13 +35,20 @@ Appointments_RESOURCE_FILES += \ Appointments_COMPONENTS += \ SOGoAptMailEnglishInvitation.wo \ - SOGoAptMailFrenchInvitation.wo \ SOGoAptMailEnglishUpdate.wo \ - SOGoAptMailFrenchUpdate.wo \ SOGoAptMailEnglishRemoval.wo \ - SOGoAptMailFrenchRemoval.wo \ SOGoAptMailEnglishDeletion.wo \ + SOGoAptMailFrenchInvitation.wo \ + SOGoAptMailFrenchUpdate.wo \ + SOGoAptMailFrenchRemoval.wo \ SOGoAptMailFrenchDeletion.wo \ + SOGoAptMailGermanInvitation.wo \ + SOGoAptMailGermanUpdate.wo \ + SOGoAptMailGermanRemoval.wo \ + SOGoAptMailGermanDeletion.wo \ + +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/wobundle.make diff --git a/SoObjects/Appointments/German.lproj/Localizable.strings b/SoObjects/Appointments/German.lproj/Localizable.strings new file mode 100644 index 000000000..95feda9fd --- /dev/null +++ b/SoObjects/Appointments/German.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Calendar" = "Personal Calendar"; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index f3e71cd8d..5f5ee41d6 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -53,6 +53,8 @@ NSMutableDictionary *uidToFilename; } +- (BOOL) isActive; + /* selection */ - (NSArray *) calendarUIDs; @@ -82,8 +84,6 @@ - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate; -- (void) deleteEntriesWithIds: (NSArray *) ids; - /* URL generation */ - (NSString *) baseURLForAptWithUID: (NSString *) _uid @@ -113,8 +113,6 @@ - (NSArray *) fetchAllSOGoAppointments; -- (NSArray *) calendarFolders; - - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass forUser: (NSString *) uid; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 5e7955a7e..a2306fba6 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -125,11 +125,6 @@ static NSNumber *sharedYes = nil; return logger; } -- (BOOL) folderIsMandatory -{ - return YES; -} - /* selection */ - (NSArray *) calendarUIDs @@ -226,6 +221,7 @@ static NSNumber *sharedYes = nil; return filterData; } +#warning filters is leaked here - (NSArray *) _parseCalendarFilters: (id ) parentNode { NSEnumerator *children; @@ -263,6 +259,7 @@ static NSNumber *sharedYes = nil; max = [filters count]; for (count = 0; count < max; count++) { +#warning huh? why not objectAtIndex: count? currentFilter = [filters objectAtIndex: 0]; apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"] to: [currentFilter objectForKey: @"end"] @@ -573,13 +570,16 @@ static NSNumber *sharedYes = nil; md = [[_record mutableCopy] autorelease]; - /* cycle is in _r */ + /* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of + the reccurence since we use those when displaying events in SOGo Web */ tmp = [_r startDate]; [tmp setTimeZone: timeZone]; [md setObject:tmp forKey:@"startDate"]; + [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"]; tmp = [_r endDate]; [tmp setTimeZone: timeZone]; [md setObject:tmp forKey:@"endDate"]; + [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"]; return md; } @@ -638,20 +638,24 @@ static NSNumber *sharedYes = nil; 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]; + if (fixedRow != nil) + { + [_ma addObject:fixedRow]; + } } } @@ -746,7 +750,7 @@ static NSNumber *sharedYes = nil; privacySqlString = @"and (c_isopaque = 1)"; else { -#warning we do not manage all the user's possible emails +#warning we do not manage all the possible user emails email = [[activeUser primaryIdentity] objectForKey: @"email"]; privacySqlString @@ -850,21 +854,20 @@ static NSNumber *sharedYes = nil; ma = [NSMutableArray arrayWithArray: records]; } - /* fetch recurrent apts now */ - sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@", - dateSqlString, componentSqlString, privacySqlString]; + /* fetch recurrent apts now. we do NOT consider the date range when doing that + as the c_startdate/c_enddate of a recurring event is always set to the first + recurrence - others are generated on the fly */ + sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@", componentSqlString, privacySqlString]; + qualifier = [EOQualifier qualifierWithQualifierFormat: sql]; - [fields addObject: @"c_cycleinfo"]; - records = [_folder fetchFields: fields matchingQualifier: qualifier]; + if (records) { - if (logger) - [self debugWithFormat: @"fetched %i cyclic records: %@", - [records count], records]; - if (r) + if (r) { records = [self fixupCyclicRecords: records fetchRange: r]; + } if (!ma) ma = [NSMutableArray arrayWithCapacity: [records count]]; @@ -905,7 +908,6 @@ static NSNumber *sharedYes = nil; component: _component]; } - - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate { @@ -932,33 +934,13 @@ static NSNumber *sharedYes = nil; @"c_status", @"c_classification", @"c_isallday", @"c_isopaque", @"c_participants", @"c_partmails", - @"c_partstates", @"c_sequence", @"c_priority", + @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo", nil]; return [self fetchFields: infos from: _startDate to: _endDate component: _component]; } -- (void) deleteEntriesWithIds: (NSArray *) ids -{ - Class objectClass; - unsigned int count, max; - NSString *currentId; - id deleteObject; - - max = [ids count]; - for (count = 0; count < max; count++) - { - currentId = [ids objectAtIndex: count]; - objectClass - = [self objectClassForResourceNamed: currentId]; - deleteObject = [objectClass objectWithName: currentId - inContainer: self]; - [deleteObject delete]; - [deleteObject primaryDelete]; - } -} - /* URL generation */ - (NSString *) baseURLForAptWithUID: (NSString *)_uid @@ -1029,7 +1011,7 @@ static NSNumber *sharedYes = nil; calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar" inContainer: userFolder]; [calendarFolder - setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]]; + setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar/personal", uid]]; [calendarFolder setOwner: uid]; return calendarFolder; @@ -1041,21 +1023,28 @@ static NSNumber *sharedYes = nil; /* Note: can return NSNull objects in the array! */ NSMutableArray *folders; NSEnumerator *e; - NSString *uid; - + NSString *uid, *ownerLogin; + id folder; + + ownerLogin = [self ownerInContext: context]; + if ([_uids count] == 0) return nil; folders = [NSMutableArray arrayWithCapacity:16]; e = [_uids objectEnumerator]; - while ((uid = [e nextObject])) { - id folder; + while ((uid = [e nextObject])) + { + if ([uid isEqualToString: ownerLogin]) + folder = self; + else + { + folder = [self lookupCalendarFolderForUID: uid]; + if (![folder isNotNull]) + [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid]; + } - folder = [self lookupCalendarFolderForUID: uid]; - 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]]; - } + [folders addObject: folder]; + } + return folders; } @@ -1196,67 +1185,67 @@ static NSNumber *sharedYes = nil; return events; } -#warning We only support ONE calendar per user at this time -- (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders - toFolderList: (NSMutableArray *) calendarFolders -{ - NSEnumerator *keys; - NSString *currentKey; - NSMutableDictionary *currentCalendar; - BOOL firstShouldBeActive; - unsigned int count; +// #warning We only support ONE calendar per user at this time +// - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders +// toFolderList: (NSMutableArray *) calendarFolders +// { +// NSEnumerator *keys; +// NSString *currentKey; +// NSMutableDictionary *currentCalendar; +// BOOL firstShouldBeActive; +// unsigned int count; - firstShouldBeActive = YES; +// firstShouldBeActive = YES; - keys = [[subscribedFolders allKeys] objectEnumerator]; - currentKey = [keys nextObject]; - count = 1; - while (currentKey) - { - currentCalendar = [NSMutableDictionary new]; - [currentCalendar autorelease]; - [currentCalendar - setDictionary: [subscribedFolders objectForKey: currentKey]]; - [currentCalendar setObject: currentKey forKey: @"folder"]; - [calendarFolders addObject: currentCalendar]; - if ([[currentCalendar objectForKey: @"active"] boolValue]) - firstShouldBeActive = NO; - count++; - currentKey = [keys nextObject]; - } +// keys = [[subscribedFolders allKeys] objectEnumerator]; +// currentKey = [keys nextObject]; +// count = 1; +// while (currentKey) +// { +// currentCalendar = [NSMutableDictionary new]; +// [currentCalendar autorelease]; +// [currentCalendar +// setDictionary: [subscribedFolders objectForKey: currentKey]]; +// [currentCalendar setObject: currentKey forKey: @"folder"]; +// [calendarFolders addObject: currentCalendar]; +// if ([[currentCalendar objectForKey: @"active"] boolValue]) +// firstShouldBeActive = NO; +// count++; +// currentKey = [keys nextObject]; +// } - return firstShouldBeActive; -} +// return firstShouldBeActive; +// } -- (NSArray *) calendarFolders -{ - NSMutableDictionary *userCalendar, *calendarDict; - NSMutableArray *calendarFolders; - SOGoUser *calendarUser; - BOOL firstActive; +// - (NSArray *) calendarFolders +// { +// NSMutableDictionary *userCalendar, *calendarDict; +// NSMutableArray *calendarFolders; +// SOGoUser *calendarUser; +// BOOL firstActive; - calendarFolders = [NSMutableArray new]; - [calendarFolders autorelease]; +// calendarFolders = [NSMutableArray new]; +// [calendarFolders autorelease]; - calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context] - roles: nil]; - userCalendar = [NSMutableDictionary new]; - [userCalendar autorelease]; - [userCalendar setObject: @"/" forKey: @"folder"]; - [userCalendar setObject: @"Calendar" forKey: @"displayName"]; - [calendarFolders addObject: userCalendar]; +// calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context] +// roles: nil]; +// userCalendar = [NSMutableDictionary new]; +// [userCalendar autorelease]; +// [userCalendar setObject: @"/" forKey: @"folder"]; +// [userCalendar setObject: @"Calendar" forKey: @"displayName"]; +// [calendarFolders addObject: userCalendar]; - calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"]; - firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue]; - firstActive = ([self _appendSubscribedFolders: - [calendarDict objectForKey: @"SubscribedFolders"] - toFolderList: calendarFolders] - || firstActive); - [userCalendar setObject: [NSNumber numberWithBool: firstActive] - forKey: @"active"]; +// calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"]; +// firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue]; +// firstActive = ([self _appendSubscribedFolders: +// [calendarDict objectForKey: @"SubscribedFolders"] +// toFolderList: calendarFolders] +// || firstActive); +// [userCalendar setObject: [NSNumber numberWithBool: firstActive] +// forKey: @"active"]; - return calendarFolders; -} +// return calendarFolders; +// } // - (NSArray *) fetchContentObjectNames // { @@ -1297,4 +1286,16 @@ static NSNumber *sharedYes = nil; return @"IPF.Appointment"; } +- (BOOL) isActive +{ + NSUserDefaults *settings; + NSArray *activeFolders; + + settings = [[context activeUser] userSettings]; + activeFolders + = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"]; + + return [activeFolders containsObject: nameInContainer]; +} + @end /* SOGoAppointmentFolder */ diff --git a/SoObjects/Mailer/SOGoMailEnglishForward.m b/SoObjects/Appointments/SOGoAppointmentFolders.h similarity index 76% rename from SoObjects/Mailer/SOGoMailEnglishForward.m rename to SoObjects/Appointments/SOGoAppointmentFolders.h index 73e2b50e0..287a09048 100644 --- a/SoObjects/Mailer/SOGoMailEnglishForward.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.h @@ -1,4 +1,4 @@ -/* SOGoMailEnglishForward.m - this file is part of SOGo +/* SOGoAppointmentFolders.h - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,10 +20,13 @@ * Boston, MA 02111-1307, USA. */ -#import "SOGoMailForward.h" +#ifndef SOGOAPPOINTMENTFOLDERS_H +#define SOGOAPPOINTMENTFOLDERS_H + +#import + +@interface SOGoAppointmentFolders : SOGoParentFolder -@interface SOGoMailEnglishForward : SOGoMailForward @end -@implementation SOGoMailEnglishForward -@end +#endif /* SOGOAPPOINTMENTFOLDERS_H */ diff --git a/UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.h b/SoObjects/Appointments/SOGoAppointmentFolders.m similarity index 67% rename from UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.h rename to SoObjects/Appointments/SOGoAppointmentFolders.m index cfc2bc5c7..151f66fc8 100644 --- a/UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.h +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -1,4 +1,4 @@ -/* SOGoACLEnglishAdditionAdvisory.h - this file is part of SOGo +/* SOGoAppointmentFolders.m - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,12 +20,27 @@ * Boston, MA 02111-1307, USA. */ -#ifndef SOGOACLFRENCHADDITIONADVISORY_H -#define SOGOACLFRENCHADDITIONADVISORY_H +#import -#import "SOGoACLAdvisory.h" +#import "SOGoAppointmentFolder.h" + +#import "SOGoAppointmentFolders.h" + +@implementation SOGoAppointmentFolders + ++ (NSString *) gcsFolderType +{ + return @"Appointment"; +} + ++ (Class) subFolderClass +{ + return [SOGoAppointmentFolder class]; +} + +- (NSString *) defaultFolderName +{ + return @"Personal calendar"; +} -@interface SOGoACLEnglishAdditionAdvisory : SOGoACLAdvisory @end - -#endif /* SOGOACLFRENCHADDITIONADVISORY_H */ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index d6fe91e86..8262970f2 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -46,13 +46,6 @@ @interface SOGoAppointmentObject : SOGoCalendarComponent -/* folder management */ - -- (id) lookupHomeFolderForUID: (NSString *) _uid - inContext: (id) _ctx; -- (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids - inContext: (id) _ctx; - /* "iCal multifolder saves" */ - (NSException *) saveContentString: (NSString *) _iCal diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 9b1b334fe..404428ea7 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -19,6 +19,8 @@ 02111-1307, USA. */ +#import + #import #import #import @@ -32,6 +34,8 @@ #import #import "NSArray+Appointments.h" +#import "SOGoAppointmentFolder.h" + #import "SOGoAppointmentObject.h" @implementation SOGoAppointmentObject @@ -94,25 +98,17 @@ return uids; } -/* 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 { +- (NSException *) saveContentString: (NSString *) _iCal + inUIDs: (NSArray *) _uids +{ NSEnumerator *e; id folder; NSException *allErrors = nil; - e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context] - objectEnumerator]; + e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context] + objectEnumerator]; while ((folder = [e nextObject]) != nil) { NSException *error; SOGoAppointmentObject *apt; @@ -156,8 +152,8 @@ id folder; NSException *allErrors = nil; - e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context] - objectEnumerator]; + e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context] + objectEnumerator]; while ((folder = [e nextObject])) { NSException *error; SOGoAppointmentObject *apt; @@ -179,6 +175,14 @@ } /* "iCal multifolder saves" */ +- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment +{ + NSCalendarDate *now; + + now = [NSCalendarDate calendarDate]; + + return ([[appointment endDate] earlierDate: now] == now); +} - (NSException *) saveContentString: (NSString *) _iCal baseSequence: (int) _v @@ -319,9 +323,11 @@ if (delError != nil) return delError; /* email notifications */ - if ([self sendEMailNotifications]) + if ([self sendEMailNotifications] + && [self _aptIsStillRelevant: newApt]) { - attendees = [NSMutableArray arrayWithArray: [changes insertedAttendees]]; + attendees + = [NSMutableArray arrayWithArray: [changes insertedAttendees]]; [attendees removePerson: organizer]; [self sendEMailUsingTemplateNamed: @"Invitation" forOldObject: nil @@ -338,19 +344,20 @@ toAttendees: attendees]; } - attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]]; + attendees + = [NSMutableArray arrayWithArray: [changes deletedAttendees]]; [attendees removePerson: organizer]; if ([attendees count]) { - iCalEvent *canceledApt; + iCalEvent *cancelledApt; - canceledApt = [newApt copy]; - [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"]; + cancelledApt = [newApt copy]; + [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"]; [self sendEMailUsingTemplateNamed: @"Removal" forOldObject: nil - andNewObject: canceledApt + andNewObject: cancelledApt toAttendees: attendees]; - [canceledApt release]; + [cancelledApt release]; } } @@ -396,7 +403,7 @@ attendees = [NSMutableArray arrayWithArray:[apt attendees]]; [attendees removePerson:[apt organizer]]; - /* flag appointment as being canceled */ + /* flag appointment as being cancelled */ [(iCalCalendar *) [apt parent] setMethod: @"cancel"]; [apt increaseSequence]; diff --git a/SoObjects/Appointments/SOGoAptMailDeletion.m b/SoObjects/Appointments/SOGoAptMailDeletion.m index dec1e8c04..e0c13c8ee 100644 --- a/SoObjects/Appointments/SOGoAptMailDeletion.m +++ b/SoObjects/Appointments/SOGoAptMailDeletion.m @@ -22,17 +22,19 @@ #include "SOGoAptMailNotification.h" @interface SOGoAptMailEnglishDeletion : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailEnglishDeletion @end @interface SOGoAptMailFrenchDeletion : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailFrenchDeletion @end + +@interface SOGoAptMailGermanDeletion : SOGoAptMailNotification +@end + +@implementation SOGoAptMailGermanDeletion +@end diff --git a/SoObjects/Appointments/SOGoAptMailEnglishDeletion.wo/SOGoAptMailEnglishDeletion.html b/SoObjects/Appointments/SOGoAptMailEnglishDeletion.wo/SOGoAptMailEnglishDeletion.html index cea93f0d7..2353f8f3c 100644 --- a/SoObjects/Appointments/SOGoAptMailEnglishDeletion.wo/SOGoAptMailEnglishDeletion.html +++ b/SoObjects/Appointments/SOGoAptMailEnglishDeletion.wo/SOGoAptMailEnglishDeletion.html @@ -1,4 +1,4 @@ -<#IsSubject>Appointment <#AptStartDate /> at <#AptStartTime /> has been canceled +<#IsSubject>Appointment <#AptStartDate /> at <#AptStartTime /> has been cancelled <#IsBody> The appointment at <#AptStartDate /> <#AptStartTime /> has been cancelled by <#Organizer />. \ No newline at end of file diff --git a/SoObjects/Appointments/SOGoAptMailFrenchDeletion.wo/SOGoAptMailFrenchDeletion.html b/SoObjects/Appointments/SOGoAptMailFrenchDeletion.wo/SOGoAptMailFrenchDeletion.html index 38d29abb3..780726512 100644 --- a/SoObjects/Appointments/SOGoAptMailFrenchDeletion.wo/SOGoAptMailFrenchDeletion.html +++ b/SoObjects/Appointments/SOGoAptMailFrenchDeletion.wo/SOGoAptMailFrenchDeletion.html @@ -1,4 +1,4 @@ -<#IsSubject>Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé +<#IsSubject>Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé <#IsBody> -Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé par <#Organizer />. +Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé par <#Organizer />. diff --git a/SoObjects/Appointments/SOGoAptMailFrenchInvitation.wo/SOGoAptMailFrenchInvitation.html b/SoObjects/Appointments/SOGoAptMailFrenchInvitation.wo/SOGoAptMailFrenchInvitation.html index 271a375c9..b3313946f 100644 --- a/SoObjects/Appointments/SOGoAptMailFrenchInvitation.wo/SOGoAptMailFrenchInvitation.html +++ b/SoObjects/Appointments/SOGoAptMailFrenchInvitation.wo/SOGoAptMailFrenchInvitation.html @@ -1,7 +1,7 @@ -<#IsSubject>Rendez-vous le <#AptStartDate /> à <#AptStartTime /> +<#IsSubject>Rendez-vous le <#AptStartDate /> à <#AptStartTime /> <#IsBody> -Vous êtes invité par <#Organizer /> à une réunion. +Vous êtes invité par <#Organizer /> à une réunion. <#HasHomePageURL> -Veuillez donner votre réponse à l'adresse <#HomePageURL />. +Veuillez donner votre réponse à l'adresse <#HomePageURL />. diff --git a/SoObjects/Appointments/SOGoAptMailFrenchRemoval.wo/SOGoAptMailFrenchRemoval.html b/SoObjects/Appointments/SOGoAptMailFrenchRemoval.wo/SOGoAptMailFrenchRemoval.html index bbe73df30..1cf438f7e 100644 --- a/SoObjects/Appointments/SOGoAptMailFrenchRemoval.wo/SOGoAptMailFrenchRemoval.html +++ b/SoObjects/Appointments/SOGoAptMailFrenchRemoval.wo/SOGoAptMailFrenchRemoval.html @@ -1,4 +1,4 @@ -<#IsSubject>Supprimé de la réunion du <#AptStartDate /> à <#AptStartTime /> +<#IsSubject>Supprimé de la réunion du <#AptStartDate /> à <#AptStartTime /> <#IsBody> -Vous ne faites plus parti de la liste des invités de la réunion du <#AptStartDate /> à <#AptStartTime /> organisée par <#Organizer />. +Vous ne faites plus parti de la liste des invités de la réunion du <#AptStartDate /> à <#AptStartTime /> organisée par <#Organizer />. diff --git a/SoObjects/Appointments/SOGoAptMailFrenchUpdate.wo/SOGoAptMailFrenchUpdate.html b/SoObjects/Appointments/SOGoAptMailFrenchUpdate.wo/SOGoAptMailFrenchUpdate.html index 901e34e14..c008d3804 100644 --- a/SoObjects/Appointments/SOGoAptMailFrenchUpdate.wo/SOGoAptMailFrenchUpdate.html +++ b/SoObjects/Appointments/SOGoAptMailFrenchUpdate.wo/SOGoAptMailFrenchUpdate.html @@ -1,5 +1,5 @@ -<#IsSubject>Rendez-vous du <#OldAptStartDate /> à <#OldAptStartTime /> modifié +<#IsSubject>Rendez-vous du <#OldAptStartDate /> à <#OldAptStartTime /> modifié <#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 />. +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 />. diff --git a/SoObjects/Appointments/SOGoAptMailGermanDeletion.wo/SOGoAptMailGermanDeletion.html b/SoObjects/Appointments/SOGoAptMailGermanDeletion.wo/SOGoAptMailGermanDeletion.html index 5653d7447..edbc30882 100644 --- a/SoObjects/Appointments/SOGoAptMailGermanDeletion.wo/SOGoAptMailGermanDeletion.html +++ b/SoObjects/Appointments/SOGoAptMailGermanDeletion.wo/SOGoAptMailGermanDeletion.html @@ -1,4 +1,4 @@ -<#IsSubject>Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde gelöscht. +<#IsSubject>Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde gelöscht. <#IsBody> -Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde von <#Organizer /> gelöscht. +Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde von <#Organizer /> gelöscht. diff --git a/SoObjects/Appointments/SOGoAptMailGermanInvitation.wo/SOGoAptMailGermanInvitation.html b/SoObjects/Appointments/SOGoAptMailGermanInvitation.wo/SOGoAptMailGermanInvitation.html index 982c66a1a..383fbb856 100644 --- a/SoObjects/Appointments/SOGoAptMailGermanInvitation.wo/SOGoAptMailGermanInvitation.html +++ b/SoObjects/Appointments/SOGoAptMailGermanInvitation.wo/SOGoAptMailGermanInvitation.html @@ -1,7 +1,7 @@ <#IsSubject>Termin am <#AptStartDate /> um <#AptStartTime /> <#IsBody> -<#Organizer /> lädt Sie zu einem Termin am <#AptStartDate /> um <#AptStartTime /> ein. +<#Organizer /> lädt Sie zu einem Termin am <#AptStartDate /> um <#AptStartTime /> ein. <#HasHomePageURL> -Bitte benutzen Sie folgende URL, um anzugeben, ob Sie an dem Termin teilnehmen können: <#HomePageURL />. +Bitte benutzen Sie folgende URL, um anzugeben, ob Sie an dem Termin teilnehmen können: <#HomePageURL />. diff --git a/SoObjects/Appointments/SOGoAptMailGermanRemoval.wo/SOGoAptMailGermanRemoval.html b/SoObjects/Appointments/SOGoAptMailGermanRemoval.wo/SOGoAptMailGermanRemoval.html index 3133ccc63..3261311e5 100644 --- a/SoObjects/Appointments/SOGoAptMailGermanRemoval.wo/SOGoAptMailGermanRemoval.html +++ b/SoObjects/Appointments/SOGoAptMailGermanRemoval.wo/SOGoAptMailGermanRemoval.html @@ -1,4 +1,4 @@ -<#IsSubject>Von dem Termin am <#AptStartDate /> um <#AptStartTime /> gelöscht +<#IsSubject>Von dem Termin am <#AptStartDate /> um <#AptStartTime /> gelöscht <#IsBody> Sie sind nicht mehr eingeladen zu dem Termin am <#AptStartDate /> um <#AptStartTime /> organisiert von <#Organizer />. diff --git a/SoObjects/Appointments/SOGoAptMailGermanUpdate.wo/SOGoAptMailGermanUpdate.html b/SoObjects/Appointments/SOGoAptMailGermanUpdate.wo/SOGoAptMailGermanUpdate.html index b0fd4dc93..f5ce36672 100644 --- a/SoObjects/Appointments/SOGoAptMailGermanUpdate.wo/SOGoAptMailGermanUpdate.html +++ b/SoObjects/Appointments/SOGoAptMailGermanUpdate.wo/SOGoAptMailGermanUpdate.html @@ -1,5 +1,5 @@ -<#IsSubject>Veränderung des Termins am <#OldAptStartDate /> um <#OldAptStartTime /> +<#IsSubject>Veränderung des Termins am <#OldAptStartDate /> um <#OldAptStartTime /> <#IsBody> -Der Termin, der ursprünglich am <#OldAptStartDate /> um <#OldAptStartTime /> stattfinden sollte, ist jetzt für den <#NewAptStartDate /> um <#NewAptStartTime /> geplant. -Bitte geben Sie an folgender URL an, ob Sie an diesem Termin zum neuen Datum teilnehmen können: <#HomePageURL />. +Der Termin, der ursprünglich am <#OldAptStartDate /> um <#OldAptStartTime /> stattfinden sollte, ist jetzt für den <#NewAptStartDate /> um <#NewAptStartTime /> geplant. +Bitte geben Sie an folgender URL an, ob Sie an diesem Termin zum neuen Datum teilnehmen können: <#HomePageURL />. diff --git a/SoObjects/Appointments/SOGoAptMailInvitation.m b/SoObjects/Appointments/SOGoAptMailInvitation.m index 64f8a8b75..84d4b3f7c 100644 --- a/SoObjects/Appointments/SOGoAptMailInvitation.m +++ b/SoObjects/Appointments/SOGoAptMailInvitation.m @@ -19,20 +19,22 @@ 02111-1307, USA. */ -#include "SOGoAptMailNotification.h" +#import "SOGoAptMailNotification.h" @interface SOGoAptMailEnglishInvitation : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailEnglishInvitation @end @interface SOGoAptMailFrenchInvitation : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailFrenchInvitation @end + +@interface SOGoAptMailGermanInvitation : SOGoAptMailNotification +@end + +@implementation SOGoAptMailGermanInvitation +@end diff --git a/SoObjects/Appointments/SOGoAptMailNotification.h b/SoObjects/Appointments/SOGoAptMailNotification.h index 934571757..ba774b185 100644 --- a/SoObjects/Appointments/SOGoAptMailNotification.h +++ b/SoObjects/Appointments/SOGoAptMailNotification.h @@ -48,21 +48,21 @@ - (id) newApt; - (void) setNewApt: (iCalEntityObject *) _newApt; -- (NSString *)homePageURL; -- (void)setHomePageURL: (NSString *)_homePageURL; +- (NSString *) homePageURL; +- (void) setHomePageURL: (NSString *) _homePageURL; -- (NSTimeZone *)viewTZ; -- (void)setViewTZ:(NSTimeZone *)_viewTZ; +- (NSTimeZone *) viewTZ; +- (void) setViewTZ: (NSTimeZone *) _viewTZ; /* Helpers */ -- (NSCalendarDate *)oldStartDate; -- (NSCalendarDate *)newStartDate; +- (NSCalendarDate *) oldStartDate; +- (NSCalendarDate *) newStartDate; /* Content Generation */ -- (NSString *)getSubject; -- (NSString *)getBody; +- (NSString *) getSubject; +- (NSString *) getBody; @end diff --git a/SoObjects/Appointments/SOGoAptMailNotification.m b/SoObjects/Appointments/SOGoAptMailNotification.m index 788669afd..912c3a0e2 100644 --- a/SoObjects/Appointments/SOGoAptMailNotification.m +++ b/SoObjects/Appointments/SOGoAptMailNotification.m @@ -28,6 +28,8 @@ #import #import +#import + #import "SOGoAptMailNotification.h" @interface SOGoAptMailNotification (PrivateAPI) @@ -88,7 +90,7 @@ static NSTimeZone *EST = nil; NSString *aptUID; aptUID = [[self newApt] uid]; - return [NSString stringWithFormat:@"%@/Calendar/%@/edit?mail-invitation=yes", + return [NSString stringWithFormat:@"%@/Calendar/personal/%@/edit?mail-invitation=yes", [self homePageURL], aptUID]; } @@ -141,7 +143,8 @@ static NSTimeZone *EST = nil; [self name]]; subject = @"ERROR: missing subject!"; } - return subject; + + return [subject asQPSubjectString: @"utf-8"]; } - (NSString *)getBody { diff --git a/SoObjects/Appointments/SOGoAptMailRemoval.m b/SoObjects/Appointments/SOGoAptMailRemoval.m index d10493f81..0053ce4be 100644 --- a/SoObjects/Appointments/SOGoAptMailRemoval.m +++ b/SoObjects/Appointments/SOGoAptMailRemoval.m @@ -19,20 +19,22 @@ 02111-1307, USA. */ -#include "SOGoAptMailNotification.h" +#import "SOGoAptMailNotification.h" @interface SOGoAptMailEnglishRemoval : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailEnglishRemoval @end @interface SOGoAptMailFrenchRemoval : SOGoAptMailNotification -{ -} @end @implementation SOGoAptMailFrenchRemoval @end + +@interface SOGoAptMailGermanRemoval : SOGoAptMailNotification +@end + +@implementation SOGoAptMailGermanRemoval +@end diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 73f9766d6..f61b756f4 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -567,7 +567,7 @@ static BOOL sendEMailNotifications = NO; } else role = SOGoCalendarRole_Organizer; - + return role; } diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.m b/SoObjects/Appointments/SOGoFreeBusyObject.m index d8559111a..ef40274dc 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.m +++ b/SoObjects/Appointments/SOGoFreeBusyObject.m @@ -35,6 +35,8 @@ #import #import +#import "SOGoAppointmentFolder.h" + #import "SOGoFreeBusyObject.h" @interface SOGoFreeBusyObject (PrivateAPI) @@ -80,17 +82,18 @@ - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate { - id calFolder; + SOGoAppointmentFolder *calFolder; // SoSecurityManager *sm; NSArray *infos; - calFolder = [container lookupName: @"Calendar" inContext: nil acquire: NO]; + calFolder = [[container lookupName: @"Calendar" inContext: nil acquire: NO] + lookupName: @"personal" inContext: nil acquire: NO]; // sm = [SoSecurityManager sharedSecurityManager]; // if (![sm validatePermission: SOGoPerm_FreeBusyLookup // onObject: calFolder // inContext: context]) - infos = [calFolder fetchFreeBusyInfosFrom: _startDate - to: _endDate]; + infos = [calFolder fetchFreeBusyInfosFrom: _startDate + to: _endDate]; // else // { // infos = [NSArray new]; @@ -169,9 +172,9 @@ info = [events nextObject]; while (info) { - if ([[info objectForKey: @"isopaque"] boolValue]) + if ([[info objectForKey: @"c_isopaque"] boolValue]) { - type = [self _fbTypeForEventStatus: [info objectForKey: @"status"]]; + type = [self _fbTypeForEventStatus: [info objectForKey: @"c_status"]]; [freebusy addFreeBusyFrom: [info objectForKey: @"startDate"] to: [info objectForKey: @"endDate"] type: type]; diff --git a/SoObjects/Appointments/SOGoTaskObject.h b/SoObjects/Appointments/SOGoTaskObject.h index 9f57bf588..da442022b 100644 --- a/SoObjects/Appointments/SOGoTaskObject.h +++ b/SoObjects/Appointments/SOGoTaskObject.h @@ -44,13 +44,6 @@ @interface SOGoTaskObject : SOGoCalendarComponent -/* folder management */ - -- (id) lookupHomeFolderForUID: (NSString *) _uid - inContext: (id) _ctx; -- (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids - inContext: (id) _ctx; - /* "iCal multifolder saves" */ - (NSException *) saveContentString: (NSString *) _iCal diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index ac3b241cf..5c628fa66 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -34,6 +34,7 @@ #import "NSArray+Appointments.h" #import "SOGoAptMailNotification.h" +#import "SOGoAppointmentFolder.h" #import "SOGoTaskObject.h" @@ -118,17 +119,6 @@ static NSString *mailTemplateDefaultLanguage = nil; return uids; } -/* 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 { @@ -136,7 +126,7 @@ static NSString *mailTemplateDefaultLanguage = nil; id folder; NSException *allErrors = nil; - e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context] + e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context] objectEnumerator]; while ((folder = [e nextObject]) != nil) { NSException *error; @@ -170,38 +160,38 @@ static NSString *mailTemplateDefaultLanguage = nil; } return allErrors; } -- (NSException *)deleteInUIDs:(NSArray *)_uids { - NSEnumerator *e; - id folder; - NSException *allErrors = nil; +// - (NSException *)deleteInUIDs:(NSArray *)_uids { +// NSEnumerator *e; +// id folder; +// NSException *allErrors = nil; - e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context] - objectEnumerator]; - while ((folder = [e nextObject])) { - NSException *error; - SOGoTaskObject *task; +// e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context] +// objectEnumerator]; +// while ((folder = [e nextObject])) { +// NSException *error; +// SOGoTaskObject *task; - task = [folder lookupName: [self nameInContainer] - inContext: context - acquire: NO]; - if (![task isNotNull]) { - [self logWithFormat:@"Note: did not find '%@' in folder: %@", - [self nameInContainer], folder]; - continue; - } - if ([task isKindOfClass: [NSException class]]) { - [self logWithFormat:@"Exception: %@", [(NSException *) task reason]]; - continue; - } +// task = [folder lookupName: [self nameInContainer] +// inContext: context +// acquire: NO]; +// if (![task isNotNull]) { +// [self logWithFormat:@"Note: did not find '%@' in folder: %@", +// [self nameInContainer], folder]; +// continue; +// } +// if ([task isKindOfClass: [NSException class]]) { +// [self logWithFormat:@"Exception: %@", [(NSException *) task reason]]; +// continue; +// } - if ((error = [task primaryDelete]) != nil) { - [self logWithFormat:@"Note: failed to delete in folder: %@", folder]; - // TODO: make compound - allErrors = error; - } - } - return allErrors; -} +// if ((error = [task primaryDelete]) != nil) { +// [self logWithFormat:@"Note: failed to delete in folder: %@", folder]; +// // TODO: make compound +// allErrors = error; +// } +// } +// return allErrors; +// } /* "iCal multifolder saves" */ @@ -376,15 +366,15 @@ static NSString *mailTemplateDefaultLanguage = nil; // attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]]; // [attendees removePerson: organizer]; // if ([attendees count]) { -// iCalToDo *canceledApt; +// iCalToDo *cancelledApt; -// canceledApt = [newApt copy]; -// [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"]; +// cancelledApt = [newApt copy]; +// [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"]; // [self sendEMailUsingTemplateNamed: @"Removal" // forOldObject: nil -// andNewObject: canceledApt +// andNewObject: cancelledApt // toAttendees: attendees]; -// [canceledApt release]; +// [cancelledApt release]; // } // } @@ -406,45 +396,48 @@ static NSString *mailTemplateDefaultLanguage = nil; - delete in removed folders - send iMIP mail for all folders not found */ - iCalToDo *task; - NSArray *removedUIDs; - NSMutableArray *attendees; +// iCalToDo *task; +// NSArray *removedUIDs; +// NSMutableArray *attendees; - /* load existing content */ + [self primaryDelete]; + + return nil; +// /* load existing content */ - task = (iCalToDo *) [self component: NO]; +// task = (iCalToDo *) [self component: NO]; - /* compare sequence if requested */ +// /* compare sequence if requested */ - if (_v != 0) { - // TODO - } +// if (_v != 0) { +// // TODO +// } - removedUIDs = [self attendeeUIDsFromTask:task]; +// removedUIDs = [self attendeeUIDsFromTask:task]; - if ([self sendEMailNotifications]) - { - /* send notification email to attendees excluding organizer */ - attendees = [NSMutableArray arrayWithArray:[task attendees]]; - [attendees removePerson:[task organizer]]; +// if ([self sendEMailNotifications]) +// { +// /* send notification email to attendees excluding organizer */ +// attendees = [NSMutableArray arrayWithArray:[task attendees]]; +// [attendees removePerson:[task organizer]]; - /* flag task as being canceled */ - [(iCalCalendar *) [task parent] setMethod: @"cancel"]; - [task increaseSequence]; +// /* flag task as being cancelled */ +// [(iCalCalendar *) [task parent] setMethod: @"cancel"]; +// [task increaseSequence]; - /* remove all attendees to signal complete removal */ - [task removeAllAttendees]; +// /* remove all attendees to signal complete removal */ +// [task removeAllAttendees]; - /* send notification email */ - [self sendEMailUsingTemplateNamed: @"Deletion" - forOldObject: nil - andNewObject: task - toAttendees: attendees]; - } +// /* send notification email */ +// [self sendEMailUsingTemplateNamed: @"Deletion" +// forOldObject: nil +// andNewObject: task +// toAttendees: attendees]; +// } - /* perform */ +// /* perform */ - return [self deleteInUIDs:removedUIDs]; +// return [self deleteInUIDs:removedUIDs]; } - (NSException *)saveContentString:(NSString *)_iCalString { diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index 6b67f2da4..3a4411161 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -23,6 +23,8 @@ #import #import +#import + #import #import @@ -32,44 +34,31 @@ - (BOOL) userIsParticipant: (SOGoUser *) user { - NSEnumerator *emails; - NSArray *identities; - NSString *currentEmail; - BOOL response; + NSEnumerator *participants; + iCalPerson *currentParticipant; + BOOL isParticipant; - response = NO; + isParticipant = NO; - identities = [user allIdentities]; - emails = [[identities objectsForKey: @"email"] objectEnumerator]; - currentEmail = [emails nextObject]; - while (!response && currentEmail) - if ([self isParticipant: currentEmail]) - response = YES; + participants = [[self participants] objectEnumerator]; + currentParticipant = [participants nextObject]; + while (!isParticipant + && currentParticipant) + if ([user hasEmail: [currentParticipant rfc822Email]]) + isParticipant = YES; else - currentEmail = [emails nextObject]; + currentParticipant = [participants nextObject]; - return response; + return isParticipant; } - (BOOL) userIsOrganizer: (SOGoUser *) user { - NSEnumerator *emails; - NSArray *identities; - NSString *currentEmail; - BOOL response; + NSString *orgMail; - response = NO; + orgMail = [[self organizer] rfc822Email]; - identities = [user allIdentities]; - emails = [[identities objectsForKey: @"email"] objectEnumerator]; - currentEmail = [emails nextObject]; - while (!response && currentEmail) - if ([self isOrganizer: currentEmail]) - response = YES; - else - currentEmail = [emails nextObject]; - - return response; + return [user hasEmail: orgMail]; } @end diff --git a/SoObjects/Appointments/product.plist b/SoObjects/Appointments/product.plist index d70b2faad..1fdd67e88 100644 --- a/SoObjects/Appointments/product.plist +++ b/SoObjects/Appointments/product.plist @@ -8,6 +8,9 @@ }; classes = { + SOGoAppointmentFolder = { + superclass = "SOGoParentFolder"; + }; SOGoAppointmentFolder = { superclass = "SOGoFolder"; defaultRoles = { diff --git a/SoObjects/Contacts/English.lproj/Localizable.strings b/SoObjects/Contacts/English.lproj/Localizable.strings new file mode 100644 index 000000000..56404b013 --- /dev/null +++ b/SoObjects/Contacts/English.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Address Book" = "Personal Address Book"; diff --git a/SoObjects/Contacts/French.lproj/Localizable.strings b/SoObjects/Contacts/French.lproj/Localizable.strings new file mode 100644 index 000000000..af4d87c3a --- /dev/null +++ b/SoObjects/Contacts/French.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Address Book" = "Carnet d'adresses personnel"; diff --git a/SoObjects/Contacts/GNUmakefile b/SoObjects/Contacts/GNUmakefile index 3c1711c11..a979a373a 100644 --- a/SoObjects/Contacts/GNUmakefile +++ b/SoObjects/Contacts/GNUmakefile @@ -19,6 +19,9 @@ Contacts_RESOURCE_FILES += \ Version \ product.plist \ +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/SoObjects/Contacts/German.lproj/Localizable.strings b/SoObjects/Contacts/German.lproj/Localizable.strings new file mode 100644 index 000000000..95feda9fd --- /dev/null +++ b/SoObjects/Contacts/German.lproj/Localizable.strings @@ -0,0 +1 @@ +"Personal Calendar" = "Personal Calendar"; diff --git a/SoObjects/Contacts/SOGoContactFolder.h b/SoObjects/Contacts/SOGoContactFolder.h index 6ced261a5..c1ff89685 100644 --- a/SoObjects/Contacts/SOGoContactFolder.h +++ b/SoObjects/Contacts/SOGoContactFolder.h @@ -43,16 +43,6 @@ @protocol SOGoContactFolder -+ (id ) contactFolderWithName: (NSString *) aName - andDisplayName: (NSString *) aDisplayName - inContainer: (SOGoObject *) aContainer; - -- (id ) initWithName: (NSString *) aName - andDisplayName: (NSString *) aDisplayName - inContainer: (SOGoObject *) aContainer; - -- (NSString *) displayName; - - (NSArray *) lookupContactsWithFilter: (NSString *) filter sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering; diff --git a/SoObjects/Contacts/SOGoContactFolders.h b/SoObjects/Contacts/SOGoContactFolders.h index 0272f3bbb..4e271e5cf 100644 --- a/SoObjects/Contacts/SOGoContactFolders.h +++ b/SoObjects/Contacts/SOGoContactFolders.h @@ -1,6 +1,6 @@ /* SOGoContactFolders.h - this file is part of SOGo * - * Copyright (C) 2006 Inverse groupe conseil + * Copyright (C) 2006, 2007 Inverse groupe conseil * * Author: Wolfgang Sourdeau * @@ -23,25 +23,9 @@ #ifndef SOGOCONTACTFOLDERS_H #define SOGOCONTACTFOLDERS_H -#import +#import -@class NSMutableDictionary; -@class NSString; -@class WOResponse; - -@interface SOGoContactFolders : SOGoObject -{ - NSMutableDictionary *contactFolders; - NSString *OCSPath; -} - -- (NSString *) defaultSourceName; - -- (void) setBaseOCSPath: (NSString *) newOCSPath; - -- (NSArray *) contactFolders; - -- (WOResponse *) newFolderWithName: (NSString *) name; +@interface SOGoContactFolders : SOGoParentFolder @end diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index f166515a0..2d75c8f0c 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -1,6 +1,6 @@ /* SOGoContactFolders.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse groupe conseil + * Copyright (C) 2006, 2007 Inverse groupe conseil * * Author: Wolfgang Sourdeau * @@ -20,7 +20,6 @@ * Boston, MA 02111-1307, USA. */ -/* exchange folder types: */ /* MailItems IPF.Note ContactItems IPF.Contact AppointmentItems IPF.Appointment @@ -28,23 +27,11 @@ TaskItems IPF.Task JournalItems IPF.Journal */ -#import +#import #import - -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import +#import #import -#import #import "SOGoContactGCSFolder.h" #import "SOGoContactLDAPFolder.h" @@ -52,89 +39,14 @@ @implementation SOGoContactFolders -- (id) init ++ (NSString *) gcsFolderType { - if ((self = [super init])) - { - contactFolders = nil; - OCSPath = nil; - } - - return self; + return @"Contact"; } -- (void) dealloc ++ (Class) subFolderClass { - if (contactFolders) - [contactFolders release]; - if (OCSPath) - [OCSPath release]; - [super dealloc]; -} - -- (void) _fetchPersonalFolders: (NSString *) sql - withChannel: (EOAdaptorChannel *) fc -{ - NSArray *attrs; - NSDictionary *row; - SOGoContactGCSFolder *ab; - BOOL hasPersonal; - NSString *key, *path; - - hasPersonal = NO; - [fc evaluateExpressionX: sql]; - attrs = [fc describeResults: NO]; - row = [fc fetchAttributes: attrs withZone: NULL]; - while (row) - { - ab = [SOGoContactGCSFolder - contactFolderWithName: [row objectForKey: @"c_path4"] - andDisplayName: [row objectForKey: @"c_foldername"] - inContainer: self]; - key = [row objectForKey: @"c_path4"]; - hasPersonal = (hasPersonal || [key isEqualToString: @"personal"]); - [ab setOCSPath: [NSString stringWithFormat: @"%@/%@", - OCSPath, key]]; - [contactFolders setObject: ab forKey: key]; - row = [fc fetchAttributes: attrs withZone: NULL]; - } - - if (!hasPersonal) - { - ab = [SOGoContactGCSFolder contactFolderWithName: @"personal" - andDisplayName: @"Contacts" - inContainer: self]; - path = [NSString stringWithFormat: - @"/Users/%@/Contacts/personal", - [self ownerInContext: context]]; - [ab setOCSPath: path]; - [contactFolders setObject: ab forKey: @"personal"]; - } -} - -- (void) appendPersonalSources -{ - GCSChannelManager *cm; - EOAdaptorChannel *fc; - NSURL *folderLocation; - NSString *sql; - - cm = [GCSChannelManager defaultChannelManager]; - folderLocation - = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; - fc = [cm acquireOpenChannelForURL: folderLocation]; - if (fc) - { - sql = [NSString - stringWithFormat: (@"SELECT c_path4, c_foldername FROM %@" - @" WHERE c_path2 = '%@'" - @" AND c_folder_type = 'Contact'"), - [folderLocation gcsTableName], [self ownerInContext: context]]; - [self _fetchPersonalFolders: sql withChannel: fc]; - [cm releaseChannel: fc]; -// sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", -// uidColumnName, [self uid]]; - } + return [SOGoContactGCSFolder class]; } - (void) appendSystemSources @@ -150,142 +62,18 @@ while (currentSourceID) { displayName = [um displayNameForSourceWithID: currentSourceID]; - currentFolder = [SOGoContactLDAPFolder contactFolderWithName: currentSourceID + currentFolder = [SOGoContactLDAPFolder folderWithName: currentSourceID andDisplayName: displayName inContainer: self]; [currentFolder setLDAPSource: [um sourceWithID: currentSourceID]]; - [contactFolders setObject: currentFolder forKey: currentSourceID]; + [subFolders setObject: currentFolder forKey: currentSourceID]; currentSourceID = [sourceIDs nextObject]; } } -- (WOResponse *) newFolderWithName: (NSString *) name +- (NSString *) defaultFolderName { - SOGoContactGCSFolder *newFolder; - WOResponse *response; - - newFolder = [SOGoContactGCSFolder contactFolderWithName: name - andDisplayName: name - inContainer: self]; - if ([newFolder isKindOfClass: [NSException class]]) - response = (WOResponse *) newFolder; - else - { - [newFolder setOCSPath: [NSString stringWithFormat: @"%@/%@", - OCSPath, name]]; - if ([newFolder create]) - { - response = [WOResponse new]; - [response setStatus: 201]; - [response autorelease]; - } - else - response = [NSException exceptionWithHTTPStatus: 400 - reason: @"The new folder could not be created"]; - } - - return response; -} - -- (void) initContactSources -{ - if (!contactFolders) - { - contactFolders = [NSMutableDictionary new]; - [self appendPersonalSources]; - [self appendSystemSources]; - } -} - -- (id) lookupName: (NSString *) name - inContext: (WOContext *) lookupContext - acquire: (BOOL) acquire -{ - id obj; - - /* first check attributes directly bound to the application */ - obj = [super lookupName: name inContext: lookupContext acquire: NO]; - if (!obj) - { - if (!contactFolders) - [self initContactSources]; - - obj = [contactFolders objectForKey: name]; - if (!obj) - obj = [NSException exceptionWithHTTPStatus: 404]; - } - - return obj; -} - -- (NSArray *) toManyRelationshipKeys -{ - if (!contactFolders) - [self initContactSources]; - - return [contactFolders allKeys]; -} - -- (NSArray *) contactFolders -{ - if (!contactFolders) - [self initContactSources]; - - return [contactFolders allValues]; -} - -/* acls */ -- (NSArray *) aclsForUser: (NSString *) uid -{ - return nil; -} - -// - (NSString *) roleOfUser: (NSString *) uid -// { -// NSArray *roles, *traversalPath; -// NSString *objectName, *role; - -// role = nil; -// traversalPath = [context objectForKey: @"SoRequestTraversalPath"]; -// if ([traversalPath count] > 2) -// { -// objectName = [traversalPath objectAtIndex: 2]; -// roles = [[context activeUser] -// rolesForObject: [self lookupName: objectName -// inContext: context -// acquire: NO] -// inContext: context]; -// if ([roles containsObject: SOGoRole_Assistant] -// || [roles containsObject: SOGoRole_Delegate]) -// role = SOGoRole_Assistant; -// } - -// return role; -// } - -- (BOOL) davIsCollection -{ - return YES; -} - -- (NSString *) davContentType -{ - return @"httpd/unix-directory"; -} - -- (void) setBaseOCSPath: (NSString *) newOCSPath -{ - if (OCSPath) - [OCSPath release]; - OCSPath = newOCSPath; - if (OCSPath) - [OCSPath retain]; -} - -/* web interface */ -- (NSString *) defaultSourceName -{ - return @"personal"; + return @"Personal Address Book"; } @end diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.h b/SoObjects/Contacts/SOGoContactGCSFolder.h index 1dd91a53b..d10913d0b 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.h +++ b/SoObjects/Contacts/SOGoContactGCSFolder.h @@ -30,9 +30,6 @@ @class NSString; @interface SOGoContactGCSFolder : SOGoFolder -{ - NSString *displayName; -} @end diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 47eb5f13c..26e0e974c 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -21,14 +21,20 @@ #import #import + #import #import #import +#import #import #import +#import #import #import #import +#import +#import +#import #import #import "SOGoContactGCSEntry.h" @@ -41,47 +47,6 @@ @implementation SOGoContactGCSFolder -+ (id ) contactFolderWithName: (NSString *) aName - andDisplayName: (NSString *) aDisplayName - inContainer: (SOGoObject *) aContainer -{ - SOGoContactGCSFolder *folder; - - folder = [[self alloc] initWithName: aName - andDisplayName: aDisplayName - inContainer: aContainer]; - [folder autorelease]; - - return folder; -} - -- (void) dealloc -{ - [displayName release]; - [super dealloc]; -} - -- (id ) initWithName: (NSString *) newName - andDisplayName: (NSString *) newDisplayName - inContainer: (SOGoObject *) newContainer -{ - if ((self = [self initWithName: newName - inContainer: newContainer])) - ASSIGN (displayName, newDisplayName); - - return self; -} - -- (BOOL) folderIsMandatory -{ - return [nameInContainer isEqualToString: @"personal"]; -} - -- (NSString *) displayName -{ - return displayName; -} - /* name lookup */ - (id ) lookupContactWithId: (NSString *) recordId @@ -105,7 +70,6 @@ BOOL isPut; isPut = NO; - /* first check attributes directly bound to the application */ obj = [super lookupName:_key inContext:_ctx acquire:NO]; if (!obj) { @@ -210,6 +174,140 @@ return newRecords; } +- (BOOL) _isValidFilter: (NSString *) theString +{ + if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame) + return YES; + + if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame) + return YES; + + return NO; +} + +- (NSDictionary *) _parseContactFilter: (id ) filterElement +{ + NSMutableDictionary *filterData; + id parentNode; + id ranges; + + parentNode = [filterElement parentNode]; + + if ([[parentNode tagName] isEqualToString: @"filter"] && + [self _isValidFilter: [filterElement attribute: @"name"]]) + { + ranges = [filterElement getElementsByTagName: @"text-match"]; + + if ([ranges count] && [[[ranges objectAtIndex: 0] childNodes] count]) + { + filterData = [NSMutableDictionary new]; + [filterData autorelease]; + [filterData setObject: [[[[ranges objectAtIndex: 0] childNodes] lastObject] data] + forKey: [filterElement attribute: @"name"]]; + } + } + else + filterData = nil; + + return filterData; +} + +#warning filters is leaked here +- (NSArray *) _parseContactFilters: (id ) parentNode +{ + NSEnumerator *children; + id node; + NSMutableArray *filters; + NSDictionary *filter; + + filters = [NSMutableArray new]; + + children = [[parentNode getElementsByTagName: @"prop-filter"] + objectEnumerator]; + + node = [children nextObject]; + + while (node) + { + filter = [self _parseContactFilter: node]; + if (filter) + [filters addObject: filter]; + node = [children nextObject]; + } + + return filters; +} + +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r +{ + SOGoContactGCSEntry *component; + Class componentClass; + NSString *name, *etagLine, *contactString; + + name = [object objectForKey: @"c_name"]; + componentClass = [SOGoContactGCSEntry class]; + + component = [componentClass objectWithName: name inContainer: self]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + [r appendContentString: baseURL]; + if (![baseURL hasSuffix: @"/"]) + [r appendContentString: @"/"]; + [r appendContentString: name]; + [r appendContentString: @"\r\n"]; + + [r appendContentString: @" \r\n"]; + [r appendContentString: @" \r\n"]; + etagLine = [NSString stringWithFormat: @" %@\r\n", + [component davEntityTag]]; + [r appendContentString: etagLine]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" HTTP/1.1 200 OK\r\n"]; + [r appendContentString: @" \r\n"]; + [r appendContentString: @" "]; + contactString = [[component contentAsString] stringByEscapingXMLString]; + [r appendContentString: contactString]; + [r appendContentString: @"\r\n"]; + [r appendContentString: @" \r\n"]; +} + +- (void) _appendComponentsMatchingFilters: (NSArray *) filters + toResponse: (WOResponse *) response +{ + unsigned int count, max; + NSDictionary *currentFilter, *contact; + NSEnumerator *contacts; + NSString *baseURL; + + baseURL = [self baseURLInContext: context]; + + max = [filters count]; + for (count = 0; count < max; count++) + { + currentFilter = [filters objectAtIndex: count]; + contacts = [[self lookupContactsWithFilter: [[currentFilter allValues] lastObject] + sortBy: @"c_givenname" + ordering: NSOrderedDescending] + objectEnumerator]; + + while ((contact = [contacts nextObject])) + { + [self appendObject: contact + withBaseURL: baseURL + toREPORTResponse: response]; + } + } +} + - (NSArray *) lookupContactsWithFilter: (NSString *) filter sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering @@ -218,13 +316,11 @@ EOQualifier *qualifier; EOSortOrdering *ordering; -// NSLog (@"fetching records matching '%@', sorted by '%@' in order %d", -// filter, sortKey, sortOrdering); - fields = folderListingFields; qualifier = [self _qualifierForFilter: filter]; dbRecords = [[self ocsFolder] fetchFields: fields matchingQualifier: qualifier]; + if ([dbRecords count] > 0) { records = [self _flattenedRecords: dbRecords]; @@ -242,7 +338,7 @@ // else // [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - //[self debugWithFormat:@"fetched %i records.", [records count]]; + [self debugWithFormat:@"fetched %i records.", [records count]]; return records; } @@ -251,6 +347,32 @@ return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"]; } +- (id) davAddressbookQuery: (id) queryContext +{ + WOResponse *r; + NSArray *filters; + id document; + + r = [context response]; + [r setStatus: 207]; + [r setContentEncoding: NSUTF8StringEncoding]; + [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [r setHeader: @"no-cache" forKey: @"pragma"]; + [r setHeader: @"no-cache" forKey: @"cache-control"]; + [r appendContentString:@"\r\n"]; + [r appendContentString: @"\r\n"]; + + document = [[context request] contentAsDOMDocument]; + filters = [self _parseContactFilters: [document documentElement]]; + + [self _appendComponentsMatchingFilters: filters + toResponse: r]; + [r appendContentString:@"\r\n"]; + + return r; +} + - (NSArray *) davComplianceClassesInContext: (id)_ctx { NSMutableArray *classes; @@ -273,14 +395,6 @@ return @"vcard-collection"; } -- (NSException *) delete -{ - return (([nameInContainer isEqualToString: @"personal"]) - ? [NSException exceptionWithHTTPStatus: 403 - reason: @"the 'personal' folder cannot be deleted"] - : [super delete]); -} - // /* GET */ // - (id) GETAction: (id)_ctx @@ -300,6 +414,20 @@ // return r; // } +/* sorting */ +- (NSComparisonResult) compare: (id) otherFolder +{ + NSComparisonResult comparison; + + if ([NSStringFromClass([otherFolder class]) + isEqualToString: @"SOGoContactLDAPFolder"]) + comparison = NSOrderedAscending; + else + comparison = [super compare: otherFolder]; + + return comparison; +} + /* folder type */ - (NSString *) folderType diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.h b/SoObjects/Contacts/SOGoContactLDAPFolder.h index ad1ceb89c..e405a3faf 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.h +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.h @@ -37,9 +37,12 @@ BOOL ignoreSoObjectHunger; } -- (id ) initWithName: (NSString *) newName - andDisplayName: (NSString *) newDisplayName - inContainer: (SOGoObject *) newContainer; ++ (id) folderWithName: (NSString *) aName + andDisplayName: (NSString *) aDisplayName + inContainer: (id) aContainer; +- (id) initWithName: (NSString *) newName + andDisplayName: (NSString *) newDisplayName + inContainer: (id) newContainer; - (void) setLDAPSource: (LDAPSource *) newLdapSource; @end diff --git a/SoObjects/Contacts/SOGoContactLDAPFolder.m b/SoObjects/Contacts/SOGoContactLDAPFolder.m index 2704be332..5f1ad9ecb 100644 --- a/SoObjects/Contacts/SOGoContactLDAPFolder.m +++ b/SoObjects/Contacts/SOGoContactLDAPFolder.m @@ -41,11 +41,11 @@ @implementation SOGoContactLDAPFolder -+ (id ) contactFolderWithName: (NSString *) aName - andDisplayName: (NSString *) aDisplayName - inContainer: (SOGoObject *) aContainer ++ (id) folderWithName: (NSString *) aName + andDisplayName: (NSString *) aDisplayName + inContainer: (id) aContainer { - SOGoContactLDAPFolder *folder; + id folder; folder = [[self alloc] initWithName: aName andDisplayName: aDisplayName @@ -68,9 +68,9 @@ return self; } -- (id ) initWithName: (NSString *) newName - andDisplayName: (NSString *) newDisplayName - inContainer: (SOGoObject *) newContainer +- (id) initWithName: (NSString *) newName + andDisplayName: (NSString *) newDisplayName + inContainer: (id) newContainer { if ((self = [self initWithName: newName inContainer: newContainer])) @@ -256,7 +256,28 @@ return YES; } +/* sorting */ +- (NSComparisonResult) compare: (id) otherFolder +{ + NSComparisonResult comparison; + + if ([NSStringFromClass([otherFolder class]) + isEqualToString: @"SOGoContactGCSFolder"]) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + + return comparison; +} + /* acls */ +- (NSString *) ownerInContext: (WOContext *) noContext +{ + return @"nobody"; +} + /* TODO: this might change one day when we support LDAP acls */ - (NSArray *) aclsForUser: (NSString *) uid { diff --git a/SoObjects/Contacts/product.plist b/SoObjects/Contacts/product.plist index cb6b5124d..e1557d613 100644 --- a/SoObjects/Contacts/product.plist +++ b/SoObjects/Contacts/product.plist @@ -9,12 +9,7 @@ classes = { SOGoContactFolders = { - superclass = "SOGoFolder"; - protectedBy = "Access Contents Information"; - defaultRoles = { - "Access Contents Information" = ( "Authenticated" ); - "WebDAV Access" = ( "Authenticated" ); - }; + superclass = "SOGoParentFolder"; }; SOGoContactGCSFolder = { superclass = "SOGoFolder"; diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index 698e6b6fa..cc04cf258 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -30,9 +30,7 @@ Mailer_OBJC_FILES += \ SOGoDraftsFolder.m \ SOGoDraftObject.m \ \ - SOGoMailForward.m \ - SOGoMailEnglishForward.m \ - SOGoMailFrenchForward.m + SOGoMailForward.m Mailer_RESOURCE_FILES += \ Version \ @@ -40,7 +38,11 @@ Mailer_RESOURCE_FILES += \ Mailer_RESOURCE_FILES += \ SOGoMailEnglishForward.wo \ - SOGoMailFrenchForward.wo + SOGoMailFrenchForward.wo \ + SOGoMailGermanForward.wo + +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/wobundle.make diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 0a2415e81..f2d448492 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -53,6 +53,7 @@ NGImap4Envelope *envelope; int IMAP4ID; NSMutableDictionary *headers; + NSString *inReplyTo; NSString *text; NSString *sourceURL; NSString *sourceFlag; diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 6c49beb97..3aa25f15e 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -53,6 +53,7 @@ #import #import +#import #import #import #import "SOGoMailAccount.h" @@ -64,37 +65,8 @@ static NSString *contentTypeValue = @"text/plain; charset=utf-8"; static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc", - @"from", @"replyTo", nil}; - -@interface NSString (NGMimeHelpers) - -- (NSString *) asQPSubjectString: (NSString *) encoding; - -@end - -@implementation NSString (NGMimeHelpers) - -- (NSString *) asQPSubjectString: (NSString *) encoding; -{ - NSString *qpString, *subjectString; - NSData *subjectData, *destSubjectData; - - subjectData = [self dataUsingEncoding: NSUTF8StringEncoding]; - destSubjectData = [subjectData dataByEncodingQuotedPrintable]; - - qpString = [[NSString alloc] initWithData: destSubjectData - encoding: NSASCIIStringEncoding]; - [qpString autorelease]; - if ([qpString length] > [self length]) - subjectString = [NSString stringWithFormat: @"=?%@?Q?%@?=", - encoding, qpString]; - else - subjectString = self; - - return subjectString; -} - -@end + @"from", @"replyTo", + nil}; @implementation SOGoDraftObject @@ -128,6 +100,7 @@ static BOOL showTextAttachmentsInline = NO; text = @""; sourceURL = nil; sourceFlag = nil; + inReplyTo = nil; } return self; @@ -141,6 +114,7 @@ static BOOL showTextAttachmentsInline = NO; [path release]; [sourceURL release]; [sourceFlag release]; + [inReplyTo release]; [super dealloc]; } @@ -190,7 +164,7 @@ static BOOL showTextAttachmentsInline = NO; id headerValue; unsigned int count; - for (count = 0; count < 6; count++) + for (count = 0; count < 7; count++) { headerValue = [newHeaders objectForKey: headerKeys[count]]; if (headerValue) @@ -216,6 +190,11 @@ static BOOL showTextAttachmentsInline = NO; return text; } +- (void) setInReplyTo: (NSString *) newInReplyTo +{ + ASSIGN (inReplyTo, newInReplyTo); +} + - (void) setSourceURL: (NSString *) newSourceURL { ASSIGN (sourceURL, newSourceURL); @@ -237,6 +216,8 @@ static BOOL showTextAttachmentsInline = NO; [infos setObject: headers forKey: @"headers"]; if (text) [infos setObject: text forKey: @"text"]; + if (inReplyTo) + [infos setObject: inReplyTo forKey: @"inReplyTo"]; if (IMAP4ID > -1) [infos setObject: [NSNumber numberWithInt: IMAP4ID] forKey: @"IMAP4ID"]; @@ -291,6 +272,10 @@ static BOOL showTextAttachmentsInline = NO; value = [infoDict objectForKey: @"sourceFlag"]; if (value) [self setSourceFlag: value]; + + value = [infoDict objectForKey: @"inReplyTo"]; + if (value) + [self setInReplyTo: value]; } - (NSString *) relativeImap4Name @@ -498,15 +483,21 @@ static BOOL showTextAttachmentsInline = NO; - (void) fetchMailForReplying: (SOGoMailObject *) sourceMail toAll: (BOOL) toAll { - NSString *contentForReply; + NSString *contentForReply, *msgID; NSMutableDictionary *info; + NGImap4Envelope *sourceEnvelope; [sourceMail fetchCoreInfos]; info = [NSMutableDictionary dictionaryWithCapacity: 16]; [info setObject: [sourceMail subjectForReply] forKey: @"subject"]; + + sourceEnvelope = [sourceMail envelope]; [self _fillInReplyAddresses: info replyToAll: toAll - envelope: [sourceMail envelope]]; + envelope: sourceEnvelope]; + msgID = [sourceEnvelope messageID]; + if ([msgID length] > 0) + [self setInReplyTo: msgID]; contentForReply = [sourceMail contentForReply]; [self setText: contentForReply]; [self setHeaders: info]; @@ -587,7 +578,7 @@ static BOOL showTextAttachmentsInline = NO; - (BOOL) isValidAttachmentName: (NSString *) _name { - static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil }; + static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", nil }; unsigned i; NSRange r; @@ -620,7 +611,8 @@ static BOOL showTextAttachmentsInline = NO; withMetadata: (NSDictionary *) metadata { NSString *p, *name, *mimeType; - + NSRange r; + if (![_attach isNotNull]) { return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ reason: @"Missing attachment content!"]; @@ -630,7 +622,13 @@ static BOOL showTextAttachmentsInline = NO; return [NSException exceptionWithHTTPStatus:500 /* Server Error */ reason: @"Could not create folder for draft!"]; } + name = [metadata objectForKey: @"filename"]; + r = [name rangeOfString: @"\\" + options: NSBackwardsSearch]; + if (r.length > 0) + name = [name substringFromIndex: r.location + 1]; + if (![self isValidAttachmentName: name]) return [self invalidAttachmentNameError: name]; @@ -1029,7 +1027,9 @@ static BOOL showTextAttachmentsInline = NO; [map setObjects:[map objectsForKey: @"from"] forKey: @"reply-to"]; /* add subject */ - + if (inReplyTo) + [map setObject: inReplyTo forKey: @"in-reply-to"]; + if ([(s = [headers objectForKey: @"subject"]) length] > 0) [map setObject: [s asQPSubjectString: @"utf-8"] forKey: @"subject"]; diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index bf2a7a587..eea913c2b 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -32,6 +32,8 @@ #import #import +#import + #import "SOGoMailFolder.h" #import "SOGoMailManager.h" #import "SOGoDraftsFolder.h" @@ -333,10 +335,32 @@ static BOOL useAltNamespace = NO; return [NSString stringWithFormat: @"folder%@", inboxFolderName]; } +- (NSString *) _userFolderNameWithPurpose: (NSString *) purpose +{ + NSUserDefaults *ud; + NSMutableDictionary *mailSettings; + NSString *folderName; + + folderName = nil; + ud = [[context activeUser] userSettings]; + mailSettings = [ud objectForKey: @"Mail"]; + if (mailSettings) + folderName + = [mailSettings objectForKey: [NSString stringWithFormat: @"%@Folder", + purpose]]; + + return folderName; +} + - (NSString *) draftsFolderNameInContext: (id) _ctx { - /* SOGo managed folder */ - return [NSString stringWithFormat: @"folder%@", draftsFolderName]; + NSString *folderName; + + folderName = [self _userFolderNameWithPurpose: @"Drafts"]; + if (!folderName) + folderName = draftsFolderName; + + return [NSString stringWithFormat: @"folder%@", folderName]; } - (NSString *) sieveFolderNameInContext: (id) _ctx @@ -346,12 +370,24 @@ static BOOL useAltNamespace = NO; - (NSString *) sentFolderNameInContext: (id)_ctx { - return [NSString stringWithFormat: @"folder%@", sentFolderName]; + NSString *folderName; + + folderName = [self _userFolderNameWithPurpose: @"Sent"]; + if (!folderName) + folderName = sentFolderName; + + return [NSString stringWithFormat: @"folder%@", folderName]; } - (NSString *) trashFolderNameInContext: (id)_ctx { - return [NSString stringWithFormat: @"folder%@", trashFolderName]; + NSString *folderName; + + folderName = [self _userFolderNameWithPurpose: @"Trash"]; + if (!folderName) + folderName = trashFolderName; + + return [NSString stringWithFormat: @"folder%@", folderName]; } - (SOGoMailFolder *) inboxFolderInContext: (id) _ctx diff --git a/SoObjects/Mailer/SOGoMailBaseObject.m b/SoObjects/Mailer/SOGoMailBaseObject.m index 11b74259b..e6c2260d4 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SoObjects/Mailer/SOGoMailBaseObject.m @@ -20,7 +20,6 @@ */ #import -#import #import #import #import diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 7087a1711..892a663c1 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -63,7 +63,7 @@ static BOOL useAltNamespace = NO; folder = [mailAccount sharedFolderName]; if (folder && [path hasPrefix: folder]) - [self setOwner: @"anyone"]; + [self setOwner: @"nobody"]; else { folder = [mailAccount otherUsersFolderName]; @@ -73,7 +73,7 @@ static BOOL useAltNamespace = NO; if ([names count] > 1) [self setOwner: [names objectAtIndex: 1]]; else - [self setOwner: @"anyone"]; + [self setOwner: @"nobody"]; } } } @@ -225,42 +225,6 @@ static BOOL useAltNamespace = NO; /* name lookup */ -- (id) lookupImap4Folder: (NSString *) _key - inContext: (id) _ctx -{ - // TODO: we might want to check for existence prior controller creation - NSURL *sf; - SOGoMailFolder *newFolder; - - /* check whether URL exists */ - - sf = [self imap4URL]; - sf = [NSURL URLWithString: [_key substringFromIndex: 6] - relativeToURL: sf]; - -// - sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key] -// - relativeToURL:sf]; - - if ([[self imap4Connection] doesMailboxExistAtURL: sf]) - newFolder = [SOGoMailFolder objectWithName: _key inContainer: self]; - else - newFolder = nil; - /* - 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 newFolder; -} - -- (id) lookupImap4Message: (NSString *) _key - inContext: (id) _ctx -{ - // TODO: we might want to check for existence prior controller creation - return [SOGoMailObject objectWithName: _key inContainer: self]; -} - - (id) lookupName: (NSString *) _key inContext: (id)_ctx acquire: (BOOL) _acquire @@ -268,13 +232,18 @@ static BOOL useAltNamespace = NO; id obj; if ([_key hasPrefix: @"folder"]) - obj = [self lookupImap4Folder: _key inContext: _ctx]; + obj = [SOGoMailFolder objectWithName: _key inContainer: self]; else { - if (isdigit ([_key characterAtIndex: 0])) - obj = [self lookupImap4Message: _key inContext: _ctx]; + if ([[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]]) + { + if (isdigit ([_key characterAtIndex: 0])) + obj = [SOGoMailObject objectWithName: _key inContainer: self]; + else + obj = [super lookupName: _key inContext: _ctx acquire: NO]; + } else - obj = [super lookupName: _key inContext: _ctx acquire: NO]; + obj = nil; } if (!obj && _acquire) @@ -616,7 +585,7 @@ static BOOL useAltNamespace = NO; return userPath; } -- (NSString *) httpURLForAdvisoryToUser: (NSString *) uid; +- (NSString *) httpURLForAdvisoryToUser: (NSString *) uid { SOGoUser *user; NSString *otherUsersPath, *url; @@ -640,7 +609,7 @@ static BOOL useAltNamespace = NO; return url; } -- (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid; +- (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid { NSURL *selfURL, *userURL; diff --git a/SoObjects/Mailer/SOGoMailForward.h b/SoObjects/Mailer/SOGoMailForward.h index 6ad18c018..d5da45494 100644 --- a/SoObjects/Mailer/SOGoMailForward.h +++ b/SoObjects/Mailer/SOGoMailForward.h @@ -38,4 +38,13 @@ @end +@interface SOGoMailEnglishForward : SOGoMailForward +@end + +@interface SOGoMailFrenchForward : SOGoMailForward +@end + +@interface SOGoMailGermanForward : SOGoMailForward +@end + #endif /* SOGOMAILFORWARD_H */ diff --git a/SoObjects/Mailer/SOGoMailForward.m b/SoObjects/Mailer/SOGoMailForward.m index a09db3736..ce3545ddb 100644 --- a/SoObjects/Mailer/SOGoMailForward.m +++ b/SoObjects/Mailer/SOGoMailForward.m @@ -150,3 +150,12 @@ } @end + +@implementation SOGoMailEnglishForward +@end + +@implementation SOGoMailFrenchForward +@end + +@implementation SOGoMailGermanForward +@end diff --git a/SoObjects/Mailer/SOGoMailFrenchForward.m b/SoObjects/Mailer/SOGoMailFrenchForward.m deleted file mode 100644 index cb229b0a4..000000000 --- a/SoObjects/Mailer/SOGoMailFrenchForward.m +++ /dev/null @@ -1,29 +0,0 @@ -/* SOGoMailFrenchForward.m - this file is part of SOGo - * - * Copyright (C) 2007 Inverse groupe conseil - * - * Author: Wolfgang Sourdeau - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import "SOGoMailForward.h" - -@interface SOGoMailFrenchForward : SOGoMailForward -@end - -@implementation SOGoMailFrenchForward -@end diff --git a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html index eb2429712..26fa08039 100644 --- a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html +++ b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html @@ -1,4 +1,4 @@ --------- Message original -------- +-------- Original E-Mail -------- Betreff: <#subject/> Datum: <#date/> Sender: <#from/> diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index c81e4103d..80e095380 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -89,6 +89,8 @@ - (BOOL)isDeletionAllowed; - (NSException *) trashInContext:(id)_ctx; +- (NSException *) copyToFolderNamed: (NSString *) folderName + inContext: (id)_ctx; - (NSException *) moveToFolderNamed: (NSString *) folderName inContext: (id)_ctx; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 0f152a4fc..cd2e8d481 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -883,31 +883,17 @@ static BOOL debugSoParts = NO; 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 *) moveToFolderNamed: (NSString *) folderName +- (NSException *) copyToFolderNamed: (NSString *) folderName inContext: (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. - */ SOGoMailAccounts *destFolder; NSEnumerator *folders; NSString *currentFolderName, *reason; - NSException *error; // TODO: check for safe HTTP method @@ -937,22 +923,27 @@ static BOOL debugSoParts = NO; [destFolder flushMailCaches]; /* a) copy */ - - error = [self davCopyToTargetObject: destFolder - 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 */ + return [self davCopyToTargetObject: destFolder + newName: @"fakeNewUnusedByIMAP4" /* autoassigned */ + inContext:_ctx]; +} - error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]]; - if (error != nil) return error; // TODO: unflag as deleted? - [self flushMailCaches]; +- (NSException *) moveToFolderNamed: (NSString *) folderName + inContext: (id)_ctx +{ + NSException *error; + + if (![self copyToFolderNamed: folderName + inContext: _ctx]) + { + /* b) mark deleted */ + + error = [[self imap4Connection] markURLDeleted: [self imap4URL]]; + if (error != nil) return error; + + [self flushMailCaches]; + } return nil; } diff --git a/SoObjects/Mailer/bundle-info.plist b/SoObjects/Mailer/bundle-info.plist new file mode 100644 index 000000000..2c63c0851 --- /dev/null +++ b/SoObjects/Mailer/bundle-info.plist @@ -0,0 +1,2 @@ +{ +} diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 0b3e7ef24..2530959f5 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -21,8 +21,9 @@ FHS_HEADER_DIRS = SOGo libSOGo_HEADER_FILES = \ SOGoObject.h \ - SOGoFolder.h \ SOGoContentObject.h \ + SOGoFolder.h \ + SOGoParentFolder.h \ SOGoUserFolder.h \ SOGoGroupsFolder.h \ SOGoGroupFolder.h \ @@ -43,14 +44,16 @@ libSOGo_HEADER_FILES = \ NSDictionary+URL.h \ NSCalendarDate+SOGo.h \ \ - SOGoAuthenticator.h \ + SOGoDAVAuthenticator.h \ + SOGoWebAuthenticator.h \ SOGoMailer.h \ SOGoUser.h \ libSOGo_OBJC_FILES = \ SOGoObject.m \ - SOGoFolder.m \ SOGoContentObject.m \ + SOGoFolder.m \ + SOGoParentFolder.m \ SOGoUserFolder.m \ SOGoGroupsFolder.m \ SOGoGroupFolder.m \ @@ -71,7 +74,8 @@ libSOGo_OBJC_FILES = \ NSNull+Utilities.m \ NSCalendarDate+SOGo.m \ \ - SOGoAuthenticator.m \ + SOGoDAVAuthenticator.m \ + SOGoWebAuthenticator.m \ SOGoMailer.m \ SOGoUser.m \ @@ -83,6 +87,8 @@ agenor_emails4uid_OBJC_FILES += agenor_emails4uid.m agenor_defaults_OBJC_FILES += agenor_defaults.m ADDITIONAL_TOOL_LIBS += -Lobj -lSOGo$(LIBRARY_NAME_SUFFIX) +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ -include GNUmakefile.preamble include ../common.make diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 3e8a3c7b4..201e16b8a 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -31,6 +31,7 @@ @interface LDAPSource : NSObject { + NSString *sourceID; NSString *bindDN; NSString *hostname; unsigned int port; @@ -67,6 +68,7 @@ - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID; - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; +- (NSString *) sourceID; @end diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index eeb0a14af..28475ad18 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -30,6 +30,7 @@ #import #import "LDAPSource.h" +#import "LDAPUserManager.h" static NSArray *commonSearchFields; static int timeLimit; @@ -114,8 +115,9 @@ static int sizeLimit; @"locality", @"birthyear", @"serialNumber", - @"calFBURL", + @"calFBURL", @"proxyAddresses", nil]; + [commonSearchFields retain]; } } @@ -138,6 +140,7 @@ static int sizeLimit; hostname = nil; port = 389; password = nil; + sourceID = nil; baseDN = nil; IDField = @"cn"; /* the first part of a user DN */ @@ -163,6 +166,7 @@ static int sizeLimit; [UIDField release]; [bindFields release]; [ldapConnection release]; + [sourceID release]; [super dealloc]; } @@ -170,6 +174,8 @@ static int sizeLimit; { self = [self init]; + ASSIGN(sourceID, [udSource objectForKey: @"id"]); + [self setBindDN: [udSource objectForKey: @"bindDN"] hostname: [udSource objectForKey: @"hostname"] port: [udSource objectForKey: @"port"] @@ -272,6 +278,8 @@ static int sizeLimit; NSString *userDN; NGLdapConnection *bindConnection; + didBind = NO; + if ([loginToCheck length] > 0) { bindConnection = [[NGLdapConnection alloc] initWithHostName: hostname @@ -290,13 +298,10 @@ static int sizeLimit; binddn: userDN credentials: passwordToCheck]; NS_HANDLER - didBind = NO; NS_ENDHANDLER } [bindConnection release]; } - else - didBind = NO; return didBind; } @@ -341,6 +346,8 @@ static int sizeLimit; - (NSArray *) _searchAttributes { + NSArray *attrs; + if (!searchAttributes) { searchAttributes = [NSMutableArray new]; @@ -351,6 +358,12 @@ static int sizeLimit; [searchAttributes addObjectsFromArray: commonSearchFields]; } + // We also include our MailFieldNames in the search + if ((attrs = [[[LDAPUserManager sharedUserManager] metadataForSourceID: sourceID] objectForKey: @"MailFieldNames"])) + { + [searchAttributes addObjectsFromArray: attrs]; + } + return searchAttributes; } @@ -494,4 +507,9 @@ static int sizeLimit; return contactEntry; } +- (NSString *) sourceID +{ + return sourceID; +} + @end diff --git a/SoObjects/SOGo/LDAPUserManager.h b/SoObjects/SOGo/LDAPUserManager.h index 6a3c6510b..5ab95c923 100644 --- a/SoObjects/SOGo/LDAPUserManager.h +++ b/SoObjects/SOGo/LDAPUserManager.h @@ -44,6 +44,7 @@ + (id) sharedUserManager; - (NSArray *) sourceIDs; +- (NSDictionary *) metadataForSourceID: (NSString *) sourceID; - (NSArray *) authenticationSourceIDs; - (NSArray *) addressBookSourceIDs; diff --git a/SoObjects/SOGo/LDAPUserManager.m b/SoObjects/SOGo/LDAPUserManager.m index e79c1f294..76e181088 100644 --- a/SoObjects/SOGo/LDAPUserManager.m +++ b/SoObjects/SOGo/LDAPUserManager.m @@ -75,6 +75,9 @@ static NSString *defaultMailDomain = nil; value = [udSource objectForKey: @"displayName"]; if (value) [metadata setObject: value forKey: @"displayName"]; + value = [udSource objectForKey: @"MailFieldNames"]; + if (value) + [metadata setObject: value forKey: @"MailFieldNames"]; [sourcesMetadata setObject: metadata forKey: sourceID]; } @@ -151,6 +154,11 @@ static NSString *defaultMailDomain = nil; return sourceIDs; } +- (NSDictionary *) metadataForSourceID: (NSString *) sourceID +{ + return [sourcesMetadata objectForKey: sourceID]; +} + - (NSArray *) authenticationSourceIDs { return [self _sourcesOfType: @"canAuthenticate"]; @@ -295,7 +303,8 @@ static NSString *defaultMailDomain = nil; NSEnumerator *ldapSources; LDAPSource *currentSource; NSString *cn, *email, *c_uid; - + NSArray *attrs; + emails = [NSMutableArray array]; cn = nil; c_uid = nil; @@ -311,15 +320,18 @@ static NSString *defaultMailDomain = nil; cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) c_uid = [userEntry objectForKey: @"c_uid"]; - email = [userEntry objectForKey: @"mail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"mozillaSecondEmail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"xmozillasecondemail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; + + if ((attrs = [[sourcesMetadata objectForKey: [currentSource sourceID]] objectForKey: @"MailFieldNames"])) + { + int i; + + for (i = 0; i < [attrs count]; i++) + { + email = [userEntry objectForKey: [attrs objectAtIndex: i]]; + if (email && ![emails containsObject: email]) + [emails addObject: email]; + } + } } currentSource = [ldapSources nextObject]; } @@ -332,7 +344,13 @@ static NSString *defaultMailDomain = nil; [currentUser setObject: emails forKey: @"emails"]; [currentUser setObject: cn forKey: @"cn"]; [currentUser setObject: c_uid forKey: @"c_uid"]; - [self _fillContactMailRecords: currentUser]; + + // If our LDAP queries gave us nothing, we add at least one default + // email address based on the default domain. + if ([emails count] == 0) + { + [self _fillContactMailRecords: currentUser]; + } } - (void) _retainUser: (NSDictionary *) newUser @@ -420,34 +438,37 @@ static NSString *defaultMailDomain = nil; while (userEntry) { uid = [userEntry objectForKey: @"c_uid"]; - returnContact = [compactContacts objectForKey: uid]; - if (!returnContact) + if ([uid length]) { - returnContact = [NSMutableDictionary dictionary]; - [returnContact setObject: uid forKey: @"c_uid"]; - [compactContacts setObject: returnContact forKey: uid]; + returnContact = [compactContacts objectForKey: uid]; + if (!returnContact) + { + returnContact = [NSMutableDictionary dictionary]; + [returnContact setObject: uid forKey: @"c_uid"]; + [compactContacts setObject: returnContact forKey: uid]; + } + if (![[returnContact objectForKey: @"c_name"] length]) + [returnContact setObject: [userEntry objectForKey: @"c_name"] + forKey: @"c_name"]; + if (![[returnContact objectForKey: @"cn"] length]) + [returnContact setObject: [userEntry objectForKey: @"c_cn"] + forKey: @"cn"]; + emails = [returnContact objectForKey: @"emails"]; + if (!emails) + { + emails = [NSMutableArray array]; + [returnContact setObject: emails forKey: @"emails"]; + } + email = [userEntry objectForKey: @"mail"]; + if (email && ![emails containsObject: email]) + [emails addObject: email]; + email = [userEntry objectForKey: @"mozillaSecondEmail"]; + if (email && ![emails containsObject: email]) + [emails addObject: email]; + email = [userEntry objectForKey: @"xmozillasecondemail"]; + if (email && ![emails containsObject: email]) + [emails addObject: email]; } - if (![[returnContact objectForKey: @"c_name"] length]) - [returnContact setObject: [userEntry objectForKey: @"c_name"] - forKey: @"c_name"]; - if (![[returnContact objectForKey: @"cn"] length]) - [returnContact setObject: [userEntry objectForKey: @"c_cn"] - forKey: @"cn"]; - emails = [returnContact objectForKey: @"emails"]; - if (!emails) - { - emails = [NSMutableArray array]; - [returnContact setObject: emails forKey: @"emails"]; - } - email = [userEntry objectForKey: @"mail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"mozillaSecondEmail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; - email = [userEntry objectForKey: @"xmozillasecondemail"]; - if (email && ![emails containsObject: email]) - [emails addObject: email]; userEntry = [contacts nextObject]; } diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index bc054c813..1c5622f13 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -29,6 +29,8 @@ @interface NSDictionary (SOGoDictionaryUtilities) ++ (NSDictionary *) dictionaryFromStringsFile: (NSString *) file; + - (NSString *) jsonRepresentation; - (NSString *) keysWithFormat: (NSString *) keyFormat; diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index 6caaeaa67..666aef023 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -21,6 +21,7 @@ */ #import +#import #import #import "NSArray+Utilities.h" @@ -29,6 +30,25 @@ @implementation NSDictionary (SOGoDictionaryUtilities) ++ (NSDictionary *) dictionaryFromStringsFile: (NSString *) file +{ + NSString *serialized; + NSMutableData *content; + NSDictionary *newDictionary; + + content = [NSMutableData new]; + [content appendBytes: "{" length: 1]; + [content appendData: [NSData dataWithContentsOfFile: file]]; + [content appendBytes: "}" length: 1]; + serialized = [[NSString alloc] initWithData: content + encoding: NSUTF8StringEncoding]; + [content release]; + newDictionary = [serialized propertyList]; + [serialized release]; + + return newDictionary; +} + - (NSString *) jsonRepresentation { NSMutableArray *values; diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 7cd8042b4..0f2e95a99 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -46,6 +46,8 @@ /* bare email addresses */ - (NSString *) pureEMailAddress; +- (NSString *) asQPSubjectString: (NSString *) encoding; + #ifndef GNUSTEP_BASE_LIBRARY - (BOOL) boolValue; #endif diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 38c09bad5..1192ccd70 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1,6 +1,6 @@ /* NSString+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse group conseil + * Copyright (C) 2006 Inverse groupe conseil * * Author: Wolfgang Sourdeau * @@ -24,6 +24,8 @@ #import #import +#import + #import "NSArray+Utilities.h" #import "NSDictionary+URL.h" @@ -142,15 +144,18 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; int start, length; NSRange workRange; +// [urlNonEndingChars addCharactersInString: @">&=,.:;\t \r\n"]; +// [urlAfterEndingChars addCharactersInString: @"()[]{}&;<\t \r\n"]; + if (!urlNonEndingChars) { urlNonEndingChars = [NSMutableCharacterSet new]; - [urlNonEndingChars addCharactersInString: @">&=,.:;\t \r\n"]; + [urlNonEndingChars addCharactersInString: @">=,.:;\t \r\n"]; } if (!urlAfterEndingChars) { urlAfterEndingChars = [NSMutableCharacterSet new]; - [urlAfterEndingChars addCharactersInString: @"()[]{}&;<\t \r\n"]; + [urlAfterEndingChars addCharactersInString: @"[]\t \r\n"]; } start = refRange.location; @@ -160,7 +165,7 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; start--; start++; length = [self length] - start; - workRange = NSMakeRange (start, length); + workRange = NSMakeRange(start, length); workRange = [self rangeOfCharacterFromSet: urlAfterEndingChars options: NSLiteralSearch range: workRange]; if (workRange.location != NSNotFound) @@ -188,13 +193,15 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; while (httpRange.location != NSNotFound) { if ([ranges hasRangeIntersection: httpRange]) - rest.location = NSMaxRange (httpRange); + rest.location = NSMaxRange(httpRange); else { currentURL = [selfCopy _rangeOfURLInRange: httpRange]; urlText = [selfCopy substringFromRange: currentURL]; if ([urlText length] > matchLength) { + if ([urlText hasPrefix: prefix]) prefix = @""; + newUrlText = [NSString stringWithFormat: @"%@", prefix, urlText, urlText]; [selfCopy replaceCharactersInRange: currentURL @@ -203,8 +210,9 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; = NSMakeRange (currentURL.location, [newUrlText length]); [ranges addRange: currentURL]; } - rest.location = NSMaxRange (currentURL); + rest.location = NSMaxRange(currentURL); } + length = [selfCopy length]; rest.length = length - rest.location; httpRange = [selfCopy rangeOfString: match @@ -268,6 +276,26 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil; return pureAddress; } +- (NSString *) asQPSubjectString: (NSString *) encoding +{ + NSString *qpString, *subjectString; + NSData *subjectData, *destSubjectData; + + subjectData = [self dataUsingEncoding: NSUTF8StringEncoding]; + destSubjectData = [subjectData dataByEncodingQuotedPrintable]; + + qpString = [[NSString alloc] initWithData: destSubjectData + encoding: NSASCIIStringEncoding]; + [qpString autorelease]; + if ([qpString length] > [self length]) + subjectString = [NSString stringWithFormat: @"=?%@?Q?%@?=", + encoding, qpString]; + else + subjectString = self; + + return subjectString; +} + #if LIB_FOUNDATION_LIBRARY - (BOOL) boolValue { diff --git a/SoObjects/SOGo/SOGoAuthenticator.h b/SoObjects/SOGo/SOGoAuthenticator.h index 6f8cd5feb..2a2f11d99 100644 --- a/SoObjects/SOGo/SOGoAuthenticator.h +++ b/SoObjects/SOGo/SOGoAuthenticator.h @@ -1,51 +1,39 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG +/* SOGoAuthenticator.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ - This file is part of OpenGroupware.org. +#ifndef SOGOAUTHENTICATOR_H +#define SOGOAUTHENTICATOR_H - 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. +#import - 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 __Main_SOGoAuthenticator_H__ -#define __Main_SOGoAuthenticator_H__ - -#include - -/* - SOGoAuthenticator - - This just overrides the login/pwd check method and always returns YES since - the password is already checked in Apache. -*/ - -@class NSUserDefaults; @class NSString; - @class SOGoUser; +@class WOContext; -@interface SOGoAuthenticator : SoHTTPAuthenticator -{ - NSString *authMethod; -} +@protocol SOGoAuthenticator -+ (id) sharedSOGoAuthenticator; - -- (SOGoUser *) userInContext: (WOContext *) _ctx; - (NSString *) passwordInContext: (WOContext *) context; +- (SOGoUser *) userInContext: (WOContext *) context; @end -#endif /* __Main_SOGoAuthenticator_H__ */ +#endif /* SOGOAUTHENTICATOR_H */ diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index 945382da5..255db3b65 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -231,7 +231,7 @@ needsLocation = NO; tmp = [[self nameInContainer] stringByDeletingPathExtension]; if ([tmp isEqualToString:@"new"]) { - tmp = [[[self container] class] globallyUniqueObjectId]; + tmp = [self globallyUniqueObjectId]; needsLocation = YES; [self debugWithFormat: @@ -359,7 +359,7 @@ - (NSArray *) aclUsers { - return [container aclUsersForObjectAtPath: [self pathArrayToSoObject]]; + return [container aclUsersForObjectAtPath: [self pathArrayToSOGoObject]]; } - (NSArray *) aclsForUser: (NSString *) uid @@ -369,7 +369,7 @@ acls = [NSMutableArray array]; ownAcls = [container aclsForUser: uid - forObjectAtPath: [self pathArrayToSoObject]]; + forObjectAtPath: [self pathArrayToSOGoObject]]; [acls addObjectsFromArray: ownAcls]; containerAcls = [container aclsForUser: uid]; if ([containerAcls count] > 0) diff --git a/SoObjects/SOGo/SOGoDAVAuthenticator.h b/SoObjects/SOGo/SOGoDAVAuthenticator.h new file mode 100644 index 000000000..21e06a8af --- /dev/null +++ b/SoObjects/SOGo/SOGoDAVAuthenticator.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. +*/ + +#ifndef __Main_SOGoDAVAuthenticator_H__ +#define __Main_SOGoDAVAuthenticator_H__ + +#import + +#import "SOGoAuthenticator.h" + +/* + SOGoDAVAuthenticator + + This just overrides the login/pwd check method and always returns YES since + the password is already checked in Apache. +*/ + +@class NSUserDefaults; +@class NSString; + +@class SOGoUser; + +@interface SOGoDAVAuthenticator : SoHTTPAuthenticator +{ + NSString *authMethod; +} + ++ (id) sharedSOGoDAVAuthenticator; + +- (SOGoUser *) userInContext: (WOContext *) _ctx; +- (NSString *) passwordInContext: (WOContext *) context; + +@end + +#endif /* __Main_SOGoDAVAuthenticator_H__ */ diff --git a/SoObjects/SOGo/SOGoAuthenticator.m b/SoObjects/SOGo/SOGoDAVAuthenticator.m similarity index 73% rename from SoObjects/SOGo/SOGoAuthenticator.m rename to SoObjects/SOGo/SOGoDAVAuthenticator.m index 20856a419..d52f57406 100644 --- a/SoObjects/SOGo/SOGoAuthenticator.m +++ b/SoObjects/SOGo/SOGoDAVAuthenticator.m @@ -31,13 +31,13 @@ #import "SOGoPermissions.h" #import "SOGoUser.h" -#import "SOGoAuthenticator.h" +#import "SOGoDAVAuthenticator.h" -@implementation SOGoAuthenticator +@implementation SOGoDAVAuthenticator -+ (id) sharedSOGoAuthenticator ++ (id) sharedSOGoDAVAuthenticator { - static SOGoAuthenticator *auth = nil; + static SOGoDAVAuthenticator *auth = nil; if (!auth) auth = [self new]; @@ -135,33 +135,4 @@ return user; } -// - (BOOL) renderException: (NSException *) exception -// inContext: (WOContext *) context -// { -// id renderedException; -// WOComponent *tmpComponent; -// WOResponse *response; -// BOOL rc; - -// rc = [super renderException: exception inContext: context]; -// if (!rc) -// { -// tmpComponent = [WOComponent new]; -// renderedException = [tmpComponent pageWithName: @"UIxException"]; -// if (renderedException) -// { -// rc = YES; -// response = [context response]; -// [response setHeader: @"text/html" forKey: @"content-type"]; -// [renderedException setClientObject: exception]; -// [context setPage: renderedException]; -// [renderedException appendToResponse: response -// inContext: context]; -// } -// [tmpComponent release]; -// } - -// return rc; -// } - -@end /* SOGoAuthenticator */ +@end /* SOGoDAVAuthenticator */ diff --git a/SoObjects/SOGo/SOGoFolder.h b/SoObjects/SOGo/SOGoFolder.h index 5358299d3..c0b492e98 100644 --- a/SoObjects/SOGo/SOGoFolder.h +++ b/SoObjects/SOGo/SOGoFolder.h @@ -43,15 +43,20 @@ @interface SOGoFolder : SOGoObject { - NSString *ocsPath; + NSMutableString *displayName; + NSString *ocsPath; GCSFolder *ocsFolder; NSMutableDictionary *aclCache; } -+ (NSString *) globallyUniqueObjectId; ++ (id) folderWithSubscriptionReference: (NSString *) reference + inContainer: (id) aContainer; /* accessors */ +- (void) setDisplayName: (NSString *) newDisplayName; +- (NSString *) displayName; + - (void) setOCSPath: (NSString *)_Path; - (NSString *) ocsPath; @@ -61,6 +66,8 @@ /* lower level fetches */ - (BOOL) nameExistsInFolder: (NSString *) objectName; +- (void) deleteEntriesWithIds: (NSArray *) ids; + - (NSArray *) fetchContentObjectNames; - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects; @@ -69,10 +76,14 @@ - (NSString *) outlookFolderClass; - (BOOL) folderIsMandatory; +- (NSString *) folderType; + +/* sorting */ +- (NSComparisonResult) compare: (SOGoFolder *) otherFolder; - (BOOL) create; - (NSException *) delete; - +- (void) renameTo: (NSString *) newName; /* dav */ - (NSArray *) davNamespaces; diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index d6676c0e5..286974476 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -19,9 +19,6 @@ 02111-1307, USA. */ -#import -#import - #import #import #import @@ -29,21 +26,28 @@ #import #import +#import #import #import #import #import +#import #import #import #import #import +#import #import #import #import +#import #import +#import +#import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "SOGoContentObject.h" #import "SOGoPermissions.h" #import "SOGoUser.h" @@ -65,33 +69,38 @@ static NSString *defaultUserID = @""; NSStringFromClass([self superclass]), [super version]); } -+ (NSString *) globallyUniqueObjectId ++ (id) folderWithSubscriptionReference: (NSString *) reference + inContainer: (id) aContainer { - /* - 4C08AE1A-A808-11D8-AC5A-000393BBAFF6 - SOGo-Web-28273-18283-288182 - printf( "%x", *(int *) &f); - */ - static int pid = 0; - static int sequence = 0; - static float rndm = 0; - float f; + id newFolder; + NSArray *elements, *pathElements; + NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName; - if (pid == 0) - { /* break if we fork ;-) */ - pid = getpid(); - rndm = random(); - } - sequence++; - f = [[NSDate date] timeIntervalSince1970]; - return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X", - pid, *(int *)&f, sequence++, random]; + elements = [reference componentsSeparatedByString: @":"]; + owner = [elements objectAtIndex: 0]; + objectPath = [elements objectAtIndex: 1]; + pathElements = [objectPath componentsSeparatedByString: @"/"]; + if ([pathElements count] > 1) + ocsName = [pathElements objectAtIndex: 1]; + else + ocsName = @"personal"; + + ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@", + owner, [pathElements objectAtIndex: 0], ocsName]; + folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName]; + newFolder = [[self alloc] initWithName: folderName + inContainer: aContainer]; + [newFolder setOCSPath: ocsPath]; + [newFolder setOwner: owner]; + + return newFolder; } - (id) init { if ((self = [super init])) { + displayName = nil; ocsPath = nil; ocsFolder = nil; aclCache = [NSMutableDictionary new]; @@ -105,6 +114,7 @@ static NSString *defaultUserID = @""; [ocsFolder release]; [ocsPath release]; [aclCache release]; + [displayName release]; [super dealloc]; } @@ -117,13 +127,12 @@ static NSString *defaultUserID = @""; - (void) setOCSPath: (NSString *) _path { - if ([ocsPath isEqualToString:_path]) - return; - - if (ocsPath) - [self warnWithFormat:@"GCS path is already set! '%@'", _path]; - - ASSIGNCOPY(ocsPath, _path); + if (![ocsPath isEqualToString:_path]) + { + if (ocsPath) + [self warnWithFormat: @"GCS path is already set! '%@'", _path]; + ASSIGN (ocsPath, _path); + } } - (NSString *) ocsPath @@ -148,9 +157,74 @@ static NSString *defaultUserID = @""; - (BOOL) folderIsMandatory { - [self subclassResponsibility: _cmd]; + return [nameInContainer isEqualToString: @"personal"]; +} - return NO; +- (void) _setDisplayNameFromRow: (NSDictionary *) row +{ + NSString *currentLogin, *ownerLogin; + NSDictionary *ownerIdentity; + + displayName + = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]]; + currentLogin = [[context activeUser] login]; + ownerLogin = [self ownerInContext: context]; + if (![currentLogin isEqualToString: ownerLogin]) + { + ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil] + primaryIdentity]; + [displayName appendFormat: @" (%@ <%@>)", + [ownerIdentity objectForKey: @"fullName"], + [ownerIdentity objectForKey: @"email"]]; + } + [displayName retain]; +} + +- (void) _fetchDisplayName +{ + GCSChannelManager *cm; + EOAdaptorChannel *fc; + NSURL *folderLocation; + NSString *sql; + NSArray *attrs; + NSDictionary *row; + + cm = [GCSChannelManager defaultChannelManager]; + folderLocation + = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; + fc = [cm acquireOpenChannelForURL: folderLocation]; + if (fc) + { + sql + = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" + @" WHERE c_path = '%@'"), + [folderLocation gcsTableName], ocsPath]; + [fc evaluateExpressionX: sql]; + attrs = [fc describeResults: NO]; + row = [fc fetchAttributes: attrs withZone: NULL]; + if (row) + [self _setDisplayNameFromRow: row]; + [fc cancelFetch]; + [cm releaseChannel: fc]; + } +} + +- (void) setDisplayName: (NSString *) newDisplayName +{ + ASSIGN (displayName, newDisplayName); +} + +- (NSString *) displayName +{ + if (!displayName) + [self _fetchDisplayName]; + + return displayName; +} + +- (NSString *) davDisplayName +{ + return [self displayName]; } - (GCSFolder *) ocsFolder @@ -163,7 +237,7 @@ static NSString *defaultUserID = @""; ocsFolder = [self ocsFolderForPath: [self ocsPath]]; userLogin = [[context activeUser] login]; if (!ocsFolder - && [userLogin isEqualToString: [self ownerInContext: context]] +/* && [userLogin isEqualToString: [self ownerInContext: context]] */ && [self folderIsMandatory] && [self create]) ocsFolder = [self ocsFolderForPath: [self ocsPath]]; @@ -183,19 +257,81 @@ static NSString *defaultUserID = @""; return @""; } +- (void) sendFolderAdvisoryTemplate: (NSString *) template +{ + NSString *language, *pageName; + SOGoUser *user; + SOGoFolderAdvisory *page; + WOApplication *app; + + user = [SOGoUser userWithLogin: [[context activeUser] login] roles: nil]; + language = [user language]; + pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory", + language, template]; + + app = [WOApplication application]; + page = [app pageWithName: pageName inContext: context]; + [page setFolderObject: self]; + [page setRecipientUID: [[context activeUser] login]]; + [page send]; +} + - (BOOL) create { NSException *result; result = [[self folderManager] createFolderOfType: [self folderType] + withName: displayName atPath: ocsPath]; + if (!result) [self sendFolderAdvisoryTemplate: @"Addition"]; + return (result == nil); } - (NSException *) delete { - return [[self folderManager] deleteFolderAtPath: ocsPath]; + NSException *error; + + // We just fetch our displayName since our table will use it! + [self displayName]; + + if ([nameInContainer isEqualToString: @"personal"]) + error = [NSException exceptionWithHTTPStatus: 403 + reason: @"folder 'personal' cannot be deleted"]; + else + error = [[self folderManager] deleteFolderAtPath: ocsPath]; + + if (!error) [self sendFolderAdvisoryTemplate: @"Removal"]; + + return error; +} + +- (void) renameTo: (NSString *) newName +{ + GCSChannelManager *cm; + EOAdaptorChannel *fc; + NSURL *folderLocation; + NSString *sql; + + [displayName release]; + displayName = nil; + + cm = [GCSChannelManager defaultChannelManager]; + folderLocation + = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; + fc = [cm acquireOpenChannelForURL: folderLocation]; + if (fc) + { + sql + = [NSString stringWithFormat: (@"UPDATE %@ SET c_foldername = '%@'" + @" WHERE c_path = '%@'"), + [folderLocation gcsTableName], newName, ocsPath]; + [fc evaluateExpressionX: sql]; + [cm releaseChannel: fc]; +// sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", +// uidColumnName, [self uid]]; + } } - (NSArray *) fetchContentObjectNames @@ -211,7 +347,7 @@ static NSString *defaultUserID = @""; } if ([records isKindOfClass: [NSException class]]) return records; - return [records valueForKey: @"c_name"]; + return [records objectsForKey: @"c_name"]; } - (BOOL) nameExistsInFolder: (NSString *) objectName @@ -231,6 +367,22 @@ static NSString *defaultUserID = @""; && [records count] > 0); } +- (void) deleteEntriesWithIds: (NSArray *) ids +{ + unsigned int count, max; + NSString *currentID; + SOGoContentObject *deleteObject; + + max = [ids count]; + for (count = 0; count < max; count++) + { + currentID = [ids objectAtIndex: count]; + deleteObject = [self lookupName: currentID + inContext: context acquire: NO]; + [deleteObject delete]; + } +} + - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects { NSDictionary *files; @@ -469,7 +621,7 @@ static NSString *defaultUserID = @""; /* acls */ - (NSArray *) aclUsers { - return [self aclUsersForObjectAtPath: [self pathArrayToSoObject]]; + return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]]; } - (NSArray *) aclsForUser: (NSString *) uid @@ -479,7 +631,7 @@ static NSString *defaultUserID = @""; acls = [NSMutableArray array]; ownAcls = [self aclsForUser: uid - forObjectAtPath: [self pathArrayToSoObject]]; + forObjectAtPath: [self pathArrayToSOGoObject]]; [acls addObjectsFromArray: ownAcls]; if ([container respondsToSelector: @selector (aclsForUser:)]) { @@ -502,13 +654,13 @@ static NSString *defaultUserID = @""; { return [self setRoles: roles forUser: uid - forObjectAtPath: [self pathArrayToSoObject]]; + forObjectAtPath: [self pathArrayToSOGoObject]]; } - (void) removeAclsForUsers: (NSArray *) users { return [self removeAclsForUsers: users - forObjectAtPath: [self pathArrayToSoObject]]; + forObjectAtPath: [self pathArrayToSOGoObject]]; } - (NSString *) defaultUserID @@ -561,6 +713,77 @@ static NSString *defaultUserID = @""; return obj; } +- (NSComparisonResult) _compareByOrigin: (SOGoFolder *) otherFolder +{ + NSArray *thisElements, *otherElements; + unsigned thisCount, otherCount; + NSComparisonResult comparison; + + thisElements = [nameInContainer componentsSeparatedByString: @"_"]; + otherElements = [[otherFolder nameInContainer] + componentsSeparatedByString: @"_"]; + thisCount = [thisElements count]; + otherCount = [otherElements count]; + if (thisCount == otherCount) + { + if (thisCount == 1) + comparison = NSOrderedSame; + else + comparison = [[thisElements objectAtIndex: 0] + compare: [otherElements objectAtIndex: 0]]; + } + else + { + if (thisCount > otherCount) + comparison = NSOrderedDescending; + else + comparison = NSOrderedAscending; + } + + return comparison; +} + +- (NSComparisonResult) _compareByNameInContainer: (SOGoFolder *) otherFolder +{ + NSString *otherName; + NSComparisonResult comparison; + + otherName = [otherFolder nameInContainer]; + if ([nameInContainer hasSuffix: @"personal"]) + { + if ([otherName hasSuffix: @"personal"]) + comparison = [nameInContainer compare: otherName]; + else + comparison = NSOrderedAscending; + } + else + { + if ([otherName hasSuffix: @"personal"]) + comparison = NSOrderedDescending; + else + comparison = NSOrderedSame; + } + + return comparison; +} + +- (NSComparisonResult) compare: (SOGoFolder *) otherFolder +{ + NSComparisonResult comparison; + + comparison = [self _compareByOrigin: otherFolder]; + if (comparison == NSOrderedSame) + { + comparison = [self _compareByNameInContainer: otherFolder]; + if (comparison == NSOrderedSame) + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } + + return comparison; +} + /* WebDAV */ - (NSArray *) davNamespaces diff --git a/SoObjects/SOGo/SOGoObject.h b/SoObjects/SOGo/SOGoObject.h index 67142bfef..5404508a0 100644 --- a/SoObjects/SOGo/SOGoObject.h +++ b/SoObjects/SOGo/SOGoObject.h @@ -61,6 +61,9 @@ id container; } ++ (NSString *) globallyUniqueObjectId; +- (NSString *) globallyUniqueObjectId; + + (id) objectWithName: (NSString *)_name inContainer:(id)_container; - (id) initWithName: (NSString *) _name inContainer:(id)_container; @@ -70,11 +73,15 @@ - (NSString *) nameInContainer; - (id) container; +- (NSArray *) pathArrayToSOGoObject; + - (NSURL *) davURL; - (NSURL *) soURL; - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid; - (NSURL *) soURLToBaseContainerForCurrentUser; +- (NSString *) labelForKey: (NSString *) key; + /* ownership */ - (void) setOwner: (NSString *) newOwner; @@ -100,7 +107,7 @@ /* etag support */ -- (NSException *)matchesRequestConditionInContext:(id)_ctx; +- (NSException *) matchesRequestConditionInContext:(id)_ctx; /* acls */ diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index 8d84e9289..abb9acb1f 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -24,6 +24,9 @@ Please use gnustep-base instead. #endif +#import + +#import #import #import #import @@ -35,6 +38,7 @@ #import #import #import +#import #import #import #import @@ -46,12 +50,13 @@ #import "SOGoPermissions.h" #import "SOGoUser.h" -#import "SOGoAuthenticator.h" +#import "SOGoDAVAuthenticator.h" #import "SOGoUserFolder.h" #import "SOGoDAVRendererTypes.h" #import "NSArray+Utilities.h" +#import "NSDictionary+Utilities.h" #import "NSString+Utilities.h" #import "SOGoObject.h" @@ -179,6 +184,35 @@ static BOOL kontactGroupDAV = YES; // asDefaultForPermission: SoPerm_WebDAVAccess]; } ++ (NSString *) globallyUniqueObjectId +{ + /* + 4C08AE1A-A808-11D8-AC5A-000393BBAFF6 + SOGo-Web-28273-18283-288182 + printf( "%x", *(int *) &f); + */ + static int pid = 0; + static int sequence = 0; + static float rndm = 0; + float f; + + if (pid == 0) + { /* break if we fork ;-) */ + pid = getpid(); + rndm = random(); + } + sequence++; + f = [[NSDate date] timeIntervalSince1970]; + + return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X", + pid, (int) f, sequence++, random]; +} + +- (NSString *) globallyUniqueObjectId +{ + return [[self class] globallyUniqueObjectId]; +} + + (void) _fillDictionary: (NSMutableDictionary *) dictionary withDAVMethods: (NSString *) firstMethod, ... { @@ -268,13 +302,13 @@ static BOOL kontactGroupDAV = YES; - (SOGoDAVSet *) davCurrentUserPrivilegeSet { - SOGoAuthenticator *sAuth; + SOGoDAVAuthenticator *sAuth; SoUser *user; NSArray *roles; SoClassSecurityInfo *sInfo; NSArray *davPermissions; - sAuth = [SOGoAuthenticator sharedSOGoAuthenticator]; + sAuth = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator]; user = [sAuth userInContext: context]; roles = [user rolesForObject: self inContext: context]; sInfo = [[self class] soClassSecurityInfo]; @@ -447,6 +481,33 @@ static BOOL kontactGroupDAV = YES; return container; } +- (NSArray *) pathArrayToSOGoObject +{ + NSMutableArray *realPathArray; + NSString *objectName; + NSArray *objectDescription; + + realPathArray + = [NSMutableArray arrayWithArray: [self pathArrayToSoObject]]; + if ([realPathArray count] > 2) + { + objectName = [realPathArray objectAtIndex: 2]; + if ([objectName isKindOfClass: [NSString class]]) + { + objectDescription = [objectName componentsSeparatedByString: @"_"]; + if ([objectDescription count] > 1) + { + [realPathArray replaceObjectAtIndex: 0 + withObject: [objectDescription objectAtIndex: 0]]; + [realPathArray replaceObjectAtIndex: 2 + withObject: [objectDescription objectAtIndex: 1]]; + } + } + } + + return realPathArray; +} + /* ownership */ - (void) setOwner: (NSString *) newOwner @@ -493,14 +554,16 @@ static BOOL kontactGroupDAV = YES; /* looking up shared objects */ -- (SOGoUserFolder *)lookupUserFolder { +- (SOGoUserFolder *) lookupUserFolder +{ if (![container respondsToSelector:_cmd]) return nil; return [container lookupUserFolder]; } -- (SOGoGroupsFolder *)lookupGroupsFolder { +- (SOGoGroupsFolder *) lookupGroupsFolder +{ return [[self lookupUserFolder] lookupGroupsFolder]; } @@ -905,6 +968,34 @@ static BOOL kontactGroupDAV = YES; return nil; } +- (NSString *) labelForKey: (NSString *) key +{ + NSString *userLanguage, *label; + NSArray *paths; + NSBundle *bundle; + NSDictionary *strings; + + bundle = [NSBundle bundleForClass: [self class]]; + if (!bundle) + bundle = [NSBundle mainBundle]; + + userLanguage = [[context activeUser] language]; + paths = [bundle pathsForResourcesOfType: @"strings" + inDirectory: [NSString stringWithFormat: @"%@.lproj", userLanguage] + forLocalization: userLanguage]; + if ([paths count] > 0) + { + strings = [NSDictionary dictionaryFromStringsFile: [paths objectAtIndex: 0]]; + label = [strings objectForKey: key]; + if (!label) + label = key; + } + else + label = key; + + return label; +} + /* description */ - (void)appendAttributesToDescription:(NSMutableString *)_ms { diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h new file mode 100644 index 000000000..c54d08f87 --- /dev/null +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -0,0 +1,51 @@ +/* SOGoParentFolder.h - this file is part of SOGo + * + * Copyright (C) 2006, 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOPARENTFOLDERS_H +#define SOGOPARENTFOLDERS_H + +#import "SOGoObject.h" + +@class NSMutableDictionary; +@class NSString; +@class WOResponse; + +@interface SOGoParentFolder : SOGoObject +{ + NSMutableDictionary *subFolders; + NSString *OCSPath; + Class subFolderClass; +} + ++ (NSString *) gcsFolderType; ++ (Class) subFolderClass; + +- (void) setBaseOCSPath: (NSString *) newOCSPath; + +- (NSArray *) subFolders; + +- (NSException *) newFolderWithName: (NSString *) name + nameInContainer: (NSString **) newNameInContainer; + +@end + +#endif /* SOGOPARENTFOLDERS_H */ diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m new file mode 100644 index 000000000..224fffbaf --- /dev/null +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -0,0 +1,287 @@ +/* SOGoParentFolder.m - this file is part of SOGo + * + * Copyright (C) 2006, 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import "SOGoFolder.h" +#import "SOGoUser.h" + +#import "SOGoParentFolder.h" + +@implementation SOGoParentFolder + +- (id) init +{ + if ((self = [super init])) + { + subFolders = nil; + OCSPath = nil; + subFolderClass = Nil; + } + + return self; +} + +- (void) dealloc +{ + [subFolders release]; + [OCSPath release]; + [super dealloc]; +} + ++ (Class) subFolderClass +{ + [self subclassResponsibility: _cmd]; + + return Nil; +} + ++ (NSString *) gcsFolderType +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (void) setBaseOCSPath: (NSString *) newOCSPath +{ + ASSIGN (OCSPath, newOCSPath); +} + +- (NSString *) defaultFolderName +{ + return @"Personal"; +} + +- (void) _fetchPersonalFolders: (NSString *) sql + withChannel: (EOAdaptorChannel *) fc +{ + NSArray *attrs; + NSDictionary *row; + SOGoFolder *folder; + BOOL hasPersonal; + NSString *key, *path, *personalName; + + if (!subFolderClass) + subFolderClass = [[self class] subFolderClass]; + + hasPersonal = NO; + [fc evaluateExpressionX: sql]; + attrs = [fc describeResults: NO]; + row = [fc fetchAttributes: attrs withZone: NULL]; + while (row) + { + key = [row objectForKey: @"c_path4"]; + if ([key isKindOfClass: [NSString class]]) + { + folder = [subFolderClass objectWithName: key inContainer: self]; + hasPersonal = (hasPersonal || [key isEqualToString: @"personal"]); + [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", + OCSPath, key]]; + [subFolders setObject: folder forKey: key]; + } + row = [fc fetchAttributes: attrs withZone: NULL]; + } + + if (!hasPersonal) + { + folder = [subFolderClass objectWithName: @"personal" inContainer: self]; + personalName = [self labelForKey: [self defaultFolderName]]; + [folder setDisplayName: personalName]; + path = [NSString stringWithFormat: @"/Users/%@/%@/personal", + [self ownerInContext: context], + nameInContainer]; + [folder setOCSPath: path]; + [subFolders setObject: folder forKey: @"personal"]; + } +} + +- (void) appendPersonalSources +{ + GCSChannelManager *cm; + EOAdaptorChannel *fc; + NSURL *folderLocation; + NSString *sql, *gcsFolderType; + + cm = [GCSChannelManager defaultChannelManager]; + folderLocation + = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; + fc = [cm acquireOpenChannelForURL: folderLocation]; + if (fc) + { + gcsFolderType = [[self class] gcsFolderType]; + + sql + = [NSString stringWithFormat: (@"SELECT c_path4 FROM %@" + @" WHERE c_path2 = '%@'" + @" AND c_folder_type = '%@'"), + [folderLocation gcsTableName], + [self ownerInContext: context], + gcsFolderType]; + [self _fetchPersonalFolders: sql withChannel: fc]; + [cm releaseChannel: fc]; +// sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", +// uidColumnName, [self uid]]; + } +} + +- (void) appendSystemSources +{ +} + +- (void) appendSubscribedSources +{ + NSArray *subscribedReferences; + NSUserDefaults *settings; + NSEnumerator *allKeys; + NSString *currentKey; + SOGoFolder *subscribedFolder; + + settings = [[context activeUser] userSettings]; + subscribedReferences = [[settings objectForKey: nameInContainer] + objectForKey: @"SubscribedFolders"]; + if ([subscribedReferences isKindOfClass: [NSArray class]]) + { + allKeys = [subscribedReferences objectEnumerator]; + currentKey = [allKeys nextObject]; + while (currentKey) + { + subscribedFolder + = [subFolderClass folderWithSubscriptionReference: currentKey + inContainer: self]; + [subFolders setObject: subscribedFolder + forKey: [subscribedFolder nameInContainer]]; + currentKey = [allKeys nextObject]; + } + } +} + +- (NSException *) newFolderWithName: (NSString *) name + nameInContainer: (NSString **) newNameInContainer +{ + NSString *newFolderID; + SOGoFolder *newFolder; + NSException *error; + + if (!subFolderClass) + subFolderClass = [[self class] subFolderClass]; + + *newNameInContainer = nil; + newFolderID = [self globallyUniqueObjectId]; + newFolder = [subFolderClass objectWithName: newFolderID inContainer: self]; + if ([newFolder isKindOfClass: [NSException class]]) + error = (NSException *) newFolder; + else + { + [newFolder setDisplayName: name]; + [newFolder setOCSPath: [NSString stringWithFormat: @"%@/%@", + OCSPath, newFolderID]]; + if ([newFolder create]) + { + error = nil; + *newNameInContainer = newFolderID; + } + else + error = [NSException exceptionWithHTTPStatus: 400 + reason: @"The new folder could not be created"]; + } + + return error; +} + +- (void) initSubFolders +{ + NSString *login; + + if (!subFolders) + { + subFolders = [NSMutableDictionary new]; + [self appendPersonalSources]; + [self appendSystemSources]; + login = [[context activeUser] login]; + if ([login isEqualToString: owner]) + [self appendSubscribedSources]; + } +} + +- (id) lookupName: (NSString *) name + inContext: (WOContext *) lookupContext + acquire: (BOOL) acquire +{ + id obj; + + /* first check attributes directly bound to the application */ + obj = [super lookupName: name inContext: lookupContext acquire: NO]; + if (!obj) + { + if (!subFolders) + [self initSubFolders]; + + obj = [subFolders objectForKey: name]; + } + + return obj; +} + +- (NSArray *) toManyRelationshipKeys +{ + if (!subFolders) + [self initSubFolders]; + + return [subFolders allKeys]; +} + +- (NSArray *) subFolders +{ + if (!subFolders) + [self initSubFolders]; + + return [[subFolders allValues] + sortedArrayUsingSelector: @selector (compare:)]; +} + +/* acls */ +- (NSArray *) aclsForUser: (NSString *) uid +{ + return nil; +} + +- (BOOL) davIsCollection +{ + return YES; +} + +- (NSString *) davContentType +{ + return @"httpd/unix-directory"; +} + +@end diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 9dedfae46..0197ca939 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -365,22 +365,31 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; if (!mailAccounts) { + NSArray *mails; + int i; + mailAccount = [NSMutableDictionary dictionary]; name = [NSString stringWithFormat: @"%@@%@", login, fallbackIMAP4Server]; [mailAccount setObject: login forKey: @"userName"]; [mailAccount setObject: fallbackIMAP4Server forKey: @"serverName"]; [mailAccount setObject: name forKey: @"name"]; - identity = [NSMutableDictionary dictionary]; - fullName = [self cn]; - if (![fullName length]) - fullName = login; - [identity setObject: fullName forKey: @"fullName"]; - [identity setObject: [self systemEmail] forKey: @"email"]; - [identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"]; - identities = [NSMutableArray array]; - [identities addObject: identity]; + mails = [self allEmails]; + + for (i = 0; i < [mails count]; i++) + { + identity = [NSMutableDictionary dictionary]; + fullName = [self cn]; + if (![fullName length]) + fullName = login; + [identity setObject: fullName forKey: @"fullName"]; + [identity setObject: [mails objectAtIndex: i] forKey: @"email"]; + + if (i == 0) [identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"]; + [identities addObject: identity]; + } + [mailAccount setObject: identities forKey: @"identities"]; mailAccounts = [NSMutableArray new]; @@ -499,7 +508,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; [(WOContext *)_ctx setObject: ((folder) ? folder - : [NSNull null]) + : (id)[NSNull null]) forKey: @"ActiveUserHomeFolder"]; return folder; } diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index 933779c33..84297af23 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -23,9 +23,10 @@ #import #import +#import #import -#import +#import #import #import #import @@ -37,6 +38,21 @@ @implementation SOGoUserFolder ++ (void) initialize +{ + SoClassSecurityInfo *sInfo; + NSArray *basicRoles; + + sInfo = [self soClassSecurityInfo]; + [sInfo declareObjectProtected: SoPerm_View]; + + basicRoles = [NSArray arrayWithObject: SoRole_Authenticated]; + + /* require Authenticated role for View and WebDAV */ + [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_View]; + [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_WebDAVAccess]; +} + /* accessors */ - (NSString *) login @@ -118,15 +134,15 @@ // : [super permissionForKey: key]); // } -- (SOGoAppointmentFolder *) privateCalendar: (NSString *) _key - inContext: (WOContext *) _ctx +- (SOGoAppointmentFolders *) privateCalendars: (NSString *) _key + inContext: (WOContext *) _ctx { - SOGoAppointmentFolder *calendar; + SOGoAppointmentFolders *calendars; - calendar = [$(@"SOGoAppointmentFolder") objectWithName: _key inContainer: self]; - [calendar setOCSPath: [self ocsPrivateCalendarPath]]; + calendars = [$(@"SOGoAppointmentFolders") objectWithName: _key inContainer: self]; + [calendars setBaseOCSPath: [self ocsPrivateCalendarPath]]; - return calendar; + return calendars; } - (SOGoContactFolders *) privateContacts: (NSString *) _key @@ -169,7 +185,7 @@ if (!obj) { if ([_key isEqualToString: @"Calendar"]) - obj = [self privateCalendar: @"Calendar" inContext: _ctx]; + obj = [self privateCalendars: @"Calendar" inContext: _ctx]; // if (![_key isEqualToString: @"Calendar"]) // obj = [obj lookupName: [_key pathExtension] // inContext: _ctx acquire: NO]; diff --git a/UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.h b/SoObjects/SOGo/SOGoWebAuthenticator.h similarity index 68% rename from UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.h rename to SoObjects/SOGo/SOGoWebAuthenticator.h index 7590f1060..464f38dc0 100644 --- a/UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.h +++ b/SoObjects/SOGo/SOGoWebAuthenticator.h @@ -1,4 +1,4 @@ -/* SOGoACLEnglishRemovalAdvisory.h - this file is part of SOGo +/* SOGoWebAuthenticator.h - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,12 +20,23 @@ * Boston, MA 02111-1307, USA. */ -#ifndef SOGOACLENGLISHREMOVALADVISORY_H -#define SOGOACLENGLISHREMOVALADVISORY_H +#ifndef _SOGOWEBAUTHENTICATOR_H_ +#define _SOGOWEBAUTHENTICATOR_H_ -#import "SOGoACLAdvisory.h" +#import +#import "SOGoAuthenticator.h" + +@class NSString; + +@class SOGoUser; + +@interface SOGoWebAuthenticator : SoCookieAuthenticator +{ + NSString *authMethod; +} + ++ (id) sharedSOGoWebAuthenticator; -@interface SOGoACLEnglishRemovalAdvisory : SOGoACLAdvisory @end -#endif /* SOGOACLENGLISHREMOVALADVISORY_H */ +#endif /* _SOGOWEBAUTHENTICATOR_H__ */ diff --git a/SoObjects/SOGo/SOGoWebAuthenticator.m b/SoObjects/SOGo/SOGoWebAuthenticator.m new file mode 100644 index 000000000..36a962826 --- /dev/null +++ b/SoObjects/SOGo/SOGoWebAuthenticator.m @@ -0,0 +1,164 @@ +/* SOGoWebAuthenticator.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +#import + +#import "LDAPUserManager.h" +#import "SOGoPermissions.h" +#import "SOGoUser.h" + +#import "SOGoWebAuthenticator.h" + +@implementation SOGoWebAuthenticator + ++ (id) sharedSOGoWebAuthenticator +{ + static SOGoWebAuthenticator *auth = nil; + + if (!auth) + auth = [self new]; + + return auth; +} + +- (id) init +{ + if ((self = [super init])) + { + authMethod = [[NSUserDefaults standardUserDefaults] + stringForKey: @"SOGoAuthentificationMethod"]; + [authMethod retain]; + } + + return self; +} + +- (void) dealloc +{ + [authMethod release]; + [super dealloc]; +} + +- (BOOL) checkLogin: (NSString *) _login + password: (NSString *) _pwd +{ + BOOL accept; + LDAPUserManager *um; + + if ([authMethod isEqualToString: @"LDAP"]) + { + um = [LDAPUserManager sharedUserManager]; + accept = [um checkLogin: _login andPassword: _pwd]; + } + else + accept = ([authMethod isEqualToString: @"bypass"] + && [_login length] > 0); + + return accept; +// || ([_login isEqualToString: @"freebusy"] +// && [_pwd isEqualToString: @"freebusy"])); +} + +- (NSString *) passwordInContext: (WOContext *) context +{ + NSArray *creds; + NSString *auth, *password; + + auth = [[context request] cookieValueForKey: + [self cookieNameInContext: context]]; + creds = [self parseCredentials: auth]; + if ([creds count] > 1) + password = [creds objectAtIndex: 1]; + else + password = nil; + + return password; +} + +/* create SOGoUser */ + +- (SOGoUser *) userWithLogin: (NSString *) login + andRoles: (NSArray *) roles + inContext: (WOContext *) ctx +{ + /* the actual factory method */ + return [SOGoUser userWithLogin: login roles: roles]; +} + +- (WOResponse *) preprocessCredentialsInContext: (WOContext *) context +{ + /* + This is called by SoObjectRequestHandler prior doing any significant + processing to allow the authenticator to reject invalid requests. + */ + WOResponse *response; + NSString *auth; + + auth = [[context request] + cookieValueForKey: [self cookieNameInContext:context]]; + if ([auth isEqualToString: @"discard"]) + { + [context setObject: [NSArray arrayWithObject: SoRole_Anonymous] + forKey: @"SoAuthenticatedRoles"]; + response = nil; + } + else + response = [super preprocessCredentialsInContext: context]; + + return response; +} + +- (void) setupAuthFailResponse: (WOResponse *) response + withReason: (NSString *) reason + inContext: (WOContext *) context +{ + WOComponent *page; + WOCookie *authCookie; + NSCalendarDate *date; + + page = [[WOApplication application] pageWithName: @"SOGoRootPage" + forRequest: [context request]]; + [[SoDefaultRenderer sharedRenderer] renderObject: page + inContext: context]; + authCookie = [WOCookie cookieWithName: [self cookieNameInContext: context] + value: @"discard"]; + [authCookie setPath: @"/"]; + date = [NSCalendarDate calendarDate]; + [authCookie setExpires: [date yesterday]]; + [response addCookie: authCookie]; +} + +@end /* SOGoWebAuthenticator */ diff --git a/UI/Common/English.lproj/Localizable.strings b/UI/Common/English.lproj/Localizable.strings index 9af10007e..1ae7857ef 100644 --- a/UI/Common/English.lproj/Localizable.strings +++ b/UI/Common/English.lproj/Localizable.strings @@ -10,7 +10,7 @@ "Address Book" = "Address Book"; "Mail" = "Mail"; "Preferences" = "Preferences"; -"Logoff" = "Logoff"; +"Sign Out" = "Sign Out"; "Right Administration" = "Right Administration"; "Log Console (dev.)" = "Log Console (dev.)"; @@ -27,8 +27,22 @@ "Sorry, the user rights can not be configured for that object." = "Sorry, the user rights can not be configured for that object."; +"browserNotCompatible" = "We've detected that your browser version is currently not supported on this site. Our recommendation is to use Firefox. Click on the link bellow to download the most current version of this browser."; +"alternativeBrowsers" = "Alternatively, you can also use the following compatible browsers"; +"alternativeBrowserSafari" = "Alternatively, you can also use Safari."; +"Download" = "Download"; + /* generic.js */ -"Unable to subscribe to that folder!" = "Unable to subscribe to that folder!"; -"You cannot subscribe to a folder that you own!" = "You cannot subscribe to a folder that you own!"; -"Unable to unsubscribe from that folder!" = "Unable to unsubscribe from that folder!"; -"You cannot unsubscribe from a folder that you own!" = "You cannot unsubscribe from a folder that you own!"; +"Unable to subscribe to that folder!" + = "Unable to subscribe to that folder!"; +"You cannot subscribe to a folder that you own!" + = "You cannot subscribe to a folder that you own!"; +"Unable to unsubscribe from that folder!" + = "Unable to unsubscribe from that folder!"; +"You cannot unsubscribe from a folder that you own!" + = "You cannot unsubscribe from a folder that you own!"; +"Unable to rename that folder!" = "Unable to rename that folder!"; +"You have already subscribed to that folder!" + = "You have already subscribed to that folder!"; +"The user rights cannot be edited for this object!" + = "The user rights cannot be edited for this object!"; diff --git a/UI/Common/French.lproj/Localizable.strings b/UI/Common/French.lproj/Localizable.strings index 18a984d3b..2de07b5c9 100644 --- a/UI/Common/French.lproj/Localizable.strings +++ b/UI/Common/French.lproj/Localizable.strings @@ -8,7 +8,7 @@ "Address Book" = "Carnet d'adresses"; "Mail" = "Courrier"; "Preferences" = "Préférences"; -"Logoff" = "Quitter"; +"Sign Out" = "Quitter"; "Right Administration" = "Partage"; "Log Console (dev.)" = "Journal (dév.)"; @@ -28,8 +28,18 @@ "Sorry, the user rights can not be configured for that object." = "Sorry, the user rights can not be configured for that object."; +"browserNotCompatible" = "La version de votre navigateur Web n'est présentement pas supportée par ce site. Nous recommandons d'utiliser Firefox. Vous trouverez un lien vers la plus récente version de ce navigateur ci-dessous:"; +"alternativeBrowsers" = "Comme alternative, vous pouvez aussi utiliser les navigateurs suivants:"; +"alternativeBrowserSafari" = "Comme alternative, vous pouvez aussi utiliser Safari."; +"Download" = "Télécharger"; + /* generic.js */ "Unable to subscribe to that folder!" = "Impossible de s'abonner à ce dossier."; "You cannot subscribe to a folder that you own!" = "Impossible de vous abonner à un dossier qui vous appartient."; "Unable to unsubscribe from that folder!" = "Impossible de se désabonner de ce dossier."; "You cannot unsubscribe from a folder that you own!" = "Impossible de vous désabonner d'un dossier qui vous appartient."; +"Unable to rename that folder!" = "Impossible de renommer ce dossier."; +"You have already subscribed to that folder!" + = "Vous êtes déja abonné à ce dossier."; +"The user rights cannot be edited for this object!" + = "Les droits sur cet objet ne peuvent pas être édités."; diff --git a/UI/Common/GNUmakefile b/UI/Common/GNUmakefile index ebdc4fea9..e25a1ae1d 100644 --- a/UI/Common/GNUmakefile +++ b/UI/Common/GNUmakefile @@ -17,6 +17,7 @@ CommonUI_OBJC_FILES += \ UIxAclEditor.m \ UIxObjectActions.m \ UIxFolderActions.m \ + UIxParentFolderActions.m \ UIxElemBuilder.m \ UIxTabView.m \ UIxTabItem.m \ @@ -38,6 +39,9 @@ CommonUI_LOCALIZED_RESOURCE_FILES += \ # make +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/Common/German.lproj/Localizable.strings b/UI/Common/German.lproj/Localizable.strings index 0693ab367..68e8862e3 100644 --- a/UI/Common/German.lproj/Localizable.strings +++ b/UI/Common/German.lproj/Localizable.strings @@ -19,17 +19,24 @@ "noJavascriptError" = "SOGo requiert l'utilisation de Javascript. Veuillez vous assurer que cette option est disponible et activée dans votre fureteur."; "noJavascriptRetry" = "Réessayer"; -"Owner:" = "Propriétaire :"; +"Add..." = "Hinzufügen..."; +"Remove" = "Löschen"; +"Owner:" = "Besitzer:"; "Associated Users:" = "Utilisateurs associés :"; "(Unchecked = assistant, checked = delegate)" = "(Coché = assistant, décoché = délégué)"; "Publish the Free/Busy information" = "Publier l'occupation du temps"; -"Default Roles" = "Rôles par défaut"; +"Default Roles" = "Standard-Rollen"; -"Sorry, the user rights can not be configured for that object." = "Sorry, the user rights can not be configured for that object."; +"Sorry, the user rights can not be configured for that object." = "Leider können die Benutzerrechte für dieses Objekt nicht konfiguriert werden."; /* generic.js */ -"Unable to subscribe to that folder!" = "Impossible de s'abonner à ce dossier."; -"You cannot subscribe to a folder that you own!" = "Impossible de vous abonner à un dossier qui vous appartient."; -"Unable to unsubscribe from that folder!" = "Impossible de se désabonner de ce dossier."; -"You cannot unsubscribe from a folder that you own!" = "Impossible de vous désabonner d'un dossier qui vous appartient."; +"Unable to subscribe to that folder!" = "Unmöglich sich an diesem Ordner zu abonnieren."; +"You cannot subscribe to a folder that you own!" = "Unmöglich sich an einem Ordner zu abonnieren, der Ihnen selbst gehört."; +"Unable to unsubscribe from that folder!" = "Unmöglich sich von diesem Ordner zu des-abonnieren."; +"You cannot unsubscribe from a folder that you own!" = "Unmöglich sich von einem Ordner zu des-abonnieren, der Ihnen selbst gehört."; +"Unable to rename that folder!" = "Unable to rename that folder!"; +"You have already subscribed to that folder!" + = "You have already subscribed to that folder!"; +"The user rights cannot be edited for this object!" + = "The user rights cannot be edited for this object!"; diff --git a/UI/Common/UIxAclEditor.h b/UI/Common/UIxAclEditor.h index f8104ceed..41f418e7c 100644 --- a/UI/Common/UIxAclEditor.h +++ b/UI/Common/UIxAclEditor.h @@ -43,6 +43,7 @@ - (NSString *) currentUser; - (NSString *) ownerName; +- (BOOL) hasOwner; @end diff --git a/UI/Common/UIxAclEditor.m b/UI/Common/UIxAclEditor.m index e6dccdfe0..123bd4961 100644 --- a/UI/Common/UIxAclEditor.m +++ b/UI/Common/UIxAclEditor.m @@ -87,6 +87,15 @@ return [self _displayNameForUID: ownerLogin]; } +- (BOOL) hasOwner +{ + NSString *ownerLogin; + + ownerLogin = [[self clientObject] ownerInContext: context]; + + return (![ownerLogin isEqualToString: @"nobody"]); +} + - (NSString *) defaultUserID { if (!defaultUserID) diff --git a/UI/Common/UIxFolderActions.h b/UI/Common/UIxFolderActions.h index b74a20a30..8794bbeab 100644 --- a/UI/Common/UIxFolderActions.h +++ b/UI/Common/UIxFolderActions.h @@ -41,7 +41,7 @@ NSString *owner; NSString *login; NSString *baseFolder; - NSMutableString *subscriptionPointer; + NSString *subscriptionPointer; NSMutableDictionary *moduleSettings; BOOL isMailInvitation; } diff --git a/UI/Common/UIxFolderActions.m b/UI/Common/UIxFolderActions.m index ca14b9d5e..1c2d59b05 100644 --- a/UI/Common/UIxFolderActions.m +++ b/UI/Common/UIxFolderActions.m @@ -31,12 +31,16 @@ #import #import #import +#import #import +#import #import -#import +#import #import +#import "WODirectAction+SOGo.h" + #import "UIxFolderActions.h" @implementation UIxFolderActions @@ -46,7 +50,8 @@ - (void) _setupContext { - NSString *clientClass, *mailInvitationParam; + NSString *folder, *mailInvitationParam; + NSArray *realFolderPath; SOGoUser *activeUser; activeUser = [context activeUser]; @@ -54,13 +59,7 @@ clientObject = [self clientObject]; owner = [clientObject ownerInContext: nil]; - clientClass = NSStringFromClass([clientObject class]); - if ([clientClass isEqualToString: @"SOGoContactGCSFolder"]) - baseFolder = @"Contacts"; - else if ([clientClass isEqualToString: @"SOGoAppointmentFolder"]) - baseFolder = @"Calendar"; - else - baseFolder = nil; + baseFolder = [[clientObject container] nameInContainer]; um = [LDAPUserManager sharedUserManager]; ud = [activeUser userSettings]; @@ -72,27 +71,29 @@ } [ud setObject: moduleSettings forKey: baseFolder]; - subscriptionPointer = [NSMutableString stringWithFormat: @"%@:%@", - owner, baseFolder]; - if ([baseFolder isEqualToString: @"Contacts"]) - [subscriptionPointer appendFormat: @"/%@", - [clientObject nameInContainer]]; + realFolderPath = [[clientObject nameInContainer] + componentsSeparatedByString: @"_"]; + if ([realFolderPath count] > 1) + folder = [realFolderPath objectAtIndex: 1]; + else + folder = [realFolderPath objectAtIndex: 0]; + subscriptionPointer = [NSString stringWithFormat: @"%@:%@/%@", + owner, baseFolder, folder]; mailInvitationParam = [[context request] formValueForKey: @"mail-invitation"]; isMailInvitation = [mailInvitationParam boolValue]; } -- (WOResponse *) _realActionWithFolderName: (NSDictionary *) folderDict +- (WOResponse *) _realSubscribe: (BOOL) reallyDo { WOResponse *response; - NSMutableDictionary *folderSubscription; + NSMutableArray *folderSubscription; NSString *mailInvitationURL; - response = [context response]; if ([owner isEqualToString: login]) { - [response setStatus: 403]; + response = [self responseWithStatus: 403]; [response appendContentString: @"You cannot (un)subscribe to a folder that you own!"]; } @@ -100,17 +101,17 @@ { folderSubscription = [moduleSettings objectForKey: @"SubscribedFolders"]; - if (!folderSubscription) + if (!(folderSubscription + && [folderSubscription isKindOfClass: [NSMutableArray class]])) { - folderSubscription = [NSMutableDictionary dictionary]; + folderSubscription = [NSMutableArray array]; [moduleSettings setObject: folderSubscription forKey: @"SubscribedFolders"]; } - if (folderDict) - [folderSubscription setObject: folderDict - forKey: subscriptionPointer]; + if (reallyDo) + [folderSubscription addObjectUniquely: subscriptionPointer]; else - [folderSubscription removeObjectForKey: subscriptionPointer]; + [folderSubscription removeObject: subscriptionPointer]; [ud synchronize]; @@ -119,12 +120,12 @@ mailInvitationURL = [[clientObject soURLToBaseContainerForCurrentUser] absoluteString]; - [response setStatus: 302]; + response = [self responseWithStatus: 302]; [response setHeader: mailInvitationURL forKey: @"location"]; } else - [response setStatus: 204]; + response = [self responseWith204]; } return response; @@ -132,73 +133,73 @@ - (WOResponse *) subscribeAction { - NSString *email; - NSMutableDictionary *folderDict; - NSString *folderName; - [self _setupContext]; - email = [NSString stringWithFormat: @"%@ <%@>", - [um getCNForUID: owner], - [um getEmailForUID: owner]]; - if ([baseFolder isEqualToString: @"Contacts"]) - folderName = [NSString stringWithFormat: @"%@ (%@)", - [clientObject nameInContainer], email]; - else - folderName = email; - folderDict = [NSMutableDictionary dictionary]; - [folderDict setObject: folderName forKey: @"displayName"]; - [folderDict setObject: [NSNumber numberWithBool: NO] forKey: @"active"]; - - return [self _realActionWithFolderName: folderDict]; + return [self _realSubscribe: YES]; } - (WOResponse *) unsubscribeAction { [self _setupContext]; - return [self _realActionWithFolderName: nil]; + return [self _realSubscribe: NO]; } - (WOResponse *) canAccessContentAction { - WOResponse *response; +#warning IMPROVEMENTS REQUIRED! + NSArray *acls; +// NSEnumerator *userAcls; +// NSString *currentAcl; - response = [context response]; - [response setStatus: 204]; + [self _setupContext]; + +// NSLog(@"canAccessContentAction %@, owner %@", subscriptionPointer, owner); - return response; + if ([login isEqualToString: owner] || [owner isEqualToString: @"nobody"]) { + return [self responseWith204]; + } + else { + acls = [clientObject aclsForUser: login]; +// userAcls = [acls objectEnumerator]; +// currentAcl = [userAcls nextObject]; +// while (currentAcl) { +// NSLog(@"ACL login %@, owner %@, folder %@: %@", +// login, owner, baseFolder, currentAcl); +// currentAcl = [userAcls nextObject]; +// } + if (([[clientObject folderType] isEqualToString: @"Contact"] && [acls containsObject: SOGoRole_ObjectReader]) || + ([[clientObject folderType] isEqualToString: @"Appointment"] && [acls containsObject: SOGoRole_AuthorizedSubscriber])) { + return [self responseWith204]; + } + } + + return [self responseWithStatus: 403]; } - (WOResponse *) _realFolderActivation: (BOOL) makeActive { - WOResponse *response; - NSMutableDictionary *folderSubscription, *folderDict; - NSNumber *active; - - response = [context response]; + NSMutableArray *folderSubscription; + NSString *folderName; [self _setupContext]; - active = [NSNumber numberWithBool: makeActive]; - if ([owner isEqualToString: login]) - [moduleSettings setObject: active forKey: @"activateUserFolder"]; - else + folderSubscription + = [moduleSettings objectForKey: @"ActiveFolders"]; + if (!folderSubscription) { - folderSubscription - = [moduleSettings objectForKey: @"SubscribedFolders"]; - if (folderSubscription) - { - folderDict = [folderSubscription objectForKey: subscriptionPointer]; - if (folderDict) - [folderDict setObject: active - forKey: @"active"]; - } + folderSubscription = [NSMutableArray array]; + [moduleSettings setObject: folderSubscription forKey: @"ActiveFolders"]; } - [ud synchronize]; - [response setStatus: 204]; + folderName = [clientObject nameInContainer]; + if (makeActive) + [folderSubscription addObjectUniquely: folderName]; + else + [folderSubscription removeObject: folderName]; - return response; + [ud synchronize]; + + return [self responseWith204]; } - (WOResponse *) activateFolderAction @@ -211,4 +212,59 @@ return [self _realFolderActivation: NO]; } +- (WOResponse *) deleteFolderAction +{ + WOResponse *response; + + response = (WOResponse *) [[self clientObject] delete]; + if (!response) + response = [self responseWith204]; + + return response; +} + +- (WOResponse *) renameFolderAction +{ + WOResponse *response; + NSString *folderName; + + folderName = [[context request] formValueForKey: @"name"]; + if ([folderName length] > 0) + { + clientObject = [self clientObject]; + [clientObject renameTo: folderName]; + response = [self responseWith204]; + } + else + { + response = [self responseWithStatus: 500]; + [response appendContentString: @"Missing 'name' parameter."]; + } + + return response; +} + +- (id) batchDeleteAction +{ + WOResponse *response; + NSString *idsParam; + NSArray *ids; + + idsParam = [[context request] formValueForKey: @"ids"]; + ids = [idsParam componentsSeparatedByString: @"/"]; + if ([ids count]) + { + clientObject = [self clientObject]; + [clientObject deleteEntriesWithIds: ids]; + response = [self responseWith204]; + } + else + { + response = [self responseWithStatus: 500]; + [response appendContentString: @"At least 1 id required."]; + } + + return response; +} + @end diff --git a/UI/Common/UIxPageFrame.h b/UI/Common/UIxPageFrame.h index 94fe9816f..1faf08cbb 100644 --- a/UI/Common/UIxPageFrame.h +++ b/UI/Common/UIxPageFrame.h @@ -23,6 +23,7 @@ #define UIXPAGEFRAME_H #import +#import @interface WOComponent (PopupExtension) @@ -56,6 +57,10 @@ - (void) setToolbar: (NSString *) newToolbar; - (NSString *) toolbar; +- (BOOL) isCompatibleBrowser; +- (BOOL) isIE7Compatible; +- (BOOL) isMac; + @end #endif /* UIXPAGEFRAME_H */ diff --git a/UI/Common/UIxPageFrame.m b/UI/Common/UIxPageFrame.m index b093fa75b..9ae46a5d9 100644 --- a/UI/Common/UIxPageFrame.m +++ b/UI/Common/UIxPageFrame.m @@ -171,7 +171,7 @@ WOComponent *page; NSString *pageJSFilename; - page = [[self context] page]; + page = [context page]; pageJSFilename = [NSString stringWithFormat: @"%@.js", NSStringFromClass([page class])]; @@ -183,7 +183,7 @@ WOComponent *page; NSString *fwJSFilename; - page = [[self context] page]; + page = [context page]; fwJSFilename = [NSString stringWithFormat: @"%@.js", [page frameworkName]]; @@ -194,7 +194,7 @@ { WOComponent *page; - page = [[self context] page]; + page = [context page]; return [NSString stringWithFormat: @"%@.SOGo", [page frameworkName]]; } @@ -214,7 +214,7 @@ WOComponent *page; NSString *pageJSFilename; - page = [[self context] page]; + page = [context page]; pageJSFilename = [NSString stringWithFormat: @"%@.css", NSStringFromClass([page class])]; @@ -226,7 +226,7 @@ WOComponent *page; NSString *fwJSFilename; - page = [[self context] page]; + page = [context page]; fwJSFilename = [NSString stringWithFormat: @"%@.css", [page frameworkName]]; @@ -235,7 +235,7 @@ - (NSString *) thisPageURL { - return [[[self context] page] uri]; + return [[context page] uri]; } - (BOOL) hasPageSpecificCSS @@ -258,4 +258,47 @@ return toolbar; } +/* browser/os identification */ + +- (BOOL) isCompatibleBrowser +{ + WEClientCapabilities *cc; + + cc = [[context request] clientCapabilities]; + + //NSLog(@"Browser = %@", [cc description]); + NSLog(@"User agent = %@", [cc userAgent]); + //NSLog(@"Browser major version = %i", [cc majorVersion]); + + return (([[cc userAgentType] isEqualToString: @"IE"] + && [cc majorVersion] >= 7) + || ([[cc userAgentType] isEqualToString: @"Mozilla"] + && [cc majorVersion] >= 5) + || ([[cc userAgentType] isEqualToString: @"Safari"] + && [cc majorVersion] >= 4) + // ([[cc userAgentType] isEqualToString: @"Konqueror"]) + ); +} + +- (BOOL) isIE7Compatible +{ + WEClientCapabilities *cc; + + cc = [[context request] clientCapabilities]; + + return ([cc isWindowsBrowser] && + ([[cc userAgent] rangeOfString: @"NT 5.1"].location != NSNotFound || + [[cc userAgent] rangeOfString: @"NT 6"].location != NSNotFound)); +} + +- (BOOL) isMac +{ + WEClientCapabilities *cc; + + cc = [[context request] clientCapabilities]; + + return [cc isMacBrowser]; +} + + @end /* UIxPageFrame */ diff --git a/UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.m b/UI/Common/UIxParentFolderActions.h similarity index 77% rename from UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.m rename to UI/Common/UIxParentFolderActions.h index f5eacbcf0..c380f0642 100644 --- a/UI/SOGoUI/SOGoACLEnglishRemovalAdvisory.m +++ b/UI/Common/UIxParentFolderActions.h @@ -1,4 +1,4 @@ -/* SOGoACLEnglishRemovalAdvisory.m - this file is part of SOGo +/* UIxParentFolderActions.h - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,13 +20,12 @@ * Boston, MA 02111-1307, USA. */ -#import "SOGoACLEnglishRemovalAdvisory.h" +#ifndef UIXPARENTFOLDERACTIONS_H +#define UIXPARENTFOLDERACTIONS_H -@implementation SOGoACLEnglishRemovalAdvisory - -- (NSString *) aclMethod -{ - return @"add"; -} +#import +@interface UIxParentFolderActions : WODirectAction @end + +#endif /* UIXPARENTFOLDERACTIONS_H */ diff --git a/UI/Common/UIxParentFolderActions.m b/UI/Common/UIxParentFolderActions.m new file mode 100644 index 000000000..8f7599365 --- /dev/null +++ b/UI/Common/UIxParentFolderActions.m @@ -0,0 +1,61 @@ +/* UIxParentFolderActions.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import +#import + +#import +#import + +#import "UIxParentFolderActions.h" + +@implementation UIxParentFolderActions + +- (id ) createFolderAction +{ + WOResponse *response; + NSString *name, *nameInContainer; + + name = [[context request] formValueForKey: @"name"]; + if ([name length] > 0) + { + response = (WOResponse *) [[self clientObject] newFolderWithName: name + nameInContainer: &nameInContainer]; + if (!response) + { + response = [self responseWithStatus: 201]; + [response setHeader: @"text/plain; charset=us-ascii" + forKey: @"content-type"]; + [response appendContentString: nameInContainer]; + } + } + else + response = [NSException exceptionWithHTTPStatus: 400 + reason: @"The name is missing"]; + + return response; +} + +@end diff --git a/UI/Common/UIxToolbar.m b/UI/Common/UIxToolbar.m index e7fd10097..1959aaac6 100644 --- a/UI/Common/UIxToolbar.m +++ b/UI/Common/UIxToolbar.m @@ -214,6 +214,15 @@ return image; } +- (NSString *) buttonTooltip +{ + NSString *key; + + key = [[self buttonInfo] valueForKey: @"tooltip"]; + + return [self labelForKey: key]; +} + /* enable/disable buttons */ - (BOOL)isButtonEnabled { diff --git a/UI/Common/WODirectAction+SOGo.h b/UI/Common/WODirectAction+SOGo.h index 92ec03206..067046b69 100644 --- a/UI/Common/WODirectAction+SOGo.h +++ b/UI/Common/WODirectAction+SOGo.h @@ -30,6 +30,8 @@ @interface WODirectAction (SOGoExtension) +- (WOResponse *) responseWithStatus: (unsigned int) status; +- (WOResponse *) responseWith204; - (WOResponse *) redirectToLocation: (NSString *) newLocation; @end diff --git a/UI/Common/WODirectAction+SOGo.m b/UI/Common/WODirectAction+SOGo.m index ddeea2aaa..13c8b86d8 100644 --- a/UI/Common/WODirectAction+SOGo.m +++ b/UI/Common/WODirectAction+SOGo.m @@ -28,15 +28,30 @@ @implementation WODirectAction (SOGoExtension) -- (WOResponse *) redirectToLocation: (NSString *) newLocation +- (WOResponse *) responseWithStatus: (unsigned int) status { WOResponse *response; response = [context response]; - [response setStatus: 302 /* moved */]; + [response setStatus: status]; + + return response; +} + +- (WOResponse *) responseWith204 +{ + return [self responseWithStatus: 204]; +} + +- (WOResponse *) redirectToLocation: (NSString *) newLocation +{ + WOResponse *response; + + response = [self responseWithStatus: 302]; [response setHeader: newLocation forKey: @"location"]; return response; } + @end diff --git a/UI/Common/product.plist b/UI/Common/product.plist index 9e014e8e9..b9b6d7e7e 100644 --- a/UI/Common/product.plist +++ b/UI/Common/product.plist @@ -1,95 +1,119 @@ -{ /* -*-javascript-*- */ - requires = ( MAIN, Mailer ); +{ /* -*-java-*- */ + requires = ( MAIN, Mailer ); + + publicResources = ( + calendar.css, + uix.css, + menu_logo_top.gif, + line_left.gif, + line_stretch.gif, + line_right.gif, + box_topleft.gif, + box_top.gif, + box_topright.gif, + box_left.gif, + box_right.gif, + box_botleft.gif, + box_bottom.gif, + box_botright.gif, + tab_selected.gif, + tab_.gif, + corner_right.gif, + closewindow.gif, + OGoLogo.gif, + upward_sorted.gif, + downward_sorted.gif, + non_sorted.gif + ); - publicResources = ( - calendar.css, - uix.css, - menu_logo_top.gif, - line_left.gif, - line_stretch.gif, - line_right.gif, - box_topleft.gif, - box_top.gif, - box_topright.gif, - box_left.gif, - box_right.gif, - box_botleft.gif, - box_bottom.gif, - box_botright.gif, - tab_selected.gif, - tab_.gif, - corner_right.gif, - closewindow.gif, - OGoLogo.gif, - upward_sorted.gif, - downward_sorted.gif, - non_sorted.gif - ); + factories = { + }; - factories = { - }; - - categories = { - SOGoObject = { - methods = { - addUserInAcls = { - protectedBy = "SaveAcls"; - actionClass = "UIxObjectActions"; - actionName = "addUserInAcls"; - }; - removeUserFromAcls = { - protectedBy = "SaveAcls"; - actionClass = "UIxObjectActions"; - actionName = "removeUserFromAcls"; - }; - acls = { - protectedBy = "ReadAcls"; - pageName = "UIxAclEditor"; - }; - saveAcls = { - protectedBy = "SaveAcls"; - pageName = "UIxAclEditor"; - actionName = "saveAcls"; - }; - userRights = { - protectedBy = "ReadAcls"; - pageName = "UIxUserRightsEditor"; - }; - saveUserRights = { - protectedBy = "ReadAcls"; - pageName = "UIxUserRightsEditor"; - actionName = "saveUserRights"; - }; - }; + categories = { + SOGoObject = { + methods = { + addUserInAcls = { + protectedBy = "SaveAcls"; + actionClass = "UIxObjectActions"; + actionName = "addUserInAcls"; + }; + removeUserFromAcls = { + protectedBy = "SaveAcls"; + actionClass = "UIxObjectActions"; + actionName = "removeUserFromAcls"; + }; + acls = { + protectedBy = "ReadAcls"; + pageName = "UIxAclEditor"; + }; + saveAcls = { + protectedBy = "SaveAcls"; + pageName = "UIxAclEditor"; + actionName = "saveAcls"; + }; + userRights = { + protectedBy = "ReadAcls"; + pageName = "UIxUserRightsEditor"; + }; + saveUserRights = { + protectedBy = "ReadAcls"; + pageName = "UIxUserRightsEditor"; + actionName = "saveUserRights"; + }; }; - SOGoFolder = { - methods = { - subscribe = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "subscribe"; - }; - unsubscribe = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "unsubscribe"; - }; - canAccessContent = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "canAccessContent"; - }; - activateFolder = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "activateFolder"; - }; - deactivateFolder = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "deactivateFolder"; - }; - }; + }; + SOGoParentFolder = { + methods = { + createFolder = { + protectedBy = "View"; + actionClass = "UIxParentFolderActions"; + actionName = "createFolder"; + }; }; - }; + }; + SOGoFolder = { + methods = { + subscribe = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "subscribe"; + }; + unsubscribe = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "unsubscribe"; + }; + canAccessContent = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "canAccessContent"; + }; + activateFolder = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "activateFolder"; + }; + deactivateFolder = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "deactivateFolder"; + }; + deleteFolder = { + protectedBy = "SaveAcls"; /* a hack to force "owner" */ + actionClass = "UIxFolderActions"; + actionName = "deleteFolder"; + }; + renameFolder = { + protectedBy = "SaveAcls"; + actionClass = "UIxFolderActions"; + actionName = "renameFolder"; + }; + batchDelete = { + protectedBy = "Delete Objects"; + actionClass = "UIxFolderActions"; + actionName = "batchDelete"; + }; + }; + }; + }; } diff --git a/UI/Contacts/English.lproj/Localizable.strings b/UI/Contacts/English.lproj/Localizable.strings index 9f8a0ac23..08044be83 100644 --- a/UI/Contacts/English.lproj/Localizable.strings +++ b/UI/Contacts/English.lproj/Localizable.strings @@ -32,6 +32,14 @@ "invalidemailwarn" = "invalidemailwarn"; "new" = "new"; +/* Tooltips */ + +"Create a new address book card" = "Create a new address book card"; +"Create a new list" = "Create a new list"; +"Edit the selected card" = "Edit the selected card"; +"Send a mail message" = "Send a mail message"; +"Delete selected card or address book" = "Delete selected card or address book"; + "htmlMailFormat_UNKNOWN" = "Unknown"; "htmlMailFormat_FALSE" = "Plain Text"; "htmlMailFormat_TRUE" = "HTML"; @@ -97,6 +105,8 @@ "Are you sure you want to delete the selected address book?" = "Are you sure you want to delete the selected address book?"; +"Address Book Name" = "Address Book Name"; + "You cannot subscribe to a folder that you own!" = "You cannot subscribe to a folder that you own!"; "Unable to subscribe to that folder!" @@ -115,3 +125,6 @@ = "This person can read the cards of this addressbook."; "This person can erase cards from this addressbook." = "This person can erase cards from this addressbook."; + +"The selected contact has no email address." += "The selected contact has no email address."; diff --git a/UI/Contacts/French.lproj/Localizable.strings b/UI/Contacts/French.lproj/Localizable.strings index f0ee23b30..5bf201634 100644 --- a/UI/Contacts/French.lproj/Localizable.strings +++ b/UI/Contacts/French.lproj/Localizable.strings @@ -39,6 +39,14 @@ "invalidemailwarn" = "Champ de l'email invalide, continuer quand même ?"; "new" = "Nouveau"; +/* Tooltips */ + +"Create a new address book card" = "Créer une nouvelle fiche"; +"Create a new list" = "Créer une nouvelle liste de diffusion"; +"Edit the selected card" = "Modifier la fiche sélectionnée"; +"Send a mail message" = "Rédiger un courrier à la sélection"; +"Delete selected card or address book" = "Supprimer la fiche sélectionnée"; + "htmlMailFormat_UNKNOWN" = "Inconnu"; "htmlMailFormat_FALSE" = "Texte simple (sans HTML)"; "htmlMailFormat_TRUE" = "HTML"; @@ -110,6 +118,8 @@ "Are you sure you want to delete the selected address book?" = "Voulez-vous vraiment supprimer le carnet d'adresses sélectionné ?"; +"Address Book Name" = "Nom du carnet d'adresses"; + "You cannot subscribe to a folder that you own!" = "Vous ne pouvez pas vous inscrire à un dossier qui vous appartient!"; "Unable to subscribe to that folder!" @@ -129,3 +139,6 @@ "This person can erase cards from this addressbook." = "Cette personne peut effacer des fiches de ce carnet d'adresses."; +"The selected contact has no email address." += "Cette personne n'a pas d'adresse courriel."; + diff --git a/UI/Contacts/GNUmakefile b/UI/Contacts/GNUmakefile index 34a2f8fc7..cb61ff5c5 100644 --- a/UI/Contacts/GNUmakefile +++ b/UI/Contacts/GNUmakefile @@ -33,6 +33,9 @@ ContactsUI_LOCALIZED_RESOURCE_FILES += \ # make +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/Contacts/German.lproj/Localizable.strings b/UI/Contacts/German.lproj/Localizable.strings index 11a6ce8b1..6d27d4e40 100644 --- a/UI/Contacts/German.lproj/Localizable.strings +++ b/UI/Contacts/German.lproj/Localizable.strings @@ -12,7 +12,7 @@ "Address Books" = "Adressbücher"; "Addresses" = "Adresses"; -"Update" = "Mettre à jour"; +"Update" = "Speichern"; "Cancel" = "Abbrechen"; "Common" = "Name"; "Contact editor" = "Éditer le contact"; @@ -50,12 +50,12 @@ "New Card" = "Neue Karte"; "New List" = "Neue Liste"; "Modify" = "Eigenschaften"; -"Sharing..." = "Partage..."; +"Sharing..." = "Benutzerrechte..."; "Write" = "Verfassen"; "Delete" = "Löschen"; "Instant Message" = "Messenger"; -"Add..." = "Ajouter..."; -"Remove" = "Enlever"; +"Add..." = "Hinzufügen..."; +"Remove" = "Löschen"; "Preferred" = "Préféré"; "Card for %@" = "%@"; @@ -102,29 +102,33 @@ "Blind Carbon Copy" = "C. carbone cachée"; "New Addressbook..." = "Neues Adressbuch..."; -"Subscribe to an Addressbook..." = "S'inscrire à un carnet d'adresses..."; +"Subscribe to an Addressbook..." = "Abonnieren..."; "Remove the selected Addressbook" = "Adressbuch löschen"; "Name of the Address Book" = "Adressbuch-Name"; "Are you sure you want to delete the selected address book?" = "Voulez-vous vraiment supprimer le carnet d'adresses sélectionné ?"; +"Address Book Name" = "Address Book Name"; + "You cannot subscribe to a folder that you own!" = "Vous ne pouvez pas vous inscrire à un dossier qui vous appartient!"; "Unable to subscribe to that folder!" = "Impossible de vous inscrire à ce dossier!"; -"Default Roles" = "Rôles par défaut"; -"User rights for:" = "Autorisations pour :"; +"Default Roles" = "Standard-Rollen"; +"User rights for:" = "Benutzerrechte für:"; "This person can add cards to this addressbook." -= "Cette personne peut ajouter des fiches à ce carnet d'adresses."; += "Diese Person kann neue Karten zu diesem Adressbuch hinzufügen."; "This person can edit the cards of this addressbook." -= "Cette personne peut éditer des fiches de ce carnet d'adresses."; += "Diese Person kann in diesem Adressbuch bestehende Karten verändern."; "This person can list the content of this addressbook." -= "Cette personne peut lister le contenu de ce carnet d'adresses."; += "Diese Person kann die Liste der Karten dieses Adressbuches anzeigen."; "This person can read the cards of this addressbook." -= "Cette personne peut visionner les fiches de ce carnet d'adresses."; += "Diese Person kann Karten dieses Adressbuches anzeigen."; "This person can erase cards from this addressbook." -= "Cette personne peut effacer des fiches de ce carnet d'adresses."; += "Diese Person kann Karten aus diesem Adressbuch löschen"; +"The selected contact has no email address." += "The selected contact has no email address."; diff --git a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar index 2bcf4b68c..6c6971f5e 100644 --- a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar +++ b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar @@ -4,26 +4,31 @@ jsLink="js_card"; label="New Card"; image="new-card.png"; - onclick = "newContact(this); return false;"; }, + onclick = "newContact(this); return false;"; + tooltip = "Create a new address book card"; }, { link = "new_list"; enabled = "NO"; label="New List"; - image="new-list.png"; } + image="new-list.png"; + tooltip = "Create a new list"; } ), ( { link = "edit"; label = "Modify"; onclick = "return onToolbarEditSelectedContacts(this);"; - image = "properties.png"; }, + image = "properties.png"; + tooltip = "Edit the selected card"; }, { link = "write"; label="Write"; onclick = "return onToolbarWriteToSelectedContacts(this);"; - image="write.png"; } + image="write.png"; + tooltip = "Send a mail message"; } ), ( { link = "delete"; label="Delete"; onclick = "return uixDeleteSelectedContacts(this);"; - image="delete.png"; } + image="delete.png"; + tooltip = "Delete selected card or address book"; } ) ) diff --git a/UI/Contacts/UIxContactEditor.m b/UI/Contacts/UIxContactEditor.m index 578c65ce3..6f3954112 100644 --- a/UI/Contacts/UIxContactEditor.m +++ b/UI/Contacts/UIxContactEditor.m @@ -584,8 +584,8 @@ id co; co = [self clientObject]; - if ([[co class] respondsToSelector: @selector (globallyUniqueObjectId)]) - objectId = [[[self clientObject] class] globallyUniqueObjectId]; + if ([co respondsToSelector: @selector (globallyUniqueObjectId)]) + objectId = [co globallyUniqueObjectId]; else objectId = nil; diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index ed6dcd9d4..fa43d8109 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -52,9 +52,7 @@ WORequest *request; folders = [self clientObject]; - action = [NSString stringWithFormat: @"../%@/%@", - [folders defaultSourceName], - actionName]; + action = [NSString stringWithFormat: @"../personal/%@", actionName]; request = [[self context] request]; @@ -75,21 +73,6 @@ return [self _selectActionForApplication: @"new"]; } -- (id ) newAbAction -{ - id response; - NSString *name; - - name = [self queryParameterForKey: @"name"]; - if ([name length] > 0) - response = [[self clientObject] newFolderWithName: name]; - else - response = [NSException exceptionWithHTTPStatus: 400 - reason: @"The name is missing"]; - - return response; -} - - (id) selectForMailerAction { return [self _selectActionForApplication: @"mailer-contacts"]; @@ -111,8 +94,7 @@ { uid = [currentContact objectForKey: @"c_uid"]; if (uid && ![results objectForKey: uid]) - [results setObject: currentContact - forKey: uid]; + [results setObject: currentContact forKey: uid]; currentContact = [folderResults nextObject]; } } @@ -139,25 +121,29 @@ { WOResponse *response; NSEnumerator *contacts; - NSString *responseString; + NSString *responseString, *email; NSDictionary *contact; response = [context response]; if ([results count] > 0) { + [response setStatus: 200]; contacts = [results objectEnumerator]; contact = [contacts nextObject]; if (contact) { - responseString = [NSString stringWithFormat: @"%@:%@:%@", - [contact objectForKey: @"c_uid"], - [contact objectForKey: @"cn"], - [contact objectForKey: @"c_email"]]; - [response setStatus: 200]; + email = [contact objectForKey: @"c_email"]; + if ([email length]) + { + responseString = [NSString stringWithFormat: @"%@:%@:%@", + [contact objectForKey: @"c_uid"], + [contact objectForKey: @"cn"], + email]; // [response setHeader: @"text/plain; charset=iso-8859-1" // forKey: @"Content-Type"]; - [response appendContentString: responseString]; + [response appendContentString: responseString]; + } // contact = [contacts nextObject]; } } @@ -187,50 +173,55 @@ return result; } -- (NSArray *) _gcsFoldersFromFolder: (SOGoContactFolders *) contactFolders +- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder { - NSMutableArray *gcsFolders; - NSEnumerator *contactSubfolders; - SOGoContactGCSFolder *currentContactFolder; - NSString *folderName, *displayName; + NSMutableArray *folders; + NSEnumerator *subfolders; + SOGoFolder *currentFolder; + NSString *folderName; NSMutableDictionary *currentDictionary; + SoSecurityManager *securityManager; - gcsFolders = [NSMutableArray new]; - [gcsFolders autorelease]; + securityManager = [SoSecurityManager sharedSecurityManager]; + +// return (([securityManager validatePermission: SoPerm_AccessContentsInformation +// onObject: contactFolder +// inContext: context] == nil) - contactSubfolders = [[contactFolders contactFolders] objectEnumerator]; - currentContactFolder = [contactSubfolders nextObject]; - while (currentContactFolder) + folders = [NSMutableArray new]; + [folders autorelease]; + + subfolders = [[parentFolder subFolders] objectEnumerator]; + currentFolder = [subfolders nextObject]; + while (currentFolder) { - if ([currentContactFolder - isKindOfClass: [SOGoContactGCSFolder class]]) + if (![securityManager validatePermission: SOGoPerm_AccessObject + onObject: currentFolder inContext: context]) { - displayName = [[currentContactFolder ocsFolder] folderName]; - if (displayName) - { - folderName = [NSString stringWithFormat: @"/Contacts/%@", - [currentContactFolder nameInContainer]]; - currentDictionary - = [NSMutableDictionary dictionaryWithCapacity: 3]; - [currentDictionary setObject: displayName forKey: @"displayName"]; - [currentDictionary setObject: folderName forKey: @"name"]; - [currentDictionary setObject: @"contact" forKey: @"type"]; - [gcsFolders addObject: currentDictionary]; - } + folderName = [NSString stringWithFormat: @"/%@/%@", + [parentFolder nameInContainer], + [currentFolder nameInContainer]]; + currentDictionary + = [NSMutableDictionary dictionaryWithCapacity: 3]; + [currentDictionary setObject: [currentFolder displayName] + forKey: @"displayName"]; + [currentDictionary setObject: folderName forKey: @"name"]; + [currentDictionary setObject: [currentFolder folderType] + forKey: @"type"]; + [folders addObject: currentDictionary]; } - currentContactFolder = [contactSubfolders nextObject]; + currentFolder = [subfolders nextObject]; } - return gcsFolders; + return folders; } - (NSArray *) _foldersForUID: (NSString *) uid ofType: (NSString *) folderType { NSObject *topFolder, *userFolder; - SOGoContactFolders *contactFolders; + SOGoParentFolder *parentFolder; NSMutableArray *folders; - NSMutableDictionary *currentDictionary; folders = [NSMutableArray new]; [folders autorelease]; @@ -239,23 +230,19 @@ userFolder = [topFolder lookupName: uid inContext: context acquire: NO]; /* FIXME: should be moved in the SOGo* classes. Maybe by having a SOGoFolderManager. */ -#warning this might need adjustments whenever we permit multiple calendar folders per-user if ([folderType length] == 0 || [folderType isEqualToString: @"calendar"]) { - currentDictionary = [NSMutableDictionary new]; - [currentDictionary autorelease]; - [currentDictionary setObject: [self labelForKey: @"Calendar"] - forKey: @"displayName"]; - [currentDictionary setObject: @"/Calendar" forKey: @"name"]; - [currentDictionary setObject: @"calendar" forKey: @"type"]; - [folders addObject: currentDictionary]; + parentFolder = [userFolder lookupName: @"Calendar" + inContext: context acquire: NO]; + [folders + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; } if ([folderType length] == 0 || [folderType isEqualToString: @"contact"]) { - contactFolders = [userFolder lookupName: @"Contacts" - inContext: context acquire: NO]; + parentFolder = [userFolder lookupName: @"Contacts" + inContext: context acquire: NO]; [folders - addObjectsFromArray: [self _gcsFoldersFromFolder: contactFolders]]; + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; } return folders; @@ -346,31 +333,30 @@ return result; } -- (SOGoContactGCSFolder *) contactFolderForUID: (NSString *) uid -{ - SOGoFolder *upperContainer; - SOGoUserFolder *userFolder; - SOGoContactFolders *contactFolders; - SOGoContactGCSFolder *contactFolder; - SoSecurityManager *securityManager; +// - (SOGoContactGCSFolder *) contactFolderForUID: (NSString *) uid +// { +// SOGoFolder *upperContainer; +// SOGoUserFolder *userFolder; +// SOGoContactFolders *contactFolders; +// SOGoContactGCSFolder *contactFolder; +// SoSecurityManager *securityManager; - upperContainer = [[[self clientObject] container] container]; - userFolder = [SOGoUserFolder objectWithName: uid - inContainer: upperContainer]; - contactFolders = [SOGoContactFolders objectWithName: @"Contacts" - inContainer: userFolder]; - contactFolder = [SOGoContactGCSFolder objectWithName: @"personal" - inContainer: contactFolders]; - [contactFolder - setOCSPath: [NSString stringWithFormat: @"/Users/%@/Contacts/personal", uid]]; - [contactFolder setOwner: uid]; +// upperContainer = [[[self clientObject] container] container]; +// userFolder = [SOGoUserFolder objectWithName: uid +// inContainer: upperContainer]; +// contactFolders = [SOGoUserFolder lookupName: @"Contacts" +// inContext: context +// acquire: NO]; +// contactFolder = [contactFolders lookupName: @"personal" +// inContext: context +// acquire: NO]; - securityManager = [SoSecurityManager sharedSecurityManager]; +// securityManager = [SoSecurityManager sharedSecurityManager]; - return (([securityManager validatePermission: SoPerm_AccessContentsInformation - onObject: contactFolder - inContext: context] == nil) - ? contactFolder : nil); -} +// return (([securityManager validatePermission: SoPerm_AccessContentsInformation +// onObject: contactFolder +// inContext: context] == nil) +// ? contactFolder : nil); +// } @end diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 01a07de90..dc789fa7b 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -282,7 +282,7 @@ { url = [[elements objectAtIndex: 0] value: 0]; data = [NSString stringWithFormat: - @"%@", + @"%@", url, url]; } else diff --git a/UI/Contacts/UIxContactsListView.m b/UI/Contacts/UIxContactsListView.m index 45dadfa62..e6b2e2ad1 100644 --- a/UI/Contacts/UIxContactsListView.m +++ b/UI/Contacts/UIxContactsListView.m @@ -81,25 +81,6 @@ return selectorComponentClass; } -- (id ) deleteAction -{ - id result; - NSException *ex; - WOResponse *response; - - ex = [[self clientObject] delete]; - if (ex) - result = ex; - else - { - response = [context response]; - [response setStatus: 200]; - result = response; - } - - return result; -} - - (NSString *) defaultSortKey { return @"displayName"; diff --git a/UI/Contacts/UIxContactsListViewContainer.h b/UI/Contacts/UIxContactsListViewContainer.h index 5be9899fd..eb0d72d87 100644 --- a/UI/Contacts/UIxContactsListViewContainer.h +++ b/UI/Contacts/UIxContactsListViewContainer.h @@ -31,28 +31,18 @@ @interface UIxContactsListViewContainer : UIxComponent { - NSString *foldersPrefix; NSString *selectorComponentClass; - NSString *currentAdditionalFolder; - NSDictionary *additionalFolders; id currentFolder; } - (void) setCurrentFolder: (id) folder; -- (NSString *) foldersPrefix; - - (NSArray *) contactFolders; - (NSString *) currentContactFolderId; +- (NSString *) currentContactFolderOwner; - (NSString *) currentContactFolderName; -- (NSArray *) additionalFolders; - -- (void) setCurrentAdditionalFolder: (NSString *) newCurrentAdditionalFolder; -- (NSString *) currentAdditionalFolder; -- (NSString *) currentAdditionalFolderName; - @end #endif /* UIXCONTACTSLISTVIEWCONTAINERBASE_H */ diff --git a/UI/Contacts/UIxContactsListViewContainer.m b/UI/Contacts/UIxContactsListViewContainer.m index 2bf4f0c09..f472b1560 100644 --- a/UI/Contacts/UIxContactsListViewContainer.m +++ b/UI/Contacts/UIxContactsListViewContainer.m @@ -29,6 +29,7 @@ #import #import +#import #import "UIxContactsListViewContainer.h" @@ -40,20 +41,12 @@ { if ((self = [super init])) { - foldersPrefix = nil; selectorComponentClass = nil; - additionalFolders = nil; } return self; } -- (void) dealloc -{ - [additionalFolders release]; - [super dealloc]; -} - - (void) setSelectorComponentClass: (NSString *) aComponentClass { selectorComponentClass = aComponentClass; @@ -85,43 +78,18 @@ currentFolder = folder; } -- (NSString *) foldersPrefix -{ - NSMutableArray *folders; - SOGoObject *currentObject; - - if (!foldersPrefix) - { - folders = [NSMutableArray new]; - [folders autorelease]; - - currentObject = [[self clientObject] container]; - while (![currentObject isKindOfClass: [SOGoContactFolders class]]) - { - [folders insertObject: [currentObject nameInContainer] atIndex: 0]; - currentObject = [currentObject container]; - } - - foldersPrefix = [folders componentsJoinedByString: @"/"]; - [foldersPrefix retain]; - } - - return foldersPrefix; -} - - (NSArray *) contactFolders { SOGoContactFolders *folderContainer; folderContainer = [[self clientObject] container]; - return [folderContainer contactFolders]; + return [folderContainer subFolders]; } - (NSString *) currentContactFolderId { - return [NSString stringWithFormat: @"%@/%@", - [self foldersPrefix], + return [NSString stringWithFormat: @"/%@", [currentFolder nameInContainer]]; } @@ -130,35 +98,9 @@ return [currentFolder displayName]; } -- (NSArray *) additionalFolders +- (NSString *) currentContactFolderOwner { - NSUserDefaults *ud; - - if (!additionalFolders) - { - ud = [[context activeUser] userSettings]; - additionalFolders - = [[ud objectForKey: @"Contacts"] objectForKey: @"SubscribedFolders"]; - [additionalFolders retain]; - } - - return [additionalFolders allKeys]; -} - -- (void) setCurrentAdditionalFolder: (NSString *) newCurrentAdditionalFolder -{ - currentAdditionalFolder = newCurrentAdditionalFolder; -} - -- (NSString *) currentAdditionalFolder -{ - return currentAdditionalFolder; -} - -- (NSString *) currentAdditionalFolderName -{ - return [[additionalFolders objectForKey: currentAdditionalFolder] - objectForKey: @"displayName"]; + return [currentFolder ownerInContext: context]; } - (BOOL) hasContactSelectionButtons diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index cff8e800d..3602be7ae 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -17,11 +17,6 @@ pageName = "UIxContactFoldersView"; actionName = "new"; }; - newAb = { - protectedBy = "View"; - pageName = "UIxContactFoldersView"; - actionName = "newAb"; - }; mailer-contacts = { protectedBy = "View"; pageName = "UIxContactFoldersView"; @@ -87,11 +82,6 @@ pageName = "UIxContactsListView"; actionName = "mailerContacts"; }; - delete = { - protectedBy = "SaveAcls"; /* a hack to force "owner" */ - pageName = "UIxContactsListView"; - actionName = "delete"; - }; userRights = { protectedBy = "ReadAcls"; pageName = "UIxContactsUserRightsEditor"; diff --git a/UI/MailPartViewers/GNUmakefile b/UI/MailPartViewers/GNUmakefile index ae6a49d24..dcfb3665e 100644 --- a/UI/MailPartViewers/GNUmakefile +++ b/UI/MailPartViewers/GNUmakefile @@ -40,6 +40,9 @@ MailPartViewers_LOCALIZED_RESOURCE_FILES += \ # make +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.h b/UI/MailPartViewers/UIxMailPartHTMLViewer.h index edc0143c3..415152ded 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.h +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.h @@ -26,6 +26,9 @@ #import "UIxMailPartViewer.h" @interface UIxMailPartHTMLViewer : UIxMailPartViewer +{ + id handler; +} - (NSString *) flatContentAsString; diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 2ea840ebf..4dbda7c49 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -72,12 +72,9 @@ - (void) dealloc { - if (crumb) - [crumb release]; - if (result) - [result release]; - if (css) - [css release]; + [crumb release]; + [result release]; + [css release]; [super dealloc]; } @@ -88,12 +85,12 @@ - (NSString *) css { - return [[css copy] autorelease]; + return css; } - (NSString *) result { - return [[result copy] autorelease]; + return result; } /* SaxContentHandler */ @@ -101,16 +98,14 @@ { showWhoWeAre(); - if (crumb) - [crumb release]; - if (result) - [result release]; - if (css) - [css release]; + [crumb release]; + [css release]; + [result release]; result = [NSMutableString new]; css = [NSMutableString new]; crumb = [NSMutableArray new]; + inBody = NO; inStyle = NO; inScript = NO; @@ -245,6 +240,15 @@ } } +- (void) _finishCSS +{ + [css replaceString: @".SOGoHTMLMail-CSS-Delimiter body" + withString: @".SOGoHTMLMail-CSS-Delimiter"]; + [css replaceString: @";" withString: @" !important;"]; + [css replaceString: @"" withString: @""]; +} + - (void) endElement: (NSString *) _localName namespace: (NSString *) _ns rawName: (NSString *) _rawName @@ -264,7 +268,11 @@ else if (inBody) { if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame) - inBody = NO; + { + inBody = NO; + if (css) + [self _finishCSS]; + } else [result appendFormat: @"", _localName]; } @@ -280,7 +288,7 @@ { if (inStyle) [self _appendStyle: _chars length: _len]; - if (inBody) + else if (inBody) { tmpString = [NSString stringWithCharacters: _chars length: _len]; [result appendString: [tmpString stringByEscapingHTMLString]]; @@ -386,6 +394,22 @@ @implementation UIxMailPartHTMLViewer +- (id) init +{ + if ((self = [super init])) + { + handler = nil; + } + + return self; +} + +- (void) dealloc +{ + [handler release]; + [super dealloc]; +} + - (void) _convertReferencesForPart: (NSDictionary *) part withCount: (unsigned int) count andBaseURL: (NSString *) url @@ -439,16 +463,11 @@ return attachmentIds; } -- (NSString *) flatContentAsString +- (void) _parseContent { id parser; - _UIxHTMLMailContentHandler *handler; - NSString *css; - NSMutableString *content; NSData *preparsedContent; - content = [NSMutableString string]; - preparsedContent = [super decodedFlatContent]; parser = [[SaxXMLReaderFactory standardXMLReaderFactory] createXMLReaderForMimeType: @"text/html"]; @@ -457,14 +476,31 @@ [handler setAttachmentIds: [self _attachmentIds]]; [parser setContentHandler: handler]; [parser parseFromSource: preparsedContent]; +} + +- (NSString *) cssContent +{ + NSString *cssContent, *css; + + if (!handler) + [self _parseContent]; css = [handler css]; if ([css length]) - [content appendFormat: @"", css]; - [content appendString: [handler result]]; - [handler release]; + cssContent = [NSString stringWithFormat: @"", + [handler css]]; + else + cssContent = @""; - return content; + return cssContent; +} + +- (NSString *) flatContentAsString +{ + if (!handler) + [self _parseContent]; + + return [handler result]; } @end diff --git a/UI/MailPartViewers/UIxMailPartICalViewer.m b/UI/MailPartViewers/UIxMailPartICalViewer.m index 6e7a690c8..8b31e5325 100644 --- a/UI/MailPartViewers/UIxMailPartICalViewer.m +++ b/UI/MailPartViewers/UIxMailPartICalViewer.m @@ -195,7 +195,8 @@ /* calendar folder support */ -- (id)calendarFolder { +- (id) calendarFolder +{ /* return scheduling calendar of currently logged-in user */ SOGoUser *user; id folder; @@ -205,7 +206,7 @@ inContext: context acquire: NO]; - return folder; + return [folder lookupName: @"personal" inContext: context acquire: NO]; } - (id)storedEventObject { diff --git a/UI/MailPartViewers/UIxMailPartTextViewer.m b/UI/MailPartViewers/UIxMailPartTextViewer.m index 450c14217..22d41a386 100644 --- a/UI/MailPartViewers/UIxMailPartTextViewer.m +++ b/UI/MailPartViewers/UIxMailPartTextViewer.m @@ -44,8 +44,6 @@ content = [NSMutableString string]; superContent = [[super flatContentAsString] stringByEscapingHTMLString]; [content appendString: [superContent stringByDetectingURLs]]; - [content replaceString: @"\r\n" withString: @"
"]; - [content replaceString: @"\n" withString: @"
"]; return content; } diff --git a/UI/MailPartViewers/UIxMailPartViewer.m b/UI/MailPartViewers/UIxMailPartViewer.m index 36c2a1ad7..ab4ace1c3 100644 --- a/UI/MailPartViewers/UIxMailPartViewer.m +++ b/UI/MailPartViewers/UIxMailPartViewer.m @@ -164,7 +164,8 @@ return 0; } -- (NSString *)flatContentAsString { +- (NSString *) flatContentAsString +{ /* Note: we even have the line count in the body-info! */ NSString *charset; NSString *s; @@ -175,18 +176,27 @@ { charset = [[bodyInfo objectForKey:@"parameterList"] objectForKey: @"charset"]; - - // TODO: properly decode charset, might need to handle encoding? - - if ([charset length] > 0) - s = [NSString stringWithData: content - usingEncodingNamed: [charset lowercaseString]]; - else + charset = [charset lowercaseString]; + if (![charset length] + || [charset isEqualToString: @"us-ascii"]) { s = [[NSString alloc] initWithData: content - encoding: NSUTF8StringEncoding]; + encoding: NSISOLatin1StringEncoding]; [s autorelease]; } + else + { + s = [NSString stringWithData: content + usingEncodingNamed: charset]; + if (![s length]) + { + /* latin 1 is used as a 8bit fallback charset... but does this + encoding accept any byte from 0 to 255? */ + s = [[NSString alloc] initWithData: content + encoding: NSISOLatin1StringEncoding]; + [s autorelease]; + } + } if (!s) { @@ -302,7 +312,7 @@ - (NSString *) pathToAttachmentObject { /* this points to the SoObject representing the part, no modifications */ - NSString *url, *n, *pext; + NSString *url, *n; /* path to mail controller object */ diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index 01d4440b4..cf14bf58d 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -19,11 +19,28 @@ "Attach" = "Attach"; "Save" = "Save"; +/* Tooltips */ + +"Send this message now" = "Send this message now"; +"Select a recipient from an Address Book" = "Select a recipient from an Address Book"; +"Include an attachment" = "Include an attachment"; +"Save this message" = "Save this message"; +"Get new messages" = "Get new messages"; +"Create a new message" = "Create a new message"; +"Go to address book" = "Go to address book"; +"Reply to the message" = "Reply to the message"; +"Reply to sender and all recipients" = "Reply to sender and all recipients"; +"Forward selected message" = "Forward selected message"; +"Delete selected message or folder" = "Delete selected message or folder"; +"Mark the selected messages as junk" = "Mark the selected messages as junk"; +"Print this message" = "Print this message"; +"Stop the current transfer" = "Stop the current transfer"; + /* Main Frame */ "Home" = "Home"; "Calendar" = "Calendar"; -"Addressbook" = "Addressbook"; +"Addressbook" = "Address Book"; "Mail" = "Mail"; "Right Administration" = "Right Administration"; @@ -71,7 +88,7 @@ "cc" = "Cc"; "bcc" = "Bcc"; -"Addressbook" = "Addressbook"; +"Addressbook" = "Address Book"; "Anais" = "Anais"; "Edit Draft..." = "Edit Draft..."; @@ -122,9 +139,66 @@ "MoveTo" = "Move …"; -"error_missingsubject" = "Missing Subject"; -"error_missingrecipients" = "Missing Recipients"; -"error_validationfailed" = "Validation failed"; +/* Address Popup menu */ +"Add to Address Book..." = "Add to Address Book..."; +"Compose Mail To" = "Compose Mail To"; +"Create Filter From Message..." = "Create Filter From Message..."; + +/* Mailbox popup menus */ +"Open in New Mail Window" = "Open in New Mail Window"; +"Copy Folder Location" = "Copy Folder Location"; +"Subscribe..." = "Subscribe..."; +"Mark Folder Read..." = "Mark Folder Read..."; +"New Folder..." = "New Folder..."; +"Compact This Folder" = "Compact This Folder"; +"Search Messages..." = "Search Messages..."; +"Sharing..." = "Sharing..."; +"New Subfolder..." = "New Subfolder..."; +"Rename Folder..." = "Rename Folder..."; +"Delete Folder" = "Delete Folder"; +"Use This Folder For" = "Use This Folder For"; +"Get Messages for Account" = "Get Messages for Account"; + +/* Use This Folder menu */ +"Sent Messages" = "Sent Messages"; +"Drafts" = "Drafts"; +"Deleted Messages" = "Deleted Messages"; + +/* Message list popup menu */ +"Open Message In New Window" = "Open Message In New Window"; +"Reply to Sender Only" = "Reply to Sender Only"; +"Reply to All" = "Reply to All"; +"Forward" = "Forward"; +"Edit As New..." = "Edit As New..."; +"Move To" = "Move To"; +"Copy To" = "Copy To"; +"Label" = "Label"; +"Mark" = "Mark"; +"Save As..." = "Save As..."; +"Print Preview" = "Print Preview"; +"View Message Source" = "View Message Source"; +"Print..." = "Print..."; +"Delete Message" = "Delete Message"; + +"This Folder" = "This Folder"; + +/* Label popup menu */ +"None" = "None"; +"Important" = "Important"; +"Work" = "Work"; +"Personal" = "Personal"; +"To Do" = "To Do"; +"Later" = "Later"; + +/* Mark popup menu */ +"As Read" = "As Read"; +"Thread As Read" = "Thread As Read"; +"As Read By Date..." = "As Read By Date..."; +"All Read" = "All Read"; +"Flag" = "Flag"; +"As Junk" = "As Junk"; +"As Not Junk" = "As Not Junk"; +"Run Junk Mail Controls" = "Run Junk Mail Controls"; /* Folder operations */ "Name :" = "Name :"; @@ -136,5 +210,14 @@ "quotasFormat" = "Quotas: %{0} used on %{1} Kb; %{2}%"; +"Please select a message." = "Please select a message."; "Please select a message to print." = "Please select a message to print."; "Please select only one message to print." = "Please select only one message to print."; + +"You need to choose a non-virtual folder!" = "You need to choose a non-virtual folder!"; +"You need to choose a root subfolder!" = "You need to choose a root subfolder!"; + +"Moving a message into its own folder is impossible!" += "Moving a message into its own folder is impossible!"; +"Copying a message into its own folder is impossible!" += "Copying a message into its own folder is impossible!"; diff --git a/UI/MailerUI/French.lproj/Localizable.strings b/UI/MailerUI/French.lproj/Localizable.strings index 20f782224..b68ab6bb3 100644 --- a/UI/MailerUI/French.lproj/Localizable.strings +++ b/UI/MailerUI/French.lproj/Localizable.strings @@ -19,6 +19,23 @@ "Attach" = "Joindre"; "Save" = "Enregistrer"; +/* Tooltips */ + +"Send this message now" = "Envoyer le message maintenant"; +"Select a recipient from an Address Book" = "Sélectionner un destinataire du carnet d'adresses"; +"Include an attachment" = "Inclure une pièce jointe"; +"Save this message" = "Enregistrer ce message"; +"Get new messages" = "Relever les nouveaux messages"; +"Create a new message" = "Créer un nouveau message"; +"Go to address book" = "Ouvrir le carnet d'adresses"; +"Reply to the message" = "Répondre au message"; +"Reply to sender and all recipients" = "Répondre à l'expéditeur et à tous les destinataires"; +"Forward selected message" = "Transférer le message sélectionné"; +"Delete selected message or folder" = "Supprimer le message ou le dossier sélectionné"; +"Mark the selected messages as junk" = "Marquer les messages sélectionnés comme indésirables"; +"Print this message" = "Imprimer ce message"; +"Stop the current transfer" = "Arrêter le transfert courant"; + /* Main Frame */ "Home" = "Accueil"; @@ -140,8 +157,14 @@ "New Subfolder..." = "Nouveau sous-dossier..."; "Rename Folder..." = "Renommer le dossier..."; "Delete Folder" = "Supprimer le dossier..."; +"Use This Folder For" = "Utiliser ce dossier pour"; "Get Messages for Account" = "Relever les messages de ce compte"; +/* Use This Folder menu */ +"Sent Messages" = "les message envoyés"; +"Drafts" = "les brouillons"; +"Deleted Messages" = "les message effacés"; + /* Message list popup menu */ "Open Message In New Window" = "Ouvrir dans une nouvelle fenétre"; "Reply to Sender Only" = "Répondre à l'expéditeur"; @@ -158,6 +181,8 @@ "Print..." = "Imprimer..."; "Delete Message" = "Supprimer le message"; +"This Folder" = "Ce dossier-ci"; + /* Label popup menu */ "None" = "Aucune"; "Important" = "Important"; @@ -186,5 +211,14 @@ "quotasFormat" = "Quotas: %{0} Ko utilisés sur %{1}; %{2}%"; +"Please select a message." = "Veuillez sélectionner un message."; "Please select a message to print." = "Veuillez sélectionner un message à imprimer."; "Please select only one message to print." = "Veuillez ne sélectionner qu'un seul message à imprimer."; + +"You need to choose a non-virtual folder!" = "Vous devez choisir un dossier non-virtuel."; +"You need to choose a root subfolder!" = "Vous devez choisir un sous-dossier de la racine."; + +"Moving a message into its own folder is impossible!" += "Le déplacement d'un message dans son propre dossier est impossible."; +"Copying a message into its own folder is impossible!" += "La copie d'un message dans son propre dossier est impossible."; diff --git a/UI/MailerUI/GNUmakefile b/UI/MailerUI/GNUmakefile index 7a7c36b49..ba2c9579f 100644 --- a/UI/MailerUI/GNUmakefile +++ b/UI/MailerUI/GNUmakefile @@ -50,6 +50,9 @@ MailerUI_LOCALIZED_RESOURCE_FILES += \ # make +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/MailerUI/German.lproj/Localizable.strings b/UI/MailerUI/German.lproj/Localizable.strings index 1bd77f2f3..31b91fe6b 100644 --- a/UI/MailerUI/German.lproj/Localizable.strings +++ b/UI/MailerUI/German.lproj/Localizable.strings @@ -14,10 +14,10 @@ "Stop" = "Stopp"; "Write" = "Verfassen"; -"Send" = "Senden" -"Contacts" = "Kontakte" +"Send" = "Senden"; +"Contacts" = "Kontakte"; "Attach" = "Anhang"; -"Save" = "Speichern" +"Save" = "Speichern"; /* Main Frame */ @@ -35,27 +35,27 @@ "Read messages" = "Lire les messages"; "Write a new message" = "Eine neue Nachricht schreiben"; -"Share: " = "Partage : "; +"Share: " = "Benutzerrechte: "; "Account: " = "Konto: "; "Shared Account: " = "Compte Partagé "; /* acls */ -"Default Roles" = "Rôles par défaut"; -"User rights for:" = "Autorisations pour:"; +"Default Roles" = "Standard-Rollen"; +"User rights for:" = "Benutzerrechte für:"; -"List and see this folder" = "Lister et voir ce dossier"; -"Read mails from this folder" = "Lire les messages de ce dossier"; -"Mark mails read and unread" = "Marquer les messages comme lus ou non-lus"; -"Modify the flags of the mails in this folder" = "Modifier les indicateurs sur les messages"; -"Insert, copy and move mails into this folder" = "Insérer, copier et déplacer des messages dans ce dossier"; -"Post mails" = "Poster des messages"; -"Add subfolders to this folder" = "Ajouter des sous-dossiers à ce dossier"; -"Remove this folder" = "Effacer ce dossier"; -"Erase mails from this folder" = "Effacer des messages de ce dossier"; -"Expunge this folder" = "Compacter ce dossier"; -"Modify the acl of this folder" = "Administrer les droits sur ce dossier"; +"List and see this folder" = "Diesen Ordner sehen"; +"Read mails from this folder" = "E-Mails in diesem Ordner lesen"; +"Mark mails read and unread" = "E-Mails in diesem Ordner als (un)gelesen markieren"; +"Modify the flags of the mails in this folder" = "Schlagwörter in diesem Ordner verändern"; +"Insert, copy and move mails into this folder" = "E-Mails in diesem Ordner hinzufügen, kopieren und verschieben"; +"Post mails" = "E-Mails versenden"; +"Add subfolders to this folder" = "Neue Unterordner anlegen"; +"Remove this folder" = "Diesen Ordner löschen"; +"Erase mails from this folder" = "E-Mails in diesem Ordner löschen"; +"Expunge this folder" = "Diesen Ordner komprimieren"; +"Modify the acl of this folder" = "Benutzerrechte dieses Ordners verändern"; -"Update" = "Mettre à jour"; +"Update" = "Speichern"; "Cancel" = "Abbrechen"; /* Mail edition */ @@ -136,12 +136,18 @@ "New Folder..." = "Neuer Ordner..."; "Compact This Folder" = "Komprimieren"; "Search Messages..." = "Suchen..."; -"Sharing..." = "Partage..."; +"Sharing..." = "Benutzerrechte..."; "New Subfolder..." = "Neuer Unterordner..."; "Rename Folder..." = "Umbenennen..."; "Delete Folder" = "Löschen"; +"Use This Folder For" = "Use This Folder For"; "Get Messages for Account" = "Neue Nachrichten empfangen"; +/* Use This Folder menu */ +"Sent Messages" = "Sent Messages"; +"Drafts" = "Drafts"; +"Deleted Messages" = "Deleted Messages"; + /* Message list popup menu */ "Open Message In New Window" = "In neuem Fenster õffnen"; "Reply to Sender Only" = "Antworten nur an Absender"; @@ -158,6 +164,8 @@ "Print..." = "Drucken..."; "Delete Message" = "Lõschen"; +"This Folder" = "This Folder"; + /* Label popup menu */ "None" = "Aucune"; "Important" = "Important"; @@ -186,5 +194,14 @@ "quotasFormat" = "Quota: %{0} von %{1} KB verwendet; %{2}%"; +"Please select a message." = "Sie müssen eine Nachricht auswählen."; "Please select a message to print." = "Sie müssen eine Nachricht zum Drucken auswählen."; "Please select only one message to print." = "Bitte wählen Sie nur eine Nachricht zum Drucken aus."; + +"You need to choose a non-virtual folder!" = "You need to choose a non-virtual folder!"; +"You need to choose a root subfolder!" = "You need to choose a root subfolder!"; + +"Moving a message into its own folder is impossible!" += "Moving a message into its own folder is impossible!"; +"Copying a message into its own folder is impossible!" += "Copying a message into its own folder is impossible!"; diff --git a/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar b/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar index 6355b0a0e..c78528b91 100644 --- a/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar +++ b/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar @@ -5,23 +5,27 @@ onclick = "return clickedEditorSend(this);"; image = "tb-compose-send-flat-24x24.png"; cssClass = "tbicon_send"; - label = "Send"; }, + label = "Send"; + tooltip = "Send this message now"; }, { link = "#"; onclick = "return onContactAdd(null);"; image = "tb-compose-contacts-flat-24x24.png"; cssClass = "tbicon_addressbook"; - label = "Contacts"; }, + label = "Contacts"; + tooltip = "Select a recipient from an Address Book"; }, { link = "#"; isSafe = NO; onclick = "return clickedEditorAttach(this)"; image = "tb-compose-attach-flat-24x24.png"; cssClass = "tbicon_attach"; - label = "Attach"; }, + label = "Attach"; + tooltip = "Include an attachment"; }, { link = "#"; isSafe = NO; onclick = "return clickedEditorSave(this);"; image = "tb-mail-file-flat-24x24.png"; cssClass = "tbicon_save"; - label = "Save"; }, + label = "Save"; + tooltip = "Save this message"; }, ) ) diff --git a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar index ac4ad5a82..a2ba2dbef 100644 --- a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar +++ b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar @@ -6,16 +6,21 @@ cssClass = "tbicon_getmail"; label = "Get Mail"; onclick = "return refreshMailbox(this);"; - }, + tooltip = "Get new messages"; }, { link = "#"; isSafe = NO; image = "tb-mail-write-flat-24x24.png"; onclick = "return onComposeMessage();"; - cssClass = "tbicon_compose"; label = "Write"; }, - { link = "#"; target = "addressbook"; + cssClass = "tbicon_compose"; + label = "Write"; + tooltip = "Create a new message"; }, + { link = "#"; + target = "addressbook"; onclick = "openAddressbook(this);return false;"; image = "tb-mail-addressbook-flat-24x24.png"; - cssClass = "tbicon_addressbook"; label = "Addressbook"; }, + cssClass = "tbicon_addressbook"; + label = "Addressbook"; + tooltip = "Go to address book"; }, ), ( // second group @@ -23,19 +28,23 @@ onclick = "return openMessageWindowsForSelection('reply');"; isSafe = NO; image = "tb-mail-reply-flat-24x24.png"; - cssClass = "tbicon_reply"; label = "Reply"; }, - + cssClass = "tbicon_reply"; + label = "Reply"; + tooltip = "Reply to the message"; }, { link = "replyall"; onclick = "return openMessageWindowsForSelection('replyall');"; isSafe = NO; image = "tb-mail-replyall-flat-24x24.png"; - cssClass = "tbicon_replyall"; label = "Reply All"; }, - + cssClass = "tbicon_replyall"; + label = "Reply All"; + tooltip = "Reply to sender and all recipients"; }, { link = "forward"; onclick = "return openMessageWindowsForSelection('forward');"; isSafe = NO; image = "tb-mail-forward-flat-24x24.png"; - cssClass = "tbicon_forward"; label = "Forward"; }, + cssClass = "tbicon_forward"; + label = "Forward"; + tooltip = "Forward selected message"; }, ), ( // third group @@ -44,22 +53,27 @@ onclick = "onMenuDeleteMessage(event);"; // enabled = showMarkDeletedButton; image = "tb-mail-delete-flat-24x24.png"; - cssClass = "tbicon_delete"; label = "Delete"; }, + cssClass = "tbicon_delete"; + label = "Delete"; + tooltip = "Delete selected message or folder"; }, { link = "#"; isSafe = NO; image = "tb-mail-junk-flat-24x24.png"; - cssClass = "tbicon_junk"; label = "Junk"; - }, + cssClass = "tbicon_junk"; + label = "Junk"; + tooltip = "Mark the selected messages as junk"; }, ), ( { link = "#"; onclick = "return onPrintCurrentMessage(event);"; cssClass = "tbicon_print"; image = "tb-mail-print-flat-24x24.png"; - label = "Print"; }, + label = "Print"; + tooltip = "Print this message"; }, { link = "#"; image = "tb-mail-stop-flat-24x24.png"; cssClass = "tbicon_stop"; - label = "Stop"; }, + label = "Stop"; + tooltip = "Stop the current transfer"; }, ), ) diff --git a/UI/MailerUI/UIxMailAccountActions.m b/UI/MailerUI/UIxMailAccountActions.m index 004c3a634..7f3a66cb3 100644 --- a/UI/MailerUI/UIxMailAccountActions.m +++ b/UI/MailerUI/UIxMailAccountActions.m @@ -108,8 +108,7 @@ rawFolders = [co allFolderPaths]; folders = [self _jsonFolders: [rawFolders objectEnumerator]]; - response = [context response]; - [response setStatus: 200]; + response = [self responseWithStatus: 200]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"content-type"]; [response appendContentString: [folders jsonRepresentation]]; diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index b8ca9cf9f..7b6d9e13e 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -25,6 +25,8 @@ #import #import #import +#import + #import #import #import @@ -36,27 +38,6 @@ @implementation UIxMailActions -- (WOResponse *) editAction -{ - SOGoMailAccount *account; - SOGoMailObject *co; - SOGoDraftsFolder *folder; - SOGoDraftObject *newMail; - NSString *newLocation; - - co = [self clientObject]; - account = [co mailAccountFolder]; - folder = [account draftsFolderInContext: context]; - newMail = [folder newDraft]; - [newMail fetchMailForEditing: co]; - [newMail storeInfo]; - - newLocation = [NSString stringWithFormat: @"%@/edit", - [newMail baseURLInContext: context]]; - - return [self redirectToLocation: newLocation]; -} - - (WOResponse *) replyToAll: (BOOL) toAll { SOGoMailAccount *account; @@ -107,7 +88,103 @@ return [self redirectToLocation: newLocation]; } +- (id) trashAction +{ + id response; + + response = [[self clientObject] trashInContext: context]; + if (!response) + response = [self responseWith204]; + + return response; +} + +- (id) moveAction +{ + NSString *destinationFolder; + id response; + + destinationFolder = [[context request] formValueForKey: @"folder"]; + if ([destinationFolder length] > 0) + { + response = [[self clientObject] moveToFolderNamed: destinationFolder + inContext: context]; + if (!response) + response = [self responseWith204]; + } + else + response = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"No destination folder given"]; + + return response; +} + +- (id) copyAction +{ + NSString *destinationFolder; + id response; + + destinationFolder = [[context request] formValueForKey: @"folder"]; + if ([destinationFolder length] > 0) + { + response = [[self clientObject] copyToFolderNamed: destinationFolder + inContext: context]; + if (!response) + response = [self responseWith204]; + } + else + response = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"No destination folder given"]; + + return response; +} + +/* active message */ + +- (id) markMessageUnreadAction +{ + id response; + + response = [[self clientObject] removeFlags: @"seen"]; + if (!response) + response = [self responseWith204]; + + return response; +} + +- (id) markMessageReadAction +{ + id response; + + response = [[self clientObject] addFlags: @"seen"]; + if (!response) + response = [self responseWith204]; + + return response; +} + /* SOGoDraftObject */ +- (WOResponse *) editAction +{ + SOGoMailAccount *account; + SOGoMailObject *co; + SOGoDraftsFolder *folder; + SOGoDraftObject *newMail; + NSString *newLocation; + + co = [self clientObject]; + account = [co mailAccountFolder]; + folder = [account draftsFolderInContext: context]; + newMail = [folder newDraft]; + [newMail fetchMailForEditing: co]; + [newMail storeInfo]; + + newLocation = [NSString stringWithFormat: @"%@/edit", + [newMail baseURLInContext: context]]; + + return [self redirectToLocation: newLocation]; +} + - (id) deleteAction { SOGoDraftObject *draft; @@ -119,10 +196,7 @@ if (error) response = error; else - { - response = [context response]; - [response setStatus: 204]; - } + response = [self responseWith204]; return response; } @@ -132,17 +206,15 @@ WOResponse *response; NSString *filename; - response = [context response]; - filename = [[context request] formValueForKey: @"filename"]; if ([filename length] > 0) { + response = [self responseWith204]; [[self clientObject] deleteAttachmentWithName: filename]; - [response setStatus: 204]; } else { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"How did you end up here?"]; } diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index a221609e1..a2eb9ee00 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -73,7 +73,6 @@ @implementation UIxMailEditor -static BOOL keepMailTmpFile = NO; static BOOL showInternetMarker = NO; static BOOL useLocationBasedSentFolder = NO; static NSDictionary *internetMailHeaders = nil; @@ -85,13 +84,9 @@ static NSArray *infoKeys = nil; infoKeys = [[NSArray alloc] initWithObjects: @"subject", @"to", @"cc", @"bcc", - @"from", @"replyTo", + @"from", @"replyTo", @"inReplyTo", nil]; - keepMailTmpFile = [ud boolForKey:@"SOGoMailEditorKeepTmpFile"]; - if (keepMailTmpFile) - NSLog(@"WARNING: keeping mail files."); - useLocationBasedSentFolder = [ud boolForKey:@"SOGoUseLocationBasedSentFolder"]; @@ -409,10 +404,7 @@ static NSArray *infoKeys = nil; { result = [[self clientObject] save]; if (!result) - { - result = [context response]; - [result setStatus: 204]; - } + result = [self responseWith204]; } else result = [self failedToSaveFormResponse]; @@ -450,7 +442,7 @@ static NSArray *infoKeys = nil; { result = [[self clientObject] sendMail]; if (!result) - result = [self jsCloseWithRefreshMethod: nil]; + result = [self jsCloseWithRefreshMethod: @"refreshFolderByType(\"sent\")"]; } else result = [self failedToSaveFormResponse]; diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index b4a184b84..ade6ed8d2 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -24,8 +24,10 @@ #import #import #import +#import #import +#import #import #import #import @@ -35,6 +37,9 @@ #import #import #import +#import + +#import #import "UIxMailFolderActions.h" @@ -49,7 +54,6 @@ NSString *folderName; co = [self clientObject]; - response = [context response]; folderName = [[context request] formValueForKey: @"name"]; if ([folderName length] > 0) @@ -58,15 +62,15 @@ error = [connection createMailbox: folderName atURL: [co imap4URL]]; if (error) { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to create folder."]; } else - [response setStatus: 204]; + response = [self responseWith204]; } else { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Missing 'name' parameter."]; } @@ -100,7 +104,6 @@ NSURL *srcURL, *destURL; co = [self clientObject]; - response = [context response]; folderName = [[context request] formValueForKey: @"name"]; if ([folderName length] > 0) @@ -112,15 +115,15 @@ toURL: destURL]; if (error) { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to rename folder."]; } else - [response setStatus: 204]; + response = [self responseWith204]; } else { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Missing 'name' parameter."]; } @@ -154,7 +157,6 @@ NSURL *srcURL, *destURL; co = [self clientObject]; - response = [context response]; connection = [co imap4Connection]; srcURL = [co imap4URL]; destURL = [self _trashedURLOfFolder: srcURL @@ -164,15 +166,64 @@ toURL: destURL]; if (error) { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to move folder."]; } else - [response setStatus: 204]; + response = [self responseWith204]; return response; } +- (WOResponse *) _setFolderPurpose: (NSString *) purpose +{ + SOGoMailFolder *co; + WOResponse *response; + NSUserDefaults *ud; + NSMutableDictionary *mailSettings; + + co = [self clientObject]; + if ([NSStringFromClass ([co class]) isEqualToString: @"SOGoMailFolder"]) + { + ud = [[context activeUser] userSettings]; + mailSettings = [ud objectForKey: @"Mail"]; + if (!mailSettings) + { + mailSettings = [NSMutableDictionary new]; + [mailSettings autorelease]; + } + [ud setObject: mailSettings forKey: @"Mail"]; + [mailSettings setObject: [co relativeImap4Name] + forKey: [NSString stringWithFormat: @"%@Folder", + purpose]]; + [ud synchronize]; + response = [self responseWith204]; + } + else + { + response = [self responseWithStatus: 500]; + [response + appendContentString: @"Unable to change the purpose of this folder."]; + } + + return response; +} + +- (WOResponse *) setAsDraftsFolderAction +{ + return [self _setFolderPurpose: @"Drafts"]; +} + +- (WOResponse *) setAsSentFolderAction +{ + return [self _setFolderPurpose: @"Sent"]; +} + +- (WOResponse *) setAsTrashFolderAction +{ + return [self _setFolderPurpose: @"Trash"]; +} + - (WOResponse *) expungeAction { NSException *error; @@ -180,18 +231,17 @@ WOResponse *response; co = [self clientObject]; - response = [context response]; error = [co expunge]; if (error) { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to expunge folder."]; } else { [co flushMailCaches]; - [response setStatus: 204]; + response = [self responseWith204]; } return response; @@ -207,7 +257,6 @@ NSURL *currentURL; co = [self clientObject]; - response = [context response]; error = [co addFlagsToAllMessages: @"deleted"]; if (!error) @@ -226,11 +275,11 @@ } if (error) { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"Unable to empty the trash folder."]; } else - [response setStatus: 204]; + response = [self responseWith204]; return response; } @@ -242,7 +291,6 @@ WOResponse *response; SOGoMailFolder *clientObject; - response = [context response]; mailInvitationParam = [[context request] formValueForKey: @"mail-invitation"]; if ([mailInvitationParam boolValue]) @@ -257,7 +305,7 @@ } else { - [response setStatus: 500]; + response = [self responseWithStatus: 500]; [response appendContentString: @"How did you end up here?"]; } @@ -283,8 +331,7 @@ NGImap4Client *client; NSString *responseString; - response = [context response]; - [response setStatus: 200]; + response = [self responseWithStatus: 200]; [response setHeader: @"text/plain; charset=UTF-8" forKey: @"content-type"]; diff --git a/UI/MailerUI/UIxMailListView.m b/UI/MailerUI/UIxMailListView.m index ae2758675..e004eae93 100644 --- a/UI/MailerUI/UIxMailListView.m +++ b/UI/MailerUI/UIxMailListView.m @@ -98,6 +98,24 @@ static int attachmentFlagSize = 8096; return [dateFormatter formattedDateAndTime: messageDate]; } +- (NSString *) messageSubject +{ + NSString *subject; + id envSubject; + + envSubject = [[message valueForKey: @"envelope"] subject]; + if ([envSubject isKindOfClass: [NSData class]]) + { + subject = [[NSString alloc] initWithData: envSubject + encoding: NSUTF8StringEncoding]; + [subject autorelease]; + } + else + subject = envSubject; + + return subject; +} + - (BOOL) showToAddress { NSString *ftype; @@ -140,6 +158,7 @@ static int attachmentFlagSize = 8096; flags = [[self message] valueForKey:@"flags"]; return [flags containsObject:@"seen"]; } + - (NSString *) messageUidString { return [[[self message] valueForKey:@"uid"] stringValue]; @@ -220,10 +239,24 @@ static int attachmentFlagSize = 8096; - (NSArray *) sortedUIDs { + EOQualifier *fetchQualifier, *notDeleted; if (!sortedUIDs) { + notDeleted = [EOQualifier qualifierWithQualifierFormat: + @"(not (flags = %@))", + @"deleted"]; + if (qualifier) + { + fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers: + notDeleted, qualifier, + nil]; + [fetchQualifier autorelease]; + } + else + fetchQualifier = notDeleted; + sortedUIDs - = [[self clientObject] fetchUIDsMatchingQualifier: qualifier + = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier sortOrdering: [self imap4SortOrdering]]; [sortedUIDs retain]; } @@ -406,36 +439,8 @@ static int attachmentFlagSize = 8096; return [self redirectToLocation:url]; } -/* active message */ - -- (SOGoMailObject *) lookupActiveMessage -{ - NSString *uid; - - if ((uid = [[context request] formValueForKey: @"uid"]) == nil) - return nil; - - return [[self clientObject] lookupName: uid - inContext: context - acquire: NO]; -} - /* actions */ -- (BOOL) isJavaScriptRequest -{ - return [[[context request] formValueForKey:@"jsonly"] boolValue]; -} - -- (id) javaScriptOK -{ - WOResponse *r; - - r = [context response]; - [r setStatus:200 /* OK */]; - return r; -} - - (int) firstMessageOfPageFor: (int) messageNbr { NSArray *messageNbrs; @@ -461,26 +466,23 @@ static int attachmentFlagSize = 8096; if ([criteria isEqualToString: @"subject"]) qualifier = [EOQualifier qualifierWithQualifierFormat: - @"(subject doesContain: %@)", - value]; + @"(subject doesContain: %@)", value]; else if ([criteria isEqualToString: @"sender"]) qualifier = [EOQualifier qualifierWithQualifierFormat: - @"(from doesContain: %@)", - value]; + @"(from doesContain: %@)", value]; else if ([criteria isEqualToString: @"subject_or_sender"]) qualifier = [EOQualifier qualifierWithQualifierFormat: - @"(subject doesContain: %@) OR " - @"(from doesContain: %@)", + @"((subject doesContain: %@)" + @" OR (from doesContain: %@))", value, value]; else if ([criteria isEqualToString: @"to_or_cc"]) qualifier = [EOQualifier qualifierWithQualifierFormat: - @"(to doesContain: %@) OR " - @"(cc doesContain: %@)", + @"((to doesContain: %@)" + @" OR (cc doesContain: %@))", value, value]; else if ([criteria isEqualToString: @"entire_message"]) qualifier = [EOQualifier qualifierWithQualifierFormat: - @"(message doesContain: %@)", - value]; + @"(body doesContain: %@)", value]; else qualifier = nil; @@ -499,8 +501,7 @@ static int attachmentFlagSize = 8096; specificMessage = [request formValueForKey: @"pageforuid"]; searchCriteria = [request formValueForKey: @"search"]; searchValue = [request formValueForKey: @"value"]; - if ([searchCriteria length] > 0 - && [searchValue length] > 0) + if ([searchValue length]) [self _setQualifierForCriteria: searchCriteria andValue: searchValue]; @@ -508,43 +509,9 @@ static int attachmentFlagSize = 8096; = ((specificMessage) ? [self firstMessageOfPageFor: [specificMessage intValue]] : [[request formValueForKey:@"idx"] intValue]); - return self; } -- (id) viewAction -{ - return [self defaultAction]; -} - -- (id) markMessageUnreadAction -{ - NSException *error; - - if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil) - // TODO: improve error handling - return error; - - if ([self isJavaScriptRequest]) - return [self javaScriptOK]; - - return [self redirectToLocation:@"view"]; -} - -- (id) markMessageReadAction -{ - NSException *error; - - if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil) - // TODO: improve error handling - return error; - - if ([self isJavaScriptRequest]) - return [self javaScriptOK]; - - return [self redirectToLocation:@"view"]; -} - - (id) getMailAction { // TODO: we might want to flush the caches? diff --git a/UI/MailerUI/UIxMailSourceView.m b/UI/MailerUI/UIxMailSourceView.m index c0586221d..60976e53d 100644 --- a/UI/MailerUI/UIxMailSourceView.m +++ b/UI/MailerUI/UIxMailSourceView.m @@ -25,6 +25,8 @@ #import #import +#import + #import "UIxMailSourceView.h" @implementation UIxMailSourceView @@ -36,8 +38,7 @@ source = [[self clientObject] contentAsString]; - response = [context response]; - [response setStatus: 200]; + response = [self responseWithStatus: 200]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"content-type"]; [response appendContentString: source]; diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index 1eb40bc79..e3da019f6 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -142,37 +142,42 @@ static NSString *mailETag = nil; - (id) defaultAction { + WOResponse *response; + NSString *s; + /* check etag to see whether we really must rerender */ - if (mailETag != nil ) { - /* - 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. - */ - NSString *s; - - if ((s = [[context request] headerForKey:@"if-none-match"])) { - if ([s rangeOfString:mailETag].length > 0) { /* not perfectly correct */ - /* client already has the proper entity */ - // [self logWithFormat:@"MATCH: %@ (tag %@)", s, mailETag]; - - if (![[self clientObject] doesMailExist]) { - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"message got deleted"]; + if (mailETag) + { + /* + 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. + */ + s = [[context request] headerForKey: @"if-none-match"]; + if (s) + { + if ([s rangeOfString:mailETag].length > 0) /* not perfectly correct */ + { + /* client already has the proper entity */ + // [self logWithFormat:@"MATCH: %@ (tag %@)", s, mailETag]; + + if (![[self clientObject] doesMailExist]) { + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"message got deleted"]; + } + + response = [context response]; + [response setStatus: 304 /* Not Modified */]; + + return response; + } } - - [[context response] setStatus:304 /* Not Modified */]; - return [context response]; - } } - } - if ([self message] == nil) { - // TODO: redirect to proper error + if (![self message]) // TODO: redirect to proper error return [NSException exceptionWithHTTPStatus:404 /* Not Found */ reason:@"did not find specified message!"]; - } return self; } @@ -201,119 +206,6 @@ static NSString *mailETag = nil; return [self redirectToLocation: url]; } -- (id) deleteAction -{ - NSException *ex; - - if (![self isDeletableClientObject]) { - return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"method cannot be invoked on " - @"the specified object"]; - } - - if ([self isInvokedBySafeMethod]) { - // TODO: fix UI to use POST for unsafe actions - [self logWithFormat:@"WARNING: method is invoked using safe HTTP method!"]; - } - - if ((ex = [[self clientObject] delete]) != nil) { - id url; - - url = [[ex reason] stringByEscapingURL]; - url = [@"view?error=" stringByAppendingString:url]; - return [self redirectToLocation:url]; - //return ex; - } - - if (![self isInlineViewer]) { - // if everything is ok, close the window (send a JS closing the Window) - id page; - - page = [self pageWithName:@"UIxMailWindowCloser"]; - [page takeValue:@"YES" forKey:@"refreshOpener"]; - return page; - } - - return [self redirectToParentFolder]; -} - -- (id) trashAction -{ - NSException *ex; - - if ([self isInvokedBySafeMethod]) { - // TODO: fix UI to use POST for unsafe actions - [self logWithFormat:@"WARNING: method is invoked using safe HTTP method!"]; - } - - if ((ex = [[self clientObject] trashInContext:context]) != nil) { - id url; - - if ([[[context request] formValueForKey:@"jsonly"] boolValue]) - /* called using XMLHttpRequest */ - return ex; - - url = [[ex reason] stringByEscapingURL]; - url = [@"view?error=" stringByAppendingString:url]; - return [self redirectToLocation:url]; - } - - if ([[[context request] formValueForKey:@"jsonly"] boolValue]) { - /* called using XMLHttpRequest */ - [[context response] setStatus:200 /* OK */]; - return [context response]; - } - - if (![self isInlineViewer]) { - // if everything is ok, close the window (send a JS closing the Window) - id page; - - page = [self pageWithName:@"UIxMailWindowCloser"]; - [page takeValue:@"YES" forKey:@"refreshOpener"]; - return page; - } - - return [self redirectToParentFolder]; -} - -- (id ) moveAction -{ - id result; - NSString *destinationFolder; - id url; - - if ([self isInvokedBySafeMethod]) { - // TODO: fix UI to use POST for unsafe actions - [self logWithFormat:@"WARNING: method is invoked using safe HTTP method!"]; - } - - destinationFolder = [self queryParameterForKey: @"tofolder"]; - if ([destinationFolder length] > 0) - { - result = [[self clientObject] moveToFolderNamed: destinationFolder - inContext: context]; - if (result) - { - if (![[[context request] formValueForKey:@"jsonly"] boolValue]) - { - url = [NSString stringWithFormat: @"view?error=%@", - [[result reason] stringByEscapingURL]]; - result = [self redirectToLocation: url]; - } - } - else - { - result = [context response]; - [result setStatus: 200]; - } - } - else - result = [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason: @"No destination folder given"]; - - return result; -} - /* generating response */ - (void) appendToResponse: (WOResponse *) _response diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index 214330cc8..083bbe403 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -1,435 +1,449 @@ -{ /* -*-javascript-*- */ -requires = ( MAIN, MainUI, CommonUI, Mailer, MailPartViewers ); /* , Sieve */ +{ /* -*-java-*- */ + requires = ( MAIN, MainUI, CommonUI, Mailer, MailPartViewers ); /* , Sieve */ - publicResources = ( - "uix.css", - "mailer.css", - "mailer.js", - "generic.js", - "searchfield.js", - "UIxAppointmentEditor.js", - "UIxContactEditor.js", - "UIxMailToSelection.js", + publicResources = ("uix.css", + "mailer.css", + "mailer.js", + "generic.js", + "searchfield.js", + "UIxAppointmentEditor.js", + "UIxContactEditor.js", + "UIxMailToSelection.js", - "lori_32x32.png", + "lori_32x32.png", - "tbtv_account_17x17.gif", - "tbtv_drafts_17x17.gif", - "tbtv_inbox_17x17.gif", - "tbtv_junction2_17x17.gif", - "tbtv_junction_17x17.gif", - "tbtv_leaf_corner_17x17.gif", - "tbtv_line_17x17.gif", - "tbtv_minus_17x17.gif", - "tbtv_plus_17x17.gif", - "tbtv_corner_17x17.gif", - "tbtv_corner_minus_17x17.gif", - "tbtv_corner_plus_17x17.gif", - "tbtv_sent_17x17.gif", - "tbtv_trash_17x17.gif", + "tbtv_account_17x17.gif", + "tbtv_drafts_17x17.gif", + "tbtv_inbox_17x17.gif", + "tbtv_junction2_17x17.gif", + "tbtv_junction_17x17.gif", + "tbtv_leaf_corner_17x17.gif", + "tbtv_line_17x17.gif", + "tbtv_minus_17x17.gif", + "tbtv_plus_17x17.gif", + "tbtv_corner_17x17.gif", + "tbtv_corner_minus_17x17.gif", + "tbtv_corner_plus_17x17.gif", + "tbtv_sent_17x17.gif", + "tbtv_trash_17x17.gif", - "tbtb_addressbook.png", - "tbtb_compose.png", - "tbtb_delete.png", - "tbtb_deletedoc.png", - "tbtb_filetofolder.png", - "tbtb_forward.png", - "tbtb_getmail.png", - "tbtb_next.png", - "tbtb_previous.png", - "tbtb_print.png", - "tbtb_reply.png", - "tbtb_replyall.png", - "tbtb_search.png", - "tbtb_trash.png", + "tbtb_addressbook.png", + "tbtb_compose.png", + "tbtb_delete.png", + "tbtb_deletedoc.png", + "tbtb_filetofolder.png", + "tbtb_forward.png", + "tbtb_getmail.png", + "tbtb_next.png", + "tbtb_previous.png", + "tbtb_print.png", + "tbtb_reply.png", + "tbtb_replyall.png", + "tbtb_search.png", + "tbtb_trash.png", - "tbtb_compose_addressbook_30x30.png", - "tbtb_compose_attach_30x30.png", - "tbtb_compose_clip_30x30.png", - "tbtb_compose_cut_30x30.png", - "tbtb_compose_dup_30x30.png", - "tbtb_compose_file_30x30.png", - "tbtb_compose_lock_30x30.png", - "tbtb_compose_quote_30x30.png", - "tbtb_compose_send_30x30.png", - "tbtb_compose_spell_30x30.png", + "tbtb_compose_addressbook_30x30.png", + "tbtb_compose_attach_30x30.png", + "tbtb_compose_clip_30x30.png", + "tbtb_compose_cut_30x30.png", + "tbtb_compose_dup_30x30.png", + "tbtb_compose_file_30x30.png", + "tbtb_compose_lock_30x30.png", + "tbtb_compose_quote_30x30.png", + "tbtb_compose_send_30x30.png", + "tbtb_compose_spell_30x30.png", - "message-mail.png", - "message-mail-read.png", + "message-mail.png", + "message-mail-read.png", - "icon_mark_flagged.gif", - "icon_mark_read.gif", - "icon_mark_unflagged.gif", - "icon_mark_unread.gif", - "icon_read.gif", - "icon_unread.gif", + "icon_mark_flagged.gif", + "icon_mark_read.gif", + "icon_mark_unflagged.gif", + "icon_mark_unread.gif", + "icon_read.gif", + "icon_unread.gif", - "title_attachment_14x14.png", - "title_config.png", - "title_junk.png", - "title_read_14x14.png", - "title_thread.png", - "title_sortdown_12x12.png", - "title_sortup_12x12.png", - ); + "title_attachment_14x14.png", + "title_config.png", + "title_junk.png", + "title_read_14x14.png", + "title_thread.png", + "title_sortdown_12x12.png", + "title_sortup_12x12.png", + ); -factories = { -}; + factories = { + }; -categories = { - SOGoMailFolder = { + categories = { + SOGoMailFolder = { slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoMailObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoMailObject.toolbar"; + }; }; methods = { - subscribe = { - protectedBy = ""; - actionClass = "UIxMailFolderActions"; - actionName = "subscribe"; - }; - unsubscribe = { - protectedBy = ""; - actionClass = "UIxMailFolderActions"; - actionName = "unsubscribe"; - }; - quotas = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "quotas"; - }; - view = { - protectedBy = "View"; - pageName = "UIxMailListView"; - }; - ajax = { - protectedBy = "View"; - pageName = "UIxMailAjaxRequest"; - }; - index = { - protectedBy = "View"; - pageName = "UIxMailListView"; - }; - GET = { /* hack to make it work as the default method */ - protectedBy = "View"; - pageName = "UIxMailListView"; - }; - markMessageUnread = { - protectedBy = "View"; - pageName = "UIxMailListView"; - actionName = "markMessageUnread"; - }; - markMessageRead = { - protectedBy = "View"; - pageName = "UIxMailListView"; - actionName = "markMessageRead"; - }; - getMail = { - protectedBy = "View"; - pageName = "UIxMailListView"; - actionName = "getMail"; - }; - expunge = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "emptyTrash"; - }; - createFolder = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "createFolder"; - }; - renameFolder = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "renameFolder"; - }; - deleteFolder = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "deleteFolder"; - }; - userRights = { - protectedBy = "ReadAcls"; - pageName = "UIxMailUserRightsEditor"; - }; - saveUserRights = { - protectedBy = "SaveAcls"; - pageName = "UIxMailUserRightsEditor"; - actionName = "saveUserRights"; - }; + subscribe = { + protectedBy = ""; + actionClass = "UIxMailFolderActions"; + actionName = "subscribe"; + }; + unsubscribe = { + protectedBy = ""; + actionClass = "UIxMailFolderActions"; + actionName = "unsubscribe"; + }; + quotas = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "quotas"; + }; + view = { + protectedBy = "View"; + pageName = "UIxMailListView"; + }; + ajax = { + protectedBy = "View"; + pageName = "UIxMailAjaxRequest"; + }; + index = { + protectedBy = "View"; + pageName = "UIxMailListView"; + }; + GET = { /* hack to make it work as the default method */ + protectedBy = "View"; + pageName = "UIxMailListView"; + }; + getMail = { + protectedBy = "View"; + pageName = "UIxMailListView"; + actionName = "getMail"; + }; + expunge = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "emptyTrash"; + }; + createFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "createFolder"; + }; + renameFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "renameFolder"; + }; + deleteFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "deleteFolder"; + }; + setAsDraftsFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "setAsDraftsFolder"; + }; + setAsSentFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "setAsSentFolder"; + }; + setAsTrashFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "setAsTrashFolder"; + }; + userRights = { + protectedBy = "ReadAcls"; + pageName = "UIxMailUserRightsEditor"; + }; + saveUserRights = { + protectedBy = "SaveAcls"; + pageName = "UIxMailUserRightsEditor"; + actionName = "saveUserRights"; + }; }; - }; + }; - SOGoTrashFolder = { + SOGoTrashFolder = { /* just a new toolbar, other things come from SOGoMailFolder */ slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoMailObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoMailObject.toolbar"; + }; }; methods = { - emptyTrash = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "emptyTrash"; - }; + emptyTrash = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "emptyTrash"; + }; }; - }; + }; - SOGoMailObject = { + SOGoMailObject = { slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoMailObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoMailObject.toolbar"; + }; }; methods = { - view = { - protectedBy = "View"; - pageName = "UIxMailView"; - }; - viewsource = { - protectedBy = "View"; - actionClass = "UIxMailSourceView"; - actionName = "viewSource"; - }; - popupview = { - protectedBy = "View"; - pageName = "UIxMailPopupView"; - }; - move = { - protectedBy = "View"; - pageName = "UIxMailView"; - actionName = "move"; - }; - delete = { - protectedBy = "View"; - pageName = "UIxMailView"; - actionName = "delete"; - }; - trash = { - protectedBy = "View"; - pageName = "UIxMailView"; - actionName = "trash"; - }; - junk = { - protectedBy = "View"; - pageName = "UIxMailView"; - actionName = "junk"; - }; - edit = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "edit"; - }; - reply = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "reply"; - }; - replyall = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "replyToAll"; - }; - forward = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "forward"; - }; + view = { + protectedBy = "View"; + pageName = "UIxMailView"; + }; + viewsource = { + protectedBy = "View"; + actionClass = "UIxMailSourceView"; + actionName = "viewSource"; + }; + popupview = { + protectedBy = "View"; + pageName = "UIxMailPopupView"; + }; + move = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "move"; + }; + copy = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "copy"; + }; + trash = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "trash"; + }; + junk = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "junk"; + }; + edit = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "edit"; + }; + reply = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "reply"; + }; + replyall = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "replyToAll"; + }; + forward = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "forward"; + }; + markMessageUnread = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "markMessageUnread"; + }; + markMessageRead = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "markMessageRead"; + }; }; - }; + }; - SOGoMailAccounts = { + SOGoMailAccounts = { slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoMailObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoMailObject.toolbar"; + }; }; methods = { - view = { - protectedBy = "View"; - pageName = "UIxMailMainFrame"; - }; - compose = { - protectedBy = "View"; - pageName = "UIxMailMainFrame"; - actionName = "compose"; - }; + view = { + protectedBy = "View"; + pageName = "UIxMailMainFrame"; + }; + compose = { + protectedBy = "View"; + pageName = "UIxMailMainFrame"; + actionName = "compose"; + }; }; - }; + }; - SOGoMailAccount = { + SOGoMailAccount = { slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoMailObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoMailObject.toolbar"; + }; }; methods = { - compose = { - protectedBy = "View"; - actionClass = "UIxMailAccountActions"; - actionName = "compose"; - }; - mailboxes = { - protectedBy = "View"; - actionClass = "UIxMailAccountActions"; - actionName = "listMailboxes"; - }; - createFolder = { - protectedBy = "View"; - actionClass = "UIxMailFolderActions"; - actionName = "createFolder"; - }; + compose = { + protectedBy = "View"; + actionClass = "UIxMailAccountActions"; + actionName = "compose"; + }; + mailboxes = { + protectedBy = "View"; + actionClass = "UIxMailAccountActions"; + actionName = "listMailboxes"; + }; + createFolder = { + protectedBy = "View"; + actionClass = "UIxMailFolderActions"; + actionName = "createFolder"; + }; }; - }; + }; - SOGoDraftsFolder = { + SOGoDraftsFolder = { slots = { - toolbar = { - protectedBy = "View"; - value = ( /* the toolbar groups */ - ( /* first group */ - { link = "getMail"; - image = "tb-mail-getmail-flat-24x24.png"; - cssClass = "tbicon_getmail"; label = "Get Mail"; }, - { - link = "#"; // "compose"; // target = "_blank"; - isSafe = NO; - onclick = "return openMessageWindow(null, 'compose');"; - image = "tb-mail-write-flat-24x24.png"; - cssClass = "tbicon_compose"; label = "Write"; }, - ) - ); - }; + toolbar = { + protectedBy = "View"; + value = ( /* the toolbar groups */ + ( /* first group */ + { link = "getMail"; + image = "tb-mail-getmail-flat-24x24.png"; + cssClass = "tbicon_getmail"; label = "Get Mail"; }, + { + link = "#"; // "compose"; // target = "_blank"; + isSafe = NO; + onclick = "return openMessageWindow(null, 'compose');"; + image = "tb-mail-write-flat-24x24.png"; + cssClass = "tbicon_compose"; label = "Write"; }, + ) + ); + }; }; methods = { - view = { - protectedBy = "View"; - pageName = "UIxMailListView"; - }; - getMail = { - protectedBy = "View"; - pageName = "UIxMailListView"; - }; + view = { + protectedBy = "View"; + pageName = "UIxMailListView"; + }; + getMail = { + protectedBy = "View"; + pageName = "UIxMailListView"; + }; }; - }; + }; - SOGoDraftObject = { + SOGoDraftObject = { slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoDraftObject.toolbar"; - }; + toolbar = { + protectedBy = "View"; + value = "SOGoDraftObject.toolbar"; + }; }; methods = { - edit = { - protectedBy = "View"; - pageName = "UIxMailEditor"; - }; - save = { - protectedBy = "View"; - pageName = "UIxMailEditor"; - actionName = "save"; - }; - send = { - protectedBy = "View"; - pageName = "UIxMailEditor"; - actionName = "send"; - }; - delete = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "delete"; - }; - deleteAttachment = { - protectedBy = "View"; - actionClass = "UIxMailActions"; - actionName = "deleteAttachment"; - }; + edit = { + protectedBy = "View"; + pageName = "UIxMailEditor"; + }; + save = { + protectedBy = "View"; + pageName = "UIxMailEditor"; + actionName = "save"; + }; + send = { + protectedBy = "View"; + pageName = "UIxMailEditor"; + actionName = "send"; + }; + delete = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "delete"; + }; + deleteAttachment = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "deleteAttachment"; + }; }; - }; + }; - /* Sieve */ + /* Sieve */ - /* SOGoSieveScriptsFolder = { - slots = { - toolbar = { - protectedBy = "View"; - value = ( - ( - { - link = "getMail"; - image = "tb-mail-getmail-flat-24x24.png"; - cssClass = "tbicon_getmail"; label = "Get Mail"; - }, - { - link = "#"; // "compose"; // target = "_blank"; - onclick = "clickedNewFilter(this); return false"; - image = "tb-mail-write-flat-24x24.png"; - cssClass = "tbicon_compose"; label = "New Filter"; - }, - ), - ( - { link = "#"; - cssClass = "tbicon_delete"; label = "Delete"; }, - ), - ); - }; - }; - methods = { - view = { - protectedBy = "View"; - pageName = "UIxFilterList"; - }; - create = { - protectedBy = "View"; - pageName = "UIxFilterList"; - actionName = "create"; - }; - }; - }; +// SOGoSieveScriptsFolder = { +// slots = { +// toolbar = { +// protectedBy = "View"; +// value = ( +// ( +// { +// link = "getMail"; +// image = "tb-mail-getmail-flat-24x24.png"; +// cssClass = "tbicon_getmail"; label = "Get Mail"; +// }, +// { +// link = "#"; // "compose"; // target = "_blank"; +// onclick = "clickedNewFilter(this); return false"; +// image = "tb-mail-write-flat-24x24.png"; +// cssClass = "tbicon_compose"; label = "New Filter"; +// }, +// ), +// ( +// { link = "#"; +// cssClass = "tbicon_delete"; label = "Delete"; }, +// ), +// ); +// }; +// }; +// methods = { +// view = { +// protectedBy = "View"; +// pageName = "UIxFilterList"; +// }; +// create = { +// protectedBy = "View"; +// pageName = "UIxFilterList"; +// actionName = "create"; +// }; +// }; +// }; - SOGoSieveScriptObject = { - slots = { - toolbar = { - protectedBy = "View"; - value = ( - ( { link = "#"; - onclick = "clickedEditorSave(this);return false;"; - image = "tb-mail-file-flat-24x24.png"; - cssClass = "tbicon_save"; label = "Save"; }, - { link = "#"; - onclick = "clickedEditorDelete(this);return false;"; - image = "tb-mail-delete-flat-24x24.png"; - cssClass = "tbicon_delete"; label = "Delete"; }, - ) - ); - }; - }; - methods = { - edit = { - protectedBy = "View"; - pageName = "UIxSieveEditor"; - actionName = "edit"; - }; - save = { - protectedBy = "View"; - pageName = "UIxSieveEditor"; - actionName = "save"; - }; - delete = { - protectedBy = "View"; - pageName = "UIxSieveEditor"; - actionName = "delete"; - }; - }; - }; */ -}; +// SOGoSieveScriptObject = { +// slots = { +// toolbar = { +// protectedBy = "View"; +// value = ( +// ( { link = "#"; +// onclick = "clickedEditorSave(this);return false;"; +// image = "tb-mail-file-flat-24x24.png"; +// cssClass = "tbicon_save"; label = "Save"; }, +// { link = "#"; +// onclick = "clickedEditorDelete(this);return false;"; +// image = "tb-mail-delete-flat-24x24.png"; +// cssClass = "tbicon_delete"; label = "Delete"; }, +// ) +// ); +// }; +// }; +// methods = { +// edit = { +// protectedBy = "View"; +// pageName = "UIxSieveEditor"; +// actionName = "edit"; +// }; +// save = { +// protectedBy = "View"; +// pageName = "UIxSieveEditor"; +// actionName = "save"; +// }; +// delete = { +// protectedBy = "View"; +// pageName = "UIxSieveEditor"; +// actionName = "delete"; +// }; +// } + }; } + diff --git a/UI/MainUI/English.lproj/Localizable.strings b/UI/MainUI/English.lproj/Localizable.strings index ae3e21df0..9cc6c319a 100644 --- a/UI/MainUI/English.lproj/Localizable.strings +++ b/UI/MainUI/English.lproj/Localizable.strings @@ -1,12 +1,10 @@ /* this file is in UTF-8 format! */ -"Homepage" = "Homepage"; +"title" = "SOGo"; -"Internet access unauthorized and" = "Internet access unauthorized and"; -"Internet access authorized and" = "Internet access authorized and"; -"internetAccessState_0" = "CLOSED"; -"internetAccessState_1" = "OPEN"; +"Login:" = "Login:"; +"Password:" = "Password:"; -"Automatic vacation messages activation" = "Automatic vacation messages activation"; -"Internet" = "Internet"; -"Intranet" = "Intranet"; +"Connect" = "Connect"; + +"Wrong username or password." = "Wrong username or password."; \ No newline at end of file diff --git a/UI/MainUI/French.lproj/Localizable.strings b/UI/MainUI/French.lproj/Localizable.strings index 90f6e89c3..2cd2ec069 100644 --- a/UI/MainUI/French.lproj/Localizable.strings +++ b/UI/MainUI/French.lproj/Localizable.strings @@ -1,12 +1,10 @@ /* this file is in UTF-8 format! */ -"Homepage" = "Accueil"; +"title" = "SOGo"; -"Internet access authorized and" = "L'accès depuis internet est autorisé et "; -"Internet access unauthorized and" = "L'accès depuis internet est interdit et "; -"internetAccessState_0" = "FERMÉ"; -"internetAccessState_1" = "OUVERT"; +"Login:" = "Nom d'utilisateur :"; +"Password:" = "Mot de passe :"; -"Automatic vacation messages activation" = "Activation du message de réponse automatique "; -"Internet" = "Internet"; -"Intranet" = "Intranet"; +"Connect" = "Connexion"; + +"Wrong username or password." = "Mauvais nom d'utilisateur ou mot de passe."; \ No newline at end of file diff --git a/UI/MainUI/GNUmakefile b/UI/MainUI/GNUmakefile index 83e84c180..5bad0b55e 100644 --- a/UI/MainUI/GNUmakefile +++ b/UI/MainUI/GNUmakefile @@ -26,6 +26,9 @@ MainUI_RESOURCE_FILES += \ MainUI_LOCALIZED_RESOURCE_FILES += \ Locale Localizable.strings +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/MainUI/German.lproj/Localizable.strings b/UI/MainUI/German.lproj/Localizable.strings index 57d2a15a5..790286f09 100644 --- a/UI/MainUI/German.lproj/Localizable.strings +++ b/UI/MainUI/German.lproj/Localizable.strings @@ -1,12 +1,8 @@ /* this file is in UTF-8 format! */ -"Homepage" = "Startseite"; +"title" = "SOGo"; -"Internet access authorized and" = "Internetzugang ist erlaubt und "; -"Internet access unauthorized and" = "Internetzugang ist nicht erlaubt und "; -"internetAccessState_0" = "GESCHLOSSEN"; -"internetAccessState_1" = "OFFEN"; +"Login:" = "Benutzername:"; +"Password:" = "Passwort:"; -"Automatic vacation messages activation" = "Activation du message de réponse automatique "; -"Internet" = "Internet"; -"Intranet" = "Intranet"; +"Connect" = "Verbinden"; diff --git a/UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.m b/UI/MainUI/SOGoRootPage.h similarity index 79% rename from UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.m rename to UI/MainUI/SOGoRootPage.h index 323684dc7..387e11cd7 100644 --- a/UI/SOGoUI/SOGoACLEnglishAdditionAdvisory.m +++ b/UI/MainUI/SOGoRootPage.h @@ -1,4 +1,4 @@ -/* SOGoACLEnglishAdditionAdvisory.m - this file is part of SOGo +/* SOGoRootPage.h - this file is part of SOGo * * Copyright (C) 2007 Inverse groupe conseil * @@ -20,13 +20,16 @@ * Boston, MA 02111-1307, USA. */ -#import "SOGoACLEnglishAdditionAdvisory.h" +#ifndef SOGOROOTPAGE_H +#define SOGOROOTPAGE_H -@implementation SOGoACLEnglishAdditionAdvisory +#import -- (NSString *) aclMethod +@interface SOGoRootPage : UIxComponent { - return @"add"; + NSString *userName; } @end + +#endif /* SOGOROOTPAGE_H */ diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 912def217..9749d13b5 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -19,145 +19,92 @@ 02111-1307, USA. */ -#import - -#import -#import #import #import +#import #import #import +#import #import -#import #import -#import -#import +#import -@interface SOGoRootPage : SoComponent -{ - NSString *userName; -} +#import +#import -@end +#import "SOGoRootPage.h" @implementation SOGoRootPage -static BOOL doNotRedirect = NO; - -+ (void)initialize { - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - - if ((doNotRedirect = [ud boolForKey:@"SOGoDoNotRedirectRootPage"])) - NSLog(@"SOGoRootPage: home-page redirect is disabled."); -} - -- (void)dealloc { - [self->userName release]; +- (void) dealloc +{ + [userName release]; [super dealloc]; } /* accessors */ -- (void)setUserName:(NSString *)_value { - ASSIGNCOPY(self->userName, _value); +- (void) setUserName: (NSString *) _value +{ + ASSIGNCOPY (userName, _value); } -- (NSString *)userName { - return self->userName; + +- (NSString *) userName +{ + return userName; +} + +- (NSString *) connectURL +{ + return [NSString stringWithFormat: @"%@connect", [self applicationPath]]; } /* actions */ +- (id ) connectAction +{ + WOResponse *response; + WOCookie *authCookie; + SOGoWebAuthenticator *auth; + NSString *cookieValue, *cookieString; -- (id)connectAction { - NSString *url; - - [self takeFormValuesForKeys:@"userName", nil]; - - if ([[self userName] length] == 0) - return nil; - - url = [@"/" stringByAppendingString:[[self userName] stringByEscapingURL]]; - if (![url hasSuffix:@"/"]) - url = [url stringByAppendingString:@"/"]; - - url = [[self context] urlWithRequestHandlerKey:@"so" - path:url queryString:nil]; - return [self redirectToLocation:url]; + auth = [[WOApplication application] + authenticatorInContext: context]; + response = [self responseWith204]; + cookieString = [NSString stringWithFormat: @"%@:%@", + [self queryParameterForKey: @"userName"], + [self queryParameterForKey: @"password"]]; + cookieValue = [NSString stringWithFormat: @"basic %@", + [cookieString stringByEncodingBase64]]; + authCookie = [WOCookie cookieWithName: [auth cookieNameInContext: context] + value: cookieValue]; + [authCookie setPath: @"/"]; + [response addCookie: authCookie]; + + return response; } -- (id)defaultAction { - WOResponse *r; - NSString *login, *rhk; - id auth, user; - id home, base; +- (id ) defaultAction +{ + id response; + NSString *login, *oldLocation; - if (doNotRedirect) - return self; - - /* - Note: ctx.activeUser is NOT set here. Don't know why, so we retrieve - the user from the authenticator. - */ - - auth = [[self clientObject] authenticatorInContext:[self context]]; - user = [auth userInContext:[self context]]; - login = [user login]; - - if ([login isEqualToString:@"anonymous"]) { - /* use root page for unauthenticated users */ - return self; - } - - /* check base */ - - base = [self application]; - rhk = [[[self context] request] requestHandlerKey]; - if (([rhk length] == 0) || ([base requestHandlerForKey:rhk] == nil)) { - base = [base lookupName:@"so" inContext:[self context] acquire:NO]; - - if (![base isNotNull] || [base isKindOfClass:[NSException class]]) { - /* use root page if home could not be found */ - [self errorWithFormat:@"Did not find 'so' request handler!"]; - return self; + login = [[context activeUser] login]; + if ([login isEqualToString: @"anonymous"]) + response = self; + else + { + oldLocation = [[self clientObject] baseURLInContext: context]; + response + = [self redirectToLocation: [NSString stringWithFormat: @"%@/%@", + oldLocation, login]]; } - } - - /* lookup home-page */ - home = [base lookupName:login inContext:[self context] acquire:NO]; - if (![home isNotNull] || [home isKindOfClass:[NSException class]]) { - /* use root page if home could not be found */ - return self; - } - - /* redirect to home-page */ - - r = [[self context] response]; - [r setStatus:302 /* moved */]; - [r setHeader:[home baseURLInContext:[self context]] forKey:@"location"]; - return r; + return response; } -/* response generation */ - -- (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx { - NSString *rhk; - - // TODO: we might also want to look into the HTTP basic-auth to redirect to - // the login URL! - - rhk = [[_ctx request] requestHandlerKey]; - if ([rhk length]==0 || [[self application] requestHandlerForKey:rhk]==nil) { - /* a small hack to redirect to a valid URL */ - NSString *url; - - url = [_ctx urlWithRequestHandlerKey:@"so" path:@"/" queryString:nil]; - [_response setStatus:302 /* moved */]; - [_response setHeader:url forKey:@"location"]; - [self logWithFormat:@"URL: %@", url]; - return; - } - - [super appendToResponse:_response inContext:_ctx]; +- (BOOL) isPublicInContext: (WOContext *) localContext +{ + return YES; } @end /* SOGoRootPage */ diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index f64869933..22c6a295c 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -26,14 +26,16 @@ #import #import #import +#import #import #import #import #import #import -#import -#import +#import +#import +#import #import static NSString *defaultModule = nil; @@ -62,8 +64,6 @@ static NSString *defaultModule = nil; @"'Calendar', 'Contacts' or Mail)", defaultModule]; defaultModule = @"Calendar"; } - else - defaultModule = @"Calendar"; } else defaultModule = @"Calendar"; @@ -97,9 +97,9 @@ static NSString *defaultModule = nil; record = [records nextObject]; while (record) { - status = [record objectForKey: @"status"]; + status = [record objectForKey: @"c_status"]; - value = [[record objectForKey: @"startdate"] intValue]; + value = [[record objectForKey: @"c_startdate"] intValue]; currentDate = [NSCalendarDate dateWithTimeIntervalSince1970: value]; if ([currentDate earlierDate: startDate] == currentDate) startInterval = 0; @@ -107,7 +107,7 @@ static NSString *defaultModule = nil; startInterval = ([currentDate timeIntervalSinceDate: startDate] / 900); - value = [[record objectForKey: @"enddate"] intValue]; + value = [[record objectForKey: @"c_enddate"] intValue]; currentDate = [NSCalendarDate dateWithTimeIntervalSince1970: value]; if ([currentDate earlierDate: endDate] == endDate) endInterval = [items count] - 1; @@ -198,4 +198,34 @@ static NSString *defaultModule = nil; return response; } +- (id ) logoffAction +{ + WOResponse *response; + WOCookie *cookie; + SOGoWebAuthenticator *auth; + id container; + NSCalendarDate *date; + + container = [[self clientObject] container]; + + response = [context response]; + [response setStatus: 302]; + [response setHeader: [container baseURLInContext: context] + forKey: @"location"]; + auth = [[self clientObject] authenticatorInContext: context]; + cookie = [WOCookie cookieWithName: [auth cookieNameInContext: context] + value: @"discard"]; + [cookie setPath: @"/"]; + date = [NSCalendarDate calendarDate]; + [cookie setExpires: [date yesterday]]; + [response addCookie: cookie]; + + [response setHeader: date forKey: @"Last-Modified"]; + [response setHeader: @"no-store, no-cache, must-revalidate, max-age=0" forKey: @"Cache-Control"]; + [response setHeader: @"post-check=0, pre-check=0" forKey: @"Cache-Control"]; + [response setHeader: @"no-cache" forKey: @"Pragma"]; + + return response; +} + @end diff --git a/UI/MainUI/product.plist b/UI/MainUI/product.plist index 7582875fb..11e282820 100644 --- a/UI/MainUI/product.plist +++ b/UI/MainUI/product.plist @@ -1,5 +1,5 @@ { - requires = ( MAIN ); + requires = ( MAIN, CommonUI ); publicResources = ( ); @@ -10,7 +10,7 @@ classes = { SOGoRootPage = { superclass = "SoComponent"; - protectedBy = "View"; + protectedBy = ""; defaultRoles = { "View" = ( "Authenticated" ); }; @@ -46,6 +46,14 @@ "Access Contents Information" = ( "Owner", "ObjectViewer" ); }; }; + SOGoParentFolder = { + superclass = "SOGoObject"; + protectedBy = "Access Contents Information"; + defaultRoles = { + "Access Contents Information" = ( "Authenticated" ); + "WebDAV Access" = ( "Authenticated" ); + }; + }; SOGoUserFolder = { superclass = "SOGoFolder"; protectedBy = "Access Contents Information"; @@ -59,13 +67,18 @@ categories = { SOGo = { // TODO: move decls to class methods = { - index = { - protectedBy = "View"; - pageName = "SOGoRootPage"; + view = { + protectedBy = ""; + pageName = "SOGoRootPage"; + }; + connect = { + protectedBy = ""; + pageName = "SOGoRootPage"; + actionName = "connect"; }; GET = { // more or less a hack, see README of dbd - protectedBy = "View"; - pageName = "SOGoRootPage"; + protectedBy = ""; + pageName = "SOGoRootPage"; }; }; }; @@ -77,13 +90,18 @@ protectedBy = ""; pageName = "SOGoUserHomePage"; }; + logoff = { + protectedBy = ""; + pageName = "SOGoUserHomePage"; + actionName = "logoff"; + }; }; }; SOGoGroupsFolder = { methods = { index = { protectedBy = "View"; - pageName = "SOGoGroupsPage"; + pageName = "SOGoGroupsPage"; }; }; }; @@ -91,7 +109,7 @@ methods = { index = { protectedBy = "View"; - pageName = "SOGoGroupPage"; + pageName = "SOGoGroupPage"; }; }; }; diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index 2e4275540..64aa487e7 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -3,8 +3,7 @@ "Close" = "Close"; /* tabs */ -"Out of Office" = "Out of Office"; -"Date and Time" = "Date and Time"; +"General" = "General"; "Calendar Options" = "Calendar Options"; "Mail Options" = "Mail Options"; "Password" = "Password"; diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index 3cf0d7128..a3e625db7 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -3,8 +3,7 @@ "Close" = "Fermer"; /* tabs */ -"Out of Office" = "Absence du bureau"; -"Date and Time" = "Date et heure"; +"General" = "Général"; "Calendar Options" = "Calendrier"; "Mail Options" = "Courrier"; "Password" = "Mot de passe"; diff --git a/UI/PreferencesUI/GNUmakefile b/UI/PreferencesUI/GNUmakefile index 2f8eaba0d..e3e707975 100644 --- a/UI/PreferencesUI/GNUmakefile +++ b/UI/PreferencesUI/GNUmakefile @@ -24,6 +24,9 @@ PreferencesUI_RESOURCE_FILES += \ PreferencesUI_LOCALIZED_RESOURCE_FILES += \ Localizable.strings \ +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/PreferencesUI/German.lproj/Localizable.strings b/UI/PreferencesUI/German.lproj/Localizable.strings index dbc7941c3..34ac16981 100644 --- a/UI/PreferencesUI/German.lproj/Localizable.strings +++ b/UI/PreferencesUI/German.lproj/Localizable.strings @@ -3,8 +3,7 @@ "Close" = "Schließen"; /* tabs */ -"Out of Office" = "Abwesenheit"; -"Date and Time" = "Datum und Zeit"; +"General" = "Allgemein"; "Calendar Options" = "Kalender"; "Mail Options" = "E-Mail"; "Password" = "Passwort"; @@ -45,7 +44,7 @@ "First week of year :" = "Erste Woche des Jahres:"; "Enable reminders for Calendar items" = "Terminerinnerung aktivieren"; "Play a sound when a reminder comes due" = "Akustisches Signal zur Terminerinnerung"; -"Default reminder :" = "Voreingestellte Terminerinnerung:"; +"Default reminder :" = "Standard Terminerinnerung:"; "firstWeekOfYear_HideWeekNumbers" = "Keine Wochennummern anzeigen"; "firstWeekOfYear_January1" = "Beginnt am 1.Januar"; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 4131db0a5..7259f60d2 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -125,7 +125,8 @@ static BOOL shouldDisplayPasswordChange = NO; - (NSArray *) timeZonesList { - return [NSTimeZone knownTimeZoneNames]; + return [[NSTimeZone knownTimeZoneNames] + sortedArrayUsingSelector: @selector (localizedCaseInsensitiveCompare:)]; } - (NSString *) userTimeZone diff --git a/UI/SOGoUI/GNUmakefile b/UI/SOGoUI/GNUmakefile index 554741b55..492823221 100644 --- a/UI/SOGoUI/GNUmakefile +++ b/UI/SOGoUI/GNUmakefile @@ -27,12 +27,8 @@ libSOGoUI_OBJC_FILES += \ SOGoAptFormatter.m \ SOGoJSStringFormatter.m \ WOContext+UIx.m \ - \ - SOGoACLAdvisory.m \ - SOGoACLFrenchAdditionAdvisory.m \ - SOGoACLFrenchRemovalAdvisory.m \ - SOGoACLEnglishAdditionAdvisory.m \ - SOGoACLEnglishRemovalAdvisory.m + SOGoACLAdvisory.m \ + SOGoFolderAdvisory.m # make diff --git a/UI/SOGoUI/SOGoACLAdvisory.h b/UI/SOGoUI/SOGoACLAdvisory.h index 788a49a02..762b89a1d 100644 --- a/UI/SOGoUI/SOGoACLAdvisory.h +++ b/UI/SOGoUI/SOGoACLAdvisory.h @@ -47,4 +47,34 @@ @end +@interface SOGoACLAdditionAdvisory : SOGoACLAdvisory + +- (NSString *) aclMethod; + +@end + +@interface SOGoACLRemovalAdvisory : SOGoACLAdvisory + +- (NSString *) aclMethod; + +@end + +@interface SOGoACLEnglishAdditionAdvisory : SOGoACLAdditionAdvisory +@end + +@interface SOGoACLFrenchAdditionAdvisory : SOGoACLAdditionAdvisory +@end + +@interface SOGoACLGermanAdditionAdvisory : SOGoACLAdditionAdvisory +@end + +@interface SOGoACLEnglishRemovalAdvisory : SOGoACLRemovalAdvisory +@end + +@interface SOGoACLFrenchRemovalAdvisory : SOGoACLRemovalAdvisory +@end + +@interface SOGoACLGermanRemovalAdvisory : SOGoACLRemovalAdvisory +@end + #endif /* SOGOACLADVISORY_H */ diff --git a/UI/SOGoUI/SOGoACLAdvisory.m b/UI/SOGoUI/SOGoACLAdvisory.m index 31b42ad6c..b52ebcc19 100644 --- a/UI/SOGoUI/SOGoACLAdvisory.m +++ b/UI/SOGoUI/SOGoACLAdvisory.m @@ -31,6 +31,7 @@ #import #import #import +#import #import "SOGoACLAdvisory.h" @@ -109,7 +110,7 @@ subject = [[self generateResponse] contentAsString]; isSubject = NO; - return [subject stringByTrimmingSpaces]; + return [[subject stringByTrimmingSpaces] asQPSubjectString: @"utf-8"]; } - (NSString *) body @@ -126,7 +127,7 @@ - (NSString *) aclMethod { [self subclassResponsibility: _cmd]; - + return nil; } @@ -209,3 +210,33 @@ } @end + +@implementation SOGoACLAdditionAdvisory + +- (NSString *) aclMethod { return @"add"; } + +@end + +@implementation SOGoACLRemovalAdvisory + +- (NSString *) aclMethod { return @"remove"; } + +@end + +@implementation SOGoACLEnglishAdditionAdvisory +@end + +@implementation SOGoACLFrenchAdditionAdvisory +@end + +@implementation SOGoACLGermanAdditionAdvisory +@end + +@implementation SOGoACLEnglishRemovalAdvisory +@end + +@implementation SOGoACLFrenchRemovalAdvisory +@end + +@implementation SOGoACLGermanRemovalAdvisory +@end diff --git a/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.h b/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.h deleted file mode 100644 index 2b482ac18..000000000 --- a/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SOGoACLFrenchAdditionAdvisory.h - this file is part of SOGo - * - * Copyright (C) 2007 Inverse groupe conseil - * - * Author: Wolfgang Sourdeau - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef SOGOACLFRENCHADDITIONADVISORY_H -#define SOGOACLFRENCHADDITIONADVISORY_H - -#import "SOGoACLAdvisory.h" - -@interface SOGoACLFrenchAdditionAdvisory : SOGoACLAdvisory -@end - -#endif /* SOGOACLFRENCHADDITIONADVISORY_H */ diff --git a/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.m b/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.m deleted file mode 100644 index 229dcbc95..000000000 --- a/UI/SOGoUI/SOGoACLFrenchAdditionAdvisory.m +++ /dev/null @@ -1,27 +0,0 @@ -/* SOGoACLFrenchAdditionAdvisory.m - this file is part of SOGo - * - * Copyright (C) 2007 Inverse groupe conseil - * - * Author: Wolfgang Sourdeau - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import "SOGoACLFrenchAdditionAdvisory.h" - -@implementation SOGoACLFrenchAdditionAdvisory - -@end diff --git a/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.h b/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.h deleted file mode 100644 index 9a6c9634d..000000000 --- a/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SOGoACLFrenchRemovalAdvisory.h - this file is part of SOGo - * - * Copyright (C) 2007 Inverse groupe conseil - * - * Author: Wolfgang Sourdeau - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef SOGOACLFRENCHREMOVALADVISORY_H -#define SOGOACLFRENCHREMOVALADVISORY_H - -#import "SOGoACLAdvisory.h" - -@interface SOGoACLFrenchRemovalAdvisory : SOGoACLAdvisory -@end - -#endif /* SOGOACLFRENCHREMOVALADVISORY_H */ diff --git a/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.m b/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.m deleted file mode 100644 index 050d2cfca..000000000 --- a/UI/SOGoUI/SOGoACLFrenchRemovalAdvisory.m +++ /dev/null @@ -1,27 +0,0 @@ -/* SOGoACLFrenchRemovalAdvisory.m - this file is part of SOGo - * - * Copyright (C) 2007 Inverse groupe conseil - * - * Author: Wolfgang Sourdeau - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This file 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#import "SOGoACLFrenchRemovalAdvisory.h" - -@implementation SOGoACLFrenchRemovalAdvisory - -@end diff --git a/UI/SOGoUI/SOGoFolderAdvisory.h b/UI/SOGoUI/SOGoFolderAdvisory.h new file mode 100644 index 000000000..3485d4c6a --- /dev/null +++ b/UI/SOGoUI/SOGoFolderAdvisory.h @@ -0,0 +1,68 @@ +/* SOGoFolderAdvisory.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOFOLDERADVISORY_H +#define SOGOFOLDERADVISORY_H + +#import "UIxComponent.h" +#import "../../SoObjects/SOGo/SOGoFolder.h" + +@interface SOGoFolderAdvisory : UIxComponent +{ + NSString *recipientUID; + SOGoFolder *folderObject; + BOOL isSubject; + BOOL isBody; +} + +- (void) setFolderObject: (SOGoFolder *) theFolder; +- (void) setRecipientUID: (NSString *) newRecipientUID; +- (void) send; + +- (BOOL) isSubject; +- (BOOL) isBody; + +- (NSString *) subject; +- (NSString *) body; +- (NSString *) folderMethod; + +@end + +@interface SOGoFolderEnglishAdditionAdvisory : SOGoFolderAdvisory +@end + +@interface SOGoFolderEnglishRemovalAdvisory : SOGoFolderAdvisory +@end + +@interface SOGoFolderFrenchAdditionAdvisory : SOGoFolderAdvisory +@end + +@interface SOGoFolderFrenchRemovalAdvisory : SOGoFolderAdvisory +@end + +@interface SOGoFolderGermanAdditionAdvisory : SOGoFolderAdvisory +@end + +@interface SOGoFolderGermanRemovalAdvisory : SOGoFolderAdvisory +@end + +#endif /* SOGOFOLDERADVISORY_H */ diff --git a/UI/SOGoUI/SOGoFolderAdvisory.m b/UI/SOGoUI/SOGoFolderAdvisory.m new file mode 100644 index 000000000..45a1e74eb --- /dev/null +++ b/UI/SOGoUI/SOGoFolderAdvisory.m @@ -0,0 +1,232 @@ +/* SOGoFolderAdvisory.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Ludovic Marcotte + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import "SOGoFolderAdvisory.h" + +@implementation SOGoFolderAdvisory + +- (id) init +{ + if ((self = [super init])) + { + recipientUID = nil; + folderObject = nil; + isSubject = NO; + isBody = NO; + } + + return self; +} + +- (void) dealloc +{ + [recipientUID release]; + [folderObject release]; + [super dealloc]; +} + +- (void) setFolderObject: (SOGoFolder *) theFolder +{ + ASSIGN(folderObject, theFolder); +} + +- (void) setRecipientUID: (NSString *) newRecipientUID +{ + ASSIGN (recipientUID, newRecipientUID); +} + +- (BOOL) isSubject +{ + return isSubject; +} + +- (BOOL) isBody +{ + return isBody; +} + +- (NSString *) displayName +{ + return [folderObject displayName]; +} + +- (NSString *) httpFolderURL +{ + NSString *absoluteString; + NSMutableString *url; + +#warning the url returned by SOGoMail may be empty, we need to handle that + absoluteString = [[folderObject soURL] absoluteString]; + url = [NSMutableString stringWithString: absoluteString]; + + if (![url hasSuffix: @"/"]) + [url appendString: @"/"]; + + return url; +} + +- (NSString *) subject +{ + NSString *subject; + + isSubject = YES; + subject = [[self generateResponse] contentAsString]; + isSubject = NO; + + return [[subject stringByTrimmingSpaces] asQPSubjectString: @"utf-8"]; +} + +- (NSString *) body +{ + NSString *body; + + isBody = YES; + body = [[self generateResponse] contentAsString]; + isBody = NO; + + return [body stringByTrimmingSpaces]; +} + +- (NSString *) folderMethod +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (NGMimeBodyPart *) _textPart +{ + NGMutableHashMap *headerMap; + NGMimeBodyPart *part; + NSData *body; + + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject: @"text/plain; charset=utf-8" forKey: @"content-type"]; + + part = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + body = [[self body] dataUsingEncoding: NSUTF8StringEncoding]; + [part setBody: [self body]]; + + return part; +} + +- (NGMimeBodyPart *) _sogoNotificationPart +{ + NGMutableHashMap *headerMap; + NGMimeBodyPart *part; + NSData *body; + + /* calendar part */ + headerMap = [NGMutableHashMap hashMapWithCapacity: 1]; + [headerMap setObject: [NSString stringWithFormat: + @"%@; method=%@; type=%@; charset=%@", + @"application/x-sogo-notification", + [self folderMethod], [folderObject folderType], + @"utf-8"] + forKey: @"content-type"]; + + part = [NGMimeBodyPart bodyPartWithHeader: headerMap]; + body = [[self httpFolderURL] dataUsingEncoding: NSUTF8StringEncoding]; + [part setBody: body]; + + return part; +} + +- (void) send +{ + NSString *recipient, *date; + NGMutableHashMap *headerMap; + NGMimeMessage *message; + NGMimeMultipartBody *body; + SOGoUser *activeUser; + NSDictionary *identity; + NSString *from, *fullMail; + + activeUser = [context activeUser]; + identity = [activeUser primaryIdentity]; + from = [identity objectForKey: @"email"]; + fullMail = [NSString stringWithFormat: @"%@ <%@>", + [identity objectForKey: @"fullName"], from]; + + recipient = [[LDAPUserManager sharedUserManager] + getFullEmailForUID: recipientUID]; + + headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; + [headerMap setObject: @"multipart/alternative" forKey: @"content-type"]; + [headerMap setObject: fullMail forKey: @"From"]; + [headerMap setObject: recipient forKey: @"To"]; + date = [[NSCalendarDate date] rfc822DateString]; + [headerMap setObject: date forKey: @"Date"]; + [headerMap setObject: [self subject] forKey: @"Subject"]; + message = [NGMimeMessage messageWithHeader: headerMap]; + + body = [[NGMimeMultipartBody alloc] initWithPart: message]; + [body addBodyPart: [self _textPart]]; + [body addBodyPart: [self _sogoNotificationPart]]; + [message setBody: body]; + [body release]; + + [[SOGoMailer sharedMailer] sendMimePart: message + toRecipients: [NSArray arrayWithObject: recipient] + sender: from]; +} + +@end + +@implementation SOGoFolderEnglishAdditionAdvisory +- (NSString *) folderMethod { return @"add"; } +@end + +@implementation SOGoFolderEnglishRemovalAdvisory +- (NSString *) folderMethod { return @"remove"; } +@end + +@implementation SOGoFolderFrenchAdditionAdvisory +- (NSString *) folderMethod { return @"add"; } +@end + +@implementation SOGoFolderFrenchRemovalAdvisory +- (NSString *) folderMethod { return @"remove"; } +@end + +@implementation SOGoFolderGermanAdditionAdvisory +- (NSString *) folderMethod { return @"add"; } +@end + +@implementation SOGoFolderGermanRemovalAdvisory +- (NSString *) folderMethod { return @"remove"; } +@end diff --git a/UI/SOGoUI/UIxComponent.h b/UI/SOGoUI/UIxComponent.h index ff5996842..f0c019083 100644 --- a/UI/SOGoUI/UIxComponent.h +++ b/UI/SOGoUI/UIxComponent.h @@ -68,26 +68,25 @@ /* date selection */ - (NSCalendarDate *) selectedDate; -- (NSString *)dateStringForDate:(NSCalendarDate *)_date; +- (NSString *) dateStringForDate: (NSCalendarDate *)_date; - (BOOL) hideFrame; - (UIxComponent *) jsCloseWithRefreshMethod: (NSString *) methodName; /* SoUser */ -- (SoUser *)user; -- (NSString *)shortUserNameForDisplay; +- (NSString *) shortUserNameForDisplay; /* labels */ -- (NSString *)labelForKey:(NSString *)_key; +- (NSString *) labelForKey:(NSString *)_key; -- (NSString *)localizedNameForDayOfWeek:(unsigned)_dayOfWeek; -- (NSString *)localizedAbbreviatedNameForDayOfWeek:(unsigned)_dayOfWeek; -- (NSString *)localizedNameForMonthOfYear:(unsigned)_monthOfYear; -- (NSString *)localizedAbbreviatedNameForMonthOfYear:(unsigned)_monthOfYear; +- (NSString *) localizedNameForDayOfWeek:(unsigned)_dayOfWeek; +- (NSString *) localizedAbbreviatedNameForDayOfWeek:(unsigned)_dayOfWeek; +- (NSString *) localizedNameForMonthOfYear:(unsigned)_monthOfYear; +- (NSString *) localizedAbbreviatedNameForMonthOfYear:(unsigned)_monthOfYear; /* HTTP method safety */ -- (BOOL)isInvokedBySafeMethod; +- (BOOL) isInvokedBySafeMethod; /* locale */ - (NSDictionary *)locale; @@ -96,6 +95,8 @@ - (WOResourceManager *) pageResourceManager; - (NSString *) urlForResourceFilename: (NSString *) filename; +- (WOResponse *) responseWith204; + /* Debugging */ - (BOOL)isUIxDebugEnabled; diff --git a/UI/SOGoUI/UIxComponent.m b/UI/SOGoUI/UIxComponent.m index e0b7f2cab..273d3e20b 100644 --- a/UI/SOGoUI/UIxComponent.m +++ b/UI/SOGoUI/UIxComponent.m @@ -199,7 +199,7 @@ static BOOL uixDebugEnabled = NO; queryParameters = [[NSMutableDictionary alloc] initWithCapacity:8]; - req = [[self context] request]; + req = [context request]; uri = [req uri]; r = [uri rangeOfString:@"?" options:NSBackwardsSearch]; if (r.length > 0) @@ -246,7 +246,7 @@ static BOOL uixDebugEnabled = NO; qp = [self queryParameters]; if ([qp count] > 0) { - ctx = [self context]; + ctx = context; qps = [ctx queryPathSeparator]; [ctx setQueryPathSeparator: @"&"]; qs = [ctx queryStringFromDictionary: qp]; @@ -264,7 +264,7 @@ static BOOL uixDebugEnabled = NO; NSString *uri; NSRange r; - uri = [[[self context] request] uri]; + uri = [[context request] uri]; /* first: cut off query parameters */ @@ -293,7 +293,7 @@ static BOOL uixDebugEnabled = NO; SOGoObject *currentObject; BOOL found; - ctx = [self context]; + ctx = context; objects = [[ctx objectTraversalStack] objectEnumerator]; currentObject = [objects nextObject]; found = NO; @@ -312,28 +312,31 @@ static BOOL uixDebugEnabled = NO; SOGoObject *currentClient, *parent; BOOL found; Class objectClass, groupFolderClass, userFolderClass; - WOContext *ctx; - - groupFolderClass = [SOGoCustomGroupFolder class]; - userFolderClass = [SOGoUserFolder class]; currentClient = [self clientObject]; - objectClass = [currentClient class]; - found = (objectClass == groupFolderClass || objectClass == userFolderClass); - while (!found && currentClient) + if (currentClient + && [currentClient isKindOfClass: [SOGoObject class]]) { - parent = [currentClient container]; - objectClass = [parent class]; - if (objectClass == groupFolderClass - || objectClass == userFolderClass) - found = YES; - else - currentClient = parent; + groupFolderClass = [SOGoCustomGroupFolder class]; + userFolderClass = [SOGoUserFolder class]; + + objectClass = [currentClient class]; + found = (objectClass == groupFolderClass || objectClass == userFolderClass); + while (!found && currentClient) + { + parent = [currentClient container]; + objectClass = [parent class]; + if (objectClass == groupFolderClass + || objectClass == userFolderClass) + found = YES; + else + currentClient = parent; + } } + else + currentClient = [WOApplication application]; - ctx = [self context]; - - return [[currentClient baseURLInContext:ctx] hostlessURL]; + return [[currentClient baseURLInContext: context] hostlessURL]; } - (NSString *) resourcesPath @@ -351,7 +354,7 @@ static BOOL uixDebugEnabled = NO; NSString *uri; NSRange r; - uri = [[[self context] request] uri]; + uri = [[context request] uri]; /* first: cut off query parameters */ @@ -397,7 +400,7 @@ static BOOL uixDebugEnabled = NO; userTimeZone = [[context activeUser] timeZone]; [_date setTimeZone: userTimeZone]; - return [_date descriptionWithCalendarFormat:@"%Y%m%d"]; + return [_date descriptionWithCalendarFormat: @"%Y%m%d"]; } - (BOOL) hideFrame @@ -418,35 +421,12 @@ static BOOL uixDebugEnabled = NO; /* SoUser */ -- (SoUser *) user -{ - WOContext *ctx; - - ctx = [self context]; - - return [[[self clientObject] authenticatorInContext: ctx] userInContext: ctx]; -} - - (NSString *) shortUserNameForDisplay { - // TODO: better use a SoUser formatter? - // TODO: who calls that? - NSString *s; - NSRange r; - - // TODO: USE USER MANAGER INSTEAD! - - s = [[self user] login]; - if ([s length] < 10) - return s; - - // TODO: algorithm might be inappropriate, depends on the actual UID - - r = [s rangeOfString:@"."]; - if (r.length == 0) - return s; - - return [s substringToIndex:r.location]; + if ([context activeUser] == nil) + return @"wrongusernamepassword"; + + return [[context activeUser] login]; } /* labels */ @@ -463,7 +443,7 @@ static BOOL uixDebugEnabled = NO; /* lookup languages */ - languages = [[self context] resourceLookupLanguages]; + languages = [context resourceLookupLanguages]; /* find resource manager */ @@ -530,7 +510,7 @@ static BOOL uixDebugEnabled = NO; // TODO: move to WORequest? NSString *m; - m = [[[self context] request] method]; + m = [[context request] method]; if ([m isEqualToString:@"GET"]) return YES; if ([m isEqualToString:@"HEAD"]) return YES; return NO; @@ -547,7 +527,7 @@ static BOOL uixDebugEnabled = NO; { WOResourceManager *rm; - if ((rm = [[[self context] page] resourceManager]) == nil) + if ((rm = [[context page] resourceManager]) == nil) rm = [[WOApplication application] resourceManager]; return rm; @@ -570,12 +550,12 @@ static BOOL uixDebugEnabled = NO; if (!url) { rm = [self pageResourceManager]; - page = [[self context] page]; + page = [context page]; pageBundle = [NSBundle bundleForClass: [page class]]; url = [rm urlForResourceNamed: filename inFramework: [pageBundle bundlePath] languages: nil - request: [[self context] request]]; + request: [context request]]; if (!url) url = @""; else @@ -592,6 +572,16 @@ static BOOL uixDebugEnabled = NO; return url; } +- (WOResponse *) responseWith204 +{ + WOResponse *response; + + response = [context response]; + [response setStatus: 204]; + + return response; +} + /* debugging */ - (BOOL)isUIxDebugEnabled { diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index f49a55a92..69afbd28c 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -1,5 +1,16 @@ /* this file is in UTF-8 format! */ +/* Tooltips */ + +"Create a new event" = "Create a new event"; +"Create a new task" = "Create a new task"; +"Edit this event or task" = "Edit this event or task"; +"Delete this event or task" = "Delete this event or task"; +"Go to today" = "Go to today"; +"Switch to day view" = "Switch to day view"; +"Switch to week view" = "Switch to week view"; +"Switch to month view" = "Switch to month view"; + /* Day */ "Sunday" = "Sunday"; @@ -99,8 +110,11 @@ /* Button Titles */ -"Add..." = "Add..."; -"Remove" = "Remove"; +"New Calendar..." = "New Calendar..."; +"Subscribe to a Calendar..." = "Subscribe to a Calendar..."; +"Remove the selected Calendar" = "Remove the selected Calendar"; + +"Name of the Calendar" = "Name of the Calendar"; "new" = "New"; "printview" = "Print View"; @@ -365,6 +379,9 @@ validate_endbeforestart = "Enddate is before startdate!"; "eventDeleteConfirmation" = "Erasing this event will be permanent.\\nWould you like to continue?"; "taskDeleteConfirmation" = "Erasing this task will be permanent.\\nWould you like to continue?"; +"Are you sure you want to delete the selected calendar?" += "Are you sure you want to delete the selected calendar?"; + /* Legend */ "Required participant" = "Required participant"; "Optional participant" = "Optional participant"; @@ -393,5 +410,5 @@ validate_endbeforestart = "Enddate is before startdate!"; "Location" = "Location"; "(Private Event)" = "(Private Event)"; -"closeThisWindowMessage" = "Thank you! You may now close this window."; +"closeThisWindowMessage" = "Thank you! You may now close this window or view your "; "Multicolumn Day View" = "Multicolumn Day View"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 9fb1d9fcc..3adda5cff 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -1,5 +1,16 @@ /* this file is in UTF-8 format! */ +/* Tooltips */ + +"Create a new event" = "Créer un nouvel événement"; +"Create a new task" = "Créer une nouvelle tâche"; +"Edit this event or task" = "Modifier l'événement ou la tâche sélectionnée"; +"Delete this event or task" = "Supprimer l'événement ou la tâche sélectionnée"; +"Go to today" = "Aller à la journée d'aujourd'hui"; +"Switch to day view" = "Passer à la vue par jour"; +"Switch to week view" = "Passer à la vue par semaine"; +"Switch to month view" = "Passer à la vue par mois"; + /* Day */ "Sunday" = "Dimanche"; @@ -100,8 +111,11 @@ /* Button Titles */ -"Add..." = "Ajouter..."; -"Remove" = "Enlever"; +"New Calendar..." = "Nouvel agenda..."; +"Subscribe to a Calendar..." = "S'inscrire à un agenda..."; +"Remove the selected Calendar" = "Enlever l'agenda sélectionné"; + +"Name of the Calendar" = "Nom de l'agenda"; "new" = "Nouveau"; "printview" = "Version imprimable"; @@ -363,6 +377,9 @@ validate_endbeforestart = "La date de fin est avant la date de début !"; "eventDeleteConfirmation" = "L'effacement de cet événement sera permanent.\\nVoulez-vous continuer?"; "taskDeleteConfirmation" = "L'effacement de cette tâche sera permanent.\\nVoulez-vous continuer?"; +"Are you sure you want to delete the selected calendar?" += "Voulez-vous vraiment supprimer l'agenda sélectionné ?"; + /* Legend */ "Required participant" = "Participant obligatoire"; "Optional participant" = "Participant facultatif"; @@ -392,5 +409,5 @@ validate_endbeforestart = "La date de fin est avant la date de début !"; "Location" = "Lieu"; "(Private Event)" = "(Événement privé)"; -"closeThisWindowMessage" = "Merci! Vous pouvez maintenant fermer cette fenêtre."; +"closeThisWindowMessage" = "Merci! Vous pouvez maintenant fermer cette fenêtre ou consulter votre "; "Multicolumn Day View" = "Multicolonne"; diff --git a/UI/Scheduler/GNUmakefile b/UI/Scheduler/GNUmakefile index 0089f139a..7ef2b6158 100644 --- a/UI/Scheduler/GNUmakefile +++ b/UI/Scheduler/GNUmakefile @@ -48,7 +48,7 @@ SchedulerUI_RESOURCE_FILES += \ product.plist SchedulerUI_RESOURCE_FILES += \ - Toolbars/SOGoAppointmentFolder.toolbar \ + Toolbars/SOGoAppointmentFolders.toolbar \ Toolbars/SOGoAppointmentObject.toolbar \ Toolbars/SOGoAppointmentObjectAccept.toolbar \ Toolbars/SOGoAppointmentObjectDecline.toolbar \ @@ -66,6 +66,9 @@ SchedulerUI_RESOURCE_FILES += \ # make +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/ + -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/bundle.make -include GNUmakefile.postamble diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 8fab1c290..2093ad21d 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -71,7 +71,7 @@ "New Calendar..." = "Neuer Kalender..."; "Delete Calendar" = "Kalender löschen"; -"Sharing..." = "Partage..."; +"Sharing..." = "Benutzerrechte..."; "Export Calendar..." = "Kalender exportieren..."; "Publish Calendar..." = "Kalender publizieren..."; "Reload Remote Calendars" = "Externe Kalender neu laden"; @@ -83,27 +83,30 @@ "Forbidden" = "Accès non autorisée"; /* acls */ -"Default Roles" = "Rôles par défaut"; -"User rights for:" = "Autorisations pour :"; +"Default Roles" = "Standard-Rollen"; +"User rights for:" = "Benutzerrechte für:"; "label_Public" = "Öffentlich"; "label_Private" = "Privat"; "label_Confidential" = "Vertraulich"; -"View All" = "Voir tout"; -"View the Date & Time" = "Voir la date & l'heure"; -"Modify" = "Modifier"; -"Respond To" = "Répondre"; -"None" = "Aucun"; +"View All" = "Alles sehen"; +"View the Date & Time" = "Datum & Uhrzeit sehen"; +"Modify" = "Ändern"; +"Respond To" = "Antworten"; +"None" = "Keine"; "This person can create objects in my calendar." -= "Cette personne peut ajouter des objets à mon agenda."; += "Diese Person kann Termine in meinem Kalender hinzufügen."; "This person can erase objects from my calendar." -= "Cette personne peut effacer des objets de mon agenda."; += "Diese Person kann Termine in meinem Kalender löschen."; /* Button Titles */ -"Add..." = "Ajouter..."; -"Remove" = "Enlever"; +"New Calendar..." = "New Calendar..."; +"Subscribe to a Calendar..." = "Subscribe to a Calendar..."; +"Remove the selected Calendar" = "Remove the selected Calendar"; -"new" = "Nouveau"; +"Name of the Calendar" = "Name of the Calendar"; + +"new" = "Neu"; "printview" = "Version imprimable"; "edit" = "Bearbeiten"; "delete" = "Löschen"; @@ -112,7 +115,7 @@ "Close" = "Schließen"; "Invite Attendees" = "Teilnehmer"; "Documents" = "Dokumente"; -"Update" = "Mettre à jour"; +"Update" = "Speichern"; "Cancel" = "Abbrechen"; "show_rejected_apts" = "Afficher les rendez-vous refusés"; "hide_rejected_apts" = "Cacher les rendez-vous refusés"; @@ -363,6 +366,9 @@ validate_endbeforestart = "Ihr Beginn ist nach dem Ende"; "eventDeleteConfirmation" = "L'effacement de cet événement sera permanent.\\nVoulez-vous continuer?"; "taskDeleteConfirmation" = "L'effacement de cette tâche sera permanent.\\nVoulez-vous continuer?"; +"Are you sure you want to delete the selected calendar?" += "Are you sure you want to delete the selected calendar?"; + /* Legend */ "Required participant" = "Participant obligatoire"; "Optional participant" = "Participant facultatif"; diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentFolder.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar similarity index 60% rename from UI/Scheduler/Toolbars/SOGoAppointmentFolder.toolbar rename to UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar index 296c30696..b50fa350e 100644 --- a/UI/Scheduler/Toolbars/SOGoAppointmentFolder.toolbar +++ b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar @@ -3,39 +3,48 @@ isSafe = NO; label = "New Event"; onclick = "return newEvent(this, 'event');"; - image = "new-event.png"; }, + image = "new-event.png"; + tooltip = "Create a new event"; }, { link = "new_task"; label="New Task"; image = "new-task.png"; onclick = "return newEvent(this, 'task');"; - image = "new-task.png"; }, + image = "new-task.png"; + tooltip = "Create a new task"; }, { link = "edit"; label="Edit"; onclick = "return editEvent(this);"; - image = "edit.png"; }, + image = "edit.png"; + tooltip = "Edit this event or task"; }, { link = "delete"; label="Delete"; onclick = "return deleteEvent(this);"; - image = "delete.png"; } ), + image = "delete.png"; + tooltip = "Delete this event or task"; } ), ( { link = "today"; label="Go to Today"; onclick = "return gotoToday();"; - image = "goto-today.png" } ), + image = "goto-today.png"; + tooltip = "Go to today"; } ), ( { link = "dayoverview"; label="Day View"; onclick = "return onDayOverview();"; - image = "day-view.png"; }, + image = "day-view.png"; + tooltip = "Switch to day view"; }, /* disabled until we fix the view */ /* { link = "dayoverview"; label="Multicolumn Day View"; onclick = "return onMulticolumnDayOverview();"; - image = "day-view-multicolumn.png"; }, */ + image = "day-view-multicolumn.png"; + tooltip = ""; }, */ { link = "weekoverview"; label="Week View"; onclick = "return onWeekOverview();"; - image = "week-view.png"; }, + image = "week-view.png"; + tooltip = "Switch to week view"; }, { link = "monthoverview"; label="Month View"; onclick = "return onMonthOverview();"; - image = "month-view.png"; } ) + image = "month-view.png"; + tooltip = "Switch to month view"; } ) ) diff --git a/UI/Scheduler/UIxAppointmentEditor.h b/UI/Scheduler/UIxAppointmentEditor.h index e90745500..c3ead6856 100644 --- a/UI/Scheduler/UIxAppointmentEditor.h +++ b/UI/Scheduler/UIxAppointmentEditor.h @@ -35,6 +35,7 @@ NSCalendarDate *aptStartDate; NSCalendarDate *aptEndDate; NSString *item; + NSString *repeat; } /* template values */ diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 365cab4e3..de4e0c472 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -29,6 +29,7 @@ #import #import +#import #import #import @@ -48,12 +49,22 @@ aptEndDate = nil; item = nil; event = nil; + repeat = nil; isAllDay = NO; } return self; } +- (void) dealloc +{ + [item release]; + [repeat release]; + [aptStartDate release]; + [aptEndDate release]; + [super dealloc]; +} + /* template values */ - (iCalEvent *) event { @@ -137,7 +148,7 @@ - (void) setItem: (NSString *) newItem { - item = newItem; + ASSIGN (item, newItem); } - (NSString *) item @@ -184,6 +195,15 @@ // return reminder; // } +- (NSString *) reminder +{ + return @""; +} + +- (void) setReminder: (NSString *) newReminder +{ +} + - (NSString *) itemReminderText { NSString *text; @@ -198,20 +218,12 @@ - (NSString *) repeat { - return @""; + return repeat; } - (void) setRepeat: (NSString *) newRepeat { -} - -- (NSString *) reminder -{ - return @""; -} - -- (void) setReminder: (NSString *) newReminder -{ + ASSIGN (repeat, newRepeat); } /* actions */ @@ -249,6 +261,7 @@ NSCalendarDate *startDate, *endDate; NSString *duration; unsigned int minutes; + iCalRecurrenceRule *rule; event = (iCalEvent *) [[self clientObject] component: NO]; if (event) @@ -276,8 +289,40 @@ ASSIGN (aptStartDate, startDate); ASSIGN (aptEndDate, endDate); + // We initialize our repeat ivars + if ([event hasRecurrenceRules]) + { + repeat = @"CUSTOM"; - /* here comes the code for initializing repeat, reminder and isAllDay... */ + rule = [[event recurrenceRules] lastObject]; + + if ([rule frequency] == iCalRecurrenceFrequenceWeekly) + { + if ([rule repeatInterval] == 1) + repeat = @"WEEKLY"; + else if ([rule repeatInterval] == 2) + repeat = @"BI-WEEKLY"; + } + else if ([rule frequency] == iCalRecurrenceFrequenceDaily) + { + if ([rule byDayMask] == (iCalWeekDayMonday + | iCalWeekDayTuesday + | iCalWeekDayWednesday + | iCalWeekDayThursday + | iCalWeekDayFriday)) + repeat = @"EVERY WEEKDAY"; + else if (![rule byDayMask]) + repeat = @"DAILY"; + } + else if ([rule frequency] == iCalRecurrenceFrequenceMonthly + && [rule repeatInterval] == 1) + repeat = @"MONTHLY"; + else if ([rule frequency] == iCalRecurrenceFrequenceYearly + && [rule repeatInterval] == 1) + repeat = @"YEARLY"; + } + else + DESTROY(repeat); return self; } @@ -286,14 +331,14 @@ { NSString *objectId, *method, *uri; id result; - Class clientKlazz; + SOGoAppointmentFolder *co; - clientKlazz = [[self clientObject] class]; - objectId = [clientKlazz globallyUniqueObjectId]; + co = [self clientObject]; + objectId = [co globallyUniqueObjectId]; if ([objectId length] > 0) { - method = [NSString stringWithFormat:@"%@/Calendar/%@/editAsAppointment", - [self userFolderPath], objectId]; + method = [NSString stringWithFormat:@"%@/%@/editAsAppointment", + [co soURL], objectId]; uri = [self completeHrefForMethod: method]; result = [self redirectToLocation: uri]; } @@ -310,7 +355,11 @@ NSString *iCalString; clientObject = [self clientObject]; + NSLog(@"saveAction, clientObject = %@", clientObject); + iCalString = [[clientObject calendar: NO] versitString]; + + NSLog(@"saveAction, iCalString = %@", iCalString); [clientObject saveContentString: iCalString]; return [self jsCloseWithRefreshMethod: @"refreshEventsAndDisplay()"]; @@ -319,8 +368,12 @@ - (BOOL) shouldTakeValuesFromRequest: (WORequest *) request inContext: (WOContext*) context { + NSString *actionName; + + actionName = [[request requestHandlerPath] lastPathComponent]; + return ([[self clientObject] isKindOfClass: [SOGoAppointmentObject class]] - && [[request method] isEqualToString: @"POST"]); + && [actionName hasPrefix: @"save"]); } - (void) takeValuesFromRequest: (WORequest *) _rq @@ -328,6 +381,7 @@ { SOGoAppointmentObject *clientObject; int nbrDays; + iCalRecurrenceRule *rule; clientObject = [self clientObject]; event = (iCalEvent *) [clientObject component: YES]; @@ -348,6 +402,36 @@ } if ([clientObject isNew]) [event setTransparency: @"OPAQUE"]; + + // We remove any repeat rules + if (!repeat && [event hasRecurrenceRules]) + [event removeAllRecurrenceRules]; + else if (!([repeat caseInsensitiveCompare: @"-"] == NSOrderedSame + || [repeat caseInsensitiveCompare: @"CUSTOM"] == NSOrderedSame)) + { + rule = [iCalRecurrenceRule new]; + + [rule setInterval: @"1"]; + if ([repeat caseInsensitiveCompare: @"BI-WEEKLY"] == NSOrderedSame) + { + [rule setFrequency: iCalRecurrenceFrequenceWeekly]; + [rule setInterval: @"2"]; + } + else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame) + { + [rule setByDayMask: (iCalWeekDayMonday + |iCalWeekDayTuesday + |iCalWeekDayWednesday + |iCalWeekDayThursday + |iCalWeekDayFriday)]; + [rule setFrequency: iCalRecurrenceFrequenceDaily]; + } + else + [rule setFrequency: + (iCalRecurrenceFrequency) [rule valueForFrequency: repeat]]; + [event setRecurrenceRules: [NSArray arrayWithObject: rule]]; + [rule release]; + } } // TODO: add tentatively diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 03f647f9d..56a608294 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -25,6 +25,7 @@ #import #import #import +#import #import #import @@ -38,6 +39,9 @@ #import #import #import +#import + +#import #import "NSArray+Scheduler.h" @@ -212,87 +216,56 @@ return aptFolder; } -- (NSArray *) _activeCalendarFolders -{ - NSMutableArray *activeFolders; - NSEnumerator *folders; - NSDictionary *currentFolderDict; - SOGoAppointmentFolder *currentFolder, *clientObject; - - activeFolders = [NSMutableArray new]; - [activeFolders autorelease]; - - clientObject = [self clientObject]; - - folders = [[clientObject calendarFolders] objectEnumerator]; - currentFolderDict = [folders nextObject]; - while (currentFolderDict) - { - if ([[currentFolderDict objectForKey: @"active"] boolValue]) - { - currentFolder - = [self _aptFolder: [currentFolderDict objectForKey: @"folder"] - withClientObject: clientObject]; - [activeFolders addObject: currentFolder]; - } - - currentFolderDict = [folders nextObject]; - } - - return activeFolders; -} - - (NSArray *) _fetchFields: (NSArray *) fields forComponentOfType: (NSString *) component { NSEnumerator *folders, *currentInfos; SOGoAppointmentFolder *currentFolder; - NSMutableDictionary *infos, *currentInfo, *newInfo; - NSString *owner, *uid; + NSMutableDictionary *newInfo; + NSMutableArray *infos; NSNull *marker; + SOGoAppointmentFolders *clientObject; marker = [NSNull null]; - infos = [NSMutableDictionary dictionary]; - folders = [[self _activeCalendarFolders] objectEnumerator]; + clientObject = [self clientObject]; + + folders = [[clientObject subFolders] objectEnumerator]; currentFolder = [folders nextObject]; + + infos = [NSMutableArray array]; while (currentFolder) { - owner = [currentFolder ownerInContext: context]; - currentInfos = [[currentFolder fetchCoreInfosFrom: startDate - to: endDate - component: component] objectEnumerator]; - newInfo = [currentInfos nextObject]; - while (newInfo) + if ([currentFolder isActive]) { - uid = [newInfo objectForKey: @"c_uid"]; - currentInfo = [infos objectForKey: uid]; - if (!currentInfo - || [owner isEqualToString: userLogin]) + currentInfos = [[currentFolder fetchCoreInfosFrom: startDate + to: endDate + component: component] objectEnumerator]; + + while ((newInfo = [currentInfos nextObject])) { [self _updatePrivacyInComponent: newInfo fromFolder: currentFolder]; - [newInfo setObject: owner forKey: @"c_owner"]; - [infos setObject: [newInfo objectsForKeys: fields - notFoundMarker: marker] - forKey: uid]; + [newInfo setObject: [currentFolder nameInContainer] + forKey: @"c_folder"]; + + [infos addObject: [newInfo objectsForKeys: fields + notFoundMarker: marker]]; } - newInfo = [currentInfos nextObject]; } currentFolder = [folders nextObject]; } - return [infos allValues]; + return infos; } - (WOResponse *) _responseWithData: (NSArray *) data { WOResponse *response; - response = [context response]; + response = [self responseWithStatus: 200]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"content-type"]; - [response setStatus: 200]; [response appendContentString: [data jsonRepresentation]]; return response; @@ -325,7 +298,7 @@ [self _setupContext]; newEvents = [NSMutableArray array]; - fields = [NSArray arrayWithObjects: @"c_name", @"c_owner", @"c_status", + fields = [NSArray arrayWithObjects: @"c_name", @"c_folder", @"c_status", @"c_title", @"c_startdate", @"c_enddate", @"c_location", @"c_isallday", nil]; events = [[self _fetchFields: fields @@ -396,7 +369,7 @@ [self _setupContext]; - fields = [NSArray arrayWithObjects: @"c_name", @"c_owner", @"c_status", + fields = [NSArray arrayWithObjects: @"c_name", @"c_folder", @"c_status", @"c_title", @"c_enddate", nil]; tasks = [[self _fetchFields: fields diff --git a/UI/Scheduler/UIxCalMainView.m b/UI/Scheduler/UIxCalMainView.m index 80b66f66d..a7c0126f0 100644 --- a/UI/Scheduler/UIxCalMainView.m +++ b/UI/Scheduler/UIxCalMainView.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -41,6 +42,15 @@ static NSMutableArray *yearMenuItems = nil; @implementation UIxCalMainView +- (NSString *) userUTCOffset +{ + NSTimeZone *userTZ; + + userTZ = [[context activeUser] timeZone]; + + return [NSString stringWithFormat: @"%d", [userTZ secondsFromGMT]]; +} + - (NSArray *) monthMenuItems { unsigned int count; @@ -99,19 +109,4 @@ static NSMutableArray *yearMenuItems = nil; return yearMenuItem; } -- (id) batchDeleteAction -{ - NSArray *ids; - SOGoAppointmentFolder *clientObject; - - ids = [[self queryParameterForKey: @"ids"] componentsSeparatedByString: @"/"]; - if (ids) - { - clientObject = [self clientObject]; - [clientObject deleteEntriesWithIds: ids]; - } - - return self; -} - @end diff --git a/UI/Scheduler/UIxCalendarSelector.h b/UI/Scheduler/UIxCalendarSelector.h index 410ce6dbb..21feda2f8 100644 --- a/UI/Scheduler/UIxCalendarSelector.h +++ b/UI/Scheduler/UIxCalendarSelector.h @@ -23,29 +23,23 @@ #ifndef UIXCALENDARSELECTOR_H #define UIXCALENDARSELECTOR_H +#import + @class NSArray; @class NSMutableArray; @class NSDictionary; -@class NSMutableDictionary; @class NSString; -@class iCalPerson; @interface UIxCalendarSelector : UIxComponent { - NSMutableDictionary *colors; - - NSDictionary *currentCalendarFolder; - NSString *currentCalendarLogin; + NSMutableArray *calendars; + NSDictionary *currentCalendar; } -- (NSArray *) calendarFolders; +- (NSArray *) calendars; -- (void) setCurrentCalendarFolder: (NSDictionary *) newCurrentCalendarFolder; -- (NSDictionary *) currentCalendarFolder; - -- (NSString *) currentCalendarSpanBG; -- (NSString *) currentCalendarLogin; -- (NSString *) currentCalendarStyle; +- (void) setCurrentCalendar: (NSDictionary *) newCalendar; +- (NSDictionary *) currentCalendar; @end diff --git a/UI/Scheduler/UIxCalendarSelector.m b/UI/Scheduler/UIxCalendarSelector.m index 7205bfec7..6bee8aad6 100644 --- a/UI/Scheduler/UIxCalendarSelector.m +++ b/UI/Scheduler/UIxCalendarSelector.m @@ -22,34 +22,30 @@ #import #import -#import -#import +#import -#import -#import - -#import -#import +#import #import +#import #import "UIxCalendarSelector.h" -static inline char -darkenedColor (const char value) -{ - char newValue; +// static inline char +// darkenedColor (const char value) +// { +// char newValue; - if (value >= '0' && value <= '9') - newValue = ((value - '0') / 2) + '0'; - else if (value >= 'a' && value <= 'f') - newValue = ((value + 10 - 'a') / 2) + '0'; - else if (value >= 'A' && value <= 'F') - newValue = ((value + 10 - 'A') / 2) + '0'; - else - newValue = value; +// if (value >= '0' && value <= '9') +// newValue = ((value - '0') / 2) + '0'; +// else if (value >= 'a' && value <= 'f') +// newValue = ((value + 10 - 'a') / 2) + '0'; +// else if (value >= 'A' && value <= 'F') +// newValue = ((value + 10 - 'A') / 2) + '0'; +// else +// newValue = value; - return newValue; -} +// return newValue; +// } static inline NSString * colorForNumber (unsigned int number) @@ -90,8 +86,8 @@ colorForNumber (unsigned int number) { if ((self = [super init])) { - colors = nil; - currentCalendarFolder = nil; + calendars = nil; + currentCalendar = nil; } return self; @@ -99,76 +95,63 @@ colorForNumber (unsigned int number) - (void) dealloc { - [currentCalendarFolder release]; - [colors release]; + [calendars release]; + [currentCalendar release]; [super dealloc]; } -- (NSArray *) calendarFolders +- (NSArray *) calendars { - NSArray *calendarFolders; - NSEnumerator *newFolders; - NSDictionary *currentFolder; - unsigned int count; + NSArray *folders; + SOGoAppointmentFolder *folder; + NSMutableDictionary *calendar; + unsigned int count, max; + NSString *folderName; + NSNumber *isActive; - calendarFolders = [[self clientObject] calendarFolders]; - if (!colors) + if (!calendars) { - colors = [NSMutableDictionary new]; - count = 0; - newFolders = [calendarFolders objectEnumerator]; - currentFolder = [newFolders nextObject]; - while (currentFolder) + folders = [[self clientObject] subFolders]; + max = [folders count]; + calendars = [[NSMutableArray alloc] initWithCapacity: max]; + for (count = 0; count < max; count++) { - [colors setObject: colorForNumber (count) - forKey: [currentFolder objectForKey: @"folder"]]; - count++; - currentFolder = [newFolders nextObject]; + folder = [folders objectAtIndex: count]; + calendar = [NSMutableDictionary dictionary]; + folderName = [folder nameInContainer]; + [calendar setObject: + [NSString stringWithFormat: @"/%@", folderName] + forKey: @"id"]; + [calendar setObject: [folder displayName] + forKey: @"displayName"]; + [calendar setObject: folderName forKey: @"folder"]; + [calendar setObject: colorForNumber (count) + forKey: @"color"]; + isActive = [NSNumber numberWithBool: [folder isActive]]; + [calendar setObject: isActive forKey: @"active"]; + [calendar setObject: [folder ownerInContext: context] + forKey: @"owner"]; + [calendars addObject: calendar]; } } - return calendarFolders; + return calendars; } -- (void) setCurrentCalendarFolder: (NSDictionary *) newCurrentCalendarFolder +- (void) setCurrentCalendar: (NSDictionary *) newCalendar { - ASSIGN (currentCalendarFolder, newCurrentCalendarFolder); + ASSIGN (currentCalendar, newCalendar); } -- (NSDictionary *) currentCalendarFolder +- (NSDictionary *) currentCalendar { - return currentCalendarFolder; -} - -- (NSString *) currentCalendarSpanBG -{ - NSString *colorKey; - - colorKey = [currentCalendarFolder objectForKey: @"folder"]; - - return [colors objectForKey: colorKey]; -} - -- (NSString *) currentCalendarLogin -{ - NSArray *parts; - - parts = [[currentCalendarFolder objectForKey: @"folder"] - componentsSeparatedByString: @":"]; - - return (([parts count] > 1) - ? [parts objectAtIndex: 0] - : [[context activeUser] login]); + return currentCalendar; } - (NSString *) currentCalendarStyle { - NSString *color; - - color = [self currentCalendarSpanBG]; - - return [NSString stringWithFormat: @"color: %@; background-color: %@;", - color, color]; + return [currentCalendar + keysWithFormat: @"color: %{color}; background-color: %{color};"]; } @end /* UIxCalendarSelector */ diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index eecd800ff..a972885d6 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -42,6 +42,7 @@ #import #import +#import #import #import #import @@ -104,7 +105,6 @@ currentAttendee = [attendees nextObject]; while (currentAttendee) { - NSLog (@"currentCN: %@", [currentAttendee cn]); [names appendFormat: @"%@,", [currentAttendee cn]]; [emails appendFormat: @"%@,", [currentAttendee rfc822Email]]; currentAttendee = [attendees nextObject]; @@ -332,19 +332,20 @@ - (NSArray *) calendarList { - SOGoAppointmentFolder *folder; + SOGoAppointmentFolder *calendar, *currentCalendar; + SOGoAppointmentFolders *calendarParent; NSEnumerator *allCalendars; - NSDictionary *currentCalendar; if (!calendarList) { calendarList = [NSMutableArray new]; - folder = [[self clientObject] container]; - allCalendars = [[folder calendarFolders] objectEnumerator]; + calendar = [[self clientObject] container]; + calendarParent = [calendar container]; + allCalendars = [[calendarParent subFolders] objectEnumerator]; currentCalendar = [allCalendars nextObject]; while (currentCalendar) { - if ([[currentCalendar objectForKey: @"active"] boolValue]) + if ([currentCalendar isActive]) [calendarList addObject: currentCalendar]; currentCalendar = [allCalendars nextObject]; } @@ -357,14 +358,18 @@ { NSArray *calendars; - calendars = [[self calendarList] valueForKey: @"folder"]; + calendars = [[self calendarList] valueForKey: @"nameInContainer"]; return [calendars componentsJoinedByString: @","]; } - (NSString *) componentCalendar { - return @"/"; + SOGoAppointmentFolder *calendar; + + calendar = [[self clientObject] container]; + + return calendar; } /* priorities */ @@ -372,14 +377,15 @@ - (NSArray *) priorities { /* 0 == undefined - 5 == normal + 9 == low + 5 == medium 1 == high */ static NSArray *priorities = nil; if (!priorities) { - priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil]; + priorities = [NSArray arrayWithObjects: @"9", @"5", @"1", nil]; [priorities retain]; } @@ -855,8 +861,8 @@ [component setUid: [clientObject nameInContainer]]; [component setCreated: now]; [component setTimeStampAsDate: now]; - [component setPriority: @"0"]; } + [component setPriority: priority]; [component setLastModified: now]; } diff --git a/UI/Scheduler/UIxTaskEditor.m b/UI/Scheduler/UIxTaskEditor.m index d2c2a342c..bc7e1bed1 100644 --- a/UI/Scheduler/UIxTaskEditor.m +++ b/UI/Scheduler/UIxTaskEditor.m @@ -324,14 +324,14 @@ { NSString *objectId, *method, *uri; id result; - Class clientKlazz; + SOGoAppointmentFolder *co; - clientKlazz = [[self clientObject] class]; - objectId = [clientKlazz globallyUniqueObjectId]; + co = [self clientObject]; + objectId = [co globallyUniqueObjectId]; if ([objectId length] > 0) { - method = [NSString stringWithFormat:@"%@/Calendar/%@/editAsTask", - [self userFolderPath], objectId]; + method = [NSString stringWithFormat:@"%@/%@/editAsTask", + [co soURL], objectId]; uri = [self completeHrefForMethod: method]; result = [self redirectToLocation: uri]; } @@ -357,8 +357,12 @@ - (BOOL) shouldTakeValuesFromRequest: (WORequest *) request inContext: (WOContext*) context { + NSString *actionName; + + actionName = [[request requestHandlerPath] lastPathComponent]; + return ([[self clientObject] isKindOfClass: [SOGoTaskObject class]] - && [[request method] isEqualToString: @"POST"]); + && [actionName hasPrefix: @"save"]); } - (void) takeValuesFromRequest: (WORequest *) _rq diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 2041d7570..6ee2a5afb 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -28,11 +28,11 @@ }; categories = { - SOGoAppointmentFolder = { + SOGoAppointmentFolders = { slots = { toolbar = { protectedBy = "View"; - value = "SOGoAppointmentFolder.toolbar"; + value = "SOGoAppointmentFolders.toolbar"; }; }; methods = { @@ -70,6 +70,38 @@ protectedBy = "View"; pageName = "UIxCalMonthView"; }; + show = { + protectedBy = "View"; + pageName = "UIxCalView"; + actionName = "redirectForUIDs"; + }; + proposal = { + protectedBy = "View"; + pageName = "UIxAppointmentProposal"; + }; + proposalSearch = { + protectedBy = "View"; + pageName = "UIxAppointmentProposal"; + actionName = "proposalSearch"; + }; + userRights = { + protectedBy = "ReadAcls"; + pageName = "UIxCalUserRightsEditor"; + }; + saveUserRights = { + protectedBy = "SaveAcls"; + pageName = "UIxCalUserRightsEditor"; + actionName = "saveUserRights"; + }; + editAttendees = { + protectedBy = "View"; + pageName = "UIxAttendeesEditor"; + }; + }; + }; + + SOGoAppointmentFolder = { + methods = { newevent = { protectedBy = "Add Documents, Images, and Files"; pageName = "UIxAppointmentEditor"; @@ -85,29 +117,6 @@ pageName = "UIxCalView"; actionName = "redirectForUIDs"; }; - proposal = { - protectedBy = "View"; - pageName = "UIxAppointmentProposal"; - }; - proposalSearch = { - protectedBy = "View"; - pageName = "UIxAppointmentProposal"; - actionName = "proposalSearch"; - }; - batchDelete = { - protectedBy = "Delete Objects"; - pageName = "UIxCalMainView"; - actionName = "batchDelete"; - }; - updateCalendars = { - protectedBy = "View"; - pageName = "UIxCalMainView"; - actionName = "updateCalendars"; - }; - editAttendees = { - protectedBy = "View"; - pageName = "UIxAttendeesEditor"; - }; userRights = { protectedBy = "ReadAcls"; pageName = "UIxCalUserRightsEditor"; @@ -119,6 +128,7 @@ }; }; }; + SOGoCalendarComponent = { }; diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index c4cc0e9ed..c1c1b45e7 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -74,6 +74,15 @@ + + + + +