02111-1307, USA.
*/
-#import "SOGoAppointmentObject.h"
+#import <Foundation/NSCalendarDate.h>
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h>
-#import <NGMime/NGMime.h>
-#import <NGMail/NGMail.h>
-#import <NGMail/NGSendMail.h>
-
-#import <SOGo/AgenorUserManager.h>
-#import <SOGo/SOGoObject.h>
-#import "SOGoAptMailNotification.h"
-#import "iCalEntityObject+Agenor.h"
-
-#import "common.h"
+#import <SoObjects/SOGo/LDAPUserManager.h>
+#import <SoObjects/SOGo/SOGoObject.h>
+#import <SoObjects/SOGo/SOGoPermissions.h>
#import "NSArray+Appointments.h"
+#import "SOGoAppointmentFolder.h"
-@interface SOGoAppointmentObject (PrivateAPI)
-- (NSString *) homePageURLForPerson: (iCalPerson *) _person;
-
-- (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
- forOldAppointment: (iCalEvent *) _newApt
- andNewAppointment: (iCalEvent *) _oldApt
- toAttendees: (NSArray *) _attendees;
-
-- (void) sendInvitationEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees;
-- (void) sendAppointmentUpdateEMailForOldAppointment: (iCalEvent *) _oldApt
- newAppointment: (iCalEvent *) _newApt
- toAttendees: (NSArray *) _attendees;
-- (void) sendAttendeeRemovalEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees;
-- (void) sendAppointmentDeletionEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees;
-@end
+#import "SOGoAppointmentObject.h"
@implementation SOGoAppointmentObject
-static NSString *mailTemplateDefaultLanguage = nil;
-static BOOL sendEMailNotifications = NO;
-
-+ (void) initialize
+- (NSString *) componentTag
{
- NSUserDefaults *ud;
- static BOOL didInit = NO;
-
- if (!didInit)
- {
- didInit = YES;
-
- ud = [NSUserDefaults standardUserDefaults];
- mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
- retain];
- if (!mailTemplateDefaultLanguage)
- mailTemplateDefaultLanguage = @"French";
-
- sendEMailNotifications
- = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
- }
-}
-
-/* accessors */
-
-- (iCalEvent *) event
-{
- return [self firstEventFromCalendar: [self calendar]];
+ return @"vevent";
}
/* iCal handling */
- (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
{
- AgenorUserManager *um;
+ LDAPUserManager *um;
NSMutableArray *uids;
NSArray *attendees;
unsigned i, count;
count = [attendees count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
- um = [AgenorUserManager sharedUserManager];
+ um = [LDAPUserManager sharedUserManager];
/* add organizer */
email = [[_apt organizer] rfc822Email];
if ([email isNotNull]) {
- uid = [um getUIDForEmail:email];
+ uid = [um getUIDForEmail: email];
if ([uid isNotNull]) {
[uids addObject:uid];
}
/* add attendees */
- for (i = 0; i < count; i++) {
- iCalPerson *person;
+ for (i = 0; i < count; i++)
+ {
+ iCalPerson *person;
- person = [attendees objectAtIndex:i];
- email = [person rfc822Email];
- if (![email isNotNull]) continue;
+ person = [attendees objectAtIndex:i];
+ email = [person rfc822Email];
+ if (![email isNotNull]) continue;
- uid = [um getUIDForEmail:email];
- if (![uid isNotNull]) {
- [self logWithFormat:@"Note: got no uid for email: '%@'", email];
- continue;
+ uid = [um getUIDForEmail:email];
+ if (![uid isNotNull]) {
+ [self logWithFormat:@"Note: got no uid for email: '%@'", email];
+ continue;
+ }
+ if (![uids containsObject:uid])
+ [uids addObject:uid];
}
- if (![uids containsObject:uid])
- [uids addObject:uid];
- }
-
- 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];
+ return uids;
}
/* 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;
- id ctx;
-
- ctx = [[WOApplication application] context];
- e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
- objectEnumerator];
+ e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
+ objectEnumerator];
while ((folder = [e nextObject]) != nil) {
NSException *error;
SOGoAppointmentObject *apt;
if (![folder isNotNull]) /* no folder was found for given UID */
continue;
- apt = [folder lookupName: [self nameInContainer] inContext:ctx
+ apt = [folder lookupName: [self nameInContainer] inContext: context
acquire: NO];
if ([apt isKindOfClass: [NSException class]])
{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
- id ctx;
- ctx = [[WOApplication application] context];
-
- e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
- objectEnumerator];
+ e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
+ objectEnumerator];
while ((folder = [e nextObject])) {
NSException *error;
SOGoAppointmentObject *apt;
- apt = [folder lookupName:[self nameInContainer] inContext:ctx
+ apt = [folder lookupName:[self nameInContainer] inContext: context
acquire:NO];
if ([apt isKindOfClass: [NSException class]]) {
[self logWithFormat: @"%@", [(NSException *) apt reason]];
return allErrors;
}
-- (iCalEvent *) firstEventFromCalendar: (iCalCalendar *) aCalendar
+/* "iCal multifolder saves" */
+- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
{
- iCalEvent *event;
- NSArray *events;
+ NSCalendarDate *now;
- events = [aCalendar childrenWithTag: @"vevent"];
- if ([events count])
- event = (iCalEvent *) [[events objectAtIndex: 0]
- groupWithClass: [iCalEvent class]];
- else
- event = nil;
+ now = [NSCalendarDate calendarDate];
- return event;
+ return ([[appointment endDate] earlierDate: now] == now);
}
-/* "iCal multifolder saves" */
-
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v
{
- delete in removed folders
- send iMIP mail for all folders not found
*/
- AgenorUserManager *um;
- iCalCalendar *newCalendar;
+ LDAPUserManager *um;
iCalEvent *oldApt, *newApt;
iCalEventChanges *changes;
iCalPerson *organizer;
updateForcesReconsider = NO;
- if ([_iCal length] == 0) {
- return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
- reason:@"got no iCalendar content to store!"];
- }
+ if ([_iCal length] == 0)
+ return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
+ reason: @"got no iCalendar content to store!"];
- um = [AgenorUserManager sharedUserManager];
+ um = [LDAPUserManager sharedUserManager];
/* handle old content */
- oldContent = [self iCalString]; /* if nil, this is a new appointment */
+ oldContent = [self contentAsString]; /* if nil, this is a new appointment */
if ([oldContent length] == 0)
{
/* new appointment */
oldApt = nil;
}
else
- oldApt = [self firstEventFromCalendar: [self calendar]];
+ oldApt = (iCalEvent *) [self component: NO];
/* compare sequence if requested */
// TODO
}
-
/* handle new content */
- newCalendar = [iCalCalendar parseSingleFromSource: _iCal];
- newApt = [self firstEventFromCalendar: newCalendar];
- if (newApt == nil) {
- return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
- reason:@"could not parse iCalendar content!"];
- }
-
+ newApt = (iCalEvent *) [self component: NO];
+ if (!newApt)
+ return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
+ reason: @"could not parse iCalendar content!"];
+
/* diff */
- changes = [iCalEventChanges changesFromEvent: oldApt
- toEvent: newApt];
-
- uids = [um getUIDsForICalPersons:[changes deletedAttendees]
- applyStrictMapping:NO];
- removedUIDs = [NSMutableArray arrayWithArray:uids];
+ changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
+ uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
+ removedUIDs = [NSMutableArray arrayWithArray: uids];
- uids = [um getUIDsForICalPersons:[newApt attendees]
- applyStrictMapping:NO];
- storeUIDs = [NSMutableArray arrayWithArray:uids];
+ uids = [self getUIDsForICalPersons: [newApt attendees]];
+ storeUIDs = [NSMutableArray arrayWithArray: uids];
props = [changes updatedProperties];
/* detect whether sequence has to be increased */
/* preserve organizer */
organizer = [newApt organizer];
- uid = [um getUIDForICalPerson:organizer];
+ uid = [self getUIDForICalPerson: organizer];
+ if (!uid)
+ uid = [self ownerInContext: nil];
if (uid) {
if (![storeUIDs containsObject:uid])
[storeUIDs addObject:uid];
/* organizer might have changed completely */
if (oldApt && ([props containsObject: @"organizer"])) {
- uid = [um getUIDForICalPerson:[oldApt organizer]];
+ uid = [self getUIDForICalPerson:[oldApt organizer]];
if (uid) {
if (![storeUIDs containsObject:uid]) {
if (![removedUIDs containsObject:uid]) {
*/
if (oldApt != nil &&
- ([props containsObject:@"startDate"] ||
- [props containsObject:@"endDate"] ||
- [props containsObject:@"duration"]))
+ ([props containsObject: @"startDate"] ||
+ [props containsObject: @"endDate"] ||
+ [props containsObject: @"duration"]))
{
NSArray *ps;
unsigned i, count;
/* perform storing */
- storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
- delError = [self deleteInUIDs:removedUIDs];
+ storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
+ delError = [self deleteInUIDs: removedUIDs];
// TODO: make compound
if (storeError != nil) return storeError;
if (delError != nil) return delError;
/* email notifications */
- if (sendEMailNotifications)
+ if ([self sendEMailNotifications]
+ && [self _aptIsStillRelevant: newApt])
{
- attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
- [attendees removePerson:organizer];
- [self sendInvitationEMailForAppointment:newApt
- toAttendees:attendees];
+ attendees
+ = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
+ [attendees removePerson: organizer];
+ [self sendEMailUsingTemplateNamed: @"Invitation"
+ forOldObject: nil
+ andNewObject: newApt
+ toAttendees: attendees];
if (updateForcesReconsider) {
attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
[attendees removeObjectsInArray:[changes insertedAttendees]];
[attendees removePerson:organizer];
- [self sendAppointmentUpdateEMailForOldAppointment:oldApt
- newAppointment:newApt
- toAttendees:attendees];
+ [self sendEMailUsingTemplateNamed: @"Update"
+ forOldObject: oldApt
+ andNewObject: newApt
+ 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"];
- [self sendAttendeeRemovalEMailForAppointment:canceledApt
+ cancelledApt = [newApt copy];
+ [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
+ [self sendEMailUsingTemplateNamed: @"Removal"
+ forOldObject: nil
+ andNewObject: cancelledApt
toAttendees: attendees];
- [canceledApt release];
+ [cancelledApt release];
}
}
- send iMIP mail for all folders not found
*/
iCalEvent *apt;
- NSArray *removedUIDs;
- NSMutableArray *attendees;
+ NSMutableArray *attendees, *removedUIDs;
/* load existing content */
- apt = [self event];
+ apt = (iCalEvent *) [self component: NO];
/* compare sequence if requested */
// // TODO
// }
- removedUIDs = [self attendeeUIDsFromAppointment:apt];
+ removedUIDs = [NSMutableArray arrayWithArray:
+ [self attendeeUIDsFromAppointment: apt]];
+ if (![removedUIDs containsObject: owner])
+ [removedUIDs addObject: owner];
- if (sendEMailNotifications)
+ if ([self sendEMailNotifications]
+ && [self _aptIsStillRelevant: apt])
{
/* send notification email to attendees excluding organizer */
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];
[apt removeAllAttendees];
/* send notification email */
- [self sendAppointmentDeletionEMailForAppointment:apt
- toAttendees:attendees];
+ [self sendEMailUsingTemplateNamed: @"Deletion"
+ forOldObject: nil
+ andNewObject: apt
+ toAttendees: attendees];
}
/* perform */
- return [self deleteInUIDs:removedUIDs];
+ return [self deleteInUIDs: removedUIDs];
}
- (NSException *) saveContentString: (NSString *) _iCalString
return [self saveContentString: _iCalString baseSequence: 0];
}
-- (NSException *) changeParticipationStatus: (NSString *) _status
- inContext: (id) _ctx
-{
- iCalEvent *apt;
- iCalPerson *p;
- NSString *newContent;
- NSException *ex;
- NSString *myEMail;
-
- ex = nil;
-
- // TODO: do we need to use SOGoAppointment? (prefer iCalEvent?)
- apt = [self event];
-
- if (apt)
- {
- myEMail = [[_ctx activeUser] email];
- p = [apt findParticipantWithEmail: myEMail];
- if (p)
- {
- // TODO: send iMIP reply mails?
-
- [p setPartStat:_status];
- newContent = [[apt parent] versitString];
- if (newContent)
- {
- ex = [self saveContentString:newContent];
- if (ex)
- // TODO: why is the exception wrapped?
- /* Server Error */
- ex = [NSException exceptionWithHTTPStatus: 500
- reason: [ex reason]];
- }
- else
- ex
- = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
- reason: @"Could not generate iCalendar data ..."];
- }
- else
- ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
- reason: @"user does not participate in this "
- @"appointment"];
- }
- else
- ex = [NSException exceptionWithHTTPStatus:500 /* Server Error */
- reason:@"unable to parse appointment record"];
-
- return ex;
-}
-
-
/* message type */
- (NSString *) outlookMessageClass
return @"IPM.Appointment";
}
-/* EMail Notifications */
-
-- (NSString *) homePageURLForPerson: (iCalPerson *) _person
-{
- NSString *baseURL;
- NSString *uid;
- WOContext *ctx;
- NSArray *traversalObjects;
-
- /* generate URL from traversal stack */
- ctx = [[WOApplication application] context];
- traversalObjects = [ctx objectTraversalStack];
- if ([traversalObjects count] > 0)
- baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext:ctx];
- else
- {
- baseURL = @"http://localhost/";
- [self warnWithFormat:@"Unable to create baseURL from context!"];
- }
- uid = [[AgenorUserManager sharedUserManager]
- getUIDForEmail: [_person rfc822Email]];
-
- return ((uid)
- ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
- : nil);
-}
-
-- (NSException *) saveContentString: (NSString *) contentString
- baseVersion: (unsigned int) baseVersion
-{
- NSString *newContentString, *oldContentString;
- iCalCalendar *eventCalendar;
- iCalEvent *event;
- NSArray *organizers;
-
- oldContentString = [self iCalString];
- if (oldContentString)
- newContentString = contentString;
- else
- {
- eventCalendar = [iCalCalendar parseSingleFromSource: contentString];
- event = [self firstEventFromCalendar: eventCalendar];
- organizers = [event childrenWithTag: @"organizer"];
- if ([organizers count])
- newContentString = contentString;
- else
- {
- [event setOrganizerWithUid: [[self container] ownerInContext: nil]];
- newContentString = [eventCalendar versitString];
- }
- }
-
- return [super saveContentString: newContentString
- baseVersion: baseVersion];
-}
-
-- (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
- forOldAppointment: (iCalEvent *) _oldApt
- andNewAppointment: (iCalEvent *) _newApt
- toAttendees: (NSArray *) _attendees
-{
- NSString *pageName;
- iCalPerson *organizer;
- NSString *cn, *sender, *iCalString;
- NGSendMail *sendmail;
- WOApplication *app;
- unsigned i, count;
- iCalPerson *attendee;
- NSString *recipient;
- SOGoAptMailNotification *p;
- NSString *subject, *text, *header;
- NGMutableHashMap *headerMap;
- NGMimeMessage *msg;
- NGMimeBodyPart *bodyPart;
- NGMimeMultipartBody *body;
-
- if ([_attendees count])
- {
- /* sender */
-
- organizer = [_newApt organizer];
- cn = [organizer cnWithoutQuotes];
- if (cn)
- sender = [NSString stringWithFormat:@"%@ <%@>",
- cn,
- [organizer rfc822Email]];
- else
- sender = [organizer rfc822Email];
-
- /* generate iCalString once */
- iCalString = [[_newApt parent] versitString];
-
- /* get sendmail object */
- sendmail = [NGSendMail sharedSendMail];
-
- /* get WOApplication instance */
- app = [WOApplication application];
-
- /* generate dynamic message content */
-
- count = [_attendees count];
- for (i = 0; i < count; i++)
- {
- attendee = [_attendees objectAtIndex:i];
-
- /* construct recipient */
- cn = [attendee cn];
- if (cn)
- recipient = [NSString stringWithFormat: @"%@ <%@>",
- cn,
- [attendee rfc822Email]];
- else
- recipient = [attendee rfc822Email];
-
- /* create page name */
- // TODO: select user's default language?
- pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
- mailTemplateDefaultLanguage,
- _pageName];
- /* construct message content */
- p = [app pageWithName: pageName inContext: [WOContext context]];
- [p setNewApt: _newApt];
- [p setOldApt: _oldApt];
- [p setHomePageURL: [self homePageURLForPerson: attendee]];
- [p setViewTZ: [self userTimeZone: cn]];
- subject = [p getSubject];
- text = [p getBody];
-
- /* construct message */
- headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
-
- /* NOTE: multipart/alternative seems like the correct choice but
- * unfortunately Thunderbird doesn't offer the rich content alternative
- * at all. Mail.app shows the rich content alternative _only_
- * so we'll stick with multipart/mixed for the time being.
- */
- [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
- [headerMap setObject: sender forKey: @"From"];
- [headerMap setObject: recipient forKey: @"To"];
- [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
- [headerMap setObject: subject forKey: @"Subject"];
- msg = [NGMimeMessage messageWithHeader: headerMap];
-
- /* multipart body */
- body = [[NGMimeMultipartBody alloc] initWithPart: msg];
-
- /* text part */
- headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
- [headerMap setObject: @"text/plain; charset=utf-8"
- forKey: @"content-type"];
- bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
- [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
-
- /* attach text part to multipart body */
- [body addBodyPart: bodyPart];
-
- /* calendar part */
- header = [NSString stringWithFormat: @"text/calendar; method=%@;"
- @" charset=utf-8",
- [(iCalCalendar *) [_newApt parent] method]];
- headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
- [headerMap setObject:header forKey: @"content-type"];
- bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
- [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
-
- /* attach calendar part to multipart body */
- [body addBodyPart: bodyPart];
-
- /* attach multipart body to message */
- [msg setBody: body];
- [body release];
-
- /* send the damn thing */
- [sendmail sendMimePart: msg
- toRecipients: [NSArray arrayWithObject: [attendee rfc822Email]]
- sender: [organizer rfc822Email]];
- }
- }
-}
-
-- (void) sendInvitationEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees
-{
- if ([_attendees count])
- [self sendEMailUsingTemplateNamed: @"Invitation"
- forOldAppointment: nil
- andNewAppointment: _apt
- toAttendees: _attendees];
-}
-
-- (void) sendAppointmentUpdateEMailForOldAppointment: (iCalEvent *) _oldApt
- newAppointment: (iCalEvent *) _newApt
- toAttendees: (NSArray *) _attendees
-{
- if ([_attendees count])
- [self sendEMailUsingTemplateNamed: @"Update"
- forOldAppointment: _oldApt
- andNewAppointment: _newApt
- toAttendees: _attendees];
-}
-
-- (void) sendAttendeeRemovalEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees
-{
- if ([_attendees count])
- [self sendEMailUsingTemplateNamed: @"Removal"
- forOldAppointment: nil
- andNewAppointment: _apt
- toAttendees: _attendees];
-}
-
-- (void) sendAppointmentDeletionEMailForAppointment: (iCalEvent *) _apt
- toAttendees: (NSArray *) _attendees
-{
- if ([_attendees count])
- [self sendEMailUsingTemplateNamed: @"Deletion"
- forOldAppointment: nil
- andNewAppointment: _apt
- toAttendees: _attendees];
-}
-
-- (NSString *) davContentType
-{
- return @"text/calendar";
-}
-
-- (NSString *) roleOfUser: (NSString *) login
- inContext: (WOContext *) context
-{
- AgenorUserManager *um;
- iCalEvent *event;
- NSString *role, *email;
-
- um = [AgenorUserManager sharedUserManager];
- email = [um getEmailForUID: login];
-
- event = [self event];
- if ([event isOrganizer: email])
- role = @"Organizer";
- else if ([event isParticipant: email])
- role = @"Participant";
- else
- role = nil;
-
- return role;
-}
-
@end /* SOGoAppointmentObject */
-
-
-