eggification
[GroupUserFolder.git] / Products / GroupUserFolder / GroupsTool.py
diff --git a/Products/GroupUserFolder/GroupsTool.py b/Products/GroupUserFolder/GroupsTool.py
new file mode 100644 (file)
index 0000000..e76caa1
--- /dev/null
@@ -0,0 +1,495 @@
+# -*- coding: utf-8 -*-
+## GroupUserFolder
+## Copyright (C)2006 Ingeniweb
+
+## This program 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 of the License, or
+## (at your option) any later version.
+
+## This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+## Copyright (c) 2003 The Connexions Project, All Rights Reserved
+## initially written by J Cameron Cooper, 11 June 2003
+## concept with Brent Hendricks, George Runyan
+"""
+Basic usergroup tool.
+"""
+__version__ = "$Revision$"
+# $Source:  $
+# $Id: GroupsTool.py 50142 2007-09-25 13:13:12Z wichert $
+__docformat__ = 'restructuredtext'
+
+from Products.CMFCore.utils import UniqueObject
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import _checkPermission
+from OFS.SimpleItem import SimpleItem
+from Globals import InitializeClass, DTMLFile, MessageDialog
+from Acquisition import aq_base
+from AccessControl.User import nobody
+from AccessControl import ClassSecurityInfo
+from ZODB.POSException import ConflictError
+# BBB CMF < 1.5
+try:
+    from Products.CMFCore.permissions import ManagePortal
+    from Products.CMFCore.permissions import View
+    from Products.CMFCore.permissions import ViewManagementScreens
+except ImportError:
+    from Products.CMFCore.CMFCorePermissions import ManagePortal
+    from Products.CMFCore.CMFCorePermissions import View
+    from Products.CMFCore.CMFCorePermissions import ViewManagementScreens
+
+from Products.GroupUserFolder import postonly
+from GroupsToolPermissions import AddGroups
+from GroupsToolPermissions import ManageGroups
+from GroupsToolPermissions import DeleteGroups
+from GroupsToolPermissions import ViewGroups
+from GroupsToolPermissions import SetGroupOwnership
+from Products.CMFCore.ActionProviderBase import ActionProviderBase
+from interfaces.portal_groups import portal_groups as IGroupsTool
+from global_symbols import *
+
+# Optional feature-preview support
+import PloneFeaturePreview
+
+class GroupsTool (UniqueObject, SimpleItem, ActionProviderBase, ):
+    """ This tool accesses group data through a GRUF acl_users object.
+
+    It can be replaced with something that groups member data in a
+    different way.
+    """
+    # Show implementation only if  IGroupsTool is defined
+    # The latter will work only with Plone 1.1 => hence, the if
+    if hasattr(ActionProviderBase, '__implements__'):
+        __implements__ = (IGroupsTool, ActionProviderBase.__implements__)
+
+    id = 'portal_groups'
+    meta_type = 'CMF Groups Tool'
+    _actions = ()
+
+    security = ClassSecurityInfo()
+
+    groupworkspaces_id = "groups"
+    groupworkspaces_title = "Groups"
+    groupWorkspacesCreationFlag = 1
+    groupWorkspaceType = "Folder"
+    groupWorkspaceContainerType = "Folder"
+
+    manage_options=(
+            ( { 'label' : 'Configure'
+                     , 'action' : 'manage_config'
+                    },
+                ) + ActionProviderBase.manage_options +
+                ( { 'label' : 'Overview'
+                     , 'action' : 'manage_overview'
+                     },
+                ) + SimpleItem.manage_options)
+
+    #                                                   #
+    #                   ZMI methods                     #
+    #                                                   #
+    security.declareProtected(ViewManagementScreens, 'manage_overview')
+    manage_overview = DTMLFile('dtml/explainGroupsTool', globals())     # unlike MembershipTool
+    security.declareProtected(ViewManagementScreens, 'manage_config')
+    manage_config = DTMLFile('dtml/configureGroupsTool', globals())
+
+    security.declareProtected(ManagePortal, 'manage_setGroupWorkspacesFolder')
+    def manage_setGroupWorkspacesFolder(self, id='groups', title='Groups', REQUEST=None):
+        """ZMI method for workspace container name set."""
+        self.setGroupWorkspacesFolder(id, title)
+        return self.manage_config(manage_tabs_message="Workspaces folder name set to %s" % id)
+
+    security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceType')
+    def manage_setGroupWorkspaceType(self, type='Folder', REQUEST=None):
+        """ZMI method for workspace type set."""
+        self.setGroupWorkspaceType(type)
+        return self.manage_config(manage_tabs_message="Group Workspaces type set to %s" % type)
+
+    security.declareProtected(ManagePortal, 'manage_setGroupWorkspaceContainerType')
+    def manage_setGroupWorkspaceContainerType(self, type='Folder', REQUEST=None):
+        """ZMI method for workspace type set."""
+        self.setGroupWorkspaceContainerType(type)
+        return self.manage_config(manage_tabs_message="Group Workspaces container type set to %s" % type)
+
+    security.declareProtected(ViewGroups, 'getGroupById')
+    def getGroupById(self, id):
+        """
+        Returns the portal_groupdata-ish object for a group corresponding to this id.
+        """
+        if id==None:
+            return None
+        g = self.acl_users.getGroupByName(id, None)
+        if g is not None:
+            g = self.wrapGroup(g)
+        return g
+
+    security.declareProtected(ViewGroups, 'getGroupsByUserId')
+    def getGroupsByUserId(self, userid):
+        """Return a list of the groups the user corresponding to 'userid' belongs to."""
+        #log("getGroupsByUserId(%s)" % userid)
+        user = self.acl_users.getUser(userid)
+        #log("user '%s' is in groups %s" % (userid, user.getGroups()))
+        if user:
+            groups = user.getGroups() or []
+        else:
+            groups = []
+        return [self.getGroupById(elt) for elt in groups]
+
+    security.declareProtected(ViewGroups, 'listGroups')
+    def listGroups(self):
+        """Return a list of the available portal_groupdata-ish objects."""
+        return [ self.wrapGroup(elt) for elt in self.acl_users.getGroups() ]
+
+    security.declareProtected(ViewGroups, 'listGroupIds')
+    def listGroupIds(self):
+        """Return a list of the available groups' ids as entered (without group prefixes)."""
+        return self.acl_users.getGroupNames()
+
+    security.declareProtected(ViewGroups, 'listGroupNames')
+    def listGroupNames(self):
+        """Return a list of the available groups' ids as entered (without group prefixes)."""
+        return self.acl_users.getGroupNames()
+
+    security.declarePublic("isGroup")
+    def isGroup(self, u):
+        """Test if a user/group object is a group or not.
+        You must pass an object you get earlier with wrapUser() or wrapGroup()
+        """
+        base = aq_base(u)
+        if hasattr(base, "isGroup") and base.isGroup():
+            return 1
+        return 0
+
+    security.declareProtected(View, 'searchForGroups')
+    def searchForGroups(self, REQUEST = {}, **kw):
+        """Return a list of groups meeting certain conditions. """
+        # arguments need to be better refined?
+        if REQUEST:
+            dict = REQUEST
+        else:
+            dict = kw
+
+        name = dict.get('name', None)
+        email = dict.get('email', None)
+        roles = dict.get('roles', None)
+        title = dict.get('title', None)
+        title_or_name = dict.get('title_or_name', None)
+        
+        last_login_time = dict.get('last_login_time', None)
+        #is_manager = self.checkPermission('Manage portal', self)
+
+        if name:
+            name = name.strip().lower()
+        if not name:
+            name = None
+        if email:
+            email = email.strip().lower()
+        if not email:
+            email = None
+        if title:
+            title = title.strip().lower()
+        if title_or_name:
+            title_or_name = title_or_name.strip().lower()
+        if not title:
+            title = None
+
+        res = []
+        portal = self.portal_url.getPortalObject()
+        for g in portal.portal_groups.listGroups():
+            #if not (g.listed or is_manager):
+            #    continue
+            if name:
+                if (g.getGroupName().lower().find(name) == -1) and (g.getGroupId().lower().find(name) == -1):
+                    continue
+            if email:
+                if g.email.lower().find(email) == -1:
+                    continue
+            if roles:
+                group_roles = g.getRoles()
+                found = 0
+                for r in roles:
+                    if r in group_roles:
+                        found = 1
+                        break
+                if not found:
+                    continue
+            if title:
+                if g.title.lower().find(title) == -1:
+                    continue
+            if title_or_name:
+                # first search for title
+                if g.title.lower().find(title_or_name) == -1:
+                    # not found, now search for name
+                    if (g.getGroupName().lower().find(title_or_name) == -1) and (g.getGroupId().lower().find(title_or_name) == -1):
+                        continue
+                
+            if last_login_time:
+                if g.last_login_time < last_login_time:
+                    continue
+            res.append(g)
+
+        return res
+
+    security.declareProtected(AddGroups, 'addGroup')
+    def addGroup(self, id, roles = [], groups = [], REQUEST=None, *args, **kw):
+        """Create a group, and a group workspace if the toggle is on, with the supplied id, roles, and domains.
+
+        Underlying user folder must support adding users via the usual Zope API.
+        Passwords for groups ARE irrelevant in GRUF."""
+        if id in self.listGroupIds():
+            raise ValueError, "Group '%s' already exists." % (id, )
+        self.acl_users.userFolderAddGroup(id, roles = roles, groups = groups )
+        self.createGrouparea(id)
+        self.getGroupById(id).setProperties(**kw)
+    addGroup = postonly(addGroup)
+
+    security.declareProtected(ManageGroups, 'editGroup')
+    def editGroup(self, id, roles = None, groups = None, REQUEST=None, *args, **kw):
+        """Edit the given group with the supplied password, roles, and domains.
+
+        Underlying user folder must support editing users via the usual Zope API.
+        Passwords for groups seem to be currently irrelevant in GRUF."""
+        self.acl_users.userFolderEditGroup(id, roles = roles, groups = groups, )
+        self.getGroupById(id).setProperties(**kw)
+    editGroup = postonly(editGroup)
+
+    security.declareProtected(DeleteGroups, 'removeGroups')
+    def removeGroups(self, ids, keep_workspaces=0, REQUEST=None):
+        """Remove the group in the provided list (if possible).
+
+        Will by default remove this group's GroupWorkspace if it exists. You may
+        turn this off by specifying keep_workspaces=true.
+        Underlying user folder must support removing users via the usual Zope API."""
+        for gid in ids:
+            gdata = self.getGroupById(gid)
+            gusers = gdata.getGroupMembers()
+            for guser in gusers:
+                gdata.removeMember(guser.id)
+
+        self.acl_users.userFolderDelGroups(ids)
+        gwf = self.getGroupWorkspacesFolder()
+        if not gwf: # _robert_
+            return
+        if not keep_workspaces:
+            for id in ids:
+                if hasattr(aq_base(gwf), id):
+                    gwf._delObject(id)
+    removeGroups = postonly(removeGroups)
+
+    security.declareProtected(SetGroupOwnership, 'setGroupOwnership')
+    def setGroupOwnership(self, group, object, REQUEST=None):
+        """Make the object 'object' owned by group 'group' (a portal_groupdata-ish object).
+
+        For GRUF this is easy. Others may have to re-implement."""
+        user = group.getGroup()
+        if user is None:
+            raise ValueError, "Invalid group: '%s'." % (group, )
+        object.changeOwnership(user)
+        object.manage_setLocalRoles(user.getId(), ['Owner'])
+    setGroupOwnership = postonly(setGroupOwnership)
+
+    security.declareProtected(ManagePortal, 'setGroupWorkspacesFolder')
+    def setGroupWorkspacesFolder(self, id="", title=""):
+        """ Set the location of the Group Workspaces folder by id.
+
+        The Group Workspaces Folder contains all the group workspaces, just like the
+        Members folder contains all the member folders.
+
+         If anyone really cares, we can probably make the id work as a path as well,
+         but for the moment it's only an id for a folder in the portal root, just like the
+         corresponding MembershipTool functionality. """
+        self.groupworkspaces_id = id.strip()
+        self.groupworkspaces_title = title
+
+    security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderId')
+    def getGroupWorkspacesFolderId(self):
+        """ Get the Group Workspaces folder object's id.
+
+        The Group Workspaces Folder contains all the group workspaces, just like the
+        Members folder contains all the member folders. """
+        return self.groupworkspaces_id
+
+    security.declareProtected(ManagePortal, 'getGroupWorkspacesFolderTitle')
+    def getGroupWorkspacesFolderTitle(self):
+        """ Get the Group Workspaces folder object's title.
+        """
+        return self.groupworkspaces_title
+
+    security.declarePublic('getGroupWorkspacesFolder')
+    def getGroupWorkspacesFolder(self):
+        """ Get the Group Workspaces folder object.
+
+        The Group Workspaces Folder contains all the group workspaces, just like the
+        Members folder contains all the member folders. """
+        parent = self.aq_inner.aq_parent
+        folder = getattr(parent, self.getGroupWorkspacesFolderId(), None)
+        return folder
+
+    security.declareProtected(ManagePortal, 'toggleGroupWorkspacesCreation')
+    def toggleGroupWorkspacesCreation(self, REQUEST=None):
+        """ Toggles the flag for creation of a GroupWorkspaces folder upon creation of the group. """
+        if not hasattr(self, 'groupWorkspacesCreationFlag'):
+            self.groupWorkspacesCreationFlag = 0
+
+        self.groupWorkspacesCreationFlag = not self.groupWorkspacesCreationFlag
+
+        m = self.groupWorkspacesCreationFlag and 'turned on' or 'turned off'
+
+        return self.manage_config(manage_tabs_message="Workspaces creation %s" % m)
+
+    security.declareProtected(ManagePortal, 'getGroupWorkspacesCreationFlag')
+    def getGroupWorkspacesCreationFlag(self):
+        """Return the (boolean) flag indicating whether the Groups Tool will create a group workspace
+        upon the creation of the group (if one doesn't exist already). """
+        return self.groupWorkspacesCreationFlag
+
+    security.declareProtected(AddGroups, 'createGrouparea')
+    def createGrouparea(self, id):
+        """Create a space in the portal for the given group, much like member home
+        folders."""
+        parent = self.aq_inner.aq_parent
+        workspaces = self.getGroupWorkspacesFolder()
+        pt = getToolByName( self, 'portal_types' )
+
+        if id and self.getGroupWorkspacesCreationFlag():
+            if workspaces is None:
+                # add GroupWorkspaces folder
+                pt.constructContent(
+                    type_name = self.getGroupWorkspaceContainerType(),
+                    container = parent,
+                    id = self.getGroupWorkspacesFolderId(),
+                    )
+                workspaces = self.getGroupWorkspacesFolder()
+                workspaces.setTitle(self.getGroupWorkspacesFolderTitle())
+                workspaces.setDescription("Container for " + self.getGroupWorkspacesFolderId())
+                # how about ownership?
+
+                # this stuff like MembershipTool...
+                portal_catalog = getToolByName( self, 'portal_catalog' )
+                portal_catalog.unindexObject(workspaces)     # unindex GroupWorkspaces folder
+                workspaces._setProperty('right_slots', (), 'lines')
+                
+            if workspaces is not None and not hasattr(workspaces.aq_base, id):
+                # add workspace to GroupWorkspaces folder
+                pt.constructContent(
+                    type_name = self.getGroupWorkspaceType(),
+                    container = workspaces,
+                    id = id,
+                    )
+                space = self.getGroupareaFolder(id)
+                space.setTitle("%s workspace" % id)
+                space.setDescription("Container for objects shared by this group")
+
+                if hasattr(space, 'setInitialGroup'):
+                    # GroupSpaces can have their own policies regarding the group
+                    # that they are created for.
+                    user = self.getGroupById(id).getGroup()
+                    if user is not None:
+                        space.setInitialGroup(user)
+                else:
+                    space.manage_delLocalRoles(space.users_with_local_role('Owner'))
+                    self.setGroupOwnership(self.getGroupById(id), space)
+
+                # Hook to allow doing other things after grouparea creation.
+                notify_script = getattr(workspaces, 'notifyGroupAreaCreated', None)
+                if notify_script is not None:
+                    notify_script()
+
+                # Re-indexation
+                portal_catalog = getToolByName( self, 'portal_catalog' )
+                portal_catalog.reindexObject(space)
+    security.declareProtected(ManagePortal, 'getGroupWorkspaceType')
+    def getGroupWorkspaceType(self):
+        """Return the Type (as in TypesTool) to make the GroupWorkspace."""
+        return self.groupWorkspaceType
+
+    security.declareProtected(ManagePortal, 'setGroupWorkspaceType')
+    def setGroupWorkspaceType(self, type):
+        """Set the Type (as in TypesTool) to make the GroupWorkspace."""
+        self.groupWorkspaceType = type
+
+    security.declareProtected(ManagePortal, 'getGroupWorkspaceContainerType')
+    def getGroupWorkspaceContainerType(self):
+        """Return the Type (as in TypesTool) to make the GroupWorkspace."""
+        return self.groupWorkspaceContainerType
+
+    security.declareProtected(ManagePortal, 'setGroupWorkspaceContainerType')
+    def setGroupWorkspaceContainerType(self, type):
+        """Set the Type (as in TypesTool) to make the GroupWorkspace."""
+        self.groupWorkspaceContainerType = type
+
+    security.declarePublic('getGroupareaFolder')
+    def getGroupareaFolder(self, id=None, verifyPermission=0):
+        """Returns the object of the group's work area."""
+        if id is None:
+            group = self.getAuthenticatedMember()
+            if not hasattr(member, 'getGroupId'):
+                return None
+            id = group.getGroupId()
+        workspaces = self.getGroupWorkspacesFolder()
+        if workspaces:
+            try:
+                folder = workspaces[id]
+                if verifyPermission and not _checkPermission('View', folder):
+                    # Don't return the folder if the user can't get to it.
+                    return None
+                return folder
+            except KeyError: pass
+        return None
+
+    security.declarePublic('getGroupareaURL')
+    def getGroupareaURL(self, id=None, verifyPermission=0):
+        """Returns the full URL to the group's work area."""
+        ga = self.getGroupareaFolder(id, verifyPermission)
+        if ga is not None:
+            return ga.absolute_url()
+        else:
+            return None
+
+    security.declarePrivate('wrapGroup')
+    def wrapGroup(self, g, wrap_anon=0):
+        ''' Sets up the correct acquisition wrappers for a group
+        object and provides an opportunity for a portal_memberdata
+        tool to retrieve and store member data independently of
+        the user object.
+        '''
+        b = getattr(g, 'aq_base', None)
+        if b is None:
+            # u isn't wrapped at all.  Wrap it in self.acl_users.
+            b = g
+            g = g.__of__(self.acl_users)
+        if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'):
+            # This user is either not recognized by acl_users or it is
+            # already registered with something that implements the
+            # member data tool at least partially.
+            return g
+
+        parent = self.aq_inner.aq_parent
+        base = getattr(parent, 'aq_base', None)
+        if hasattr(base, 'portal_groupdata'):
+            # Get portal_groupdata to do the wrapping.
+            Log(LOG_DEBUG, "parent", parent)
+            gd = getToolByName(parent, 'portal_groupdata')
+            Log(LOG_DEBUG, "group data", gd)
+            try:
+                #log("wrapping group %s" % g)
+                portal_group = gd.wrapGroup(g)
+                return portal_group
+            except ConflictError:
+                raise
+            except:
+                import logging
+                logger = logging.getLogger('GroupUserFolder.GroupsTool')
+                logger.exception('Error during wrapGroup')
+        # Failed.
+        return g
+
+InitializeClass(GroupsTool)