Logo Search packages:      
Sourcecode: zope-cmfmember version File versions  Download package

memberdatacontainer.py

from Products.CMFCore.interfaces.portal_memberdata import MemberData as IMemberData

from Products.CMFCore.interfaces.portal_memberdata import portal_memberdata as IMemberDataTool

import string

from DateTime import DateTime
from Globals import InitializeClass, DTMLFile
from AccessControl import getSecurityManager, ClassSecurityInfo, Unauthorized
from Acquisition import aq_base, aq_parent, aq_inner, ImplicitAcquisitionWrapper
from BTrees.OOBTree import OOBTree
from OFS.SimpleItem import SimpleItem
from OFS.PropertyManager import PropertyManager
from OFS.ObjectManager import ObjectManager
from ZODB.POSException import ConflictError

from Products.CMFCore import CMFCorePermissions
from Products.CMFCore.ActionProviderBase import ActionProviderBase
from Products.CMFCore.utils import UniqueObject, getToolByName
from Products.CMFCore.PortalFolder import PortalFolder

from Products.Archetypes.debug import log
from Products.Archetypes.public import *

from Products.CMFMember.content.member import Member
from Products.CMFMember.permission import ADD_MEMBER_PERMISSION 
from Products.CMFMember.utils import logException, changeOwnership
from Products.CMFMember.Extensions.Workflow import triggerAutomaticTransitions
from Products.CMFMember.config import DEFAULT_TYPE, PKG_NAME, USE_SCHEMA_EDITOR

if USE_SCHEMA_EDITOR:
    from Products.ATSchemaEditorNG.SchemaEditor import SchemaEditor \
         as MDCBase
else:
    class MDCBase:
        pass


_marker = []

ORPHANED_VOCAB = DisplayList((
    ('transfer_to_current', 'Transfer ownership to the person who deletes the member.'),
    ('delete', 'Delete the content.')
    ))

schema = BaseFolderSchema + Schema((
         TextField('description',
              default_content_type = 'text/plain',
              default_output_type = 'text/html',
              widget = TextAreaWidget(rows = 5)),

         StringField('version',
                     widget = ComputedWidget(
                          label='Version',
                          label_msgid='label_mdcontainer_version',
                          description="Version of this instance.",
                          description_msgid='help_mdcontainer_version',
                          i18n_domain='cmfmember',
                          ),
                     ),

         StringField('typeName',
                     default = DEFAULT_TYPE,
                     vocabulary = 'getAllowedMemberTypes',
                     read_permission = CMFCorePermissions.View,
                     accessor = 'getTypeName',
                     mutator = 'setDefaultType',
                     widget = SelectionWidget(
                          label='Default member type',
                          label_msgid='label_default_member_type',
                          description="Choose default member type.",
                          description_msgid='help_default_member_type',
                          i18n_domain='cmfmember',
                          ),
                     ),

         LinesField('allowedMemberTypes',
                    default = (DEFAULT_TYPE,), ###DWM unnecessary?
                    mode='rw',
                    searchable = 0,
                    vocabulary = 'getAvailableMemberTypes',
                    enforceVocabulary=1,
                    accessor='getAllowedMemberTypes',
                    mutator='setAllowedMemberTypes',
                    widget = MultiSelectionWidget(
                          label='Allowed member types',
                          label_msgid='label_allowed_member_types',
                          description="Indicate the allowed member types.",
                          description_msgid='help_allowed_member_types',
                          i18n_domain='cmfmember',
                          ),
                    ),

         StringField('orphanedContentDestination',
                    vocabulary=ORPHANED_VOCAB,
                    required=1,
                    default='transfer_to_current',
                    searchable=0,
                    enforceVocabulary=1,
                    widget=SelectionWidget(format='flex',
                          label='Orphaned content',
                          label_msgid='label_mdcontainer_orphaned_content',
                          description="Indicate what should happen to a member's content when the member is deleted.",
                          description_msgid='help_mdcontainer_orphaned_content',
                          i18n_domain='cmfmember',
                          ),
                    ),
         ))

search_catalog = 'member_catalog'
tool_types = (
    'ControlTool',
    'MemberDataContainer'
    )

class TempFolder(PortalFolder):
    portal_type = meta_type = 'MemberArea'

00119 class MemberDataContainer(MDCBase, BaseBTreeFolder):
    """ CMFMember replacement for portal_memberdata """
    __implements__ = (IMemberDataTool)

    schema = schema
    filter_content_types = 1
    allowed_content_types = [DEFAULT_TYPE]

    security=ClassSecurityInfo()

    id = 'portal_memberdata'
    archetype_name = meta_type = PKG_NAME + ' Container'
    portal_type = 'MemberDataContainer'

    global_allow = 0

    _defaultMember = None
    _instanceVersion = ''

    manage_options=( BaseBTreeFolder.manage_options +
                     ActionProviderBase.manage_options
                     )
   
00142     def manage_afterAdd(self, item, container):
        """
        have to set the default member type here instead of at the
        class level b/c we need a context to access the tools to get
        at all of the info we need
        """
        BaseBTreeFolder.manage_afterAdd(self, item, container)
        self.setDefaultType(DEFAULT_TYPE)
        
    # Methods: wrapUser, getMemberDataContents and pruneMemberDataContents
    # Implementation of CMFCore.interfaces.portal_memberdata.portal_memberdata
    security.declarePrivate('wrapUser')
00154     def wrapUser(self, user):
        """
        If possible, returns the Member object that corresponds
        to the given User object.
        """
        try:
            name = user.getUserName()
            m = self.get(name, None)
            if not m:
                ## XXX Delegate to the factory and create a new site specific
                ## member object for this user
                addMember=getMemberFactory(self, self.getTypeName())
                addMember(name)
                m = self.get(name)
                m.setUser(user)
                # trigger any workflow transitions that need to occur
                triggerAutomaticTransitions(m)

            # Return a wrapper with self as containment and
            # the user as context following CMFCore portal_memberdata
            return m.__of__(self).__of__(user)
        except:
            import traceback
            import sys
            sys.stdout.write('\n'.join(traceback.format_exception(*sys.exc_info())))
            raise

    security.declareProtected(CMFCorePermissions.ManageProperties,
                              'getMemberDataContents')
00183     def getMemberDataContents(self):
        """
        Returns a list containing a dictionary with information
        about the _members BTree contents: member_count is the
        total number of member instances stored in the memberdata-
        tool while orphan_count is the number of member instances
        that for one reason or another are no longer in the
        underlying acl_users user folder.
        The result is designed to be iterated over in a dtml-in
        """

        members = self.objectValues(spec=self.getAllowedMemberTypes())
        oc = 0
        member_ids = []
        for member in members:
            if member.isOrphan(): oc +=1
            member_ids.append(member.getUserName())

        return [{
            'member_count' : len(member_ids),
            'orphan_count' : oc
            }]

    security.declareProtected(CMFCorePermissions.ManageProperties,
                              'pruneMemberDataContents')
00208     def pruneMemberDataContents(self):
        """
        Check for every Member object if it's orphan and delete it.

        The impl can override the pruneOrphan(id) method to do things
        like manage its workflow state. The default impl will remove.
        """
        members = self.objectValues(spec=self.getAllowedMemberTypes())
        for member in members:
            if member.isOrphan():
                self.pruneOrphan(member.getUserName())

00220     def fixOwnership(self):
        """
        A utility method for transferring ownership for users who no longer
        exist
        """

        portal = getToolByName(self, 'portal_url').getPortalObject()
        catalog = getToolByName(self, 'portal_catalog')

        missing_users = []

        users = catalog.uniqueValuesFor('indexedOwner')
        for u in users:
            sp = u.split('/')
            user_id = sp[-1]
            path = '/'.join(sp[:-1])
            acl_users = portal.unrestrictedTraverse(path)
            user = acl_users.getUser(user_id)
            if user is None:
                missing_users.append(user_id)

        reindex = []
        for u in missing_users:
            ownedObjects = catalog.search({'indexedOwner':u})

            for o in ownedObjects:
                object = o.getObject()
                if object is not None and object != self:
                    if self.handleOrphanedContent(object):
                        reindex.append(object)

        for o in reindex:
            o.reindexObject()


00255     def handleOrphanedContent(self, object, new_user=None):
        """
        Handle orphaned content.  If new_user is not None, ownership is
        transferred to the new user.  If new_user is None, the policy is
        determined by container properties.  Returns 1 if the object's
        ownership changes.
        """
        if new_user:
            changeOwnership(object, new_user)
            return 1
        else:
            if self.getOrphanedContentDestination() == 'delete':
                try:
                    parent = aq_parent(aq_inner(object))
                    if parent.isPrincipiaFolderish:
                        parent.manage_delObjects([object.getId()])
                except ConflictError:
                    raise
                except:
                    logException()
                return 0
            else:
                member = getToolByName(self,
                                       'portal_membership').getAuthenticatedMember()
                new_user = member.getUser()
                changeOwnership(object, new_user)
                return 1

    security.declarePublic('index_html')
00284     def index_html(self, REQUEST, RESPONSE):
        """
        Return Member search form as default page.
        """
        search_form = self.restrictedTraverse('member_search_form')
        return search_form(REQUEST, RESPONSE)

00291     def folder_delete(self, REQUEST, RESPONSE):
        """ Override the delete method to enable confirmation
            before deletion. """
        prefs_users_overview = self.restrictedTraverse('prefs_users_overview')
        if REQUEST.get('folder_confirm_delete'): 
            from Products.CMFPlone import transaction_note
            ids=REQUEST.get('ids', [])
            titles=[]
            titles_and_ids=[]
            
            status='failure'
            message='Please select one or more items to delete.'
            
            for id in ids:
                obj=self.restrictedTraverse(id)
                titles.append(obj.title_or_id())
                titles_and_ids.append('%s (%s)' % (obj.title_or_id(), obj.getId()))
            
            if ids:
                status='success'
                message=', '.join(titles)+' has been deleted.'
                transaction_note('Deleted %s from %s' % (', '.join(titles_and_ids), self.absolute_url()))
                self.manage_delObjects(ids)
            REQUEST['portal_status_message'] = message
            return prefs_users_overview(REQUEST, RESPONSE)
        else:
            REQUEST['folder_confirm_delete'] = True
            REQUEST['portal_status_message'] ='Confirm deletion by pressing the delete button again. %s' % REQUEST.get('ids')
            REQUEST['ids_checked'] = REQUEST.get('ids')
            return prefs_users_overview(REQUEST, RESPONSE)
           
    security.declarePrivate('_deleteMember')
00323     def _deleteMember(self, id):
        """
        Remove a member
        """
        self._delObject(id)

    # XXX This is untested implementation.
    ### and what uses this? should I test it?
    security.declarePrivate( 'searchMemberDataContents' )
00332     def searchMemberDataContents( self, search_param, search_term ):
        """
        Search members
        """

        results=[]
        if search_param == 'username':
            search_param = 'getId'

        catalog=getToolByName(self, search_catalog)
        indexes=catalog.indexes()
        query={}

        if search_param in indexes:
            query[search_param] = search_term

        if query:
            query['portal_type'] = allowed_content_types
            results=catalog(query)

        return [{'username':getattr(r,'id'),'email':getattr(r,'email')} \
                for r in [r.getObject() for r in results] if hasattr(r,'id')]


00356     def searchForMembers( self, REQUEST=None, **kw ):
        """
        Do a catalog search on a sites members. If a 'brains' argument is set
        to a True value, search will return only member_catalog metadata.
        Otherwise, memberdata objects returned.

        If 'brains' is a False value and a 'portal_only' parameter is passed
        in with a True value then only members from the portal's acl_users
        folder will be returned.
        """

        if REQUEST:
            search_dict = getattr(REQUEST, 'form', REQUEST)
        else:
            REQUEST = {}
            search_dict = kw

        results=[]
        catalog=getToolByName(self, search_catalog)

        # no reason to iterate over all those indexes
        try:
            from sets import Set
            indexes=Set(catalog.indexes())
            indexes = indexes & Set(search_dict.keys())
        except:
            # Unless we are on 2.3
            catalog.indexes()

        query={}

        def dateindex_query(field_value, field_usage):
            usage, val = field_usage.split(':')
            return { 'query':  field_value, usage:val }

        def zctextindex_query(field_value):
            # Auto Globbing
            if not field_value.endswith('*') and field_value.find(' ') == -1:
                field_value += '*'
            return field_value

        special_query = dict((
            ( 'DateIndex',    dateindex_query ),
            ( 'ZCTextIndex',  zctextindex_query )
            ))

        if search_dict:
            # Make a indexname: fxToApply dict
            idx_fx = dict(\
                [(x.id, special_query[x.meta_type])\
                 for x in catalog.Indexes.objectValues()\
                 if (x.meta_type in special_query.keys() and x.id in indexes)]\
                )

            for i in indexes:
                val=search_dict.get(i, None)
                usage_val = search_dict.get('%s_usage' %i)
                if type(val) == type([]):
                    val = filter(None, val)

                if (i in idx_fx.keys() and val):
                    if usage_val:
                        val = idx_fx[i](val, usage_val)
                    else:
                        val = idx_fx[i](val)

                if val:
                    query.update({i:val})

        results=catalog(query) 

        if results and not (search_dict.get('brains', False) or \
                            REQUEST.get('brains', False)):
            if search_dict.get('portal_only', False) or \
                   REQUEST.get('portal_only', False):
                res = []
                for r in results:
                    mem = r.getObject()
                    if mem._isPortalUser():
                        res.append(mem)
                results = res
            else:
                results = [r.getObject() for r in results]

        return filter(None, results)

    security.declarePrivate('registerMemberData')
00443     def registerMemberData(self, m, id):
        """
        Adds the given member data to the _members dict.
        This is done as late as possible to avoid side effect
        transactions and to reduce the necessary number of
        entries.
        """
        self._setObject(id, m)

00452     def _getMemberInstance(self):
        """Get an instance of the Member class.  Used for
           extracting default property values, etc."""
        if self._defaultMember is None:
            tempFolder = PortalFolder('temp').__of__(self)
            getMemberFactory(tempFolder, self.getTypeName())('cmfmemberdefault')
            self._defaultMember = getattr(tempFolder,'cmfmemberdefault')
            getattr(tempFolder,'cmfmemberdefault').unindexObject()
            # don't store _defaultMember in the catalog
            tempFolder.unindexObject()
            # don't store _defaultMember in the catalog
            self._defaultMember.unindexObject()
        return self._defaultMember

    ## Folderish Methods
    security.declareProtected(ADD_MEMBER_PERMISSION, 'invokeFactory')
00468     def allowedContentTypes(self):
        """
        List type info objects for types which can be added in
        this folder.  Override the default in PortalFolder to make
        sure that all allowed member types are included in case the
        allowed types and the allowed member types are not in sync.
        """
        portal_types = getToolByName(self, 'portal_types')
        myType = portal_types.getTypeInfo(self)

        if myType is not None:
            result = {}
            for contentType in portal_types.listTypeInfo(self):
                typeId = contentType.getId()
                if myType.allowType( typeId ):
                    result[typeId] = contentType
                    
            for contentType in self.getAllowedMemberTypes():
                if not result.has_key(contentType):
                    result[contentType] = portal_types.getTypeInfo(contentType)
            result = result.values()
        else:
            result = portal_types.listTypeInfo()

        return filter( lambda typ, container=self:
                         typ and typ.isConstructionAllowed( container )
                       , result )

    security.declareProtected(CMFCorePermissions.ManagePortal, 'getAvailableMemberTypes')
00497     def getAvailableMemberTypes(self):
        """
        Get vocabulary for allowed member types
        """
        return getToolByName(self, 'cmfmember_control').getAvailableMemberTypes()


    def filtered_meta_types(self, user=None):
        # Filters the list of available meta types.
        allowedTypes = [self._getMemberInstance()._getTypeName()]
        meta_types = []
        for meta_type in self.all_meta_types():
            if meta_type['name'] in allowedTypes:
                meta_types.append(meta_type)
        return meta_types

    
    security.declareProtected(CMFCorePermissions.ListPortalMembers, 'contentValues')
    def contentValues(self, spec=None, filter=None):
        objects = [v.__of__(self) for v in self.objectValues(spec=spec)]
        l = []
        for v in objects:
            id = v.getId()
            try:
                if getSecurityManager().validate(self, self, id, v):
                    l.append(v)
            except Unauthorized:
                pass
        return l


    def _checkId(self, id, allow_dup=0):
        #PortalFolder._checkId(self, id, allow_dup)
        BaseBTreeFolder._checkId(self, id, allow_dup)


    # register type type of Member object
    # that the MemberDataConatiner will store
    def registerType(self, new_type_name, default=False):
        ### DWM: add implementation check
        if default:
            self.setDefaultType(new_type_name)
        typestool=getToolByName(self, 'portal_types')
        act = typestool.MemberDataContainer.allowed_content_types
        if new_type_name not in act:
            act = act + (new_type_name,)
        typestool.MemberDataContainer.allowed_content_types = act

00545     def unregisterType(self, type_name, new_default_type='Member'):
        """
        Unregister a member type. Oposite of registerType.
        """
        amt = list(self.getAllowedMemberTypes())
        # Remove type from alowed member types.
        if type_name in amt:
            amt.remove(type_name)
        self.setAllowedMemberTypes(amt)
        # If type_name is the default, set another default type.
        if self.getTypeName() == type_name:
            self.setDefaultType(new_default_type)


    # Migrate members when changing member type
    # 1) rename old member to some temp name
    # 2) create new member with old id
    # 3) transfer user assets to new member
    # 4) delete old member
    #
    # new_type_name = meta_type for the new Member type
    # workflow_transfer = dict mapping each state in old members to a list of transitions
    #       that must be executed to move the new member to the old member's state
    def migrateMembers(self, out, new_type_name, workflow_transfer={}):

        self.registerType(new_type_name)
        factory = getMemberFactory(self, self.getTypeName())

        portal = getToolByName(self, 'portal_url').getPortalObject()
        workflow_tool = getToolByName(self, 'portal_workflow')

        for m in self.objectIds():
            old_member = self.get(m)
            temp_id = 'temp_' + m
            while self.get(temp_id):
                temp_id = '_' + temp_id
            old_member._migrating = 1
            self.manage_renameObjects([m], [temp_id])
            factory(m)
            old_member = self.get(temp_id)
            new_member = self.get(m)
            # copy over old member attributes
            new_member._migrate(old_member, [], out)

            # copy workflow state
            old_member_state = workflow_tool.getInfoFor(old_member,
                                                        'review_state', '')
            print >> out, 'state = %s' % (old_member_state,)
            transitions = workflow_transfer.get(old_member_state, [])
            print >> out, 'transitions = %s' % (str(transitions),)
            for t in transitions:
                workflow_tool.doActionFor(new_member, t)

            self.manage_delObjects(temp_id)
            
        from Products.CMFMember.Extensions.Install import setupNavigation
        setupNavigation(self, out, new_type_name)


    #################################
    # version awareness

    def setVersion(self, version):
        self._instanceVersion = version

    def getVersion(self):
        return self._instanceVersion or 'development'


    ##SUBCLASS HOOKS
    security.declarePrivate('pruneOrphan')
00616     def pruneOrphan(self, id):
        """
        Called when a member object exists for something not in the
        acl_users folder
        """
        self._deleteMember(id)

    security.declareProtected(CMFCorePermissions.View,
                              'getMemberSchema')
00625     def getMemberSchema(self, **kw):
        """
        Returns acquisition wrapped member schema.

        The following keyword arguments are supported:

        - schema_id: name of the member type for which schema
                     to return
        """
        if not USE_SCHEMA_EDITOR:
            ctrl_tool = getToolByName(self, 'cmfmember_control')
            klass_dict = ctrl_tool.getMemberKlasses()

        if kw.has_key('schema_id'):
            klass_id = kw['schema_id']
            if USE_SCHEMA_EDITOR:
                schema = self.atse_getSchemaById(klass_id)
            else:
                schema = klass_dict[klass_id].schema
        else:
            if USE_SCHEMA_EDITOR:
                schema=self.atse_getDefaultSchema()
            else:
                schema = klass_dict[self.getTypeName()].schema

        schema = ImplicitAcquisitionWrapper(schema, self)
        return schema

00653     def processSchemaEditor(self, context, field, custom_data):
        """ process the data that is returned from the schema
            editor 'post_macro' """
        if custom_data and custom_data.get('regfield') == 'regfield':
            field.regfield = 1
        elif getattr(field, 'regfield', 0):
            field.regfield = 0


    # AT methods
00663     def setDefaultType(self, type_name, **kw):
        """ Overide so we can set appropriate schema info for new
        type and blank default member """
        self.schema['typeName'].set(self, type_name, **kw)
        types = self.getAllowedMemberTypes()
        if type_name not in types:
            types += (type_name,)
        self.setAllowedMemberTypes(types)
        
        t_tool = getToolByName(self, 'portal_types')
        at_tool = getToolByName(self, 'archetype_tool')
        type_info = t_tool.getTypeInfo(type_name)
        pkg = type_info.product
        self.defaultMemberSchema = at_tool.lookupType(pkg, type_name)['schema']
        self._defaultMember = None # nuke the default member (which was of the old Member type)

00679     def setAllowedMemberTypes(self, memberTypes, **kwargs):
        """
        Overriding default mutator since TypesTool checks the
        variable allowed_content_types directly.  No checking
        for whether types are real, proper, etc.
        """
        memberTypes = filter(None, memberTypes)
        
        field=self.getField('allowedMemberTypes')
        field.set(self, memberTypes, **kwargs)

        allowedMemberTypes = getToolByName(self,
                                           'cmfmember_control').getMemberKlasses()

        allowed_content_types = [ item for item in memberTypes \
                                  if allowedMemberTypes.has_key(item) ]

        if USE_SCHEMA_EDITOR:
            for act in allowed_content_types:
                self.atse_registerObject(allowedMemberTypes[act],
                                         domain='plone')

        type_tool = getToolByName(self, 'portal_types')
        type_tool.MemberDataContainer.allowed_content_types = \
                                                tuple(allowed_content_types)
        self.allowed_content_types = allowed_content_types


# Put this outside the MemberData tool so that it can be used for
# conversion of old MemberData during installation
def getMemberFactory(self, type_name):
    """
    Return a callable that is the registered object returning a
    contentish member object
    """
    # Assumptions: there is a types_tool type called Member, you
    # want one of these in the folder, changing this changes the
    # types of members in your site.
    types_tool = getToolByName(self, 'portal_types')
    ti = types_tool.getTypeInfo(type_name)

    try:
        p = self.manage_addProduct[ti.product]
        action = getattr(p, ti.factory, None)
    except AttributeError:
        raise ValueError, 'No type information installed'
    if action is None:
        raise ValueError, ('Invalid Factory for %s'
                           % ti.getId())
    return action


registerType(MemberDataContainer)

Generated by  Doxygen 1.6.0   Back to index