##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Type registration tool.

$Id: TypesTool.py 68524 2006-06-08 16:54:49Z efge $
"""

from warnings import warn
import logging

from AccessControl import ClassSecurityInfo
from AccessControl import getSecurityManager
from Acquisition import aq_base
from Acquisition import aq_get
from Globals import DTMLFile
from Globals import InitializeClass
from OFS.Folder import Folder
from OFS.ObjectManager import IFAwareObjectManager
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import Products

from ActionProviderBase import ActionProviderBase
from exceptions import AccessControl_Unauthorized
from exceptions import BadRequest
from exceptions import zExceptions_Unauthorized
from interfaces import ITypeInformation
from interfaces import ITypesTool
from interfaces.portal_types \
     import ContentTypeInformation as z2ITypeInformation
from interfaces.portal_types import portal_types as z2ITypesTool
from permissions import AccessContentsInformation
from permissions import ManagePortal
from permissions import View
from utils import _checkPermission
from utils import _dtmldir
from utils import _wwwdir
from utils import cookString
from utils import getActionContext
from utils import SimpleItemWithProperties
from utils import UniqueObject
from utils import getToolByName

from zope.interface import implements

logger = logging.getLogger('CMFCore.TypesTool')


_marker = []  # Create a new marker.


class TypeInformation(SimpleItemWithProperties, ActionProviderBase):
    """
    Base class for information about a content type.
    """

    _isTypeInformation = 1

    manage_options = ( SimpleItemWithProperties.manage_options[:1]
                     + ( {'label':'Aliases',
                          'action':'manage_aliases'}, )
                     + ActionProviderBase.manage_options
                     + SimpleItemWithProperties.manage_options[1:]
                     )

    security = ClassSecurityInfo()

    security.declareProtected(ManagePortal, 'manage_editProperties')
    security.declareProtected(ManagePortal, 'manage_changeProperties')
    security.declareProtected(ManagePortal, 'manage_propertiesForm')

    _basic_properties = (
        {'id':'title', 'type': 'string', 'mode':'w',
         'label':'Title'},
        {'id':'description', 'type': 'text', 'mode':'w',
         'label':'Description'},
        {'id':'content_icon', 'type': 'string', 'mode':'w',
         'label':'Icon'},
        {'id':'content_meta_type', 'type': 'string', 'mode':'w',
         'label':'Product meta type'},
        )

    _advanced_properties = (
        {'id':'immediate_view', 'type': 'string', 'mode':'w',
         'label':'Initial view name'},
        {'id':'global_allow', 'type': 'boolean', 'mode':'w',
         'label':'Implicitly addable?'},
        {'id':'filter_content_types', 'type': 'boolean', 'mode':'w',
         'label':'Filter content types?'},
        {'id':'allowed_content_types'
         , 'type': 'multiple selection'
         , 'mode':'w'
         , 'label':'Allowed content types'
         , 'select_variable':'listContentTypes'
         },
        { 'id': 'allow_discussion', 'type': 'boolean', 'mode': 'w'
          , 'label': 'Allow Discussion?'
          },
        )

    title = ''
    description = ''
    content_meta_type = ''
    content_icon = ''
    immediate_view = ''
    filter_content_types = True
    allowed_content_types = ()
    allow_discussion = False
    global_allow = True

    def __init__(self, id, **kw):

        self.id = id

        if not kw:
            return

        kw = kw.copy()  # Get a modifiable dict.

        if (not kw.has_key('content_meta_type')
            and kw.has_key('meta_type')):
            kw['content_meta_type'] = kw['meta_type']

        if (not kw.has_key('content_icon')
            and kw.has_key('icon')):
            kw['content_icon'] = kw['icon']

        self.manage_changeProperties(**kw)

        actions = kw.get( 'actions', () )
        # make sure we have a copy
        _actions = []
        for action in actions:
            _actions.append( action.copy() )
        actions = tuple(_actions)
        # We don't know if actions need conversion, so we always add oldstyle
        # _actions and convert them.
        self._actions = actions
        self._convertActions()

        aliases = kw.get( 'aliases', _marker )
        if aliases is _marker:
            self._guessMethodAliases()
        else:
            self.setMethodAliases(aliases)

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_aliases')
    manage_aliases = PageTemplateFile( 'typeinfoAliases.zpt', _wwwdir )

    security.declareProtected(ManagePortal, 'manage_setMethodAliases')
    def manage_setMethodAliases(self, REQUEST):
        """ Config method aliases.
        """
        form = REQUEST.form
        aliases = {}
        for k, v in form['aliases'].items():
            v = v.strip()
            if v:
                aliases[k] = v

        _dict = {}
        for k, v in form['methods'].items():
            if aliases.has_key(k):
                _dict[ aliases[k] ] = v
        self.setMethodAliases(_dict)
        REQUEST.RESPONSE.redirect('%s/manage_aliases' % self.absolute_url())

    #
    #   Accessors
    #
    security.declareProtected(View, 'Type')
    def Type(self):
        """ Deprecated. Use Title(). """
        warn('TypeInformation.Type() is deprecated, use Title().',
             DeprecationWarning)
        return self.Title()

    security.declareProtected(View, 'Title')
    def Title(self):
        """
            Return the "human readable" type name (note that it
            may not map exactly to the 'portal_type', e.g., for
            l10n/i18n or where a single content class is being
            used twice, under different names.
        """
        return self.title or self.getId()

    security.declareProtected(View, 'Description')
    def Description(self):
        """
            Textual description of the class of objects (intended
            for display in a "constructor list").
        """
        return self.description

    security.declareProtected(View, 'Metatype')
    def Metatype(self):
        """
            Returns the Zope 'meta_type' for this content object.
            May be used for building the list of portal content
            meta types.
        """
        return self.content_meta_type

    security.declareProtected(View, 'getIcon')
    def getIcon(self):
        """
            Returns the icon for this content object.
        """
        return self.content_icon

    security.declarePublic('allowType')
    def allowType( self, contentType ):
        """
            Can objects of 'contentType' be added to containers whose
            type object we are?
        """
        if not self.filter_content_types:
            ti = self.getTypeInfo( contentType )
            if ti is None or ti.globalAllow():
                return 1

        #If a type is enabled to filter and no content_types are allowed
        if not self.allowed_content_types:
            return 0

        if contentType in self.allowed_content_types:
            return 1

        return 0

    security.declarePublic('getId')
    def getId(self):
        return self.id

    security.declarePublic('allowDiscussion')
    def allowDiscussion( self ):
        """
            Can this type of object support discussion?
        """
        return self.allow_discussion

    security.declarePublic('globalAllow')
    def globalAllow(self):
        """
        Should this type be implicitly addable anywhere?
        """
        return self.global_allow

    security.declarePublic('listActions')
    def listActions(self, info=None, object=None):
        """ Return a sequence of the action info objects for this type.
        """
        if self._actions and isinstance(self._actions[0], dict):
            self._convertActions()

        return self._actions or ()

    security.declarePublic('getActionById')
    def getActionById( self, id, default=_marker ):
        """ Get method ID by action ID.
        """
        warn('getActionById() is deprecated and will be removed in CMF 2.0. '
             'Please use getActionInfo()[\'url\'] if you need an URL or '
             'queryMethodID() if you need a method ID.',
             DeprecationWarning)
        context = getActionContext( self )
        for action in self.listActions():

            __traceback_info__ = (self.getId(), action)

            if action.getId() == id:
                target = action.action(context).strip()
                if target.startswith('/'):
                    target = target[1:]
                return target
            else:
                # Temporary backward compatibility.
                if action.Title().lower() == id:
                    target = action.action(context).strip()
                    if target.startswith('/'):
                        target = target[1:]
                    return target

        if default is _marker:
            raise ValueError, ('No action "%s" for type "%s"'
                               % (id, self.getId()))
        else:
            return default

    security.declarePrivate( '_convertActions' )
    def _convertActions( self ):
        """ Upgrade dictionary-based actions.
        """
        aa, self._actions = self._actions, ()

        for action in aa:

            # Some backward compatibility stuff.
            if not 'id' in action:
                action['id'] = cookString(action['name'])

            if not 'title' in action:
                action['title'] = action.get('name', action['id'].capitalize())

            # historically, action['action'] is simple string
            actiontext = action.get('action').strip() or 'string:${object_url}'
            if actiontext[:7] not in ('python:', 'string:'):
                actiontext = 'string:${object_url}/%s' % actiontext

            self.addAction(
                  id=action['id']
                , name=action['title']
                , action=actiontext
                , condition=action.get('condition')
                , permission=action.get( 'permissions', () )
                , category=action.get('category', 'object')
                , visible=action.get('visible', True)
                )

    security.declarePublic('constructInstance')
    def constructInstance(self, container, id, *args, **kw):
        """Build an instance of the type.

        Builds the instance in 'container', using 'id' as its id.
        Returns the object.
        """
        if not self.isConstructionAllowed(container):
            raise AccessControl_Unauthorized('Cannot create %s' % self.getId())

        ob = self._constructInstance(container, id, *args, **kw)

        return self._finishConstruction(ob)

    security.declarePrivate('_finishConstruction')
    def _finishConstruction(self, ob):
        """
            Finish the construction of a content object.
            Set its portal_type, insert it into the workflows.
        """
        if hasattr(ob, '_setPortalTypeName'):
            ob._setPortalTypeName(self.getId())

        if hasattr(aq_base(ob), 'notifyWorkflowCreated'):
            ob.notifyWorkflowCreated()

        ob.reindexObject()
        return ob

    security.declareProtected(ManagePortal, 'getMethodAliases')
    def getMethodAliases(self):
        """ Get method aliases dict.
        """
        if not hasattr(self, '_aliases'):
            self._guessMethodAliases()
        aliases = self._aliases
        # for aliases created with CMF 1.5.0beta
        for key, method_id in aliases.items():
            if isinstance(method_id, tuple):
                aliases[key] = method_id[0]
                self._p_changed = True
        return aliases.copy()

    security.declareProtected(ManagePortal, 'setMethodAliases')
    def setMethodAliases(self, aliases):
        """ Set method aliases dict.
        """
        _dict = {}
        for k, v in aliases.items():
            v = v.strip()
            if v:
                _dict[ k.strip() ] = v
        if not getattr(self, '_aliases', None) == _dict:
            self._aliases = _dict
            return True
        else:
            return False

    security.declarePublic('queryMethodID')
    def queryMethodID(self, alias, default=None, context=None):
        """ Query method ID by alias.
        """
        if not hasattr(self, '_aliases'):
            self._guessMethodAliases()
        aliases = self._aliases
        method_id = aliases.get(alias, default)
        # for aliases created with CMF 1.5.0beta
        if isinstance(method_id, tuple):
            method_id = method_id[0]
        return method_id

    security.declarePrivate('_guessMethodAliases')
    def _guessMethodAliases(self):
        """ Guess and set Method Aliases. Used for upgrading old TIs.
        """
        context = getActionContext(self)
        actions = self.listActions()
        ordered = []
        _dict = {}
        viewmethod = ''

        # order actions and search 'mkdir' action
        for action in actions:
            if action.getId() == 'view':
                ordered.insert(0, action)
            elif action.getId() == 'mkdir':
                try:
                    mkdirmethod = action.action(context).strip()
                except AttributeError:
                    continue
                if mkdirmethod.startswith('/'):
                    mkdirmethod = mkdirmethod[1:]
                _dict['mkdir'] = mkdirmethod
            else:
                ordered.append(action)

        # search 'view' action
        for action in ordered:
            perms = action.getPermissions()
            if not perms or View in perms:
                try:
                    viewmethod = action.action(context).strip()
                except (AttributeError, TypeError):
                    break
                if viewmethod.startswith('/'):
                    viewmethod = viewmethod[1:]
                if not viewmethod:
                    viewmethod = '(Default)'
                break
        else:
            viewmethod = '(Default)'
        if viewmethod:
            _dict['view'] = viewmethod

        # search default action
        for action in ordered:
            try:
                defmethod = action.action(context).strip()
            except (AttributeError, TypeError):
                break
            if defmethod.startswith('/'):
                defmethod = defmethod[1:]
            if not defmethod:
                break
        else:
            if viewmethod:
                _dict['(Default)'] = viewmethod

        # correct guessed values if we know better
        if self.content_meta_type in ('Portal File', 'Portal Folder',
                                      'Portal Image'):
            _dict['(Default)'] = 'index_html'
            if viewmethod == '(Default)':
                _dict['view'] = 'index_html'
        if self.content_meta_type in ('Document', 'News Item'):
            _dict['gethtml'] = 'source_html'

        self.setMethodAliases(_dict)
        return 1

InitializeClass( TypeInformation )


class FactoryTypeInformation(TypeInformation):
    """
    Portal content factory.
    """

    implements(ITypeInformation)
    __implements__ = z2ITypeInformation

    meta_type = 'Factory-based Type Information'
    security = ClassSecurityInfo()

    _properties = (TypeInformation._basic_properties + (
        {'id':'product', 'type': 'string', 'mode':'w',
         'label':'Product name'},
        {'id':'factory', 'type': 'string', 'mode':'w',
         'label':'Product factory method'},
        ) + TypeInformation._advanced_properties)

    product = ''
    factory = ''

    #
    #   Agent methods
    #
    def _getFactoryMethod(self, container, check_security=1):
        if not self.product or not self.factory:
            raise ValueError, ('Product factory for %s was undefined' %
                               self.getId())
        p = container.manage_addProduct[self.product]
        m = getattr(p, self.factory, None)
        if m is None:
            raise ValueError, ('Product factory for %s was invalid' %
                               self.getId())
        if not check_security:
            return m
        if getSecurityManager().validate(p, p, self.factory, m):
            return m
        raise AccessControl_Unauthorized( 'Cannot create %s' % self.getId() )

    def _queryFactoryMethod(self, container, default=None):

        if not self.product or not self.factory or container is None:
            return default

        # In case we aren't wrapped.
        dispatcher = getattr(container, 'manage_addProduct', None)

        if dispatcher is None:
            return default

        try:
            p = dispatcher[self.product]
        except AttributeError:
            logger.exception("_queryFactoryMethod raised an exception")
            return default

        m = getattr(p, self.factory, None)

        if m:
            try:
                # validate() can either raise Unauthorized or return 0 to
                # mean unauthorized.
                if getSecurityManager().validate(p, p, self.factory, m):
                    return m
            except zExceptions_Unauthorized:  # Catch *all* Unauths!
                pass

        return default

    security.declarePublic('isConstructionAllowed')
    def isConstructionAllowed( self, container ):
        """
        a. Does the factory method exist?

        b. Is the factory method usable?

        c. Does the current user have the permission required in
        order to invoke the factory method?
        """
        m = self._queryFactoryMethod(container)
        return (m is not None)

    security.declarePrivate('_constructInstance')
    def _constructInstance(self, container, id, *args, **kw):
        """Build a bare instance of the appropriate type.

        Does not do any security checks.

        Returns the object without calling _finishConstruction().
        """
        m = self._getFactoryMethod(container, check_security=0)

        id = str(id)

        if getattr(aq_base(m), 'isDocTemp', 0):
            kw['id'] = id
            newid = m(m.aq_parent, self.REQUEST, *args, **kw)
        else:
            newid = m(id, *args, **kw)
        # allow factory to munge ID
        newid = newid or id

        return container._getOb(newid)

InitializeClass( FactoryTypeInformation )


class ScriptableTypeInformation( TypeInformation ):
    """
    Invokes a script rather than a factory to create the content.
    """

    implements(ITypeInformation)
    __implements__ = z2ITypeInformation

    meta_type = 'Scriptable Type Information'
    security = ClassSecurityInfo()

    _properties = (TypeInformation._basic_properties + (
        {'id':'permission', 'type': 'string', 'mode':'w',
         'label':'Constructor permission'},
        {'id':'constructor_path', 'type': 'string', 'mode':'w',
         'label':'Constructor path'},
        ) + TypeInformation._advanced_properties)

    permission = ''
    constructor_path = ''

    #
    #   Agent methods
    #
    security.declarePublic('isConstructionAllowed')
    def isConstructionAllowed( self, container ):
        """
        Does the current user have the permission required in
        order to construct an instance?
        """
        permission = self.permission
        if permission and not _checkPermission( permission, container ):
            return 0
        return 1

    security.declarePrivate('_constructInstance')
    def _constructInstance(self, container, id, *args, **kw):
        """Build a bare instance of the appropriate type.

        Does not do any security checks.

        Returns the object without calling _finishConstruction().
        """
        constructor = self.restrictedTraverse( self.constructor_path )

        # make sure ownership is explicit before switching the context
        if not hasattr( aq_base(constructor), '_owner' ):
            constructor._owner = aq_get(constructor, '_owner')
        #   Rewrap to get into container's context.
        constructor = aq_base(constructor).__of__( container )

        id = str(id)
        return constructor(container, id, *args, **kw)

InitializeClass( ScriptableTypeInformation )


# Provide aliases for backward compatibility.
ContentFactoryMetadata = FactoryTypeInformation
ContentTypeInformation = ScriptableTypeInformation


allowedTypes = [
    'Script (Python)',
    'Python Method',
    'DTML Method',
    'External Method',
    ]


class TypesTool(UniqueObject, IFAwareObjectManager, Folder,
                ActionProviderBase):
    """
        Provides a configurable registry of portal content types.
    """

    implements(ITypesTool)
    __implements__ = (z2ITypesTool, ActionProviderBase.__implements__)

    id = 'portal_types'
    meta_type = 'CMF Types Tool'
    _product_interfaces = (ITypeInformation,)

    security = ClassSecurityInfo()

    manage_options = ( Folder.manage_options[:1]
                     + ( {'label':'Aliases',
                          'action':'manage_aliases'}, )
                     + ActionProviderBase.manage_options
                     + ( {'label':'Overview',
                          'action':'manage_overview'}, )
                     + Folder.manage_options[1:]
                     )

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLFile( 'explainTypesTool', _dtmldir )

    security.declareProtected(ManagePortal, 'manage_aliases')
    manage_aliases = PageTemplateFile( 'typesAliases.zpt', _wwwdir )

    #
    #   ObjectManager methods
    #
    def all_meta_types(self):
        # this is a workaround and should be removed again if allowedTypes
        # have an interface we can use in _product_interfaces
        all = TypesTool.inheritedAttribute('all_meta_types')(self)
        others = [ mt for mt in Products.meta_types
                   if mt['name'] in allowedTypes ]
        return tuple(all) + tuple(others)

    #
    #   other methods
    #
    security.declareProtected(ManagePortal, 'listDefaultTypeInformation')
    def listDefaultTypeInformation(self):
        # Scans for factory_type_information attributes
        # of all products and factory dispatchers within products.
        res = []
        products = self.aq_acquire('_getProducts')()
        for product in products.objectValues():
            product_id = product.getId()

            if hasattr(aq_base(product), 'factory_type_information'):
                ftis = product.factory_type_information
            else:
                package = getattr(Products, product_id, None)
                dispatcher = getattr(package, '__FactoryDispatcher__', None)
                ftis = getattr(dispatcher, 'factory_type_information', None)

            if ftis is not None:
                if callable(ftis):
                    ftis = ftis()

                for fti in ftis:
                    mt = fti.get('meta_type', None)
                    id = fti.get('id', '')

                    if mt:
                        p_id = '%s: %s (%s)' % (product_id, id, mt)
                        res.append( (p_id, fti) )

        return res

    # BBB: DTML based ZMI form and the following add methods are for
    #      CMF-1.5 compatibility
    _addTIForm = DTMLFile( 'addTypeInfo', _dtmldir )

    security.declareProtected(ManagePortal, 'manage_addFactoryTIForm')
    def manage_addFactoryTIForm(self, REQUEST):
        ' '
        warn('Please use the manage_addFactoryTIForm function in the '
             'TypesTool module; this method on the TypesTool itself '
             'will disappear in CMF 2.0', DeprecationWarning,
             stacklevel=2)
        return self._addTIForm(
            self, REQUEST,
            add_meta_type=FactoryTypeInformation.meta_type,
            types=self.listDefaultTypeInformation())

    security.declareProtected(ManagePortal, 'manage_addScriptableTIForm')
    def manage_addScriptableTIForm(self, REQUEST):
        ' '
        warn('Please use the manage_addScriptableTIForm function in the '
             'TypesTool module; this method on the TypesTool itself '
             'will disappear in CMF 2.0', DeprecationWarning,
             stacklevel=2)
        return self._addTIForm(
            self, REQUEST,
            add_meta_type=ScriptableTypeInformation.meta_type,
            types=self.listDefaultTypeInformation())

    security.declareProtected(ManagePortal, 'manage_addTypeInformation')
    def manage_addTypeInformation(self, add_meta_type, id=None,
                                  typeinfo_name=None, RESPONSE=None):
        """
        Create a TypeInformation in self.
        """
        fti = None
        if typeinfo_name:
            info = self.listDefaultTypeInformation()

            # Nasty workaround to stay backwards-compatible
            # This workaround will disappear in CMF 2.0
            if typeinfo_name.endswith(')'):
                # This is a new-style name. Proceed normally.
                for (name, ft) in info:
                    if name == typeinfo_name:
                        fti = ft
                        break
            else:
                # Attempt to work around the old way
                # This attempt harbors the problem that the first match on
                # meta_type will be used. There could potentially be more
                # than one TypeInformation sharing the same meta_type.
                warn("Please switch to the new format "
                     "'product_id: type_id (meta_type)' "
                     "for typeinfo name %r, the old "
                     "spelling will disappear in CMF 2.0" % typeinfo_name,
                     DeprecationWarning, stacklevel=2)

                ti_prod, ti_mt = [x.strip() for x in typeinfo_name.split(':')]

                for name, ft in info:
                    if ( name.startswith(ti_prod) and
                         name.endswith('(%s)' % ti_mt) ):
                        fti = ft
                        break

            if fti is None:
                warn("Typeinfo name %r not found. "
                     "Note that the typeinfo_name "
                     "argument will not be used at all "
                     "in CMF 2.0." % typeinfo_name,
                     DeprecationWarning, stacklevel=2)
            else:
                if not id:
                    id = fti.get('id', None)

        if not id:
            raise BadRequest('An id is required.')
        for mt in Products.meta_types:
            if mt['name'] == add_meta_type:
                klass = mt['instance']
                break
        else:
            raise ValueError, (
                'Meta type %s is not a type class.' % add_meta_type)
        id = str(id)
        if fti is not None:
            fti = fti.copy()
            if fti.has_key('id'):
                del fti['id']
            ob = klass(id, **fti)
        else:
            ob = klass(id)
        self._setObject(id, ob)
        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_main' % self.absolute_url())

    security.declareProtected(ManagePortal, 'manage_setTIMethodAliases')
    def manage_setTIMethodAliases(self, REQUEST):
        """ Config method aliases.
        """
        form = REQUEST.form
        aliases = {}
        for k, v in form['aliases'].items():
            v = v.strip()
            if v:
                aliases[k] = v

        for ti in self.listTypeInfo():
            _dict = {}
            for k, v in form[ ti.getId() ].items():
                if aliases.has_key(k):
                    _dict[ aliases[k] ] = v
            ti.setMethodAliases(_dict)
        REQUEST.RESPONSE.redirect('%s/manage_aliases' % self.absolute_url())

    security.declareProtected(AccessContentsInformation, 'getTypeInfo')
    def getTypeInfo( self, contentType ):
        """
            Return an instance which implements the
            TypeInformation interface, corresponding to
            the specified 'contentType'.  If contentType is actually
            an object, rather than a string, attempt to look up
            the appropriate type info using its portal_type.
        """
        if not isinstance(contentType, basestring):
            if hasattr(aq_base(contentType), 'getPortalTypeName'):
                contentType = contentType.getPortalTypeName()
                if contentType is None:
                    return None
            else:
                return None
        ob = getattr( self, contentType, None )
        if getattr(aq_base(ob), '_isTypeInformation', 0):
            return ob
        else:
            return None

    security.declareProtected(AccessContentsInformation, 'listTypeInfo')
    def listTypeInfo( self, container=None ):
        """
            Return a sequence of instances which implement the
            TypeInformation interface, one for each content
            type registered in the portal.
        """
        rval = []
        for t in self.objectValues():
            # Filter out things that aren't TypeInformation and
            # types for which the user does not have adequate permission.
            if not getattr(aq_base(t), '_isTypeInformation', 0):
                continue
            if not t.getId():
                # XXX What's this used for ?
                # Not ready.
                continue
            # check we're allowed to access the type object
            if container is not None:
                if not t.isConstructionAllowed(container):
                    continue
            rval.append(t)
        return rval

    security.declareProtected(AccessContentsInformation, 'listContentTypes')
    def listContentTypes( self, container=None, by_metatype=0 ):
        """
            Return list of content types.

        o Passing 'by_metatype' is deprecated (type information may not
          correspond 1:1 to an underlying meta_type).
        """
        typenames = {}
        for t in self.listTypeInfo( container ):

            if by_metatype:
                warn('TypeInformation.listContentTypes(by_metatype=1) is '
                     'deprecated.',
                     DeprecationWarning)
                name = t.Metatype()
            else:
                name = t.getId()

            if name:
                typenames[ name ] = None

        result = typenames.keys()
        result.sort()
        return result

    security.declarePublic('constructContent')
    def constructContent( self
                        , type_name
                        , container
                        , id
                        , RESPONSE=None
                        , *args
                        , **kw
                        ):
        """
            Build an instance of the appropriate content class in
            'container', using 'id'.
        """
        info = self.getTypeInfo( type_name )
        if info is None:
            raise ValueError('No such content type: %s' % type_name)

        ob = info.constructInstance(container, id, *args, **kw)

        if RESPONSE is not None:
            immediate_url = '%s/%s' % ( ob.absolute_url()
                                      , info.immediate_view )
            RESPONSE.redirect( immediate_url )

        return ob.getId()

    security.declarePrivate( 'listActions' )
    def listActions(self, info=None, object=None):
        """ List all the actions defined by a provider.
        """
        actions = list( self._actions )

        if object is None and info is not None:
            object = info.object
        if object is not None:
            type_info = self.getTypeInfo(object)
            if type_info is not None:
                actions.extend( type_info.listActions() )

        return actions

    security.declareProtected(ManagePortal, 'listMethodAliasKeys')
    def listMethodAliasKeys(self):
        """ List all defined method alias names.
        """
        _dict = {}
        for ti in self.listTypeInfo():
            aliases = ti.getMethodAliases()
            for k, v in aliases.items():
                _dict[k] = 1
        rval = _dict.keys()
        rval.sort()
        return rval

InitializeClass( TypesTool )
