eggification
[GroupUserFolder.git] / Products / GroupUserFolder / GroupsTool.py
1 # -*- coding: utf-8 -*-
2 ## GroupUserFolder
3 ## Copyright (C)2006 Ingeniweb
4
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
9
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
14
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program; see the file COPYING. If not, write to the
17 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 ## Copyright (c) 2003 The Connexions Project, All Rights Reserved
20 ## initially written by J Cameron Cooper, 11 June 2003
21 ## concept with Brent Hendricks, George Runyan
22 """
23 Basic usergroup tool.
24 """
25 __version__ = "$Revision$"
26 # $Source: $
27 # $Id: GroupsTool.py 50142 2007-09-25 13:13:12Z wichert $
28 __docformat__ = 'restructuredtext'
29
30 from Products.CMFCore.utils import UniqueObject
31 from Products.CMFCore.utils import getToolByName
32 from Products.CMFCore.utils import _checkPermission
33 from OFS.SimpleItem import SimpleItem
34 from Globals import InitializeClass, DTMLFile, MessageDialog
35 from Acquisition import aq_base
36 from AccessControl.User import nobody
37 from AccessControl import ClassSecurityInfo
38 from ZODB.POSException import ConflictError
39 # BBB CMF < 1.5
40 try:
41 from Products.CMFCore.permissions import ManagePortal
42 from Products.CMFCore.permissions import View
43 from Products.CMFCore.permissions import ViewManagementScreens
44 except ImportError:
45 from Products.CMFCore.CMFCorePermissions import ManagePortal
46 from Products.CMFCore.CMFCorePermissions import View
47 from Products.CMFCore.CMFCorePermissions import ViewManagementScreens
48
49 from Products.GroupUserFolder import postonly
50 from GroupsToolPermissions import AddGroups
51 from GroupsToolPermissions import ManageGroups
52 from GroupsToolPermissions import DeleteGroups
53 from GroupsToolPermissions import ViewGroups
54 from GroupsToolPermissions import SetGroupOwnership
55 from Products.CMFCore.ActionProviderBase import ActionProviderBase
56 from interfaces.portal_groups import portal_groups as IGroupsTool
57 from global_symbols import *
58
59 # Optional feature-preview support
60 import PloneFeaturePreview
61
62 class GroupsTool (UniqueObject, SimpleItem, ActionProviderBase, ):
63 """ This tool accesses group data through a GRUF acl_users object.
64
65 It can be replaced with something that groups member data in a
66 different way.
67 """
68 # Show implementation only if IGroupsTool is defined
69 # The latter will work only with Plone 1.1 => hence, the if
70 if hasattr(ActionProviderBase, '__implements__'):
71 __implements__ = (IGroupsTool, ActionProviderBase.__implements__)
72
73 id = 'portal_groups'
74 meta_type = 'CMF Groups Tool'
75 _actions = ()
76
77 security = ClassSecurityInfo()
78
79 groupworkspaces_id = "groups"
80 groupworkspaces_title = "Groups"
81 groupWorkspacesCreationFlag = 1
82 groupWorkspaceType = "Folder"
83 groupWorkspaceContainerType = "Folder"
84
85 manage_options=(
86 ( { 'label' : 'Configure'
87 , 'action' : 'manage_config'
88 },
89 ) + ActionProviderBase.manage_options +
90 ( { 'label' : 'Overview'
91 , 'action' : 'manage_overview'
92 },
93 ) + SimpleItem.manage_options)
94
95 # #
96 # ZMI methods #
97 # #
98 security.declareProtected(ViewManagementScreens, 'manage_overview')
99 manage_overview = DTMLFile('dtml/explainGroupsTool', globals()) # unlike MembershipTool
100 security.declareProtected(ViewManagementScreens, 'manage_config')
101 manage_config = DTMLFile('dtml/configureGroupsTool', globals())
102
103 security.declareProtected(ManagePortal, 'manage_setGroupWorkspacesFolder')
104 def manage_setGroupWorkspacesFolder(self, id='groups', title='Groups', REQUEST=None):
105 """ZMI method for workspace container name set."""
106 self.setGroupWorkspacesFolder(id, title)
107 return self.manage_config(manage_tabs_message="Workspaces folder name set to %s" % id)
108
109 security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceType')
110 def manage_setGroupWorkspaceType(self, type='Folder', REQUEST=None):
111 """ZMI method for workspace type set."""
112 self.setGroupWorkspaceType(type)
113 return self.manage_config(manage_tabs_message="Group Workspaces type set to %s" % type)
114
115 security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceContainerType')
116 def manage_setGroupWorkspaceContainerType(self, type='Folder', REQUEST=None):
117 """ZMI method for workspace type set."""
118 self.setGroupWorkspaceContainerType(type)
119 return self.manage_config(manage_tabs_message="Group Workspaces container type set to %s" % type)
120
121 security.declareProtected(ViewGroups, 'getGroupById')
122 def getGroupById(self, id):
123 """
124 Returns the portal_groupdata-ish object for a group corresponding to this id.
125 """
126 if id==None:
127 return None
128 g = self.acl_users.getGroupByName(id, None)
129 if g is not None:
130 g = self.wrapGroup(g)
131 return g
132
133 security.declareProtected(ViewGroups, 'getGroupsByUserId')
134 def getGroupsByUserId(self, userid):
135 """Return a list of the groups the user corresponding to 'userid' belongs to."""
136 #log("getGroupsByUserId(%s)" % userid)
137 user = self.acl_users.getUser(userid)
138 #log("user '%s' is in groups %s" % (userid, user.getGroups()))
139 if user:
140 groups = user.getGroups() or []
141 else:
142 groups = []
143 return [self.getGroupById(elt) for elt in groups]
144
145 security.declareProtected(ViewGroups, 'listGroups')
146 def listGroups(self):
147 """Return a list of the available portal_groupdata-ish objects."""
148 return [ self.wrapGroup(elt) for elt in self.acl_users.getGroups() ]
149
150 security.declareProtected(ViewGroups, 'listGroupIds')
151 def listGroupIds(self):
152 """Return a list of the available groups' ids as entered (without group prefixes)."""
153 return self.acl_users.getGroupNames()
154
155 security.declareProtected(ViewGroups, 'listGroupNames')
156 def listGroupNames(self):
157 """Return a list of the available groups' ids as entered (without group prefixes)."""
158 return self.acl_users.getGroupNames()
159
160 security.declarePublic("isGroup")
161 def isGroup(self, u):
162 """Test if a user/group object is a group or not.
163 You must pass an object you get earlier with wrapUser() or wrapGroup()
164 """
165 base = aq_base(u)
166 if hasattr(base, "isGroup") and base.isGroup():
167 return 1
168 return 0
169
170 security.declareProtected(View, 'searchForGroups')
171 def searchForGroups(self, REQUEST = {}, **kw):
172 """Return a list of groups meeting certain conditions. """
173 # arguments need to be better refined?
174 if REQUEST:
175 dict = REQUEST
176 else:
177 dict = kw
178
179 name = dict.get('name', None)
180 email = dict.get('email', None)
181 roles = dict.get('roles', None)
182 title = dict.get('title', None)
183 title_or_name = dict.get('title_or_name', None)
184
185 last_login_time = dict.get('last_login_time', None)
186 #is_manager = self.checkPermission('Manage portal', self)
187
188 if name:
189 name = name.strip().lower()
190 if not name:
191 name = None
192 if email:
193 email = email.strip().lower()
194 if not email:
195 email = None
196 if title:
197 title = title.strip().lower()
198 if title_or_name:
199 title_or_name = title_or_name.strip().lower()
200 if not title:
201 title = None
202
203 res = []
204 portal = self.portal_url.getPortalObject()
205 for g in portal.portal_groups.listGroups():
206 #if not (g.listed or is_manager):
207 # continue
208 if name:
209 if (g.getGroupName().lower().find(name) == -1) and (g.getGroupId().lower().find(name) == -1):
210 continue
211 if email:
212 if g.email.lower().find(email) == -1:
213 continue
214 if roles:
215 group_roles = g.getRoles()
216 found = 0
217 for r in roles:
218 if r in group_roles:
219 found = 1
220 break
221 if not found:
222 continue
223 if title:
224 if g.title.lower().find(title) == -1:
225 continue
226 if title_or_name:
227 # first search for title
228 if g.title.lower().find(title_or_name) == -1:
229 # not found, now search for name
230 if (g.getGroupName().lower().find(title_or_name) == -1) and (g.getGroupId().lower().find(title_or_name) == -1):
231 continue
232
233 if last_login_time:
234 if g.last_login_time < last_login_time:
235 continue
236 res.append(g)
237
238 return res
239
240 security.declareProtected(AddGroups, 'addGroup')
241 def addGroup(self, id, roles = [], groups = [], REQUEST=None, *args, **kw):
242 """Create a group, and a group workspace if the toggle is on, with the supplied id, roles, and domains.
243
244 Underlying user folder must support adding users via the usual Zope API.
245 Passwords for groups ARE irrelevant in GRUF."""
246 if id in self.listGroupIds():
247 raise ValueError, "Group '%s' already exists." % (id, )
248 self.acl_users.userFolderAddGroup(id, roles = roles, groups = groups )
249 self.createGrouparea(id)
250 self.getGroupById(id).setProperties(**kw)
251 addGroup = postonly(addGroup)
252
253 security.declareProtected(ManageGroups, 'editGroup')
254 def editGroup(self, id, roles = None, groups = None, REQUEST=None, *args, **kw):
255 """Edit the given group with the supplied password, roles, and domains.
256
257 Underlying user folder must support editing users via the usual Zope API.
258 Passwords for groups seem to be currently irrelevant in GRUF."""
259 self.acl_users.userFolderEditGroup(id, roles = roles, groups = groups, )
260 self.getGroupById(id).setProperties(**kw)
261 editGroup = postonly(editGroup)
262
263 security.declareProtected(DeleteGroups, 'removeGroups')
264 def removeGroups(self, ids, keep_workspaces=0, REQUEST=None):
265 """Remove the group in the provided list (if possible).
266
267 Will by default remove this group's GroupWorkspace if it exists. You may
268 turn this off by specifying keep_workspaces=true.
269 Underlying user folder must support removing users via the usual Zope API."""
270 for gid in ids:
271 gdata = self.getGroupById(gid)
272 gusers = gdata.getGroupMembers()
273 for guser in gusers:
274 gdata.removeMember(guser.id)
275
276 self.acl_users.userFolderDelGroups(ids)
277 gwf = self.getGroupWorkspacesFolder()
278 if not gwf: # _robert_
279 return
280 if not keep_workspaces:
281 for id in ids:
282 if hasattr(aq_base(gwf), id):
283 gwf._delObject(id)
284 removeGroups = postonly(removeGroups)
285
286 security.declareProtected(SetGroupOwnership, 'setGroupOwnership')
287 def setGroupOwnership(self, group, object, REQUEST=None):
288 """Make the object 'object' owned by group 'group' (a portal_groupdata-ish object).
289
290 For GRUF this is easy. Others may have to re-implement."""
291 user = group.getGroup()
292 if user is None:
293 raise ValueError, "Invalid group: '%s'." % (group, )
294 object.changeOwnership(user)
295 object.manage_setLocalRoles(user.getId(), ['Owner'])
296 setGroupOwnership = postonly(setGroupOwnership)
297
298 security.declareProtected(ManagePortal, 'setGroupWorkspacesFolder')
299 def setGroupWorkspacesFolder(self, id="", title=""):
300 """ Set the location of the Group Workspaces folder by id.
301
302 The Group Workspaces Folder contains all the group workspaces, just like the
303 Members folder contains all the member folders.
304
305 If anyone really cares, we can probably make the id work as a path as well,
306 but for the moment it's only an id for a folder in the portal root, just like the
307 corresponding MembershipTool functionality. """
308 self.groupworkspaces_id = id.strip()
309 self.groupworkspaces_title = title
310
311 security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderId')
312 def getGroupWorkspacesFolderId(self):
313 """ Get the Group Workspaces folder object's id.
314
315 The Group Workspaces Folder contains all the group workspaces, just like the
316 Members folder contains all the member folders. """
317 return self.groupworkspaces_id
318
319 security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderTitle')
320 def getGroupWorkspacesFolderTitle(self):
321 """ Get the Group Workspaces folder object's title.
322 """
323 return self.groupworkspaces_title
324
325 security.declarePublic('getGroupWorkspacesFolder')
326 def getGroupWorkspacesFolder(self):
327 """ Get the Group Workspaces folder object.
328
329 The Group Workspaces Folder contains all the group workspaces, just like the
330 Members folder contains all the member folders. """
331 parent = self.aq_inner.aq_parent
332 folder = getattr(parent, self.getGroupWorkspacesFolderId(), None)
333 return folder
334
335 security.declareProtected(ManagePortal, 'toggleGroupWorkspacesCreation')
336 def toggleGroupWorkspacesCreation(self, REQUEST=None):
337 """ Toggles the flag for creation of a GroupWorkspaces folder upon creation of the group. """
338 if not hasattr(self, 'groupWorkspacesCreationFlag'):
339 self.groupWorkspacesCreationFlag = 0
340
341 self.groupWorkspacesCreationFlag = not self.groupWorkspacesCreationFlag
342
343 m = self.groupWorkspacesCreationFlag and 'turned on' or 'turned off'
344
345 return self.manage_config(manage_tabs_message="Workspaces creation %s" % m)
346
347 security.declareProtected(ManagePortal, 'getGroupWorkspacesCreationFlag')
348 def getGroupWorkspacesCreationFlag(self):
349 """Return the (boolean) flag indicating whether the Groups Tool will create a group workspace
350 upon the creation of the group (if one doesn't exist already). """
351 return self.groupWorkspacesCreationFlag
352
353 security.declareProtected(AddGroups, 'createGrouparea')
354 def createGrouparea(self, id):
355 """Create a space in the portal for the given group, much like member home
356 folders."""
357 parent = self.aq_inner.aq_parent
358 workspaces = self.getGroupWorkspacesFolder()
359 pt = getToolByName( self, 'portal_types' )
360
361 if id and self.getGroupWorkspacesCreationFlag():
362 if workspaces is None:
363 # add GroupWorkspaces folder
364 pt.constructContent(
365 type_name = self.getGroupWorkspaceContainerType(),
366 container = parent,
367 id = self.getGroupWorkspacesFolderId(),
368 )
369 workspaces = self.getGroupWorkspacesFolder()
370 workspaces.setTitle(self.getGroupWorkspacesFolderTitle())
371 workspaces.setDescription("Container for " + self.getGroupWorkspacesFolderId())
372 # how about ownership?
373
374 # this stuff like MembershipTool...
375 portal_catalog = getToolByName( self, 'portal_catalog' )
376 portal_catalog.unindexObject(workspaces) # unindex GroupWorkspaces folder
377 workspaces._setProperty('right_slots', (), 'lines')
378
379 if workspaces is not None and not hasattr(workspaces.aq_base, id):
380 # add workspace to GroupWorkspaces folder
381 pt.constructContent(
382 type_name = self.getGroupWorkspaceType(),
383 container = workspaces,
384 id = id,
385 )
386 space = self.getGroupareaFolder(id)
387 space.setTitle("%s workspace" % id)
388 space.setDescription("Container for objects shared by this group")
389
390 if hasattr(space, 'setInitialGroup'):
391 # GroupSpaces can have their own policies regarding the group
392 # that they are created for.
393 user = self.getGroupById(id).getGroup()
394 if user is not None:
395 space.setInitialGroup(user)
396 else:
397 space.manage_delLocalRoles(space.users_with_local_role('Owner'))
398 self.setGroupOwnership(self.getGroupById(id), space)
399
400 # Hook to allow doing other things after grouparea creation.
401 notify_script = getattr(workspaces, 'notifyGroupAreaCreated', None)
402 if notify_script is not None:
403 notify_script()
404
405 # Re-indexation
406 portal_catalog = getToolByName( self, 'portal_catalog' )
407 portal_catalog.reindexObject(space)
408
409 security.declareProtected(ManagePortal, 'getGroupWorkspaceType')
410 def getGroupWorkspaceType(self):
411 """Return the Type (as in TypesTool) to make the GroupWorkspace."""
412 return self.groupWorkspaceType
413
414 security.declareProtected(ManagePortal, 'setGroupWorkspaceType')
415 def setGroupWorkspaceType(self, type):
416 """Set the Type (as in TypesTool) to make the GroupWorkspace."""
417 self.groupWorkspaceType = type
418
419 security.declareProtected(ManagePortal, 'getGroupWorkspaceContainerType')
420 def getGroupWorkspaceContainerType(self):
421 """Return the Type (as in TypesTool) to make the GroupWorkspace."""
422 return self.groupWorkspaceContainerType
423
424 security.declareProtected(ManagePortal, 'setGroupWorkspaceContainerType')
425 def setGroupWorkspaceContainerType(self, type):
426 """Set the Type (as in TypesTool) to make the GroupWorkspace."""
427 self.groupWorkspaceContainerType = type
428
429 security.declarePublic('getGroupareaFolder')
430 def getGroupareaFolder(self, id=None, verifyPermission=0):
431 """Returns the object of the group's work area."""
432 if id is None:
433 group = self.getAuthenticatedMember()
434 if not hasattr(member, 'getGroupId'):
435 return None
436 id = group.getGroupId()
437 workspaces = self.getGroupWorkspacesFolder()
438 if workspaces:
439 try:
440 folder = workspaces[id]
441 if verifyPermission and not _checkPermission('View', folder):
442 # Don't return the folder if the user can't get to it.
443 return None
444 return folder
445 except KeyError: pass
446 return None
447
448 security.declarePublic('getGroupareaURL')
449 def getGroupareaURL(self, id=None, verifyPermission=0):
450 """Returns the full URL to the group's work area."""
451 ga = self.getGroupareaFolder(id, verifyPermission)
452 if ga is not None:
453 return ga.absolute_url()
454 else:
455 return None
456
457 security.declarePrivate('wrapGroup')
458 def wrapGroup(self, g, wrap_anon=0):
459 ''' Sets up the correct acquisition wrappers for a group
460 object and provides an opportunity for a portal_memberdata
461 tool to retrieve and store member data independently of
462 the user object.
463 '''
464 b = getattr(g, 'aq_base', None)
465 if b is None:
466 # u isn't wrapped at all. Wrap it in self.acl_users.
467 b = g
468 g = g.__of__(self.acl_users)
469 if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'):
470 # This user is either not recognized by acl_users or it is
471 # already registered with something that implements the
472 # member data tool at least partially.
473 return g
474
475 parent = self.aq_inner.aq_parent
476 base = getattr(parent, 'aq_base', None)
477 if hasattr(base, 'portal_groupdata'):
478 # Get portal_groupdata to do the wrapping.
479 Log(LOG_DEBUG, "parent", parent)
480 gd = getToolByName(parent, 'portal_groupdata')
481 Log(LOG_DEBUG, "group data", gd)
482 try:
483 #log("wrapping group %s" % g)
484 portal_group = gd.wrapGroup(g)
485 return portal_group
486 except ConflictError:
487 raise
488 except:
489 import logging
490 logger = logging.getLogger('GroupUserFolder.GroupsTool')
491 logger.exception('Error during wrapGroup')
492 # Failed.
493 return g
494
495 InitializeClass(GroupsTool)