2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import <Foundation/NSArray.h>
23 #import <Foundation/NSDate.h>
24 #import <Foundation/NSDictionary.h>
25 #import <Foundation/NSException.h>
26 #import <Foundation/NSKeyValueCoding.h>
27 #import <Foundation/NSURL.h>
29 #import <NGObjWeb/NSException+HTTP.h>
30 #import <NGObjWeb/SoObject.h>
31 #import <NGObjWeb/SoObject+SoDAV.h>
32 #import <NGObjWeb/SoSelectorInvocation.h>
33 #import <NGObjWeb/WOContext+SoObjects.h>
34 #import <NGExtensions/NSNull+misc.h>
35 #import <NGExtensions/NSObject+Logs.h>
36 #import <EOControl/EOQualifier.h>
37 #import <GDLAccess/EOAdaptorChannel.h>
38 #import <GDLContentStore/GCSChannelManager.h>
39 #import <GDLContentStore/GCSFolderManager.h>
40 #import <GDLContentStore/GCSFolder.h>
41 #import <GDLContentStore/GCSFolderType.h>
42 #import <GDLContentStore/NSURL+GCS.h>
43 #import <SaxObjC/XMLNamespaces.h>
45 #import "NSArray+Utilities.h"
46 #import "NSString+Utilities.h"
48 #import "SOGoPermissions.h"
51 #import "SOGoFolder.h"
53 static NSString *defaultUserID = @"<default>";
55 @implementation SOGoFolder
59 return [super version] + 0 /* v0 */;
64 NSAssert2([super version] == 0,
65 @"invalid superclass (%@) version %i !",
66 NSStringFromClass([self superclass]), [super version]);
69 + (id) folderWithSubscriptionReference: (NSString *) reference
70 inContainer: (id) aContainer
73 NSArray *elements, *pathElements;
74 NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
76 elements = [reference componentsSeparatedByString: @":"];
77 owner = [elements objectAtIndex: 0];
78 objectPath = [elements objectAtIndex: 1];
79 pathElements = [objectPath componentsSeparatedByString: @"/"];
80 if ([pathElements count] > 1)
81 ocsName = [pathElements objectAtIndex: 1];
83 ocsName = @"personal";
85 ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@",
86 owner, [pathElements objectAtIndex: 0], ocsName];
87 folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName];
88 newFolder = [[self alloc] initWithName: folderName
89 inContainer: aContainer];
90 [newFolder setOCSPath: ocsPath];
91 [newFolder setOwner: owner];
98 if ((self = [super init]))
103 aclCache = [NSMutableDictionary new];
114 [displayName release];
125 - (void) setOCSPath: (NSString *) _path
127 if (![ocsPath isEqualToString:_path])
130 [self warnWithFormat: @"GCS path is already set! '%@'", _path];
131 ASSIGN (ocsPath, _path);
135 - (NSString *) ocsPath
140 - (GCSFolderManager *) folderManager
142 static GCSFolderManager *folderManager = nil;
145 folderManager = [GCSFolderManager defaultFolderManager];
147 return folderManager;
150 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
152 return [[self folderManager] folderAtPath: _path];
155 - (BOOL) folderIsMandatory
157 return [nameInContainer isEqualToString: @"personal"];
160 - (void) _setDisplayNameFromRow: (NSDictionary *) row
162 NSString *currentLogin, *ownerLogin;
163 NSDictionary *ownerIdentity;
166 = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
167 currentLogin = [[context activeUser] login];
168 ownerLogin = [self ownerInContext: context];
169 if (![currentLogin isEqualToString: ownerLogin])
171 ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
173 [displayName appendFormat: @" (%@ <%@>)",
174 [ownerIdentity objectForKey: @"fullName"],
175 [ownerIdentity objectForKey: @"email"]];
179 - (void) _fetchDisplayName
181 GCSChannelManager *cm;
182 EOAdaptorChannel *fc;
183 NSURL *folderLocation;
188 cm = [GCSChannelManager defaultChannelManager];
190 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
191 fc = [cm acquireOpenChannelForURL: folderLocation];
195 = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
196 @" WHERE c_path = '%@'"),
197 [folderLocation gcsTableName], ocsPath];
198 [fc evaluateExpressionX: sql];
199 attrs = [fc describeResults: NO];
200 row = [fc fetchAttributes: attrs withZone: NULL];
202 [self _setDisplayNameFromRow: row];
204 [cm releaseChannel: fc];
208 - (void) setDisplayName: (NSString *) newDisplayName
210 ASSIGN (displayName, newDisplayName);
213 - (NSString *) displayName
216 [self _fetchDisplayName];
221 - (NSString *) davDisplayName
223 return [self displayName];
226 - (GCSFolder *) ocsFolder
233 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
234 userLogin = [[context activeUser] login];
236 && [userLogin isEqualToString: [self ownerInContext: context]]
237 && [self folderIsMandatory]
239 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
243 if ([ocsFolder isNotNull])
251 - (NSString *) folderType
261 result = [[self folderManager] createFolderOfType: [self folderType]
262 withName: displayName
265 return (result == nil);
268 - (NSException *) delete
272 if ([nameInContainer isEqualToString: @"personal"])
273 error = [NSException exceptionWithHTTPStatus: 403
274 reason: @"folder 'personal' cannot be deleted"];
276 error = [[self folderManager] deleteFolderAtPath: ocsPath];
281 - (void) renameTo: (NSString *) newName
283 GCSChannelManager *cm;
284 EOAdaptorChannel *fc;
285 NSURL *folderLocation;
288 [displayName release];
291 cm = [GCSChannelManager defaultChannelManager];
293 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
294 fc = [cm acquireOpenChannelForURL: folderLocation];
298 = [NSString stringWithFormat: (@"UPDATE %@ SET c_foldername = '%@'"
299 @" WHERE c_path = '%@'"),
300 [folderLocation gcsTableName], newName, ocsPath];
301 [fc evaluateExpressionX: sql];
302 [cm releaseChannel: fc];
303 // sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'",
304 // uidColumnName, [self uid]];
308 - (NSArray *) fetchContentObjectNames
310 NSArray *fields, *records;
312 fields = [NSArray arrayWithObject: @"c_name"];
313 records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
314 if (![records isNotNull])
316 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
319 if ([records isKindOfClass: [NSException class]])
321 return [records objectsForKey: @"c_name"];
324 - (BOOL) nameExistsInFolder: (NSString *) objectName
326 NSArray *fields, *records;
327 EOQualifier *qualifier;
330 = [EOQualifier qualifierWithQualifierFormat:
331 [NSString stringWithFormat: @"c_name='%@'", objectName]];
333 fields = [NSArray arrayWithObject: @"c_name"];
334 records = [[self ocsFolder] fetchFields: fields
335 matchingQualifier: qualifier];
337 && ![records isKindOfClass:[NSException class]]
338 && [records count] > 0);
341 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
345 files = [[self ocsFolder] fetchContentsOfAllFiles];
346 if (![files isNotNull])
348 [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
351 if ([files isKindOfClass:[NSException class]])
358 - (NSString *) defaultFilenameExtension
361 Override to add an extension to a filename
363 Note: be careful with that, needs to be consistent with object lookup!
368 - (NSArray *) davResourceType
370 NSArray *rType, *groupDavCollection;
372 if ([self respondsToSelector: @selector (groupDavResourceType)])
375 = [NSArray arrayWithObjects: [self groupDavResourceType],
376 XMLNS_GROUPDAV, nil];
377 rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
381 rType = [NSArray arrayWithObject: @"collection"];
386 - (NSString *) davContentType
388 return @"httpd/unix-directory";
391 - (NSArray *) toOneRelationshipKeys
393 /* toOneRelationshipKeys are the 'files' contained in a folder */
396 NSString *name, *ext;
400 names = [self fetchContentObjectNames];
401 count = [names count];
402 ext = [self defaultFilenameExtension];
403 if (count && [ext length] > 0)
405 ma = [NSMutableArray arrayWithCapacity: count];
406 for (i = 0; i < count; i++)
408 name = [names objectAtIndex: i];
409 r = [name rangeOfString: @"."];
411 name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
412 [ma addObject: name];
421 /* acls as a container */
423 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
425 EOQualifier *qualifier;
429 qs = [NSString stringWithFormat: @"c_object = '/%@'",
430 [objectPathArray componentsJoinedByString: @"/"]];
431 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
432 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
434 return [records valueForKey: @"c_uid"];
437 - (NSArray *) _fetchAclsForUser: (NSString *) uid
438 forObjectAtPath: (NSString *) objectPath
440 EOQualifier *qualifier;
442 NSMutableArray *acls;
445 qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
447 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
448 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
450 acls = [NSMutableArray array];
451 if ([records count] > 0)
453 [acls addObject: SOGoRole_AuthorizedSubscriber];
454 [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
460 - (void) _cacheRoles: (NSArray *) roles
461 forUser: (NSString *) uid
462 forObjectAtPath: (NSString *) objectPath
464 NSMutableDictionary *aclsForObject;
466 aclsForObject = [aclCache objectForKey: objectPath];
469 aclsForObject = [NSMutableDictionary dictionary];
470 [aclCache setObject: aclsForObject
474 [aclsForObject setObject: roles forKey: uid];
476 [aclsForObject removeObjectForKey: uid];
479 - (NSArray *) aclsForUser: (NSString *) uid
480 forObjectAtPath: (NSArray *) objectPathArray
483 NSString *objectPath;
484 NSDictionary *aclsForObject;
486 objectPath = [objectPathArray componentsJoinedByString: @"/"];
487 aclsForObject = [aclCache objectForKey: objectPath];
489 acls = [aclsForObject objectForKey: uid];
494 acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
495 [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
498 if (!([acls count] || [uid isEqualToString: defaultUserID]))
499 acls = [self aclsForUser: defaultUserID
500 forObjectAtPath: objectPathArray];
505 - (void) removeAclsForUsers: (NSArray *) users
506 forObjectAtPath: (NSArray *) objectPathArray
508 EOQualifier *qualifier;
509 NSString *uids, *qs, *objectPath;
510 NSMutableDictionary *aclsForObject;
512 if ([users count] > 0)
514 objectPath = [objectPathArray componentsJoinedByString: @"/"];
515 aclsForObject = [aclCache objectForKey: objectPath];
517 [aclsForObject removeObjectsForKeys: users];
518 uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
520 stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
522 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
523 [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
527 - (void) _commitRoles: (NSArray *) roles
528 forUID: (NSString *) uid
529 forObject: (NSString *) objectPath
531 EOAdaptorChannel *channel;
533 NSEnumerator *userRoles;
534 NSString *SQL, *currentRole;
536 folder = [self ocsFolder];
537 channel = [folder acquireAclChannel];
538 userRoles = [roles objectEnumerator];
539 currentRole = [userRoles nextObject];
542 SQL = [NSString stringWithFormat: @"INSERT INTO %@"
543 @" (c_object, c_uid, c_role)"
544 @" VALUES ('/%@', '%@', '%@')",
545 [folder aclTableName],
546 objectPath, uid, currentRole];
547 [channel evaluateExpressionX: SQL];
548 currentRole = [userRoles nextObject];
551 [folder releaseChannel: channel];
554 - (void) setRoles: (NSArray *) roles
555 forUser: (NSString *) uid
556 forObjectAtPath: (NSArray *) objectPathArray
558 NSString *objectPath;
559 NSMutableArray *newRoles;
561 [self removeAclsForUsers: [NSArray arrayWithObject: uid]
562 forObjectAtPath: objectPathArray];
564 newRoles = [NSMutableArray arrayWithArray: roles];
565 [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
566 [newRoles removeObject: SOGoRole_None];
567 objectPath = [objectPathArray componentsJoinedByString: @"/"];
568 [self _cacheRoles: newRoles forUser: uid
569 forObjectAtPath: objectPath];
570 if (![newRoles count])
571 [newRoles addObject: SOGoRole_None];
573 [self _commitRoles: newRoles forUID: uid forObject: objectPath];
577 - (NSArray *) aclUsers
579 return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
582 - (NSArray *) aclsForUser: (NSString *) uid
584 NSMutableArray *acls;
585 NSArray *ownAcls, *containerAcls;
587 acls = [NSMutableArray array];
588 ownAcls = [self aclsForUser: uid
589 forObjectAtPath: [self pathArrayToSOGoObject]];
590 [acls addObjectsFromArray: ownAcls];
591 if ([container respondsToSelector: @selector (aclsForUser:)])
593 containerAcls = [container aclsForUser: uid];
594 if ([containerAcls count] > 0)
596 if ([containerAcls containsObject: SOGoRole_ObjectReader])
597 [acls addObject: SOGoRole_ObjectViewer];
598 #warning this should be checked
599 if ([containerAcls containsObject: SOGoRole_ObjectEraser])
600 [acls addObject: SOGoRole_ObjectEraser];
607 - (void) setRoles: (NSArray *) roles
608 forUser: (NSString *) uid
610 return [self setRoles: roles
612 forObjectAtPath: [self pathArrayToSOGoObject]];
615 - (void) removeAclsForUsers: (NSArray *) users
617 return [self removeAclsForUsers: users
618 forObjectAtPath: [self pathArrayToSOGoObject]];
621 - (NSString *) defaultUserID
623 return defaultUserID;
626 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
628 return [[self soURL] absoluteString];
631 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
633 return [[self davURL] absoluteString];
636 - (id) lookupName: (NSString *) lookupName
637 inContext: (id) localContext
638 acquire: (BOOL) acquire
641 NSArray *davNamespaces;
642 NSDictionary *davInvocation;
643 NSString *objcMethod;
645 obj = [super lookupName: lookupName inContext: localContext
649 davNamespaces = [self davNamespaces];
650 if ([davNamespaces count] > 0)
652 davInvocation = [lookupName asDavInvocation];
655 containsObject: [davInvocation objectForKey: @"ns"]])
657 objcMethod = [[davInvocation objectForKey: @"method"]
659 obj = [[SoSelectorInvocation alloc]
660 initWithSelectorNamed:
661 [NSString stringWithFormat: @"%@:", objcMethod]
662 addContextParameter: YES];
673 - (NSArray *) davNamespaces
678 - (BOOL) davIsCollection
680 return [self isFolderish];
685 - (NSString *) outlookFolderClass
687 [self subclassResponsibility: _cmd];
694 - (void) appendAttributesToDescription: (NSMutableString *) _ms
696 [super appendAttributesToDescription:_ms];
698 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
701 - (NSString *) loggingPrefix
703 return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
704 self, NSStringFromClass([self class]),
705 [self nameInContainer]];
708 @end /* SOGoFolder */