1 # -*- coding: utf-8 -*-
3 ## Copyright (C)2006 Ingeniweb
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.
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.
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.
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
25 __version__
= "$Revision$"
27 # $Id: GroupsTool.py 50142 2007-09-25 13:13:12Z wichert $
28 __docformat__
= 'restructuredtext'
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
41 from Products
.CMFCore
.permissions
import ManagePortal
42 from Products
.CMFCore
.permissions
import View
43 from Products
.CMFCore
.permissions
import ViewManagementScreens
45 from Products
.CMFCore
.CMFCorePermissions
import ManagePortal
46 from Products
.CMFCore
.CMFCorePermissions
import View
47 from Products
.CMFCore
.CMFCorePermissions
import ViewManagementScreens
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 *
59 # Optional feature-preview support
60 import PloneFeaturePreview
62 class GroupsTool (UniqueObject
, SimpleItem
, ActionProviderBase
, ):
63 """ This tool accesses group data through a GRUF acl_users object.
65 It can be replaced with something that groups member data in a
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
__)
74 meta_type
= 'CMF Groups Tool'
77 security
= ClassSecurityInfo()
79 groupworkspaces_id
= "groups"
80 groupworkspaces_title
= "Groups"
81 groupWorkspacesCreationFlag
= 1
82 groupWorkspaceType
= "Folder"
83 groupWorkspaceContainerType
= "Folder"
86 ( { 'label' : 'Configure'
87 , 'action' : 'manage_config'
89 ) + ActionProviderBase
.manage_options
+
90 ( { 'label' : 'Overview'
91 , 'action' : 'manage_overview'
93 ) + SimpleItem
.manage_options
)
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())
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)
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)
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)
121 security
.declareProtected(ViewGroups
, 'getGroupById')
122 def getGroupById(self
, id):
124 Returns the portal_groupdata-ish object for a group corresponding to this id.
128 g
= self
.acl_users
.getGroupByName(id, None)
130 g
= self
.wrapGroup(g
)
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()))
140 groups
= user
.getGroups() or []
143 return [self
.getGroupById(elt
) for elt
in groups
]
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() ]
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()
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()
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()
166 if hasattr(base
, "isGroup") and base
.isGroup():
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?
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)
185 last_login_time
= dict.get('last_login_time', None)
186 #is_manager = self.checkPermission('Manage portal', self)
189 name
= name
.strip().lower()
193 email
= email
.strip().lower()
197 title
= title
.strip().lower()
199 title_or_name
= title_or_name
.strip().lower()
204 portal
= self
.portal_url
.getPortalObject()
205 for g
in portal
.portal_groups
.listGroups():
206 #if not (g.listed or is_manager):
209 if (g
.getGroupName().lower().find(name
) == -1) and (g
.getGroupId().lower().find(name
) == -1):
212 if g
.email
.lower().find(email
) == -1:
215 group_roles
= g
.getRoles()
224 if g
.title
.lower().find(title
) == -1:
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):
234 if g
.last_login_time
< last_login_time
:
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.
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
)
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.
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
)
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).
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."""
271 gdata
= self
.getGroupById(gid
)
272 gusers
= gdata
.getGroupMembers()
274 gdata
.removeMember(guser
.id)
276 self
.acl_users
.userFolderDelGroups(ids
)
277 gwf
= self
.getGroupWorkspacesFolder()
278 if not gwf
: # _robert_
280 if not keep_workspaces
:
282 if hasattr(aq_base(gwf
), id):
284 removeGroups
= postonly(removeGroups
)
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).
290 For GRUF this is easy. Others may have to re-implement."""
291 user
= group
.getGroup()
293 raise ValueError, "Invalid group: '%s'." % (group
, )
294 object.changeOwnership(user
)
295 object.manage_setLocalRoles(user
.getId(), ['Owner'])
296 setGroupOwnership
= postonly(setGroupOwnership
)
298 security
.declareProtected(ManagePortal
, 'setGroupWorkspacesFolder')
299 def setGroupWorkspacesFolder(self
, id="", title
=""):
300 """ Set the location of the Group Workspaces folder by id.
302 The Group Workspaces Folder contains all the group workspaces, just like the
303 Members folder contains all the member folders.
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
311 security
.declareProtected(ManagePortal
, 'getGroupWorkspacesFolderId')
312 def getGroupWorkspacesFolderId(self
):
313 """ Get the Group Workspaces folder object's id.
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
319 security
.declareProtected(ManagePortal
, 'getGroupWorkspacesFolderTitle')
320 def getGroupWorkspacesFolderTitle(self
):
321 """ Get the Group Workspaces folder object's title.
323 return self
.groupworkspaces_title
325 security
.declarePublic('getGroupWorkspacesFolder')
326 def getGroupWorkspacesFolder(self
):
327 """ Get the Group Workspaces folder object.
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)
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
341 self
.groupWorkspacesCreationFlag
= not self
.groupWorkspacesCreationFlag
343 m
= self
.groupWorkspacesCreationFlag
and 'turned on' or 'turned off'
345 return self
.manage_config(manage_tabs_message
="Workspaces creation %s" % m
)
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
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
357 parent
= self
.aq_inner
.aq_parent
358 workspaces
= self
.getGroupWorkspacesFolder()
359 pt
= getToolByName( self
, 'portal_types' )
361 if id and self
.getGroupWorkspacesCreationFlag():
362 if workspaces
is None:
363 # add GroupWorkspaces folder
365 type_name
= self
.getGroupWorkspaceContainerType(),
367 id = self
.getGroupWorkspacesFolderId(),
369 workspaces
= self
.getGroupWorkspacesFolder()
370 workspaces
.setTitle(self
.getGroupWorkspacesFolderTitle())
371 workspaces
.setDescription("Container for " + self
.getGroupWorkspacesFolderId())
372 # how about ownership?
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')
379 if workspaces
is not None and not hasattr(workspaces
.aq_base
, id):
380 # add workspace to GroupWorkspaces folder
382 type_name
= self
.getGroupWorkspaceType(),
383 container
= workspaces
,
386 space
= self
.getGroupareaFolder(id)
387 space
.setTitle("%s workspace" % id)
388 space
.setDescription("Container for objects shared by this group")
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()
395 space
.setInitialGroup(user
)
397 space
.manage_delLocalRoles(space
.users_with_local_role('Owner'))
398 self
.setGroupOwnership(self
.getGroupById(id), space
)
400 # Hook to allow doing other things after grouparea creation.
401 notify_script
= getattr(workspaces
, 'notifyGroupAreaCreated', None)
402 if notify_script
is not None:
406 portal_catalog
= getToolByName( self
, 'portal_catalog' )
407 portal_catalog
.reindexObject(space
)
409 security
.declareProtected(ManagePortal
, 'getGroupWorkspaceType')
410 def getGroupWorkspaceType(self
):
411 """Return the Type (as in TypesTool) to make the GroupWorkspace."""
412 return self
.groupWorkspaceType
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
419 security
.declareProtected(ManagePortal
, 'getGroupWorkspaceContainerType')
420 def getGroupWorkspaceContainerType(self
):
421 """Return the Type (as in TypesTool) to make the GroupWorkspace."""
422 return self
.groupWorkspaceContainerType
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
429 security
.declarePublic('getGroupareaFolder')
430 def getGroupareaFolder(self
, id=None, verifyPermission
=0):
431 """Returns the object of the group's work area."""
433 group
= self
.getAuthenticatedMember()
434 if not hasattr(member
, 'getGroupId'):
436 id = group
.getGroupId()
437 workspaces
= self
.getGroupWorkspacesFolder()
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.
445 except KeyError: pass
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
)
453 return ga
.absolute_url()
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
464 b
= getattr(g
, 'aq_base', None)
466 # u isn't wrapped at all. Wrap it in self.acl_users.
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.
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
)
483 #log("wrapping group %s" % g)
484 portal_group
= gd
.wrapGroup(g
)
486 except ConflictError
:
490 logger
= logging
.getLogger('GroupUserFolder.GroupsTool')
491 logger
.exception('Error during wrapGroup')
495 InitializeClass(GroupsTool
)