Ejemplo n.º 1
0
class ElementSpec(Persistent):
    """
        Represent all the tool knows about a single metadata element.
    """
    #
    #   Default values.
    #
    is_multi_valued = 0

    def __init__(self, is_multi_valued):
        self.is_multi_valued = is_multi_valued
        self.policies = PersistentMapping()
        self.policies[None] = self._makePolicy()  # set default policy

    def _makePolicy(self):
        return MetadataElementPolicy(self.is_multi_valued)

    def isMultiValued(self):
        """
            Is this element multi-valued?
        """
        return self.is_multi_valued

    def getPolicy(self, typ=None):
        """
            Find the policy this element for objects whose type
            object name is 'typ';  return a default, if none found.
        """
        try:
            return self.policies[typ]
        except KeyError:
            return self.policies[None]

    def listPolicies(self):
        """
            Return a list of all policies for this element.
        """
        return self.policies.items()

    def addPolicy(self, typ):
        """
            Add a policy to this element for objects whose type
            object name is 'typ'.
        """
        if typ is None:
            raise MetadataError, "Can't replace default policy."

        if self.policies.has_key(typ):
            raise MetadataError, "Existing policy for content type:" + typ

        self.policies[typ] = self._makePolicy()

    def removePolicy(self, typ):
        """
            Remove the policy from this element for objects whose type
            object name is 'typ' (*not* the default, however).
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[typ]
Ejemplo n.º 2
0
class Resource(Persistent):
    security = ClassSecurityInfo()

    def __init__(self, id, **kwargs):
        self._data = PersistentMapping()
        self._data['id'] = id
        self._data['expression'] = kwargs.get('expression', '')
        self._data['enabled'] = kwargs.get('enabled', True)
        self._data['cookable'] = kwargs.get('cookable', True)
        self._data['cacheable'] = kwargs.get('cacheable', True)

    def copy(self):
        result = self.__class__(self.getId())
        for key, value in self._data.items():
            if key != 'id':
                result._data[key] = value
        return result

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

    def _setId(self, id):
        self._data['id'] = id

    security.declarePublic('getExpression')
    def getExpression(self):
        return self._data['expression']

    security.declareProtected(permissions.ManagePortal, 'setExpression')
    def setExpression(self, expression):
        self._data['expression'] = expression

    security.declarePublic('getEnabled')
    def getEnabled(self):
        return bool(self._data['enabled'])

    security.declareProtected(permissions.ManagePortal, 'setEnabled')
    def setEnabled(self, enabled):
        self._data['enabled'] = enabled

    security.declarePublic('getCookable')
    def getCookable(self):
        return self._data['cookable']

    security.declareProtected(permissions.ManagePortal, 'setCookable')
    def setCookable(self, cookable):
        self._data['cookable'] = cookable

    security.declarePublic('getCacheable')
    def getCacheable(self):
        # as this is a new property, old instance might not have that value, so
        # return True as default
        return self._data.get('cacheable', True)

    security.declareProtected(permissions.ManagePortal, 'setCacheable')
    def setCacheable(self, cacheable):
        self._data['cacheable'] = cacheable
Ejemplo n.º 3
0
class DiscussionItemContainer( Persistent, Implicit, Traversable ):
    """
        Store DiscussionItem objects. Discussable content that
        has DiscussionItems associated with it will have an
        instance of DiscussionItemContainer injected into it to
        hold the discussion threads.
    """

    __implements__ = Discussable

    # for the security machinery to allow traversal
    #__roles__ = None

    security = ClassSecurityInfo()

    def __init__(self):
        self.id = 'talkback'
        self._container = PersistentMapping()

    security.declareProtected(View, 'getId')
    def getId( self ):
        return self.id

    security.declareProtected(View, 'getReply')
    def getReply( self, reply_id ):
        """
            Return a discussion item, given its ID;  raise KeyError
            if not found.
        """
        return self._container.get( reply_id ).__of__(self)

    # Is this right?
    security.declareProtected(View, '__bobo_traverse__')
    def __bobo_traverse__(self, REQUEST, name):
        """
        This will make this container traversable
        """
        target = getattr(self, name, None)
        if target is not None:
            return target

        else:
            try:
                return self.getReply(name)
            except:
                parent = aq_parent( aq_inner( self ) )
                if parent.getId() == name:
                    return parent
                else:
                    REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))

    security.declarePrivate('manage_afterAdd')
    def manage_afterAdd(self, item, container):
        """
            We have juste been added or moved.
            Add the contained items to the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).manage_afterAdd(item, container)

    security.declarePrivate('manage_afterClone')
    def manage_afterClone(self, item):
        """
            We have just been cloned.
            Notify the workflow about the contained items.
        """
        for obj in self.objectValues():
            obj.__of__(self).manage_afterClone(item)

    security.declarePrivate( 'manage_beforeDelete' )
    def manage_beforeDelete(self, item, container):
        """
            Remove the contained items from the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__( self ).manage_beforeDelete( item, container )

    #
    #   OFS.ObjectManager query interface.
    #
    security.declareProtected(AccessContentsInformation, 'objectIds')
    def objectIds( self, spec=None ):
        """
            Return a list of the ids of our DiscussionItems.
        """
        if spec and spec is not DiscussionItem.meta_type:
            return []
        return self._container.keys()


    security.declareProtected(AccessContentsInformation, 'objectItems')
    def objectItems(self, spec=None):
        """
            Return a list of (id, subobject) tuples for our DiscussionItems.
        """
        r=[]
        a=r.append
        g=self._container.get
        for id in self.objectIds(spec):
            a( (id, g( id ) ) )
        return r


    security.declareProtected(AccessContentsInformation, 'objectValues')
    def objectValues(self):
        """
            Return a list of our DiscussionItems.
        """
        return self._container.values()

    #
    #   Discussable interface
    #
    security.declareProtected(ReplyToItem, 'createReply')
    def createReply( self, title, text, Creator=None, text_format='structured-text' ):
        """
            Create a reply in the proper place
        """
        container = self._container

        id = int(DateTime().timeTime())
        while self._container.get( str(id), None ) is not None:
            id = id + 1
        id = str( id )

        item = DiscussionItem( id, title=title, description=title )
        item._edit( text_format=text_format, text=text )
        item.__of__(self).addCreator(Creator)
        item.__of__(self).indexObject()

        item.setReplyTo( self._getDiscussable() )
        item.__of__(self).notifyWorkflowCreated()

        self._container[ id ] = item

        return id

    security.declareProtected(ManagePortal, 'deleteReply')
    def deleteReply( self, reply_id ):
        """ Remove a reply from this container """
        if self._container.has_key( reply_id ):
            reply = self._container.get( reply_id ).__of__( self )
            my_replies = reply.talkback.getReplies()
            for my_reply in my_replies:
                my_reply_id = my_reply.getId()
                if hasattr( my_reply, 'unindexObject' ):
                    my_reply.unindexObject()

                del self._container[my_reply_id]

            if hasattr( reply, 'unindexObject' ):
                reply.unindexObject()

            del self._container[reply_id]


    security.declareProtected(View, 'hasReplies')
    def hasReplies( self, content_obj ):
        """
            Test to see if there are any dicussion items
        """
        outer = self._getDiscussable( outer=1 )
        if content_obj == outer:
            return not not len( self._container )
        else:
            return not not len( content_obj.talkback._getReplyResults() )

    security.declareProtected(View, 'replyCount')
    def replyCount( self, content_obj ):
        """ How many replies do i have? """
        outer = self._getDiscussable( outer=1 )
        if content_obj == outer:
            return len( self._container )
        else:
            replies = content_obj.talkback.getReplies()
            return self._repcount( replies )

    security.declarePrivate('_repcount')
    def _repcount( self, replies ):
        """  counts the total number of replies by recursing thru the various levels
        """
        count = 0

        for reply in replies:
            count = count + 1

            #if there is at least one reply to this reply
            replies = reply.talkback.getReplies()
            if replies:
                count = count + self._repcount( replies )

        return count

    security.declareProtected(View, 'getReplies')
    def getReplies( self ):
        """
            Return a sequence of the DiscussionResponse objects which are
            associated with this Discussable
        """
        objects = []
        a = objects.append
        result_ids = self._getReplyResults()

        for id in result_ids:
            a( self._container.get( id ).__of__( self ) )

        return objects

    security.declareProtected(View, 'quotedContents')
    def quotedContents(self):
        """
            Return this object's contents in a form suitable for inclusion
            as a quote in a response.
        """

        return ""

    #
    #   Utility methods
    #
    security.declarePrivate( '_getReplyParent' )
    def _getReplyParent( self, in_reply_to ):
        """
            Return the object indicated by the 'in_reply_to', where
            'None' represents the "outer" content object.
        """
        outer = self._getDiscussable( outer=1 )
        if in_reply_to is None:
            return outer
        parent = self._container[ in_reply_to ].__of__( aq_inner( self ) )
        return parent.__of__( outer )

    security.declarePrivate( '_getDiscussable' )
    def _getDiscussable( self, outer=0 ):
        """
        """
        tb = outer and aq_inner( self ) or self
        return getattr( tb, 'aq_parent', None )

    security.declarePrivate( '_getReplyResults' )
    def _getReplyResults( self ):
        """
           Get a list of ids of DiscussionItems which are replies to
           our Discussable.
        """
        discussable = self._getDiscussable()
        outer = self._getDiscussable( outer=1 )

        if discussable == outer:
            in_reply_to = None
        else:
            in_reply_to = discussable.getId()

        result = []
        a = result.append
        for key, value in self._container.items():
            if value.in_reply_to == in_reply_to:
                a( ( key, value ) )

        result.sort( lambda a, b: cmp(a[1].creation_date, b[1].creation_date) )

        return [ x[0] for x in result ]
Ejemplo n.º 4
0
class DiscussionItemContainer(Persistent, Implicit, Traversable):
    """
        Store DiscussionItem objects. Discussable content that
        has DiscussionItems associated with it will have an
        instance of DiscussionItemContainer injected into it to
        hold the discussion threads.
    """

    __implements__ = Discussable

    # for the security machinery to allow traversal
    #__roles__ = None

    security = ClassSecurityInfo()

    def __init__(self):
        self.id = 'talkback'
        self._container = PersistentMapping()

    security.declareProtected(View, 'getId')

    def getId(self):
        return self.id

    security.declareProtected(View, 'getReply')

    def getReply(self, reply_id):
        """
            Return a discussion item, given its ID;  raise KeyError
            if not found.
        """
        return self._container.get(reply_id).__of__(self)

    # Is this right?
    security.declareProtected(View, '__bobo_traverse__')

    def __bobo_traverse__(self, REQUEST, name):
        """
        This will make this container traversable
        """
        target = getattr(self, name, None)
        if target is not None:
            return target

        else:
            try:
                return self.getReply(name)
            except:
                parent = aq_parent(aq_inner(self))
                if parent.getId() == name:
                    return parent
                else:
                    REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))

    security.declarePrivate('manage_afterAdd')

    def manage_afterAdd(self, item, container):
        """
            We have juste been added or moved.
            Add the contained items to the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).manage_afterAdd(item, container)

    security.declarePrivate('manage_afterClone')

    def manage_afterClone(self, item):
        """
            We have just been cloned.
            Notify the workflow about the contained items.
        """
        for obj in self.objectValues():
            obj.__of__(self).manage_afterClone(item)

    security.declarePrivate('manage_beforeDelete')

    def manage_beforeDelete(self, item, container):
        """
            Remove the contained items from the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).manage_beforeDelete(item, container)

    #
    #   OFS.ObjectManager query interface.
    #
    security.declareProtected(AccessContentsInformation, 'objectIds')

    def objectIds(self, spec=None):
        """
            Return a list of the ids of our DiscussionItems.
        """
        if spec and spec is not DiscussionItem.meta_type:
            return []
        return self._container.keys()

    security.declareProtected(AccessContentsInformation, 'objectItems')

    def objectItems(self, spec=None):
        """
            Return a list of (id, subobject) tuples for our DiscussionItems.
        """
        r = []
        a = r.append
        g = self._container.get
        for id in self.objectIds(spec):
            a((id, g(id)))
        return r

    security.declareProtected(AccessContentsInformation, 'objectValues')

    def objectValues(self):
        """
            Return a list of our DiscussionItems.
        """
        return self._container.values()

    #
    #   Discussable interface
    #
    security.declareProtected(ReplyToItem, 'createReply')

    def createReply(self, title, text, Creator=None):
        """
            Create a reply in the proper place
        """
        container = self._container

        id = int(DateTime().timeTime())
        while self._container.get(str(id), None) is not None:
            id = id + 1
        id = str(id)

        item = DiscussionItem(id, title=title, description=title)
        item._edit(text_format='structured-text', text=text)

        if Creator:
            item.creator = Creator

        item.__of__(self).indexObject()

        item.setReplyTo(self._getDiscussable())

        self._container[id] = item

        return id

    security.declareProtected(ManagePortal, 'deleteReply')

    def deleteReply(self, reply_id):
        """ Remove a reply from this container """
        if self._container.has_key(reply_id):
            reply = self._container.get(reply_id).__of__(self)
            my_replies = reply.talkback.getReplies()
            for my_reply in my_replies:
                my_reply_id = my_reply.getId()
                if hasattr(my_reply, 'unindexObject'):
                    my_reply.unindexObject()

                del self._container[my_reply_id]

            if hasattr(reply, 'unindexObject'):
                reply.unindexObject()

            del self._container[reply_id]

    security.declareProtected(View, 'hasReplies')

    def hasReplies(self, content_obj):
        """
            Test to see if there are any dicussion items
        """
        outer = self._getDiscussable(outer=1)
        if content_obj == outer:
            return not not len(self._container)
        else:
            return not not len(content_obj.talkback._getReplyResults())

    security.declareProtected(View, 'replyCount')

    def replyCount(self, content_obj):
        """ How many replies do i have? """
        outer = self._getDiscussable(outer=1)
        if content_obj == outer:
            return len(self._container)
        else:
            replies = content_obj.talkback.getReplies()
            return self._repcount(replies)

    security.declarePrivate('_repcount')

    def _repcount(self, replies):
        """  counts the total number of replies by recursing thru the various levels
        """
        count = 0

        for reply in replies:
            count = count + 1

            #if there is at least one reply to this reply
            replies = reply.talkback.getReplies()
            if replies:
                count = count + self._repcount(replies)

        return count

    security.declareProtected(View, 'getReplies')

    def getReplies(self):
        """
            Return a sequence of the DiscussionResponse objects which are
            associated with this Discussable
        """
        objects = []
        a = objects.append
        result_ids = self._getReplyResults()

        for id in result_ids:
            a(self._container.get(id).__of__(self))

        return objects

    security.declareProtected(View, 'quotedContents')

    def quotedContents(self):
        """
            Return this object's contents in a form suitable for inclusion
            as a quote in a response.
        """

        return ""

    #
    #   Utility methods
    #
    security.declarePrivate('_getReplyParent')

    def _getReplyParent(self, in_reply_to):
        """
            Return the object indicated by the 'in_reply_to', where
            'None' represents the "outer" content object.
        """
        outer = self._getDiscussable(outer=1)
        if in_reply_to is None:
            return outer
        parent = self._container[in_reply_to].__of__(aq_inner(self))
        return parent.__of__(outer)

    security.declarePrivate('_getDiscussable')

    def _getDiscussable(self, outer=0):
        """
        """
        tb = outer and aq_inner(self) or self
        return getattr(tb, 'aq_parent', None)

    security.declarePrivate('_getReplyResults')

    def _getReplyResults(self):
        """
           Get a list of ids of DiscussionItems which are replies to
           our Discussable.
        """
        discussable = self._getDiscussable()
        outer = self._getDiscussable(outer=1)

        if discussable == outer:
            in_reply_to = None
        else:
            in_reply_to = discussable.getId()

        result = []
        a = result.append
        for key, value in self._container.items():
            if value.in_reply_to == in_reply_to:
                a(key)

        return result
Ejemplo n.º 5
0
class MetadataSchema( SimpleItem ):
    """ Describe a metadata schema.
    """
    security = ClassSecurityInfo()

    meta_type = 'Metadata Schema'
    publisher = ''

    def __init__( self, id, element_specs=() ):
        self._setId( id )
        self.element_specs = PersistentMapping()
        for name, is_multi_valued in element_specs:
            self.element_specs[ name ] = ElementSpec( is_multi_valued )


    #
    #   ZMI methods
    #
    manage_options = ( ( { 'label'      : 'Elements'
                         , 'action'     : 'elementPoliciesForm'
                         }
                       ,
                       )
                     + SimpleItem.manage_options
                     )

    security.declareProtected(ManagePortal, 'elementPoliciesForm')
    elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir )

    security.declareProtected(ManagePortal, 'addElementPolicy')
    def addElementPolicy( self
                        , element
                        , content_type
                        , is_required
                        , supply_default
                        , default_value
                        , enforce_vocabulary
                        , allowed_vocabulary
                        , REQUEST=None
                        ):
        """ Add a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.addPolicy( content_type )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+added.'
               )

    security.declareProtected(ManagePortal, 'removeElementPolicy')
    def removeElementPolicy( self
                           , element
                           , content_type
                           , REQUEST=None
                           ):
        """ Remvoe a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.removePolicy( content_type )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+removed.'
               )

    security.declareProtected(ManagePortal, 'updateElementPolicy')
    def updateElementPolicy( self
                           , element
                           , content_type
                           , is_required
                           , supply_default
                           , default_value
                           , enforce_vocabulary
                           , allowed_vocabulary
                           , REQUEST=None
                           ):
        """ Update a policy for one of our elements 
        
        o 'content_type' will be '<default>' when we edit the default.
        """
        if content_type == '<default>':
            content_type = None
        spec = self.getElementSpec( element )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+updated.'
               )


    #
    #   Element spec manipulation.
    #
    security.declareProtected(ManagePortal, 'listElementSpecs')
    def listElementSpecs( self ):
        """ Return a list of ElementSpecs representing the elements we manage.
        """
        res = []
        for k, v in self.element_specs.items():
            res.append((k, v.__of__(self)))
        return res

    security.declareProtected(ManagePortal, 'getElementSpec')
    def getElementSpec( self, element ):
        """ Return an ElementSpec for the given 'element'.
        """
        return self.element_specs[ element ].__of__( self )

    security.declareProtected(ManagePortal, 'addElementSpec')
    def addElementSpec( self, element, is_multi_valued, REQUEST=None ):
        """ Add 'element' to our list of managed elements.
        """
        # Don't replace.
        if self.element_specs.has_key( element ):
           return

        self.element_specs[ element ] = ElementSpec( is_multi_valued )

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/propertiesForm'
               + '?manage_tabs_message=Element+' + element + '+added.'
               )

    security.declareProtected(ManagePortal, 'removeElementSpec')
    def removeElementSpec( self, element, REQUEST=None ):
        """ Remove 'element' from our list of managed elements.
        """
        del self.element_specs[ element ]

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/propertiesForm'
               + '?manage_tabs_message=Element+' + element + '+removed.'
               )

    security.declareProtected(ManagePortal, 'listPolicies')
    def listPolicies( self, typ=None ):
        """ Show all policies for a given content type
        
        o If 'typ' is none, return the list of default policies.
        """
        result = []
        for element, spec in self.listElementSpecs():
            result.append( ( element, spec.getPolicy( typ ) ) )
        return result
Ejemplo n.º 6
0
class MetadataTool(UniqueObject, SimpleItem):

    id = 'portal_metadata'
    meta_type = 'Default Metadata Tool'

    security = ClassSecurityInfo()

    #
    #   Default values.
    #
    publisher = ''
    element_specs = None

    #initial_values_hook = None
    #validation_hook     = None

    def __init__(
            self,
            publisher=None
        #, initial_values_hook=None
        #, validation_hook=None
        ,
            element_specs=DEFAULT_ELEMENT_SPECS):

        self.editProperties(publisher
                            #, initial_values_hook
                            #, validation_hook
                            )

        self.element_specs = PersistentMapping()

        for name, is_multi_valued in element_specs:
            self.element_specs[name] = ElementSpec(is_multi_valued)

    #
    #   ZMI methods
    #
    manage_options = ((
        {
            'label': 'Overview',
            'action': 'manage_overview'
        }, {
            'label': 'Properties',
            'action': 'propertiesForm'
        }, {
            'label': 'Elements',
            'action': 'elementPoliciesForm'
        }
        # TODO     , { 'label'      : 'Types'
        #            , 'action'     : 'typesForm'
        #            }
    ) + SimpleItem.manage_options)

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'manage_overview')
    manage_overview = DTMLFile('explainMetadataTool', _dtmldir)

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'propertiesForm')
    propertiesForm = DTMLFile('metadataProperties', _dtmldir)

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'editProperties')

    def editProperties(
            self,
            publisher=None
        # TODO , initial_values_hook=None
        # TODO , validation_hook=None
        ,
            REQUEST=None):
        """
            Form handler for "tool-wide" properties (including list of
            metadata elements).
        """
        if publisher is not None:
            self.publisher = publisher

        # TODO self.initial_values_hook = initial_values_hook
        # TODO self.validation_hook = validation_hook

        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                                         '/propertiesForm' +
                                         '?manage_tabs_message=Tool+updated.')

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'elementPoliciesForm')
    elementPoliciesForm = DTMLFile('metadataElementPolicies', _dtmldir)

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'addElementPolicy')

    def addElementPolicy(self, element, content_type, is_required,
                         supply_default, default_value, enforce_vocabulary,
                         allowed_vocabulary, REQUEST):
        """
            Add a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec(element)
        spec.addPolicy(content_type)
        policy = spec.getPolicy(content_type)
        policy.edit(is_required, supply_default, default_value,
                    enforce_vocabulary, allowed_vocabulary)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                                         '/elementPoliciesForm' + '?element=' +
                                         element +
                                         '&manage_tabs_message=Policy+added.')

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'removeElementPolicy')

    def removeElementPolicy(self, element, content_type, REQUEST):
        """
            Remvoe a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec(element)
        spec.removePolicy(content_type)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/elementPoliciesForm' + '?element=' +
                element + '&manage_tabs_message=Policy+removed.')

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'updateElementPolicy')

    def updateElementPolicy(self,
                            element,
                            content_type,
                            is_required,
                            supply_default,
                            default_value,
                            enforce_vocabulary,
                            allowed_vocabulary,
                            REQUEST=None):
        """
            Update a policy for one of our elements ('content_type'
            will be '<default>' when we edit the default).
        """
        if content_type == '<default>':
            content_type = None
        spec = self.getElementSpec(element)
        policy = spec.getPolicy(content_type)
        policy.edit(is_required, supply_default, default_value,
                    enforce_vocabulary, allowed_vocabulary)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/elementPoliciesForm' + '?element=' +
                element + '&manage_tabs_message=Policy+updated.')

    #
    #   Element spec manipulation.
    #
    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'listElementSpecs')

    def listElementSpecs(self):
        """
            Return a list of ElementSpecs representing
            the elements managed by the tool.
        """
        return tuple(self.element_specs.items())

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'getElementSpec')

    def getElementSpec(self, element):
        """
            Return an ElementSpec representing the tool's knowledge
            of 'element'.
        """
        return self.element_specs[element]

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'addElementSpec')

    def addElementSpec(self, element, is_multi_valued):
        """
            Add 'element' to our list of managed elements.
        """
        # Don't replace.
        if self.element_specs.has_key(element):
            return

        self.element_specs[element] = ElementSpec(is_multi_valued)

    security.declareProtected(CMFCorePermissions.ManagePortal,
                              'removeElementSpec')

    def removeElementSpec(self, element):
        """
            Remove 'element' from our list of managed elements.
        """
        del self.element_specs[element]

    security.declareProtected('listPolicies')

    def listPolicies(self, typ=None):
        """
            Show all policies for a given content type, or the default
            if None.
        """
        result = []
        for element, spec in self.listElementSpecs():
            result.append((element, spec.getPolicy(typ)))
        return result

    #
    #   'portal_metadata' interface
    #
    security.declarePrivate('getFullName')

    def getFullName(self, userid):
        """
            Convert an internal userid to a "formal" name, if
            possible, perhaps using the 'portal_membership' tool.

            Used to map userid's for Creator, Contributor DCMI
            queries.
        """
        return userid  # TODO: do lookup here

    security.declarePublic('getPublisher')

    def getPublisher(self):
        """
            Return the "formal" name of the publisher of the
            portal.
        """
        return self.publisher

    security.declarePrivate('_listAllowedVocabulary')

    def _listAllowedVocabulary(self, element, content=None):
        """
            List allowed keywords for a given meta_type, or all
            possible keywords if none supplied.
        """
        spec = self.getElementSpec(element)
        typ = content and content.Type() or None
        return spec.getPolicy(typ).allowedVocabulary()

    security.declarePublic('listAllowedSubjects')

    def listAllowedSubjects(self, content=None):
        """
            List allowed keywords for a given meta_type, or all
            possible keywords if none supplied.
        """
        return self._listAllowedVocabulary('Subject', content)

    security.declarePublic('listAllowedFormats')

    def listAllowedFormats(self, content=None):
        """
            List the allowed 'Content-type' values for a particular
            meta_type, or all possible formats if none supplied.
        """
        return self._listAllowedVocabulary('Format', content)

    security.declarePublic('listAllowedLanguages')

    def listAllowedLanguages(self, content=None):
        """
            List the allowed language values.
        """
        return self._listAllowedVocabulary('Language', content)

    security.declarePublic('listAllowedRights')

    def listAllowedRights(self, content=None):
        """
            List the allowed values for a "Rights"
            selection list;  this gets especially important where
            syndication is involved.
        """
        return self._listAllowedVocabulary('Rights', content)

    security.declarePrivate('setInitialMetadata')

    def setInitialMetadata(self, content):
        """
            Set initial values for content metatdata, supplying
            any site-specific defaults.
        """
        for element, policy in self.listPolicies(content.Type()):

            if not getattr(content, element)():

                if policy.supplyDefault():
                    setter = getattr(content, 'set%s' % element)
                    setter(policy.defaultValue())
                elif policy.isRequired():
                    raise MetadataError, \
                          'Metadata element %s is required.' % element

        # TODO:  Call initial_values_hook, if present

    security.declarePrivate('validateMetadata')

    def validateMetadata(self, content):
        """
            Enforce portal-wide policies about DCI, e.g.,
            requiring non-empty title/description, etc.  Called
            by the CMF immediately before saving changes to the
            metadata of an object.
        """
        for element, policy in self.listPolicies(content.Type()):

            value = getattr(content, element)()
            if not value and policy.isRequired():
                raise MetadataError, \
                        'Metadata element %s is required.' % element

            if policy.enforceVocabulary():
                values = policy.isMultiValued() and value or [value]
                for value in values:
                    if not value in policy.allowedValues():
                        raise MetadataError, \
                        'Value %s is not in allowed vocabulary for' \
                        'metadata element %s.' % ( value, element )
Ejemplo n.º 7
0
class ElementSpec( Persistent ):
    """
        Represent all the tool knows about a single metadata element.
    """
    #
    #   Default values.
    #
    is_multi_valued = 0

    def __init__( self, is_multi_valued ):
        self.is_multi_valued  = is_multi_valued
        self.policies         = PersistentMapping()
        self.policies[ None ] = self._makePolicy()  # set default policy
        
    
    def _makePolicy( self ):
        return MetadataElementPolicy( self.is_multi_valued )

    def isMultiValued( self ):
        """
            Is this element multi-valued?
        """
        return self.is_multi_valued

    def getPolicy( self, typ=None ):
        """
            Find the policy this element for objects whose type
            object name is 'typ';  return a default, if none found.
        """
        try:
            return self.policies[ typ ]
        except KeyError:
            return self.policies[ None ]

    def listPolicies( self ):
        """
            Return a list of all policies for this element.
        """
        return self.policies.items()

    def addPolicy( self, typ ):
        """
            Add a policy to this element for objects whose type
            object name is 'typ'.
        """
        if typ is None:
            raise MetadataError, "Can't replace default policy."

        if self.policies.has_key( typ ):
            raise MetadataError, "Existing policy for content type:" + typ

        self.policies[ typ ] = self._makePolicy()

    def removePolicy( self, typ ):
        """
            Remove the policy from this element for objects whose type
            object name is 'typ' (*not* the default, however).
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[ typ ]
Ejemplo n.º 8
0
class DiscussionItemContainer( Persistent, Implicit, Traversable ):
    """
        Store DiscussionItem objects. Discussable content that
        has DiscussionItems associated with it will have an
        instance of DiscussionItemContainer injected into it to
        hold the discussion threads.
    """

    # for the security machinery to allow traversal
    #__roles__ = None

    security = ClassSecurityInfo()

    def __init__(self):
        self.id = 'talkback'
        self._container = PersistentMapping()

    security.declareProtected( CMFCorePermissions.View, 'getId' )
    def getId( self ):
        return self.id

    # Is this right?
    security.declareProtected( CMFCorePermissions.View, '__bobo_traverse__' )
    def __bobo_traverse__(self, REQUEST, name):
        """
        This will make this container traversable
        """
        target = getattr(self, name, None)
        if target is not None:
            return target
        else:
            try:
                return self._container.get(name).__of__(self)
            except:
                REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))

    security.declarePrivate( 'manage_beforeDelete' )
    def manage_beforeDelete(self, item, container):
        """
            Remove the contained items from the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__( self ).manage_beforeDelete( item, container )

    #
    #   OFS.ObjectManager query interface.
    #
    security.declareProtected( CMFCorePermissions.AccessContentsInformation
                             , 'objectIds' )
    def objectIds( self, spec=None ):
        """
            Return a list of the ids of our DiscussionItems.
        """
        if spec and spec is not DiscussionItem.meta_type:
            return []
        return self._container.keys()


    security.declareProtected( CMFCorePermissions.AccessContentsInformation
                             , 'objectItems' )
    def objectItems(self, spec=None):
        """
            Return a list of (id, subobject) tuples for our DiscussionItems.
        """
        r=[]
        a=r.append
        g=self._container.get
        for id in self.objectIds(spec):
            a( (id, g( id ) ) )
        return r


    security.declareProtected( CMFCorePermissions.AccessContentsInformation
                             , 'objectValues' )
    def objectValues(self):
        """
            Return a list of our DiscussionItems.
        """
        return self._container.values()

    #
    #   Discussable interface
    #
    security.declareProtected( CMFCorePermissions.ReplyToItem, 'createReply' )
    def createReply( self, title, text, Creator=None ):
        """
            Create a reply in the proper place
        """
        container = self._container

        id = int(DateTime().timeTime())
        while self._container.get( str(id), None ) is not None:
            id = id + 1
        id = str( id )

        item = DiscussionItem( id, title=title, description=title )
        item._edit( text_format='structured-text', text=text )

        if Creator:
            item.creator = Creator

        item.__of__( self ).indexObject()

        item.setReplyTo( self._getDiscussable() )
 
        self._container[ id ] = item

        return id

    security.declareProtected( CMFCorePermissions.View, 'hasReplies' )
    def hasReplies( self ):
        """
            Test to see if there are any dicussion items
        """
        if len(self._container) == 0:
            return 0

        return len( self._getReplyResults() )

    security.declareProtected( CMFCorePermissions.View, 'getReplies' )
    def getReplies( self ):
        """
            Return a sequence of the DiscussionResponse objects which are
            associated with this Discussable
        """
        objects = []
        a = objects.append
        result_ids = self._getReplyResults()

        for id in result_ids:
            a( self._container.get( id ).__of__( self ) )

        return objects

    security.declareProtected( CMFCorePermissions.View, 'quotedContents' )
    def quotedContents(self):
        """
            Return this object's contents in a form suitable for inclusion
            as a quote in a response.
        """
 
        return ""
    
    #
    #   Utility methods
    #
    security.declarePrivate( '_getReplyParent' )
    def _getReplyParent( self, in_reply_to ):
        """
            Return the object indicated by the 'in_reply_to', where
            'None' represents the "outer" content object.
        """
        outer = self._getDiscussable( outer=1 )
        if in_reply_to is None:
            return outer
        parent = self._container[ in_reply_to ].__of__( aq_inner( self ) )
        return parent.__of__( outer )
        

    security.declarePrivate( '_getDiscussable' )
    def _getDiscussable( self, outer=0 ):
        """
        """
        tb = outer and aq_inner( self ) or self
        return getattr( tb, 'aq_parent', None )

    security.declarePrivate( '_getReplyResults' )
    def _getReplyResults( self ):
        """
           Get a list of ids of DiscussionItems which are replies to
           our Discussable.
        """
        discussable = self._getDiscussable()
        outer = self._getDiscussable( outer=1 )

        if discussable == outer:
            in_reply_to = None
        else:
            in_reply_to = discussable.getId()

        result = []
        a = result.append
        for key, value in self._container.items():
            if value.in_reply_to == in_reply_to:
                a( key )

        return result
Ejemplo n.º 9
0
class MetadataSchema(SimpleItem):
    """ Describe a metadata schema.
    """
    security = ClassSecurityInfo()

    meta_type = 'Metadata Schema'
    publisher = ''

    def __init__(self, id, element_specs=()):
        self._setId(id)
        self.element_specs = PersistentMapping()
        for name, is_multi_valued in element_specs:
            self.element_specs[name] = ElementSpec(is_multi_valued)

    #
    #   ZMI methods
    #
    manage_options = (({
        'label': 'Elements',
        'action': 'elementPoliciesForm'
    }, ) + SimpleItem.manage_options)

    security.declareProtected(ManagePortal, 'elementPoliciesForm')
    elementPoliciesForm = DTMLFile('metadataElementPolicies', _dtmldir)

    security.declareProtected(ManagePortal, 'addElementPolicy')

    def addElementPolicy(self,
                         element,
                         content_type,
                         is_required,
                         supply_default,
                         default_value,
                         enforce_vocabulary,
                         allowed_vocabulary,
                         REQUEST=None):
        """ Add a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec(element)
        spec.addPolicy(content_type)
        policy = spec.getPolicy(content_type)
        policy.edit(is_required, supply_default, default_value,
                    enforce_vocabulary, allowed_vocabulary)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                                         '/elementPoliciesForm' + '?element=' +
                                         element +
                                         '&manage_tabs_message=Policy+added.')

    security.declareProtected(ManagePortal, 'removeElementPolicy')

    def removeElementPolicy(self, element, content_type, REQUEST=None):
        """ Remvoe a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec(element)
        spec.removePolicy(content_type)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/elementPoliciesForm' + '?element=' +
                element + '&manage_tabs_message=Policy+removed.')

    security.declareProtected(ManagePortal, 'updateElementPolicy')

    def updateElementPolicy(self,
                            element,
                            content_type,
                            is_required,
                            supply_default,
                            default_value,
                            enforce_vocabulary,
                            allowed_vocabulary,
                            REQUEST=None):
        """ Update a policy for one of our elements 
        
        o 'content_type' will be '<default>' when we edit the default.
        """
        if content_type == '<default>':
            content_type = None
        spec = self.getElementSpec(element)
        policy = spec.getPolicy(content_type)
        policy.edit(is_required, supply_default, default_value,
                    enforce_vocabulary, allowed_vocabulary)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/elementPoliciesForm' + '?element=' +
                element + '&manage_tabs_message=Policy+updated.')

    #
    #   Element spec manipulation.
    #
    security.declareProtected(ManagePortal, 'listElementSpecs')

    def listElementSpecs(self):
        """ Return a list of ElementSpecs representing the elements we manage.
        """
        res = []
        for k, v in self.element_specs.items():
            res.append((k, v.__of__(self)))
        return res

    security.declareProtected(ManagePortal, 'getElementSpec')

    def getElementSpec(self, element):
        """ Return an ElementSpec for the given 'element'.
        """
        return self.element_specs[element].__of__(self)

    security.declareProtected(ManagePortal, 'addElementSpec')

    def addElementSpec(self, element, is_multi_valued, REQUEST=None):
        """ Add 'element' to our list of managed elements.
        """
        # Don't replace.
        if self.element_specs.has_key(element):
            return

        self.element_specs[element] = ElementSpec(is_multi_valued)

        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                                         '/propertiesForm' +
                                         '?manage_tabs_message=Element+' +
                                         element + '+added.')

    security.declareProtected(ManagePortal, 'removeElementSpec')

    def removeElementSpec(self, element, REQUEST=None):
        """ Remove 'element' from our list of managed elements.
        """
        del self.element_specs[element]

        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                                         '/propertiesForm' +
                                         '?manage_tabs_message=Element+' +
                                         element + '+removed.')

    security.declareProtected(ManagePortal, 'listPolicies')

    def listPolicies(self, typ=None):
        """ Show all policies for a given content type
        
        o If 'typ' is none, return the list of default policies.
        """
        result = []
        for element, spec in self.listElementSpecs():
            result.append((element, spec.getPolicy(typ)))
        return result
Ejemplo n.º 10
0
class DiscussionItemContainer(Persistent, Implicit, Traversable):
    """
        Store DiscussionItem objects. Discussable content that
        has DiscussionItems associated with it will have an
        instance of DiscussionItemContainer injected into it to
        hold the discussion threads.
    """

    # for the security machinery to allow traversal
    #__roles__ = None

    security = ClassSecurityInfo()

    def __init__(self):
        self.id = 'talkback'
        self._container = PersistentMapping()

    security.declareProtected(CMFCorePermissions.View, 'getId')

    def getId(self):
        return self.id

    # Is this right?
    security.declareProtected(CMFCorePermissions.View, '__bobo_traverse__')

    def __bobo_traverse__(self, REQUEST, name):
        """
        This will make this container traversable
        """
        target = getattr(self, name, None)
        if target is not None:
            return target
        else:
            try:
                return self._container.get(name).__of__(self)
            except:
                REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))

    security.declarePrivate('manage_beforeDelete')

    def manage_beforeDelete(self, item, container):
        """
            Remove the contained items from the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).manage_beforeDelete(item, container)

    #
    #   OFS.ObjectManager query interface.
    #
    security.declareProtected(CMFCorePermissions.AccessContentsInformation,
                              'objectIds')

    def objectIds(self, spec=None):
        """
            Return a list of the ids of our DiscussionItems.
        """
        if spec and spec is not DiscussionItem.meta_type:
            return []
        return self._container.keys()

    security.declareProtected(CMFCorePermissions.AccessContentsInformation,
                              'objectItems')

    def objectItems(self, spec=None):
        """
            Return a list of (id, subobject) tuples for our DiscussionItems.
        """
        r = []
        a = r.append
        g = self._container.get
        for id in self.objectIds(spec):
            a((id, g(id)))
        return r

    security.declareProtected(CMFCorePermissions.AccessContentsInformation,
                              'objectValues')

    def objectValues(self):
        """
            Return a list of our DiscussionItems.
        """
        return self._container.values()

    #
    #   Discussable interface
    #
    security.declareProtected(CMFCorePermissions.ReplyToItem, 'createReply')

    def createReply(self, title, text, Creator=None):
        """
            Create a reply in the proper place
        """
        container = self._container

        id = int(DateTime().timeTime())
        while self._container.get(str(id), None) is not None:
            id = id + 1
        id = str(id)

        item = DiscussionItem(id, title=title, description=title)
        item._edit(text_format='structured-text', text=text)

        if Creator:
            item.creator = Creator

        item.__of__(self).indexObject()

        item.setReplyTo(self._getDiscussable())

        self._container[id] = item

        return id

    security.declareProtected(CMFCorePermissions.View, 'hasReplies')

    def hasReplies(self):
        """
            Test to see if there are any dicussion items
        """
        if len(self._container) == 0:
            return 0

        return len(self._getReplyResults())

    security.declareProtected(CMFCorePermissions.View, 'getReplies')

    def getReplies(self):
        """
            Return a sequence of the DiscussionResponse objects which are
            associated with this Discussable
        """
        objects = []
        a = objects.append
        result_ids = self._getReplyResults()

        for id in result_ids:
            a(self._container.get(id).__of__(self))

        return objects

    security.declareProtected(CMFCorePermissions.View, 'quotedContents')

    def quotedContents(self):
        """
            Return this object's contents in a form suitable for inclusion
            as a quote in a response.
        """

        return ""

    #
    #   Utility methods
    #
    security.declarePrivate('_getReplyParent')

    def _getReplyParent(self, in_reply_to):
        """
            Return the object indicated by the 'in_reply_to', where
            'None' represents the "outer" content object.
        """
        outer = self._getDiscussable(outer=1)
        if in_reply_to is None:
            return outer
        parent = self._container[in_reply_to].__of__(aq_inner(self))
        return parent.__of__(outer)

    security.declarePrivate('_getDiscussable')

    def _getDiscussable(self, outer=0):
        """
        """
        tb = outer and aq_inner(self) or self
        return getattr(tb, 'aq_parent', None)

    security.declarePrivate('_getReplyResults')

    def _getReplyResults(self):
        """
           Get a list of ids of DiscussionItems which are replies to
           our Discussable.
        """
        discussable = self._getDiscussable()
        outer = self._getDiscussable(outer=1)

        if discussable == outer:
            in_reply_to = None
        else:
            in_reply_to = discussable.getId()

        result = []
        a = result.append
        for key, value in self._container.items():
            if value.in_reply_to == in_reply_to:
                a(key)

        return result
Ejemplo n.º 11
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = "MessageCatalog"

    security = ClassSecurityInfo()

    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header

    #######################################################################
    # Private API
    #######################################################################
    def get_message_key(self, message):
        if message in self._messages:
            return message
        # A message may be stored as unicode or byte string
        encoding = HTTPRequest.default_encoding
        if isinstance(message, unicode):
            message = message.encode(encoding)
        else:
            message = unicode(message, encoding)
        if message in self._messages:
            return message

    def get_translations(self, message):
        message = self.get_message_key(message)
        return self._messages[message]

    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic("message_exists")

    def message_exists(self, message):
        """ """
        return self._messages.has_key(message)

    security.declareProtected("Manage messages", "message_edit")

    def message_edit(self, message, language, translation, note):
        """ """
        self._messages[message][language] = translation
        self._messages[message]["note"] = note

    security.declareProtected("Manage messages", "message_del")

    def message_del(self, message):
        """ """
        del self._messages[message]

    security.declarePublic("gettext")

    def gettext(self, message, lang=None, add=1, default=None):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """
        if not isinstance(message, (str, unicode)):
            raise TypeError, "only strings can be translated."

        message = message.strip()
        # assume empty message is always translated as empty message
        if not message:
            return message

        if default is None:
            default = message

        # Add it if it's not in the dictionary
        if add and not self._messages.has_key(message) and message:
            update_transaction_note()
            self._messages[message] = PersistentMapping()

        if message and not self._messages[message].has_key("en"):
            self._messages[message]["en"] = default

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default

    __call__ = gettext

    def translate(self, domain, msgid, *args, **kw):
        """This method is required to get the i18n namespace from ZPT working.
        """

        default = kw.get("default")
        if default is not None and not default.strip():
            default = None
        msgstr = self.gettext(msgid, default=default)
        mapping = kw.get("mapping")
        return interpolate(msgstr, mapping)

    #######################################################################
    # Management screens
    #######################################################################
    def manage_options(self):
        """ """
        options = (
            (
                {"label": u"Messages", "action": "manage_messages", "help": ("Localizer", "MC_messages.stx")},
                {"label": u"Properties", "action": "manage_propertiesForm"},
                {"label": u"Import", "action": "manage_Import_form", "help": ("Localizer", "MC_importExport.stx")},
                {"label": u"Export", "action": "manage_Export_form", "help": ("Localizer", "MC_importExport.stx")},
            )
            + LanguageManager.manage_options
            + SimpleItem.manage_options
        )

        r = []
        for option in options:
            option = option.copy()
            option["label"] = _(option["label"])
            r.append(option)

        return r

    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected("Manage messages", "manage_messages")
    manage_messages = LocalDTMLFile("ui/MC_messages", globals())

    security.declarePublic("get_namespace")

    def get_namespace(self, REQUEST):
        """For the management interface, allows to filter the messages to
        show.
        """
        # Check whether there are languages or not
        languages = self.get_languages_mapping()
        if not languages:
            return {}

        # Input
        batch_start = REQUEST.get("batch_start", 0)
        batch_size = REQUEST.get("batch_size", 15)
        empty = REQUEST.get("empty", 0)
        regex = REQUEST.get("regex", "")
        message = REQUEST.get("msg", None)

        # Build the namespace
        namespace = {}
        namespace["batch_size"] = batch_size
        namespace["empty"] = empty
        namespace["regex"] = regex

        # The language
        lang = REQUEST.get("lang", None) or languages[0]["code"]
        namespace["language"] = lang

        # Filter the messages
        query = regex.strip()
        try:
            query = compile(query)
        except:
            query = compile("")

        messages = []
        for m, t in self._messages.items():
            if query.search(m) and (not empty or not t.get(lang, "").strip()):
                messages.append(m)
        messages.sort(filter_sort)
        # How many messages
        n = len(messages)
        namespace["n_messages"] = n

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size
        if batch_start < 0:
            batch_start = 0
        namespace["batch_start"] = batch_start
        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]
        # Batch links
        namespace["previous"] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty)
        namespace["next"] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty)

        # Get the message
        message_encoded = None
        translations = {}
        if message is None:
            if messages:
                message = messages[0]
                translations = self.get_translations(message)
                message = to_unicode(message)
                message_encoded = message_encode(message)
        else:
            message_encoded = message
            message = message_decode(message_encoded)
            translations = self.get_translations(message)
            message = to_unicode(message)
        namespace["message"] = message
        namespace["message_encoded"] = message_encoded
        namespace["translations"] = translations
        namespace["translation"] = translations.get(lang, "")
        namespace["note"] = translations.get("note", "")

        # Calculate the current message
        namespace["messages"] = []
        for x in messages:
            x = to_unicode(x)
            x_encoded = message_encode(x)
            url = get_url(REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded)
            namespace["messages"].append(
                {"message": x, "message_encoded": x_encoded, "current": x == message, "url": url}
            )

        # The languages
        for language in languages:
            code = language["code"]
            language["name"] = _(language["name"], language=code)
            language["url"] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded)
        namespace["languages"] = languages

        return namespace

    security.declareProtected("Manage messages", "manage_editMessage")

    def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE):
        """Modifies a message.
        """
        message_encoded = message
        message = message_decode(message_encoded)
        message_key = self.get_message_key(message)
        self.message_edit(message_key, language, translation, note)

        url = get_url(
            REQUEST.URL1 + "/manage_messages",
            REQUEST["batch_start"],
            REQUEST["batch_size"],
            REQUEST["regex"],
            REQUEST.get("lang", ""),
            REQUEST.get("empty", 0),
            msg=message_encoded,
            manage_tabs_message=_(u"Saved changes."),
        )
        RESPONSE.redirect(url)

    security.declareProtected("Manage messages", "manage_delMessage")

    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = message_decode(message)
        message_key = self.get_message_key(message)
        self.message_del(message_key)

        url = get_url(
            REQUEST.URL1 + "/manage_messages",
            REQUEST["batch_start"],
            REQUEST["batch_size"],
            REQUEST["regex"],
            REQUEST.get("lang", ""),
            REQUEST.get("empty", 0),
            manage_tabs_message=_(u"Saved changes."),
        )
        RESPONSE.redirect(url)

    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected("View management screens", "manage_propertiesForm")
    manage_propertiesForm = LocalDTMLFile("ui/MC_properties", globals())

    security.declareProtected("View management screens", "manage_properties")

    def manage_properties(self, title, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title

        if RESPONSE is not None:
            RESPONSE.redirect("manage_propertiesForm")

    # Properties management screen
    security.declareProtected("View management screens", "get_po_header")

    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), "_po_headers"):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)

    security.declareProtected("View management screens", "update_po_header")

    def update_po_header(
        self,
        lang,
        last_translator_name=None,
        last_translator_email=None,
        language_team=None,
        charset=None,
        REQUEST=None,
        RESPONSE=None,
    ):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header["last_translator_name"]

        if last_translator_email is None:
            last_translator_email = header["last_translator_email"]

        if language_team is None:
            language_team = header["language_team"]

        if charset is None:
            charset = header["charset"]

        header = {
            "last_translator_name": last_translator_name,
            "last_translator_email": last_translator_email,
            "language_team": language_team,
            "charset": charset,
        }

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect("manage_propertiesForm")

    security.declareProtected("View management screens", "manage_Import_form")
    manage_Import_form = LocalDTMLFile("ui/MC_Import_form", globals())

    security.declarePublic("get_charsets")

    def get_charsets(self):
        """ """
        return charsets[:]

    security.declarePublic("manage_export")

    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header["last_translator_name"]
        last_translator_email = header["last_translator_email"]
        language_team = header["language_team"]
        charset = header["charset"]

        # PO file header, empty message.
        po_revision_date = strftime("%Y-%m-%d %H:%m+%Z", gmtime(time()))
        pot_creation_date = po_revision_date
        last_translator = "%s <%s>" % (last_translator_name, last_translator_email)

        if x == "locale.pot":
            language_team = "LANGUAGE <*****@*****.**>"
        else:
            language_team = "%s <%s>" % (x, language_team)

        r = [
            'msgid ""',
            'msgstr "Project-Id-Version: %s\\n"' % self.title,
            '"POT-Creation-Date: %s\\n"' % pot_creation_date,
            '"PO-Revision-Date: %s\\n"' % po_revision_date,
            '"Last-Translator: %s\\n"' % last_translator,
            '"Language-Team: %s\\n"' % language_team,
            '"MIME-Version: 1.0\\n"',
            '"Content-Type: text/plain; charset=%s\\n"' % charset,
            '"Content-Transfer-Encoding: 8bit\\n"',
            "",
            "",
        ]

        # Get the messages, and perhaps its translations.
        d = {}
        if x == "locale.pot":
            filename = x
            for k in self._messages.keys():
                d[k] = ""
        else:
            filename = "%s.po" % x
            for k, v in self._messages.items():
                try:
                    d[k] = v[x]
                except KeyError:
                    d[k] = ""

        # Generate the file
        def backslashescape(x):
            quote_esc = compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t")]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append("")

        if RESPONSE is not None:
            RESPONSE.setHeader("Content-type", "application/data")
            RESPONSE.setHeader("Content-Disposition", "inline;filename=%s" % filename)

        r2 = []
        for x in r:
            if isinstance(x, unicode):
                r2.append(x.encode(charset))
            else:
                r2.append(x)

        return "\n".join(r2)

    security.declareProtected("Manage messages", "po_import")

    def po_import(self, lang, data):
        """ """
        messages = self._messages

        # Load the data
        po = POFile(string=data)
        for msgid in po.get_msgids():
            if msgid:
                msgstr = po.get_msgstr(msgid) or ""
                if not messages.has_key(msgid):
                    messages[msgid] = PersistentMapping()
                messages[msgid][lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=po.get_encoding())

    security.declareProtected("Manage messages", "manage_import")

    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect("manage_messages")

    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute("objectItems")(self, spec)
        return r

    #######################################################################
    # TMX support
    security.declareProtected("View management screens", "manage_Export_form")
    manage_Export_form = LocalDTMLFile("ui/MC_Export_form", globals())

    security.declareProtected("Manage messages", "tmx_export")

    def tmx_export(self, REQUEST, RESPONSE=None):
        """Exports the content of the message catalog to a TMX file
        """
        orglang = self._default_language

        # Get the header info
        header = self.get_po_header(orglang)
        charset = header["charset"]

        # build data structure for the xml header
        xml_header = {}
        xml_header["standalone"] = -1
        xml_header["xml_version"] = u"1.0"
        xml_header["document_type"] = (u"tmx", u"http://www.lisa.org/tmx/tmx14.dtd")
        # build data structure for the tmx header
        version = u"1.4"
        tmx_header = {}
        tmx_header["creationtool"] = u"Localizer"
        tmx_header["creationtoolversion"] = u"1.x"
        tmx_header["datatype"] = u"plaintext"
        tmx_header["segtype"] = u"paragraph"
        tmx_header["adminlang"] = u"%s" % orglang
        tmx_header["srclang"] = u"%s" % orglang
        tmx_header["o-encoding"] = u"%s" % charset.lower()

        # handle messages
        d = {}
        filename = "%s.tmx" % self.id
        for msgkey, transunit in self._messages.items():
            sentences = {}
            for lang in transunit.keys():
                if lang != "note":
                    s = Sentence(transunit[lang], {"lang": "%s" % lang})
                    sentences[lang] = s

            if orglang not in transunit.keys():
                s = Sentence(msgkey, {"lang": "%s" % orglang})
                sentences[orglang] = s

            if transunit.has_key("note"):
                d[msgkey] = Message(sentences, {}, [Note(transunit.get("note"))])
            else:
                d[msgkey] = Message(sentences)

        tmx = TMX()
        tmx.build(xml_header, version, tmx_header, d)

        if RESPONSE is not None:
            RESPONSE.setHeader("Content-type", "application/data")
            RESPONSE.setHeader("Content-Disposition", 'attachment; filename="%s"' % filename)

        return tmx.to_str()

    security.declareProtected("Manage messages", "tmx_import")

    def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None):
        """Imports a TMX level 1 file.
        """
        try:
            data = file.read()
            tmx = TMX(string=data)
        except:
            return MessageDialog(
                title="Parse error", message=_("impossible to parse the file"), action="manage_Import_form"
            )

        num_notes = 0
        num_trans = 0

        if howmuch == "clear":
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = tmx.get_srclang()

        for (id, msg) in tmx.state.messages.items():
            if not self._messages.has_key(id) and howmuch == "existing":
                pass
            else:
                msg.msgstr.pop(self._default_language)
                if not self._messages.has_key(id):
                    self._messages[id] = {}
                for lang in msg.msgstr.keys():
                    # normalize the languageTag and extract the core
                    (core, local) = LanguageTag.decode(lang)
                    lang = LanguageTag.encode((core, local))
                    if lang not in self._languages:
                        self._languages += (lang,)
                    if msg.msgstr[lang].text:
                        self._messages[id][lang] = msg.msgstr[lang].text
                        if core != lang and core != self._default_language:
                            if core not in self._languages:
                                self._languages += (core,)
                            if not msg.msgstr.has_key(core):
                                self._messages[id][core] = msg.msgstr[lang].text
                if msg.notes:
                    ns = [m.text for m in msg.notes]
                    self._messages[id]["note"] = u" ".join(ns)
                    num_notes += 1
                num_trans += 1

        if REQUEST is not None:
            return MessageDialog(
                title=_(u"Messages imported"),
                message=_(u"Imported %d messages and %d notes") % (num_trans, num_notes),
                action="manage_messages",
            )

    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it

    security.declareProtected("Manage messages", "xliff_export")

    def xliff_export(self, x, export_all=1, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog to an XLIFF file
        """
        orglang = self._default_language
        export_all = int(export_all)
        from DateTime import DateTime

        # Generate the XLIFF file header
        RESPONSE.setHeader("Content-Type", "text/xml; charset=UTF-8")
        RESPONSE.setHeader("Content-Disposition", 'attachment; filename="%s_%s_%s.xlf"' % (self.id, orglang, x))
        # build data structure for the xml header
        xml_header = {}
        xml_header["standalone"] = -1
        xml_header["xml_version"] = u"1.0"
        xml_header["document_type"] = (u"xliff", u"http://www.oasis-open.org/committees/xliff/documents/xliff.dtd")

        version = u"1.0"

        # build the data-stucture for the File tag
        attributes = {}
        attributes["original"] = u"/%s" % self.absolute_url(1)
        attributes["product-name"] = u"Localizer"
        attributes["product-version"] = u"1.1.x"
        attributes["data-type"] = u"plaintext"
        attributes["source-language"] = orglang
        attributes["target-language"] = x
        attributes["date"] = DateTime().HTML4()

        # Get the messages, and perhaps its translations.
        d = {}
        for msgkey, transunit in self._messages.items():
            target = transunit.get(x, "")
            # if export_all=1 export all messages otherwise export
            # only untranslated messages
            if export_all or not target:
                id = md5text(msgkey)
                notes = []
                if transunit.has_key("note") and transunit["note"]:
                    notes = [xliff_Note(transunit["note"])]
                if target:
                    t = Translation(msgkey, target, {"id": id}, notes)
                else:
                    t = Translation(msgkey, msgkey, {"id": id}, notes)
                d[msgkey] = t

        files = [xliff_File(d, attributes)]

        xliff = XLIFF()
        xliff.build(xml_header, version, files)

        return xliff.to_str()

    security.declareProtected("Manage messages", "xliff_import")

    def xliff_import(self, howmuch, file, REQUEST=None):
        """XLIFF is the XML Localization Interchange File Format designed by a
        group of software providers.  It is specified by www.oasis-open.org
        """
        try:
            data = file.read()
            xliff = XLIFF(string=data)
        except:
            return MessageDialog(
                title="Parse error", message=_("impossible to parse the file"), action="manage_Import_form"
            )

        num_notes = 0
        num_trans = 0
        (file_ids, sources, targets) = xliff.get_languages()

        if howmuch == "clear":
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = sources[0]

        # update languages
        if len(sources) > 1 or sources[0] != self._default_language:
            return MessageDialog(
                title="Language error", message=_("incompatible language sources"), action="manage_Import_form"
            )
        for lang in targets:
            if lang != self._default_language and lang not in self._languages:
                self._languages += (lang,)

        # get messages
        for file in xliff.state.files:
            cur_target = file.attributes.get("target-language", "")
            for msg in file.body.keys():
                if not self._messages.has_key(msg) and howmuch == "existing":
                    pass
                else:
                    if not self._messages.has_key(msg):
                        self._messages[msg] = {}

                    if cur_target and file.body[msg].target:
                        self._messages[msg][cur_target] = file.body[msg].target
                        num_trans += 1
                    if file.body[msg].notes:
                        ns = [n.text for n in file.body[msg].notes]
                        comment = " ".join(ns)
                        self._messages[msg]["note"] = comment
                        num_notes += 1

        if REQUEST is not None:
            return MessageDialog(
                title=_(u"Messages imported"),
                message=(_(u"Imported %d messages and %d notes to %s") % (num_trans, num_notes, " ".join(targets))),
                action="manage_messages",
            )
Ejemplo n.º 12
0
class ArchetypeTool(UniqueObject, ActionProviderBase, \
                    SQLStorageConfig, Folder):
    """Archetypes tool, manage aspects of Archetype instances.
    """
    id = TOOL_NAME
    meta_type = TOOL_NAME.title().replace('_', ' ')

    implements(IArchetypeTool)

    isPrincipiaFolderish = True # Show up in the ZMI

    security = ClassSecurityInfo()

    meta_types = all_meta_types = ()

    manage_options = (
        (
        { 'label'  : 'Types',
          'action' : 'manage_debugForm',
          },

        {  'label'  : 'Catalogs',
           'action' : 'manage_catalogs',
           },

        { 'label'  : 'Templates',
          'action' : 'manage_templateForm',
          },

        {  'label'  : 'UIDs',
           'action' : 'manage_uids',
           },

        { 'label'  : 'Update Schema',
          'action' : 'manage_updateSchemaForm',
          },

        { 'label'  : 'Migration',
          'action' : 'manage_migrationForm',
          },

        ) + SQLStorageConfig.manage_options
        )

    security.declareProtected(permissions.ManagePortal,
                              'manage_uids')
    manage_uids = PageTemplateFile('viewContents', _www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_templateForm')
    manage_templateForm = PageTemplateFile('manageTemplates',_www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_debugForm')
    manage_debugForm = PageTemplateFile('generateDebug', _www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_updateSchemaForm')
    manage_updateSchemaForm = PageTemplateFile('updateSchemaForm', _www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_migrationForm')
    manage_migrationForm = PageTemplateFile('migrationForm', _www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_dumpSchemaForm')
    manage_dumpSchemaForm = PageTemplateFile('schema', _www)
    security.declareProtected(permissions.ManagePortal,
                              'manage_catalogs')
    manage_catalogs = PageTemplateFile('manage_catalogs', _www)


    def __init__(self):
        self._schemas = PersistentMapping()
        self._templates = PersistentMapping()
        self._registeredTemplates = PersistentMapping()
        # meta_type -> [names of CatalogTools]
        self.catalog_map = PersistentMapping()
        self.catalog_map['Reference'] = [] # References not in portal_catalog
        # DM (avoid persistency bug): "_types" now maps known schemas to signatures
        self._types = {}

    security.declareProtected(permissions.ManagePortal,
                              'manage_dumpSchema')
    def manage_dumpSchema(self, REQUEST=None):
        """XML Dump Schema of passed in class.
        """
        from Products.Archetypes.Schema import getSchemata
        package = REQUEST.get('package', '')
        type_name = REQUEST.get('type_name', '')
        spec = self.getTypeSpec(package, type_name)
        type = self.lookupType(package, type_name)
        options = {}
        options['classname'] = spec
        options['schematas'] = getSchemata(type['klass'])
        REQUEST.RESPONSE.setHeader('Content-Type', 'text/xml')
        return self.manage_dumpSchemaForm(**options)

    # Template Management
    # Views can be pretty generic by iterating the schema so we don't
    # register by type anymore, we just create per site selection
    # lists
    #
    # We keep two lists, all register templates and their
    # names/titles and the mapping of type to template bindings both
    # are persistent
    security.declareProtected(permissions.ManagePortal,
                              'registerTemplate')
    def registerTemplate(self, template, name=None):
        # Lookup the template by name
        if not name:
            obj = self.unrestrictedTraverse(template, None)
            try:
                name = obj.title_or_id()
            except:
                name = template

        self._registeredTemplates[template] = name


    security.declareProtected(permissions.View, 'lookupTemplates')
    def lookupTemplates(self, instance_or_portaltype=None):
        """Lookup templates by giving an instance or a portal_type.

        Returns a DisplayList.
        """
        results = []
        if not isinstance(instance_or_portaltype, basestring):
            portal_type = instance_or_portaltype.getTypeInfo().getId()
        else:
            portal_type = instance_or_portaltype
        try:
            templates = self._templates[portal_type]
        except KeyError:
            return DisplayList()
            # XXX Look this up in the types tool later
            # self._templates[instance] = ['base_view',]
            # templates = self._templates[instance]
        for t in templates:
            results.append((t, self._registeredTemplates[t]))            

        return DisplayList(results).sortedByValue()

    security.declareProtected(permissions.View, 'listTemplates')
    def listTemplates(self):
        """Lists all the templates.
        """
        return DisplayList(self._registeredTemplates.items()).sortedByValue()

    security.declareProtected(permissions.ManagePortal, 'bindTemplate')
    def bindTemplate(self, portal_type, templateList):
        """Creates binding between a type and its associated views.
        """
        self._templates[portal_type] = templateList

    security.declareProtected(permissions.ManagePortal,
                              'manage_templates')
    def manage_templates(self, REQUEST=None):
        """Sets all the template/type mappings.
        """
        prefix = 'template_names_'
        for key in REQUEST.form.keys():
            if key.startswith(prefix):
                k = key[len(prefix):]
                v = REQUEST.form.get(key)
                self.bindTemplate(k, v)

        add = REQUEST.get('addTemplate')
        name = REQUEST.get('newTemplate')
        if add and name:
            self.registerTemplate(name)

        return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_templateForm')
    
    security.declareProtected(permissions.View, 'typeImplementsInterfaces')
    def typeImplementsInterfaces(self, type, interfaces):
        """Checks if an type uses one of the given interfaces.
        """
        if isinstance(type, dict) and type.has_key('klass'):
            type = type['klass']
        for iface in interfaces:
            res = iface.isImplementedByInstancesOf(type)
            if res:
                return True
        return False
    
    security.declareProtected(permissions.View, 'isTemplateEnabled')
    def isTemplateEnabled(self, type):
        """Checks if an type uses ITemplateMixin.
        """
        return self.typeImplementsInterfaces(type, [ITemplateMixin])
        
    security.declareProtected(permissions.View, 'listTemplateEnabledPortalTypes')
    def listTemplateEnabledPortalTypes(self):
        """Return a list of portal_types with ITemplateMixin
        """
        return self.listPortalTypesWithInterfaces([ITemplateMixin])
        
    security.declareProtected(permissions.View, 'listPortalTypesWithInterfaces')
    def listPortalTypesWithInterfaces(self, ifaces):
        """Returns a list of ftis of which the types implement one of
        the given interfaces.  Only returns AT types.

        Get a list of FTIs of types implementing IReferenceable:
        >>> tool = getToolByName(self.portal, TOOL_NAME)
        >>> meth = tool.listPortalTypesWithInterfaces
        >>> ftis = tool.listPortalTypesWithInterfaces([IReferenceable])
        
        Sort the type ids and print them:
        >>> type_ids = [fti.getId() for fti in ftis]
        >>> type_ids.sort()
        >>> type_ids
        ['ATBIFolder', 'ComplexType', ...]
        """
        pt = getToolByName(self, 'portal_types')
        value = []
        for data in listTypes():
            klass = data['klass']
            for iface in ifaces:
                if iface.isImplementedByInstancesOf(klass):
                    ti = pt.getTypeInfo(data['portal_type'])
                    if ti is not None:
                        value.append(ti)
        return value

    # Type/Schema Management
    security.declareProtected(permissions.View, 'listRegisteredTypes')
    def listRegisteredTypes(self, inProject=False, portalTypes=False):
        """Return the list of sorted types.
        """

        def type_sort(a, b):
            v = cmp(a['package'], b['package'])
            if v != False: return v
            c = cmp(a['klass'].__class__.__name__,
                    b['klass'].__class__.__name__)

            if c == False:
                return cmp(a['package'], b['package'])
            return c

        values = listTypes()
        values.sort(type_sort)

        if inProject:
            # portal_type can change (as it does after ATCT-migration), so we
            # need to check against the content_meta_type of each type-info
            ttool = getToolByName(self, 'portal_types')
            types = [ti.Metatype() for ti in ttool.listTypeInfo()]
	    if portalTypes:
                values = [v for v in values if v['portal_type'] in types]
            else:
                values = [v for v in values if v['meta_type'] in types]

        return values

    security.declareProtected(permissions.View, 'getTypeSpec')
    def getTypeSpec(self, package, type):
        t = self.lookupType(package, type)
        module = t['klass'].__module__
        klass = t['name']
        return '%s.%s' % (module, klass)

    security.declareProtected(permissions.View, 'listTypes')
    def listTypes(self, package=None, type=None):
        """Just the class.
        """
        if type is None:
            return [t['klass'] for t in listTypes(package)]
        else:
            return [getType(type, package)['klass']]

    security.declareProtected(permissions.View, 'lookupType')
    def lookupType(self, package, type):
        types = self.listRegisteredTypes()
        for t in types:
            if t['package'] != package:
                continue
            if t['meta_type'] == type:
                # We have to return the schema wrapped into the acquisition of
                # something to allow access. Otherwise we will end up with:
                # Your user account is defined outside the context of the object
                # being accessed.
                t['schema'] = ImplicitAcquisitionWrapper(t['schema'], self)
                return t
        return None

    security.declareProtected(permissions.ManagePortal,
                              'manage_installType')
    def manage_installType(self, typeName, package=None,
                           uninstall=None, REQUEST=None):
        """Un/Install a type TTW.
        """
        typesTool = getToolByName(self, 'portal_types')
        try:
            typesTool._delObject(typeName)
        except (ConflictError, KeyboardInterrupt):
            raise
        except: # XXX bare exception
            pass
        if uninstall is not None:
            if REQUEST:
                return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                                 '/manage_debugForm')
            return

        typeinfo_name = '%s: %s' % (package, typeName)

        # We want to run the process/modify_fti code which might not
        # have been called
        typeDesc = getType(typeName, package)
        process_types([typeDesc], package)
        klass = typeDesc['klass']
        
        # get the meta type of the FTI from the class, use the default FTI as default
        fti_meta_type = getattr(klass, '_at_fti_meta_type', None)
        if fti_meta_type in (None, 'simple item'):
            fti_meta_type = FactoryTypeInformation.meta_type

        typesTool.manage_addTypeInformation(fti_meta_type,
                                            id=typeName,
                                            typeinfo_name=typeinfo_name)
        t = getattr(typesTool, typeName, None)
        if t:
            t.title = getattr(klass, 'archetype_name',
                              typeDesc['portal_type'])

        # and update the actions as needed
        fixActionsForType(klass, typesTool)

        if REQUEST:
            return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                             '/manage_debugForm')

    security.declarePublic('getSearchWidgets')
    def getSearchWidgets(self, package=None, type=None,
                         context=None, nosort=None):
        """Empty widgets for searching.
        """
        return self.getWidgets(package=package, type=type,
                               context=context, mode='search', nosort=nosort)

    security.declarePublic('getWidgets')
    def getWidgets(self, instance=None,
                   package=None, type=None,
                   context=None, mode='edit',
                   fields=None, schemata=None, nosort=None):
        """Empty widgets for standalone rendering.
        """
        widgets = []
        w_keys = {}
        context = context is not None and context or self
        instances = instance is not None and [instance] or []
        f_names = fields
        if not instances:
            for t in self.listTypes(package, type):
                instance = t('fake_instance')
                instance._at_is_fake_instance = True
                wrapped = instance.__of__(context)
                wrapped.initializeArchetype()
                instances.append(wrapped)
        for instance in instances:
            if schemata is not None:
                schema = instance.Schemata()[schemata].copy()
            else:
                schema = instance.Schema().copy()
            fields = schema.fields()
            if mode == 'search':
                # Include only fields which have an index
                # XXX duplicate fieldnames may break this,
                # as different widgets with the same name
                # on different schemas means only the first
                # one found will be used
                indexes=self.portal_catalog.indexes()
                fields = [f for f in fields
                          if (f.accessor and
                              not w_keys.has_key(f.accessor)
                              and f.accessor in indexes)]
            if f_names is not None:
                fields = filter(lambda f: f.getName() in f_names, fields)
            for field in fields:
                widget = field.widget
                field_name = field.getName()
                accessor = field.getAccessor(instance)
                if mode == 'search':
                    field.required = False
                    field.addable = False # for ReferenceField
                    if not isinstance(field.vocabulary, DisplayList):
                        field.vocabulary = field.Vocabulary(instance)
                    if '' not in field.vocabulary.keys():
                        field.vocabulary = DisplayList([('', _(u'at_search_any', default=u'<any>'))]) + \
                                           field.vocabulary
                    widget.populate = False
                    field_name = field.accessor
                    # accessor must be a method which doesn't take an argument
                    # this lambda is facking an accessor
                    accessor = lambda: field.getDefault(instance)

                w_keys[field_name] = None
                widgets.append((field_name, WidgetWrapper(
                    field_name=field_name,
                    mode=mode,
                    widget=widget,
                    instance=instance,
                    field=field,
                    accessor=accessor)))
        if mode == 'search' and nosort == None:
            widgets.sort()
        return [widget for name, widget in widgets]

    security.declarePrivate('_rawEnum')
    def _rawEnum(self, callback, *args, **kwargs):
        """Finds all object to check if they are 'referenceable'.
        """
        catalog = getToolByName(self, 'portal_catalog')
        brains = catalog(id=[])
        for b in brains:
            o = b.getObject()
            if o is not None:
                if IBaseObject.isImplementedBy(o):
                    callback(o, *args, **kwargs)
            else:
                log('no object for brain: %s:%s' % (b,b.getURL()))


    security.declareProtected(permissions.View, 'enum')
    def enum(self, callback, *args, **kwargs):
        catalog = getToolByName(self, UID_CATALOG)
        keys = catalog.uniqueValuesFor('UID')
        for uid in keys:
            o = self.getObject(uid)
            if o:
                callback(o, *args, **kwargs)
            else:
                log('No object for %s' % uid)


    security.declareProtected(permissions.View, 'Content')
    def Content(self):
        """Return a list of all the content ids.
        """
        catalog = getToolByName(self, UID_CATALOG)
        keys = catalog.uniqueValuesFor('UID')
        results = catalog(UID=keys)
        return results


    ## Management Forms
    security.declareProtected(permissions.ManagePortal,
                              'manage_doGenerate')
    def manage_doGenerate(self, sids=(), REQUEST=None):
        """(Re)generate types.
        """
        schemas = []
        for sid in sids:
            schemas.append(self.getSchema(sid))

        for s in schemas:
            s.generate()

        if REQUEST:
            return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                             '/manage_workspace')

    security.declareProtected(permissions.ManagePortal,
                              'manage_inspect')
    def manage_inspect(self, UID, REQUEST=None):
        """Dump some things about an object hook in the debugger for now.
        """
        object = self.getObject(UID)
        log(object, object.Schema(), dir(object))

        return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                         '/manage_uids')

    security.declareProtected(permissions.ManagePortal,
                              'manage_reindex')
    def manage_reindex(self, REQUEST=None):
        """Assign UIDs to all basecontent objects.
        """

        def _index(object, archetype_tool):
            archetype_tool.registerContent(object)

        self._rawEnum(_index, self)

        return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                         '/manage_uids')

    security.declareProtected(permissions.ManagePortal, 'index')
    index = manage_reindex

    def _listAllTypes(self):
        """List all types -- either currently known or known to us.
        """
        allTypes = _types.copy(); allTypes.update(self._types)
        return allTypes.keys()

    security.declareProtected(permissions.ManagePortal,
                              'getChangedSchema')
    def getChangedSchema(self):
        """Returns a list of tuples indicating which schema have changed.

        Tuples have the form (schema, changed).
        """
        list = []
        currentTypes = _types
        ourTypes = self._types
        modified = False
        keys = self._listAllTypes()
        keys.sort()
        for t in keys:
            if t not in ourTypes:
                # Add it
                ourTypes[t] = currentTypes[t]['signature']
                modified = True
                list.append((t, 0))
            elif t not in currentTypes:
                # Huh: what shall we do? We remove it -- this might be wrong!
                del ourTypes[t]
                modified = True
                # We do not add an entry because we cannot update
                # these objects (having no longer type information for them)
            else:
                list.append((t, ourTypes[t] != currentTypes[t]['signature']))
        if modified:
            self._p_changed = True
        return list


    security.declareProtected(permissions.ManagePortal,
                              'manage_updateSchema')
    def manage_updateSchema(self, REQUEST=None, update_all=None,
                            remove_instance_schemas=None):
        """Make sure all objects' schema are up to date.
        """
        out = StringIO()
        print >> out, 'Updating schema...'

        update_types = []
        if REQUEST is None:
            # DM (avoid persistency bug): avoid code duplication
            update_types = [ti[0] for ti in self.getChangedSchema() if ti[1]]
        else:
            # DM (avoid persistency bug):
            for t in self._listAllTypes():
                if REQUEST.form.get(t, False):
                    update_types.append(t)
            update_all = REQUEST.form.get('update_all', False)
            remove_instance_schemas = REQUEST.form.get(
                'remove_instance_schemas', False)

        # XXX: Enter this block only when there are types to update!
        if update_types:
            # Use the catalog's ZopeFindAndApply method to walk through
            # all objects in the portal.  This works much better than
            # relying on the catalog to find objects, because an object
            # may be uncatalogable because of its schema, and then you
            # can't update it if you require that it be in the catalog.
            catalog = getToolByName(self, 'portal_catalog')
            portal = getToolByName(self, 'portal_url').getPortalObject()
            meta_types = [_types[t]['meta_type'] for t in update_types]
            if remove_instance_schemas:
                func_update_changed = self._removeSchemaAndUpdateChangedObject
                func_update_all = self._removeSchemaAndUpdateObject
            else:
                func_update_changed = self._updateChangedObject
                func_update_all = self._updateObject
            if update_all:
                catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types,
                    search_sub=True, apply_func=func_update_all)
            else:
                catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types,
                    search_sub=True, apply_func=func_update_changed)
            for t in update_types:
                self._types[t] = _types[t]['signature']
            self._p_changed = True

        print >> out, 'Done.'
        return out.getvalue()

    # A counter to ensure that in a given interval a subtransaction
    # commit is done.
    subtransactioncounter = 0

    def _updateObject(self, o, path, remove_instance_schemas=None):
        o._updateSchema(remove_instance_schemas=remove_instance_schemas)
        # Subtransactions to avoid eating up RAM when used inside a
        # 'ZopeFindAndApply' like in manage_updateSchema
        self.subtransactioncounter += 1
        # Only every 250 objects a sub-commit, otherwise it eats up all diskspace
        if not self.subtransactioncounter % 250:
            transaction.savepoint(optimistic=True)

    def _updateChangedObject(self, o, path):
        if not o._isSchemaCurrent():
            self._updateObject(o, path)

    def _removeSchemaAndUpdateObject(self, o, path):
        self._updateObject(o, path, remove_instance_schemas=True)

    def _removeSchemaAndUpdateChangedObject(self, o, path):
        if not o._isSchemaCurrent():
            self._removeSchemaAndUpdateObject(o, path)

    security.declareProtected(permissions.ManagePortal,
                              'manage_updateSchema')
    def manage_migrate(self, REQUEST=None):
        """Run Extensions.migrations.migrate.
        """
        from Products.Archetypes.Extensions.migrations import migrate
        out = migrate(self)
        self.manage_updateSchema()
        return out

    # Catalog management
    security.declareProtected(permissions.View,
                              'listCatalogs')
    def listCatalogs(self):
        """Show the catalog mapping.
        """
        return self.catalog_map

    security.declareProtected(permissions.ManagePortal,
                              'manage_updateCatalogs')
    def manage_updateCatalogs(self, REQUEST=None):
        """Set the catalog map for meta_type to include the list
        catalog_names.
        """
        prefix = 'catalog_names_'
        for key in REQUEST.form.keys():
            if key.startswith(prefix):
                k = key[len(prefix):]
                v = REQUEST.form.get(key)
                self.setCatalogsByType(k, v)

        return REQUEST.RESPONSE.redirect(self.absolute_url() +
                                         '/manage_catalogs')

    security.declareProtected(permissions.ManagePortal,
                              'setCatalogsByType')
    def setCatalogsByType(self, portal_type, catalogList):
        """ associate catalogList with meta_type. (unfortunally not portal_type).
        
            catalogList is a list of strings with the ids of the catalogs.
            Each catalog is has to be a tool, means unique in site root.
        """
        self.catalog_map[portal_type] = catalogList


    security.declareProtected(permissions.View, 'getCatalogsByType')
    def getCatalogsByType(self, portal_type):
        """Return the catalog objects assoicated with a given type.
        """
        catalogs = []
        catalog_map = getattr(self, 'catalog_map', None)
        if catalog_map is not None:
            names = self.catalog_map.get(portal_type, ['portal_catalog'])
        else:
            names = ['portal_catalog']
        portal = getToolByName(self, 'portal_url').getPortalObject()
        for name in names:
            try:
                catalogs.append(getToolByName(portal, name))
            except (ConflictError, KeyboardInterrupt):
                raise
            except Exception, E:
                log('No tool', name, E)
                pass
        return catalogs
Ejemplo n.º 13
0
class TransformTool(UniqueObject, ActionProviderBase, Folder):

    id = 'portal_transforms'
    meta_type = id.title().replace('_', ' ')
    isPrincipiaFolderish = 1 # Show up in the ZMI

    __implements__ = iengine
    implements(IPortalTransformsTool)

    meta_types = all_meta_types = (
        { 'name'   : 'Transform',
          'action' : 'manage_addTransformForm'},
        { 'name'   : 'TransformsChain',
          'action' : 'manage_addTransformsChainForm'},
        )

    manage_addTransformForm = PageTemplateFile('addTransform', _www)
    manage_addTransformsChainForm = PageTemplateFile('addTransformsChain', _www)
    manage_cacheForm = PageTemplateFile('setCacheTime', _www)
    manage_editTransformationPolicyForm = PageTemplateFile('editTransformationPolicy', _www)
    manage_reloadAllTransforms = PageTemplateFile('reloadAllTransforms', _www)

    manage_options = ((Folder.manage_options[0],) + Folder.manage_options[2:] +
                      (
        { 'label'   : 'Caches',
          'action' : 'manage_cacheForm'},
        { 'label'   : 'Policy',
          'action' : 'manage_editTransformationPolicyForm'},
        { 'label'   : 'Reload transforms',
          'action' : 'manage_reloadAllTransforms'},
        )
                      )

    security = ClassSecurityInfo()

    def __init__(self, policies=None, max_sec_in_cache=3600):
        self._mtmap = PersistentMapping()
        self._policies = policies or PersistentMapping()
        self.max_sec_in_cache = max_sec_in_cache
        self._new_style_pt = 1

    # mimetype oriented conversions (iengine interface) ########################

    def unregisterTransform(self, name):
        """ unregister a transform
        name is the name of a registered transform
        """
        self._unmapTransform(getattr(self, name))
        if name in self.objectIds():
            self._delObject(name)


    def convertTo(self, target_mimetype, orig, data=None, object=None,
                  usedby=None, context=None, **kwargs):
        """Convert orig to a given mimetype

        * orig is an encoded string

        * data an optional idatastream object. If None a new datastream will be
        created and returned

        * optional object argument is the object on which is bound the data.
        If present that object will be used by the engine to bound cached data.

        * additional arguments (kwargs) will be passed to the transformations.
        Some usual arguments are : filename, mimetype, encoding

        return an object implementing idatastream or None if no path has been
        found.
        """
        target_mimetype = str(target_mimetype)

        if object is not None:
            cache = Cache(object)
            data = cache.getCache(target_mimetype)
            if data is not None:
                time, data = data
                if self.max_sec_in_cache == 0 or time < self.max_sec_in_cache:
                    return data

        if data is None:
            data = self._wrap(target_mimetype)

        registry = getToolByName(self, 'mimetypes_registry')

        if not getattr(aq_base(registry), 'classify', None):
            # avoid problems when importing a site with an old mimetype registry
            # XXX return None or orig?
            return None

        orig_mt = registry.classify(orig,
                                    mimetype=kwargs.get('mimetype'),
                                    filename=kwargs.get('filename'))
        orig_mt = str(orig_mt)
        if not orig_mt:
            log('Unable to guess input mime type (filename=%s, mimetype=%s)' %(
                kwargs.get('mimetype'), kwargs.get('filename')), severity=DEBUG)
            return None

        target_mt = registry.lookup(target_mimetype)
        if target_mt:
            target_mt = target_mt[0]
        else:
            log('Unable to match target mime type %s'% str(target_mimetype),
                severity=DEBUG)
            return None

        ## fastpath
        # If orig_mt and target_mt are the same, we only allow
        # a one-hop transform, a.k.a. filter.
        # XXX disabled filtering for now
        filter_only = False
        if orig_mt == str(target_mt):
            filter_only = True
            data.setData(orig)
            md = data.getMetadata()
            md['mimetype'] = str(orig_mt)
            if object is not None:
                cache.setCache(str(target_mimetype), data)
            return data

        ## get a path to output mime type
        requirements = self._policies.get(str(target_mt), [])
        path = self._findPath(orig_mt, target_mt, list(requirements))
        if not path and requirements:
            log('Unable to satisfy requirements %s' % ', '.join(requirements),
                severity=DEBUG)
            path = self._findPath(orig_mt, target_mt)

        if not path:
            log('NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path),
                severity=DEBUG)
            return None #XXX raise TransformError

        if len(path) > 1:
            ## create a chain on the fly (sly)
            transform = chain()
            for t in path:
                transform.registerTransform(t)
        else:
            transform = path[0]

        result = transform.convert(orig, data, context=context, usedby=usedby, **kwargs)
        assert(idatastream.isImplementedBy(result),
               'result doesn\'t implemented idatastream')
        self._setMetaData(result, transform)

        # set cache if possible
        if object is not None and result.isCacheable():
            cache.setCache(str(target_mimetype), result)

        # return idatastream object
        return result

    security.declarePublic('convertToData')
    def convertToData(self, target_mimetype, orig, data=None, object=None,
                      usedby=None, context=None, **kwargs):
        """Convert to a given mimetype and return the raw data
        ignoring subobjects. see convertTo for more information
        """
        data =self.convertTo(target_mimetype, orig, data, object, usedby,
                       context, **kwargs)
        if data:
            return data.getData()
        return None

    security.declarePublic('convert')
    def convert(self, name, orig, data=None, context=None, **kwargs):
        """run a tranform of a given name on data

        * name is the name of a registered transform

        see convertTo docstring for more info
        """
        if not data:
            data = self._wrap(name)
        try:
            transform = getattr(self, name)
        except AttributeError:
            raise Exception('No such transform "%s"' % name)
        data = transform.convert(orig, data, context=context, **kwargs)
        self._setMetaData(data, transform)
        return data


    def __call__(self, name, orig, data=None, context=None, **kwargs):
        """run a transform by its name, returning the raw data product

        * name is the name of a registered transform.

        return an encoded string.
        see convert docstring for more info on additional arguments.
        """
        data = self.convert(name, orig, data, context, **kwargs)
        return data.getData()


    # utilities ###############################################################

    def _setMetaData(self, datastream, transform):
        """set metadata on datastream according to the given transform
        (mime type and optionaly encoding)
        """
        md = datastream.getMetadata()
        if hasattr(transform, 'output_encoding'):
            md['encoding'] = transform.output_encoding
        md['mimetype'] = transform.output

    def _wrap(self, name):
        """wrap a data object in an icache"""
        return datastream(name)

    def _unwrap(self, data):
        """unwrap data from an icache"""
        if idatastream.isImplementedBy(data):
            data = data.getData()
        return data

    def _mapTransform(self, transform):
        """map transform to internal structures"""
        registry = getToolByName(self, 'mimetypes_registry')
        inputs = getattr(transform, 'inputs', None)
        if not inputs:
            raise TransformException('Bad transform %s : no input MIME type' %
                                     (transform))
        for i in inputs:
            mts = registry.lookup(i)
            if not mts:
                msg = 'Input MIME type %r for transform %s is not registered '\
                      'in the MIME types registry' % (i, transform.name())
                raise TransformException(msg)
            for mti in mts:
                for mt in mti.mimetypes:
                    mt_in = self._mtmap.setdefault(mt, PersistentMapping())
                    output = getattr(transform, 'output', None)
                    if not output:
                        msg = 'Bad transform %s : no output MIME type'
                        raise TransformException(msg % transform.name())
                    mto = registry.lookup(output)
                    if not mto:
                        msg = 'Output MIME type %r for transform %s is not '\
                              'registered in the MIME types registry' % \
                              (output, transform.name())
                        raise TransformException(msg)
                    if len(mto) > 1:
                        msg = 'Wildcarding not allowed in transform\'s output '\
                              'MIME type'
                        raise TransformException(msg)

                    for mt2 in mto[0].mimetypes:
                        try:
                            if not transform in mt_in[mt2]:
                                mt_in[mt2].append(transform)
                        except KeyError:
                            mt_in[mt2] = PersistentList([transform])

    def _unmapTransform(self, transform):
        """unmap transform from internal structures"""
        registry = getToolByName(self, 'mimetypes_registry')
        for i in transform.inputs:
            for mti in registry.lookup(i):
                for mt in mti.mimetypes:
                    mt_in = self._mtmap.get(mt, {})
                    output = transform.output
                    mto = registry.lookup(output)
                    for mt2 in mto[0].mimetypes:
                        l = mt_in[mt2]
                        for i in range(len(l)):
                            if transform.name() == l[i].name():
                                l.pop(i)
                                break
                        else:
                            log('Can\'t find transform %s from %s to %s' % (
                                transform.name(), mti, mt),
                                severity=DEBUG)

    def _findPath(self, orig, target, required_transforms=()):
        """return the shortest path for transformation from orig mimetype to
        target mimetype
        """
        path = []

        if not self._mtmap:
            return None

        # naive algorithm :
        #  find all possible paths with required transforms
        #  take the shortest
        #
        # it should be enough since we should not have so much possible paths
        shortest, winner = 9999, None
        for path in self._getPaths(str(orig), str(target), required_transforms):
            if len(path) < shortest:
                winner = path
                shortest = len(path)

        return winner

    def _getPaths(self, orig, target, requirements, path=None, result=None):
        """return a all path for transformation from orig mimetype to
        target mimetype
        """
        if path is None:
            result = []
            path = []
            requirements = list(requirements)
        outputs = self._mtmap.get(orig)
        if outputs is None:
            return result
        path.append(None)
        for o_mt, transforms in outputs.items():
            for transform in transforms:
                required = 0
                name = transform.name()
                if name in requirements:
                    requirements.remove(name)
                    required = 1
                if transform in path:
                    # avoid infinite loop...
                    continue
                path[-1] = transform
                if o_mt == target:
                    if not requirements:
                        result.append(path[:])
                else:
                    self._getPaths(o_mt, target, requirements, path, result)
                if required:
                    requirements.append(name)
        path.pop()

        return result

    security.declarePrivate('manage_afterAdd')
    def manage_afterAdd(self, item, container):
        """ overload manage_afterAdd to finish initialization when the
        transform tool is added
        """
        Folder.manage_afterAdd(self, item, container)
        transforms.initialize(self)
        # XXX required?
        #try:
        #    # first initialization
        #    transforms.initialize(self)
        #except:
        #    # may fail on copy
        #    pass

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_addTransform(self, id, module, REQUEST=None):
        """ add a new transform to the tool """
        transform = Transform(id, module)
        self._setObject(id, transform)
        self._mapTransform(transform)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_addTransformsChain(self, id, description, REQUEST=None):
        """ add a new transform to the tool """
        transform = TransformsChain(id, description)
        self._setObject(id, transform)
        self._mapTransform(transform)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_setCacheValidityTime(self, seconds, REQUEST=None):
        """set  the lifetime of cached data in seconds"""
        self.max_sec_in_cache = int(seconds)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'reloadTransforms')
    def reloadTransforms(self, ids=()):
        """ reload transforms with the given ids
        if no ids, reload all registered transforms

        return a list of (transform_id, transform_module) describing reloaded
        transforms
        """
        if not ids:
            ids = self.objectIds()
        reloaded = []
        for id in ids:
            o = getattr(self, id)
            o.reload()
            reloaded.append((id, o.module))
        return reloaded

    # Policy handling methods #################################################

    def manage_addPolicy(self, output_mimetype, required_transforms, REQUEST=None):
        """ add a policy for a given output mime types"""
        registry = getToolByName(self, 'mimetypes_registry')
        if not registry.lookup(output_mimetype):
            raise TransformException('Unknown MIME type')
        if self._policies.has_key(output_mimetype):
            msg = 'A policy for output %s is yet defined' % output_mimetype
            raise TransformException(msg)

        required_transforms = tuple(required_transforms)
        self._policies[output_mimetype] = required_transforms
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_editTransformationPolicyForm')

    def manage_delPolicies(self, outputs, REQUEST=None):
        """ remove policies for given output mime types"""
        for mimetype in outputs:
            del self._policies[mimetype]
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_editTransformationPolicyForm')

    def listPolicies(self):
        """ return the list of defined policies

        a policy is a 2-uple (output_mime_type, [list of required transforms])
        """
        # XXXFIXME: backward compat, should be removed latter
        if not hasattr(self, '_policies'):
            self._policies = PersistentMapping()
        return self._policies.items()

    # mimetype oriented conversions (iengine interface) ########################

    def registerTransform(self, transform):
        """register a new transform

        transform isn't a Zope Transform (the wrapper) but the wrapped transform
        the persistence wrapper will be created here
        """
        # needed when call from transform.transforms.initialize which
        # register non zope transform
        module = str(transform.__module__)
        transform = Transform(transform.name(), module, transform)
        if not itransform.isImplementedBy(transform):
            raise TransformException('%s does not implement itransform' % transform)
        name = transform.name()
        __traceback_info__ = (name, transform)
        if name not in self.objectIds():
            self._setObject(name, transform)
            self._mapTransform(transform)

    security.declareProtected(ManagePortal, 'ZopeFind')
    def ZopeFind(self, *args, **kwargs):
        """Don't break ZopeFind feature when a transform can't be loaded
        """
        try:
            return Folder.ZopeFind(self, *args, **kwargs)
        except MissingBinary:
            log('ZopeFind: catched MissingBinary exception')

    security.declareProtected(View, 'objectItems')
    def objectItems(self, *args, **kwargs):
        """Don't break ZopeFind feature when a transform can't be loaded
        """
        try:
            return Folder.objectItems(self, *args, **kwargs)
        except MissingBinary:
            log('objectItems: catched MissingBinary exception')
            return []

    # available mimetypes ####################################################
    def listAvailableTextInputs(self):
        """ Returns a list of mimetypes that can be used as input for textfields
            by building a list of the inputs beginning with "text/" of all transforms.
        """
        available_types = []
        candidate_transforms = [object[1] for object in self.objectItems()]
        for candidate in candidate_transforms:
            for input in candidate.inputs:
                if input.startswith("text/") and input not in available_types:
                    available_types.append(input)
        return available_types
Ejemplo n.º 14
0
class ElementSpec( SimpleItem ):
    """
        Represent all the tool knows about a single metadata element.
    """
    security = ClassSecurityInfo()

    #
    #   Default values.
    #
    is_multi_valued = 0

    def __init__( self, is_multi_valued ):
        self.is_multi_valued  = is_multi_valued
        self.policies         = PersistentMapping()
        self.policies[ None ] = self._makePolicy()  # set default policy

    security.declarePrivate( '_makePolicy' )
    def _makePolicy( self ):
        return MetadataElementPolicy( self.is_multi_valued )

    security.declareProtected(View , 'isMultiValued')
    def isMultiValued( self ):
        """
            Is this element multi-valued?
        """
        return self.is_multi_valued

    security.declareProtected(View , 'getPolicy')
    def getPolicy( self, typ=None ):
        """
            Find the policy this element for objects whose type
            object name is 'typ';  return a default, if none found.
        """
        try:
            return self.policies[ typ ].__of__(self)
        except KeyError:
            return self.policies[ None ]

    security.declareProtected(View , 'listPolicies')
    def listPolicies( self ):
        """
            Return a list of all policies for this element.
        """
        res = []
        for k, v in self.policies.items():
            res.append((k, v.__of__(self)))
        return res

    security.declareProtected(ManagePortal , 'addPolicy')
    def addPolicy( self, typ ):
        """
            Add a policy to this element for objects whose type
            object name is 'typ'.
        """
        if typ is None:
            raise MetadataError, "Can't replace default policy."

        if self.policies.has_key( typ ):
            raise MetadataError, "Existing policy for content type:" + typ

        self.policies[ typ ] = self._makePolicy()

    security.declareProtected(ManagePortal, 'removePolicy')
    def removePolicy( self, typ ):
        """
            Remove the policy from this element for objects whose type
            object name is 'typ' (*not* the default, however).
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[ typ ]
Ejemplo n.º 15
0
class ElementSpec(SimpleItem):
    """
        Represent all the tool knows about a single metadata element.
    """
    security = ClassSecurityInfo()

    #
    #   Default values.
    #
    is_multi_valued = 0

    def __init__(self, is_multi_valued):
        self.is_multi_valued = is_multi_valued
        self.policies = PersistentMapping()
        self.policies[None] = self._makePolicy()  # set default policy

    security.declarePrivate('_makePolicy')

    def _makePolicy(self):
        return MetadataElementPolicy(self.is_multi_valued)

    security.declareProtected(CMFCorePermissions.View, 'isMultiValued')

    def isMultiValued(self):
        """
            Is this element multi-valued?
        """
        return self.is_multi_valued

    security.declareProtected(CMFCorePermissions.View, 'getPolicy')

    def getPolicy(self, typ=None):
        """
            Find the policy this element for objects whose type
            object name is 'typ';  return a default, if none found.
        """
        try:
            return self.policies[typ].__of__(self)
        except KeyError:
            return self.policies[None]

    security.declareProtected(CMFCorePermissions.View, 'listPolicies')

    def listPolicies(self):
        """
            Return a list of all policies for this element.
        """
        res = []
        for k, v in self.policies.items():
            res.append((k, v.__of__(self)))
        return res

    security.declareProtected(CMFCorePermissions.ManagePortal, 'addPolicy')

    def addPolicy(self, typ):
        """
            Add a policy to this element for objects whose type
            object name is 'typ'.
        """
        if typ is None:
            raise MetadataError, "Can't replace default policy."

        if self.policies.has_key(typ):
            raise MetadataError, "Existing policy for content type:" + typ

        self.policies[typ] = self._makePolicy()

    security.declareProtected(CMFCorePermissions.ManagePortal, 'removePolicy')

    def removePolicy(self, typ):
        """
            Remove the policy from this element for objects whose type
            object name is 'typ' (*not* the default, however).
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[typ]
Ejemplo n.º 16
0
class MetadataTool( UniqueObject, SimpleItem, ActionProviderBase ):

    __implements__ = (IMetadataTool, ActionProviderBase.__implements__)

    id = 'portal_metadata'
    meta_type = 'Default Metadata Tool'
    _actions = ()

    #
    #   Default values.
    #
    publisher           = ''
    element_specs       = None
    #initial_values_hook = None
    #validation_hook     = None

    security = ClassSecurityInfo()

    def __init__( self
                , publisher=None
               #, initial_values_hook=None
               #, validation_hook=None
                , element_specs=DEFAULT_ELEMENT_SPECS
                ):

        self.editProperties( publisher
                          #, initial_values_hook
                          #, validation_hook
                           )

        self.element_specs = PersistentMapping()

        for name, is_multi_valued in element_specs:
            self.element_specs[ name ] = ElementSpec( is_multi_valued )

    #
    #   ZMI methods
    #
    manage_options = ( ActionProviderBase.manage_options +
                     ( { 'label'      : 'Overview'
                         , 'action'     : 'manage_overview'
                         }
                       , { 'label'      : 'Properties'
                         , 'action'     : 'propertiesForm'
                         }
                       , { 'label'      : 'Elements'
                         , 'action'     : 'elementPoliciesForm'
                         }
            # TODO     , { 'label'      : 'Types'
            #            , 'action'     : 'typesForm'
            #            }
                       )
                     + SimpleItem.manage_options
                     )

    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLFile( 'explainMetadataTool', _dtmldir )

    security.declareProtected(ManagePortal, 'propertiesForm')
    propertiesForm = DTMLFile( 'metadataProperties', _dtmldir )

    security.declareProtected(ManagePortal, 'editProperties')
    def editProperties( self
                      , publisher=None
               # TODO , initial_values_hook=None
               # TODO , validation_hook=None
                      , REQUEST=None
                      ):
        """
            Form handler for "tool-wide" properties (including list of
            metadata elements).
        """
        if publisher is not None:
            self.publisher = publisher

        # TODO self.initial_values_hook = initial_values_hook
        # TODO self.validation_hook = validation_hook

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
                                        + '/propertiesForm'
                                        + '?manage_tabs_message=Tool+updated.'
                                        )

    security.declareProtected(ManagePortal, 'elementPoliciesForm')
    elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir )

    security.declareProtected(ManagePortal, 'addElementPolicy')
    def addElementPolicy( self
                        , element
                        , content_type
                        , is_required
                        , supply_default
                        , default_value
                        , enforce_vocabulary
                        , allowed_vocabulary
                        , REQUEST=None
                        ):
        """
            Add a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.addPolicy( content_type )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+added.'
               )

    security.declareProtected(ManagePortal, 'removeElementPolicy')
    def removeElementPolicy( self
                           , element
                           , content_type
                           , REQUEST=None
                           ):
        """
            Remvoe a type-specific policy for one of our elements.
        """
        if content_type == '<default>':
            content_type = None

        spec = self.getElementSpec( element )
        spec.removePolicy( content_type )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+removed.'
               )

    security.declareProtected(ManagePortal, 'updateElementPolicy')
    def updateElementPolicy( self
                           , element
                           , content_type
                           , is_required
                           , supply_default
                           , default_value
                           , enforce_vocabulary
                           , allowed_vocabulary
                           , REQUEST=None
                           ):
        """
            Update a policy for one of our elements ('content_type'
            will be '<default>' when we edit the default).
        """
        if content_type == '<default>':
            content_type = None
        spec = self.getElementSpec( element )
        policy = spec.getPolicy( content_type )
        policy.edit( is_required
                   , supply_default
                   , default_value
                   , enforce_vocabulary
                   , allowed_vocabulary
                   )
        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/elementPoliciesForm'
               + '?element=' + element
               + '&manage_tabs_message=Policy+updated.'
               )


    #
    #   Element spec manipulation.
    #
    security.declareProtected(ManagePortal, 'listElementSpecs')
    def listElementSpecs( self ):
        """
            Return a list of ElementSpecs representing
            the elements managed by the tool.
        """
        res = []
        for k, v in self.element_specs.items():
            res.append((k, v.__of__(self)))
        return res

    security.declareProtected(ManagePortal, 'getElementSpec')
    def getElementSpec( self, element ):
        """
            Return an ElementSpec representing the tool's knowledge
            of 'element'.
        """
        return self.element_specs[ element ].__of__( self )

    security.declareProtected(ManagePortal, 'addElementSpec')
    def addElementSpec( self, element, is_multi_valued, REQUEST=None ):
        """
            Add 'element' to our list of managed elements.
        """
        # Don't replace.
        if self.element_specs.has_key( element ):
           return

        self.element_specs[ element ] = ElementSpec( is_multi_valued )

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/propertiesForm'
               + '?manage_tabs_message=Element+' + element + '+added.'
               )

    security.declareProtected(ManagePortal, 'removeElementSpec')
    def removeElementSpec( self, element, REQUEST=None ):
        """
            Remove 'element' from our list of managed elements.
        """
        del self.element_specs[ element ]

        if REQUEST is not None:
            REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
               + '/propertiesForm'
               + '?manage_tabs_message=Element+' + element + '+removed.'
               )

    security.declareProtected(ManagePortal, 'listPolicies')
    def listPolicies( self, typ=None ):
        """
            Show all policies for a given content type, or the default
            if None.
        """
        result = []
        for element, spec in self.listElementSpecs():
            result.append( ( element, spec.getPolicy( typ ) ) )
        return result

    #
    #   'portal_metadata' interface
    #
    security.declarePrivate( 'getFullName' )
    def getFullName( self, userid ):
        """
            Convert an internal userid to a "formal" name, if
            possible, perhaps using the 'portal_membership' tool.

            Used to map userid's for Creator, Contributor DCMI
            queries.
        """
        return userid   # TODO: do lookup here

    security.declarePublic( 'getPublisher' )
    def getPublisher( self ):
        """
            Return the "formal" name of the publisher of the
            portal.
        """
        return self.publisher

    security.declarePublic( 'listAllowedVocabulary' )
    def listAllowedVocabulary( self, element, content=None, content_type=None ):
        """
            List allowed keywords for a given portal_type, or all
            possible keywords if none supplied.
        """
        spec = self.getElementSpec( element )
        if content_type is None and content:
            content_type = content.getPortalTypeName()
        return spec.getPolicy( content_type ).allowedVocabulary()

    security.declarePublic( 'listAllowedSubjects' )
    def listAllowedSubjects( self, content=None, content_type=None ):
        """
            List allowed keywords for a given portal_type, or all
            possible keywords if none supplied.
        """
        return self.listAllowedVocabulary( 'Subject', content, content_type )

    security.declarePublic( 'listAllowedFormats' )
    def listAllowedFormats( self, content=None, content_type=None ):
        """
            List the allowed 'Content-type' values for a particular
            portal_type, or all possible formats if none supplied.
        """
        return self.listAllowedVocabulary( 'Format', content, content_type )

    security.declarePublic( 'listAllowedLanguages' )
    def listAllowedLanguages( self, content=None, content_type=None ):
        """
            List the allowed language values.
        """
        return self.listAllowedVocabulary( 'Language', content, content_type )

    security.declarePublic( 'listAllowedRights' )
    def listAllowedRights( self, content=None, content_type=None ):
        """
            List the allowed values for a "Rights"
            selection list;  this gets especially important where
            syndication is involved.
        """
        return self.listAllowedVocabulary( 'Rights', content, content_type )

    security.declareProtected(ModifyPortalContent, 'setInitialMetadata')
    def setInitialMetadata( self, content ):
        """
            Set initial values for content metatdata, supplying
            any site-specific defaults.
        """
        for element, policy in self.listPolicies(content.getPortalTypeName()):

            if not getattr( content, element )():

                if policy.supplyDefault():
                    setter = getattr( content, 'set%s' % element )
                    setter( policy.defaultValue() )
                elif policy.isRequired():
                    raise MetadataError, \
                          'Metadata element %s is required.' % element

        # TODO:  Call initial_values_hook, if present


    security.declareProtected(View, 'validateMetadata')
    def validateMetadata( self, content ):
        """
            Enforce portal-wide policies about DCI, e.g.,
            requiring non-empty title/description, etc.  Called
            by the CMF immediately before saving changes to the
            metadata of an object.
        """
        for element, policy in self.listPolicies(content.getPortalTypeName()):

            value = getattr( content, element )()
            if not value and policy.isRequired():
                raise MetadataError, \
                        'Metadata element %s is required.' % element

            if value and policy.enforceVocabulary():
                values = policy.isMultiValued() and value or [ value ]
                for value in values:
                    if not value in policy.allowedVocabulary():
                        raise MetadataError, \
                        'Value %s is not in allowed vocabulary for ' \
                        'metadata element %s.' % ( value, element )
Ejemplo n.º 17
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """
    Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'

    security = ClassSecurityInfo()


    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header


    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_encode')
    def message_encode(self, message):
        """
        Encodes a message to an ASCII string.
        To be used in the user interface, to avoid problems with the
        encodings, HTML entities, etc..
        """
        if type(message) is UnicodeType:
            msg = 'u' + message.encode('utf8')
        else:
            msg = 'n' + message

        return base64.encodestring(msg)


    security.declarePublic('message_decode')
    def message_decode(self, message):
        """
        Decodes a message from an ASCII string.
        To be used in the user interface, to avoid problems with the
        encodings, HTML entities, etc..
        """
        message = base64.decodestring(message)
        type = message[0]
        message = message[1:]
        if type == 'u':
            return unicode(message, 'utf8')
        return message


    security.declarePublic('message_exists')
    def message_exists(self, message):
        """ """
        return self._messages.has_key(message)


    security.declareProtected('Manage messages', 'message_edit')
    def message_edit(self, message, language, translation, note):
        """ """
        self._messages[message][language] = translation
        self._messages[message]['note'] = note


    security.declareProtected('Manage messages', 'message_del')
    def message_del(self, message):
        """ """
        del self._messages[message]


    security.declarePublic('gettext')
    def gettext(self, message, lang=None, add=1, default=_marker):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """

        if type(message) not in (StringType, UnicodeType):
            raise TypeError, 'only strings can be translated.'

        message = message.strip()

        if default is _marker:
            default = message

        # Add it if it's not in the dictionary
        if add and not self._messages.has_key(message) and message:
            self._messages[message] = PersistentMapping()

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default


    __call__ = gettext


    def translate(self, domain, msgid, *args, **kw):
        """
        This method is required to get the i18n namespace from ZPT working.
        """
        return self.gettext(msgid)


    #######################################################################
    # Management screens
    #######################################################################
    def manage_options(self):
        """ """
        options = (
            {'label': N_('Messages'), 'action': 'manage_messages',
             'help': ('Localizer', 'MC_messages.stx')},
            {'label': N_('Properties'), 'action': 'manage_propertiesForm'},
            {'label': N_('Import/Export'), 'action': 'manage_importExport',
             'help': ('Localizer', 'MC_importExport.stx')},
            {'label': N_('TMX'), 'action': 'manage_tmx'}) \
            + LanguageManager.manage_options \
            + SimpleItem.manage_options

        r = []
        for option in options:
            option = option.copy()
            option['label'] = _(option['label'])
            r.append(option)

        return r


    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected('Manage messages', 'manage_messages')
    manage_messages = LocalDTMLFile('ui/MC_messages', globals())


    security.declareProtected('Manage messages', 'get_translations')
    def get_translations(self, message):
        """ """
        return self._messages[message]


    security.declarePublic('get_url')
    def get_url(self, url, batch_start, batch_size, regex, lang, empty, **kw):
        """ """
        params = []
        for key, value in kw.items():
            if value is not None:
                params.append('%s=%s' % (key, quote(value)))

        params.extend(['batch_start:int=%d' % batch_start,
                       'batch_size:int=%d' % batch_size,
                       'regex=%s' % quote(regex),
                       'empty=%s' % (empty and 'on' or '')])

        if lang:
            params.append('lang=%s' % lang)

        return url + '?' + '&amp;'.join(params)

    def to_unicode(self, x):
        """
        In Zope the ISO-8859-1 encoding has an special status, normal strings
        are considered to be in this encoding by default.
        """
        if type(x) is StringType:
            x = unicode(x, 'iso-8859-1')
        return x


    def filter_sort(self, x, y):
        x = self.to_unicode(x)
        y = self.to_unicode(y)
        return cmp(x, y)


    security.declarePublic('filter')
    def filter(self, message, lang, empty, regex, batch_start, batch_size=15):
        """
        For the management interface, allows to filter the messages to show.
        """
        # Filter the messages
        regex = regex.strip()

        try:
            regex = re.compile(regex)
        except:
            regex = re.compile('')

        messages = []
        for m, t in self._messages.items():
            if regex.search(m) and (not empty or not t.get(lang, '').strip()):
                messages.append(m)
        messages.sort(self.filter_sort)

        # How many messages
        n = len(messages)

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size

        if batch_start < 0:
            batch_start = 0

        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]

        # Get the message
        message_encoded = None
        if message is None:
            if messages:
                message = messages[0]
                message_encoded = self.message_encode(message)
        else:
            message_encoded = message
            message = self.message_decode(message)

        # Calculate the current message
        aux = []
        for x in messages:
            current = type(x) is type(message) \
                      and self.to_unicode(x) == self.to_unicode(message)
            aux.append({'message': x, 'current': current})

        return {'messages': aux,
                'n_messages': n,
                'batch_start': batch_start,
                'message': message,
                'message_encoded': message_encoded}


    security.declareProtected('Manage messages', 'manage_editMessage')
    def manage_editMessage(self, message, language, translation, note,
                           REQUEST, RESPONSE):
        """Modifies a message."""
        message_encoded = message
        message = self.message_decode(message_encoded)
        self.message_edit(message, language, translation, note)

        url = self.get_url(REQUEST.URL1 + '/manage_messages',
                           REQUEST['batch_start'], REQUEST['batch_size'],
                           REQUEST['regex'], REQUEST.get('lang', ''),
                           REQUEST.get('empty', 0),
                           msg=message_encoded,
                           manage_tabs_message=_('Saved changes.'))
        RESPONSE.redirect(url)


    security.declareProtected('Manage messages', 'manage_delMessage')
    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = self.message_decode(message)
        self.message_del(message)

        url = self.get_url(REQUEST.URL1 + '/manage_messages',
                           REQUEST['batch_start'], REQUEST['batch_size'],
                           REQUEST['regex'], REQUEST.get('lang', ''),
                           REQUEST.get('empty', 0),
                           manage_tabs_message=_('Saved changes.'))
        RESPONSE.redirect(url)



    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected('View management screens',
                              'manage_propertiesForm')
    manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals())


    security.declareProtected('View management screens', 'manage_properties')
    def manage_properties(self, title, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties."""
        self.title = title

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')


    # Properties management screen
    security.declareProtected('View management screens', 'get_po_header')
    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), '_po_headers'):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)


    security.declareProtected('View management screens', 'update_po_header')
    def update_po_header(self, lang,
                         last_translator_name=None,
                         last_translator_email=None,
                         language_team=None,
                         charset=None,
                         REQUEST=None, RESPONSE=None):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header['last_translator_name']

        if last_translator_email is None:
            last_translator_email = header['last_translator_email']

        if language_team is None:
            language_team = header['language_team']

        if charset is None:
            charset = header['charset']

        header = {'last_translator_name': last_translator_name,
                  'last_translator_email': last_translator_email,
                  'language_team': language_team,
                  'charset': charset}

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')



    security.declareProtected('View management screens', 'manage_importExport')
    manage_importExport = LocalDTMLFile('ui/MC_importExport', globals())


    security.declarePublic('get_charsets')
    def get_charsets(self):
        """ """
        return charsets[:]


    security.declarePublic('manage_export')
    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """
        Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header['last_translator_name']
        last_translator_email = header['last_translator_email']
        language_team = header['language_team']
        charset = header['charset']

        # PO file header, empty message.
        po_revision_date = time.strftime('%Y-%m-%d %H:%m+%Z',
                                         time.gmtime(time.time()))
        pot_creation_date = po_revision_date
        last_translator = '%s <%s>' % (last_translator_name,
                                       last_translator_email)

        if x == 'locale.pot':
            language_team = 'LANGUAGE <*****@*****.**>'
        else:
            language_team = '%s <%s>' % (x, language_team)

        r = ['msgid ""',
             'msgstr "Project-Id-Version: %s\\n"' % self.title,
             '"POT-Creation-Date: %s\\n"' % pot_creation_date,
             '"PO-Revision-Date: %s\\n"' % po_revision_date,
             '"Last-Translator: %s\\n"' % last_translator,
             '"Language-Team: %s\\n"' % language_team,
             '"MIME-Version: 1.0\\n"',
             '"Content-Type: text/plain; charset=%s\\n"' % charset,
             '"Content-Transfer-Encoding: 8bit\\n"',
             '', '']


        # Get the messages, and perhaps its translations.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[k] = ""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                try:
                    d[k] = v[x]
                except KeyError:
                    d[k] = ""

        # Generate the file
        def backslashescape(x):
            quote_esc = re.compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append('')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type','application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'inline;filename=%s' % filename)

        r2 = []
        for x in r:
            if type(x) is UnicodeType:
                r2.append(x.encode(charset))
            else:
                r2.append(x)

        return '\n'.join(r2)


    security.declareProtected('Manage messages', 'po_import')
    def po_import(self, lang, data):
        """ """
        messages = self._messages

        resource = memory.File(data)
        po = PO.PO(resource)

        # Load the data
        for msgid in po.get_msgids():
            if msgid:
                msgstr = po.get_msgstr(msgid) or ''
                if not messages.has_key(msgid):
                    messages[msgid] = PersistentMapping()
                messages[msgid][lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=po.get_encoding())


    security.declareProtected('Manage messages', 'manage_import')
    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_messages')


    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute('objectItems')(self, spec)
        return r


    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_tmx')
    manage_tmx = LocalDTMLFile('ui/MC_tmx', globals())


    security.declareProtected('Manage messages', 'tmx_export')
    def tmx_export(self, REQUEST, RESPONSE=None):
        """
        Exports the content of the message catalog to a TMX file
        """
        orglang = self._default_language
#       orglang = orglang.lower()

        # Get the header info
        header = self.get_po_header(orglang)
        charset = header['charset']

        r = []

        # Generate the TMX file header
        r.append('<?xml version="1.0" encoding="utf-8"?>')
        r.append('<!DOCTYPE tmx SYSTEM "http://www.lisa.org/tmx/tmx14.dtd">')
        r.append('<tmx version="1.4">')
        r.append('<header')
        r.append('creationtool="Localizer"')
        r.append('creationtoolversion="1.x"')
        r.append('datatype="plaintext"')
        r.append('segtype="paragraph"')
        r.append('adminlang="%s"' % orglang)
        r.append('srclang="%s"' % orglang)
        r.append('o-encoding="%s"' % charset.lower())

        r.append('>')
        r.append('</header>')
        r.append('')

        # Get the messages, and perhaps its translations.
        d = {}
        filename = '%s.tmx' % self.id
        for msgkey, transunit in self._messages.items():
            try:
                d[msgkey] = transunit[orglang]
            except KeyError:
                d[msgkey] = ""

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        r.append('<body>')
        for msgkey in dkeys:
            r.append('<tu>')
            transunit = self._messages.get(msgkey)
            if transunit.has_key('note') and transunit['note']:
                r.append('<note>%s</note>' % escape(transunit['note']))
            r.append('<tuv xml:lang="%s">' % orglang)
            # The key is the message
            r.append('<seg>%s</seg></tuv>' % escape(msgkey))

            for tlang in self._languages:
                if tlang != orglang:
                    v = transunit.get(tlang,'')
                    v = escape(v)
                    r.append('<tuv xml:lang="%s">' % tlang)
                    r.append('<seg>%s</seg></tuv>' % v)

            r.append('</tu>')
            r.append('')

        r.append('</body>')
        r.append('</tmx>')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type','application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'attachment; filename="%s"' % filename)

        r2 = []
        for x in r:
            if type(x) is UnicodeType:
                r2.append(x.encode('utf-8'))
            else:
                r2.append(x)

        return '\r\n'.join(r2)

    def _normalize_lang(self,langcode):
        """ Get the core language (The part before the '-') and return it
            in lowercase. If there is a local part, return it in uppercase.
        """
        dash = langcode.find('-')
        if dash == -1:
            la = langcode.lower()
            return (la,la)
        else:
            la = langcode[:dash].lower()
            lo = langcode[dash+1:].upper()
            return (la+'-'+lo,la)

    def _tmx_header(self, attrs):
        """ Works on a header (<header>)
        """
        if attrs.has_key('srclang'):
            self._v_srclang = attrs['srclang']

    def _tmx_tu(self, unit):
        """ Works on a translation unit (<tu>)
        """
        src_lang = self._default_language
        # We look up the message for the language we have chosen to be our
        # working language
        if unit.has_key(self._default_language):
            src_lang = self._default_language
        else:
            # This MUST exist. Otherwise the TMX file is bad
            src_lang = self._v_srclang
        key = unit[src_lang]
        if key == u'':
            return # Don't add empty messages
        keysum = md5text(key) # For future indexing on md5 sums

        messages = self._messages
        languages = list(self._languages)
        if not messages.has_key(key):
            if self._v_howmuch == 'clear' or self._v_howmuch == 'all':
                messages[key] = PersistentMapping()
            else:
                return # Don't add unknown messages

        self._v_num_translations = self._v_num_translations + 1

        for lang in unit.keys():
            # Since the messagecatalog's default language overrides our
            # source language anyway, we handle "*all*" correctly already.
            # In the test below "*all*" should not be allowed.
            # Languages that start with '_' are other properties
            if lang == '_note':
                messages[key]['note'] = unit[lang]
                self._v_num_notes = self._v_num_notes + 1
                continue
            if lang[0] == '_':  # Unknown special property
                continue
            if lang == '*all*' or lang == '*none*':
                lang = self._v_srclang
            (target_lang, core_lang) = self._normalize_lang(lang)
            # If the core language is not seen before then add it
            if core_lang != src_lang and core_lang not in languages:
                languages.append(core_lang)
            # If the language+locality is not seen before then add it
            if target_lang != src_lang and target_lang not in languages:
                languages.append(target_lang)
            # Add message for language+locality
            if target_lang != src_lang:
                messages[key][target_lang] = unit[target_lang]
            # Add message for core language
            if not (unit.has_key(core_lang) or core_lang == src_lang):
                messages[key][core_lang] = unit[target_lang]

        self._languages = tuple(languages)
        self._messages = messages

    security.declareProtected('Manage messages', 'tmx_import')
    def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None):
        """ Imports a TMX level 1 file.
            We use the SAX parser. It has the benefit that it internally
            converts everything to python unicode strings.
        """
        if howmuch == 'clear':
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()

        self._v_howmuch = howmuch
        self._v_srclang = self._default_language
        self._v_num_translations = 0
        self._v_num_notes = 0
        # Create a parser
        parser = make_parser()
        chandler = HandleTMXParsing(self._tmx_tu, self._tmx_header)
        # Tell the parser to use our handler
        parser.setContentHandler(chandler)
        # Don't load the DTD from the Internet
        parser.setFeature(handler.feature_external_ges, 0)
        inputsrc = InputSource()

        if type(file) is StringType:
            inputsrc.setByteStream(StringIO(file))
        else:
            content = file.read()
            inputsrc.setByteStream(StringIO(content))
        parser.parse(inputsrc)

        num_translations = self._v_num_translations
        num_notes = self._v_num_notes
        del self._v_srclang
        del self._v_howmuch
        del self._v_num_translations
        del self._v_num_notes

        if REQUEST is not None:
            return MessageDialog(
                title = _('Messages imported'),
                message = _('Imported %d messages and %d notes')
                          % (num_translations, num_notes),
                action = 'manage_messages')


    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it

    security.declareProtected('Manage messages', 'xliff_export')
    def xliff_export(self, x, export_all=1, REQUEST=None, RESPONSE=None):
        """ Exports the content of the message catalog to an XLIFF file
        """
        orglang = self._default_language
        from DateTime import DateTime
        r = []
        # alias for append function. For optimization purposes
        r_append = r.append
        # Generate the XLIFF file header
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        RESPONSE.setHeader('Content-Disposition',
                           'attachment; filename="%s_%s_%s.xlf"' % (self.id,
                                                                    orglang,
                                                                    x))

        r_append('<?xml version="1.0" encoding="UTF-8"?>')
        # Version 1.1 of the DTD is not yet available - use version 1.0
        r_append('<!DOCTYPE xliff SYSTEM "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd">')
        # Force a UTF-8 char in the start
        r_append(u'<!-- XLIFF Format Copyright \xa9 OASIS Open 2001-2003 -->')
        r_append('<xliff version="1.0">')
        r_append('<file')
        r_append('original="/%s"' % self.absolute_url(1))
        r_append('product-name="Localizer"')
        r_append('product-version="1.1.x"')
        r_append('datatype="plaintext"')
        r_append('source-language="%s"' % orglang)
        r_append('target-language="%s"' % x)
        r_append('date="%s"' % DateTime().HTML4())
        r_append('>')
        r_append('<header>')
#       r_append('<phase-group>')
#       r_append('<phase ')
#       r_append('phase-name="%s"' % REQUEST.get('phase_name', ''))
#       r_append('process-name="Export"')
#       r_append('tool="Localizer"')
#       r_append('date="%s"' % DateTime().HTML4())
#       r_append('company-name="%s"' % REQUEST.get('company_name', ''))
#       r_append('job-id="%s"' % REQUEST.get('job_id', ''))
#       r_append('contact-name="%s"' % REQUEST.get('contact_name', ''))
#       r_append('contact-email="%s"' % REQUEST.get('contact_email', ''))
#       r_append('/>')
#       r_append('</phase-group>')
        r_append('</header>')
        r_append('<body>')

        # Get the messages, and perhaps its translations.
        d = {}
        for msgkey, transunit in self._messages.items():
            try:
                # if export_all=1 export all messages otherwise export
                # only untranslated messages
                if int(export_all) == 1 \
                       or (int(export_all) == 0 and transunit[x] == ''):
                    d[msgkey] = transunit[x]
            except KeyError:
                d[msgkey] = ""
            if d[msgkey] == "":
                d[msgkey] = msgkey
        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for msgkey in dkeys:
            transunit = self._messages[msgkey]
            r_append('<trans-unit id="%s">' % md5text(msgkey))
            r_append(' <source>%s</source>' % escape(msgkey))
            r_append(' <target>%s</target>' % escape(d[msgkey]))
            if transunit.has_key('note') and transunit['note']:
                r_append(' <note>%s</note>' % escape(transunit['note']))
            r_append('</trans-unit>')

        r_append('</body>')
        r_append('</file>')
        r_append('</xliff>')

        r2 = []
        for x in r:
            if type(x) is UnicodeType:
                r2.append(x.encode('utf-8'))
            else:
                r2.append(x)

        return '\r\n'.join(r2)

    security.declareProtected('Manage messages', 'xliff_import')
    def xliff_import(self, file, REQUEST=None):
        """ XLIFF is the XML Localization Interchange File Format
            designed by a group of software providers.
            It is specified by www.oasis-open.org
        """

        messages = self._messages

        # Build a table of messages hashed on the md5 sum of the message
        # This is because sometimes the xliff file has the sources translated,
        # not the targets
        md5hash = {}
        for mes in messages.keys():
            hash = md5text(mes)
            md5hash[hash] = mes

        parser = HandleXliffParsing()

        # parse the xliff information
        chandler = parser.parseXLIFFFile(file)
        if chandler is None:
            return MessageDialog(title = 'Parse error',
             message = 'Unable to parse XLIFF file' ,
             action = 'manage_main',)

        header_info = chandler.getFileTag()
        #get the target language
        lang = [x for x in header_info if x[0]=='target-language'][0][1]
        (targetlang, core_lang) = self._normalize_lang(lang)

        # return a dictionary {id: (source, target)}
        body_info = chandler.getBody()

        num_notes = 0
        num_translations = 0
        # load the data
        for msgkey, transunit in body_info.items():
            # If message is not in catalog, then it is new in xliff file
            # -- not legal
            if md5hash.has_key(msgkey):
                # Normal add
                srcmsg = md5hash[msgkey]
                if transunit['note'] != messages[srcmsg].get('note',u''):
                    messages[srcmsg]['note'] = transunit['note']
                    num_notes = num_notes + 1
                if srcmsg == transunit['target']:
                    # No translation was done
                    continue
                num_translations = num_translations + 1
                if transunit['target'] == u'' and transunit['source'] != srcmsg:
                # The source was translated. Happens sometimes
                    messages[srcmsg][targetlang] = transunit['source']
                else:
                    messages[srcmsg][targetlang] = transunit['target']

        if REQUEST is not None:
            return MessageDialog(title = _('Messages imported'),
             message = _('Imported %d messages and %d notes to %s') % \
                (num_translations, num_notes, targetlang) ,
             action = 'manage_messages',)