Ejemplo n.º 1
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(IDiscussable, ICallableOpaqueItemEvents)

    # 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).indexObject()

    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).notifyWorkflowCreated()

    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).unindexObject()

    #
    #   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()

    #
    #   IDiscussable 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)
        self._container[id] = item
        item = item.__of__(self)

        item.setFormat(text_format)
        item._edit(text)
        item.addCreator(Creator)
        item.setReplyTo(self._getDiscussable())

        item.indexObject()
        item.notifyWorkflowCreated()

        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()
                self.deleteReply(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 bool(len(self._container))
        else:
            return bool(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 IDiscussionResponse 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]
class NyMessageCatalog(Persistent):
    """Stores messages and their translations"""

    implements(INyTranslationCatalog)

    def __init__(self, id, title, languages=('en', )):

        self.id = id
        self.title = title

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

        # We suppose all zope/portal products are written in English
        # therefore we consider all new messages in English
        # Default language in Catalog is always 'en', thus it can be different
        # from the default language of the portal
        self._default_language = 'en'  # self._languages[0]

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

    ### INyTranslationCatalog

    def edit_message(self, msgid, lang, translation):
        # language existance test **not present in Localizer**:
        if lang not in self.get_languages():
            return
        # Add-by-edit functionality **not present in Localizer**:
        if not self._message_exists(msgid):
            self.gettext(msgid, lang)
        self._messages[msgid][lang] = translation

    def del_message(self, msgid):
        """ """
        if self._messages.has_key(msgid):
            del self._messages[msgid]

    def gettext(self, msgid, lang=None, default=None):
        """Returns the corresponding translation of msgid in Catalog.
        """
        msgstr = None
        if not isinstance(msgid, basestring):
            raise TypeError('Only strings can be translated.')
        # saving everything unicode, utf-8
        elif isinstance(msgid, str):
            msgstr = msgid
            msgid = force_to_unicode(msgid)
        if not lang:
            raise ValueError("No language provided for gettext")
        msgid = msgid.strip()
        # empty message is translated as empty message, regardless of lang
        if not msgid:
            return msgid
        # default `default translation` is the msgid itself
        if default is None:
            default = msgid

        if lang not in self.get_languages():
            # we don't have that lang, thus we can't translate and won't add msg
            return default

        # Add it if it's not in the dictionary
        if not self._message_exists(msgid):
            if msgstr is not None:
                import logging
                logger = logging.getLogger(__name__)
                logger.warn('Got str "%s" in gettext, expecting unicode' %
                            msgstr)
            self._messages[msgid] = PersistentMapping()
            update_transaction_note()

        if not self._messages[msgid].has_key(self._default_language):
            self._messages[msgid][self._default_language] = default

        # translation may be blank (supposition), then-> default (usually msgid)
        in_catalog = self._messages[msgid].get(lang, '')
        return in_catalog or default

    def get_languages(self):
        """Get available languages"""
        return self._languages

    def add_language(self, lang):
        """Add language"""
        if lang not in self._languages:
            self._languages = self._languages + (lang, )

    def del_language(self, lang):
        """Delete language with corresponding messages"""
        if lang not in self.get_languages():
            return
        langlist = list(self._languages)
        langlist.pop(langlist.index(lang))
        self._languages = tuple(langlist)

    def clear(self):
        """Erase all messages"""
        self._messages.clear()

    def messages(self):
        """
        Returns a generator used for catalog entries iteration.
        """
        for (msgid, translations_dict) in self._messages.items():
            yield (msgid, translations_dict)

    def _message_exists(self, message):
        return self._messages.has_key(message)

    ### Dictionary-like API

    def __getitem__(self, key):
        return self._messages[key]

    def __delitem__(self, key):
        del self._messages[key]
Ejemplo n.º 3
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 for this element for objects of the given type.

        o Return a default, if none found.
        """
        try:
            return self.policies[ typ ].__of__(self)
        except KeyError:
            return self.policies[ None ].__of__(self)

    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 of the given type.
        """
        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 of the given type.

        o Do *not* remvoe the default, however.
        """
        if typ is None:
            raise MetadataError, "Can't remove default policy."
        del self.policies[ typ ]
Ejemplo n.º 4
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.º 5
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(IDiscussable, ICallableOpaqueItemEvents)

    # 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).indexObject()

    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).notifyWorkflowCreated()

    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).unindexObject()

    #
    #   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()

    #
    #   IDiscussable 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 )
        self._container[id] = item
        item = item.__of__(self)

        item.setFormat(text_format)
        item._edit(text)
        item.addCreator(Creator)
        item.setReplyTo(self._getDiscussable())

        item.indexObject()
        item.notifyWorkflowCreated()

        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()
                self.deleteReply(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 bool( len(self._container) )
        else:
            return bool( 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 IDiscussionResponse 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.º 6
0
class WorklistDefinition(SimpleItem):
    """Worklist definiton"""

    meta_type = 'Worklist'

    security = ClassSecurityInfo()
    security.declareObjectProtected(ManagePortal)

    description = ''
    var_matches = None  # Compared with catalog when set.
    actbox_name = ''
    actbox_url = ''
    actbox_icon = ''
    actbox_category = 'global'
    guard = None

    manage_options = (
        {'label': 'Properties', 'action': 'manage_properties'},
        )

    def __init__(self, id):
        self.id = id

    def getGuard(self):
        if self.guard is not None:
            return self.guard
        else:
            return Guard().__of__(self)  # Create a temporary guard.

    def getGuardSummary(self):
        res = None
        if self.guard is not None:
            res = self.guard.getSummary()
        return res

    def getWorkflow(self):
        return aq_parent(aq_inner(aq_parent(aq_inner(self))))

    def getAvailableCatalogVars(self):
        res = []
        res.append(self.getWorkflow().state_var)
        for id, vdef in self.getWorkflow().variables.items():
            if vdef.for_catalog:
                res.append(id)
        res.sort()
        return res

    def getVarMatchKeys(self):
        if self.var_matches:
            return self.var_matches.keys()
        else:
            return []

    def getVarMatch(self, id):
        if self.var_matches:
            matches = self.var_matches.get(id, ())
            if not isinstance(matches, (tuple, Expression)):
                # Old version, convert it.
                matches = (matches,)
                self.var_matches[id] = matches
            return matches
        else:
            return ()

    def getVarMatchText(self, id):
        values = self.getVarMatch(id)
        if isinstance(values, Expression):
            return values.text
        return '; '.join(values)

    _properties_form = DTMLFile('worklist_properties', _dtmldir)

    def manage_properties(self, REQUEST, manage_tabs_message=None):
        '''
        '''
        return self._properties_form(REQUEST,
                                     management_view='Properties',
                                     manage_tabs_message=manage_tabs_message,
                                     )

    def setProperties(self, description,
                      actbox_name='', actbox_url='', actbox_category='global',
                      actbox_icon='', props=None, REQUEST=None):
        '''
        '''
        if props is None:
            props = REQUEST
        self.description = str(description)
        for key in self.getAvailableCatalogVars():
            # Populate var_matches.
            fieldname = 'var_match_%s' % key
            v = props.get(fieldname, '')
            if v:
                if not self.var_matches:
                    self.var_matches = PersistentMapping()

                if tales_re.match(v).group(1):
                    # Found a TALES prefix
                    self.var_matches[key] = Expression(v)
                else:
                    # Falling back to formatted string
                    v = [ var.strip() for var in v.split(';') ]
                    self.var_matches[key] = tuple(v)

            else:
                if self.var_matches and self.var_matches.has_key(key):
                    del self.var_matches[key]
        self.actbox_name = str(actbox_name)
        self.actbox_url = str(actbox_url)
        self.actbox_category = str(actbox_category)
        self.actbox_icon = str(actbox_icon)
        g = Guard()
        if g.changeFromProperties(props or REQUEST):
            self.guard = g
        else:
            self.guard = None
        if REQUEST is not None:
            return self.manage_properties(REQUEST, 'Properties changed.')

    def search(self, info=None, **kw):
        """ Perform the search corresponding to this worklist

        Returns sequence of ZCatalog brains
        - info is a mapping for resolving formatted string variable references
        - additional keyword/value pairs may be used to restrict the query
        """
        if not self.var_matches:
            return

        if info is None:
            info = {}

        catalog = getToolByName(self, 'portal_catalog')
        criteria = {}

        for key, values in self.var_matches.items():
            if isinstance(values, Expression):
                wf = self.getWorkflow()
                portal = wf._getPortalRoot()
                context = createExprContext(StateChangeInfo(portal, wf))
                criteria[key] = values(context)
            else:
                criteria[key] = [x % info for x in values]

        criteria.update(kw)

        return catalog.searchResults(**criteria)
Ejemplo n.º 7
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)


    security = ClassSecurityInfo()


    POLICY_ADD_FALSE = 0
    POLICY_ADD_TRUE = 1
    POLICY_ADD_LOG = 2


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

        self.title = title
        self.policy = self.POLICY_ADD_TRUE

        # 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


    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)


    def translate(self, msgid, mapping=None, context=None,
                  target_language=None, default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        # BBB support str in mapping by converting to unicode for
        # backward compatibility.
        if mapping:
            mapping = dict([to_unicode(k), to_unicode(v)]
                            for k, v in mapping.iteritems())
        return interpolate(msgstr, mapping)


    #######################################################################
    # 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]


    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')


    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_exists')
    def message_exists(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        return bool(self.get_message_key(message))


    security.declareProtected('Manage messages', 'message_edit')
    def message_edit(self, message, language, translation, note):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        self._messages[message][language] = translation
        self._messages[message]['note'] = note


    security.declareProtected('Manage messages', 'message_del')
    def message_del(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        del self._messages[message]


    security.declarePublic('gettext')
    def gettext(self, message, lang=None, add=None, 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, basestring):
            raise TypeError, 'only strings can be translated.'

        if default is None:
            default = message

        message = message.strip()

        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or to_unicode(message)

        # Add it if it's not in the dictionary
        if add is None:
            add = getattr(self, 'policy', self.POLICY_ADD_TRUE)
        if add != self.POLICY_ADD_FALSE and not self._messages.has_key(message) and message:
            if add == self.POLICY_ADD_LOG:
                LOG('New entry added to message catalog %s :' % self.id,  INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1]))))
            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


    #######################################################################
    # Management screens
    #######################################################################
    manage_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


    #######################################################################
    # 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, policy, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title
        self.policy = int(policy)

        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_policies')
    def get_policies(self):
        """ """
        if not hasattr(self, 'policy'):
            self.policy = self.POLICY_ADD_TRUE
        policies = [
            [self.POLICY_ADD_FALSE, "Never add new entries automatically"],
            [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"],
            [self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace"],
        ]
        return policies


    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.
        # Convert keys to unicode for proper sorting.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[to_unicode(k, encoding=charset)] = u""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                k = to_unicode(k, encoding=charset)
                d[k] = to_unicode(v.get(x, ""), encoding=charset)

        # Generate the file
        def backslashescape(x):
            x = to_str(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)

        return '\n'.join(r)


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

        # Load the data
        po = polib.pofile(data)
        encoding = to_str(po.encoding)
        for entry in po:
            msgid = to_unicode(entry.msgid, encoding=encoding)
            if msgid:
                msgstr = to_unicode(entry.msgstr or '', encoding=encoding)
                translation_map = messages.get(msgid)
                if translation_map is None:
                    # convert old non-unicode translations if they exist:
                    translation_map = messages.pop(self.get_message_key(msgid),
                                                   None)
                    if translation_map is None:
                        translation_map = PersistentMapping()
                    messages[msgid] = translation_map
                translation_map[lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=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())


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

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it
Ejemplo n.º 8
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)


    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


    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)


    def translate(self, msgid, mapping=None, context=None,
                  target_language=None, default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        return interpolate(msgstr, mapping)


    #######################################################################
    # 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]


    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')


    #######################################################################
    # 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()

        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:
            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


    #######################################################################
    # Management screens
    #######################################################################
    manage_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


    #######################################################################
    # 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 = itools.gettext.POFile(string=data)
        for msgid in po.get_msgids():
            # TODO Keep the context if any
            _context, msgid = msgid
            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
        """
        src_lang = self._default_language

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

        # Init the TMX handler
        tmx = TMXFile()
        tmx.header['creationtool'] = u'Localizer'
        tmx.header['creationtoolversion'] = u'1.x'
        tmx.header['datatype'] = u'plaintext'
        tmx.header['segtype'] = u'paragraph'
        tmx.header['adminlang'] = src_lang
        tmx.header['srclang'] = src_lang
        tmx.header['o-encoding'] = u'%s' % charset.lower()

        # handle messages
        for msgkey, transunit in self._messages.items():
            unit = TMXUnit({})
            for lang in transunit.keys():
                if lang != 'note':
                    sentence = Sentence({'lang': lang})
                    sentence.text = transunit[lang]
                    unit.msgstr[lang] = sentence

            if src_lang not in transunit.keys():
                sentence = Sentence({'lang': src_lang})
                sentence.text = msgkey
                unit.msgstr[src_lang] = sentence

            if transunit.has_key('note'):
                note = TMXNote(transunit.get('note'))
                unit.notes.append(note)
            tmx.messages[msgkey] = unit

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

        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 = TMXFile(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.messages.items():
            if not self._messages.has_key(id) and howmuch == 'existing':
                continue
            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:
            message = _(u'Imported %d messages and %d notes')
            return MessageDialog(
                title = _(u'Messages imported'),
                message = message % (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, dst_lang, export_all=1, REQUEST=None,
                     RESPONSE=None):
        """Exports the content of the message catalog to an XLIFF file
        """
        from DateTime import DateTime

        src_lang = self._default_language
        export_all = int(export_all)

        # Init the XLIFF handler
        xliff = XLFFile()
        # Add the translation units
        original = '/%s' % self.absolute_url(1)
        for msgkey, transunit in self._messages.items():
            target = transunit.get(dst_lang, '')
            # If 'export_all' is true export all messages, otherwise export
            # only untranslated messages
            if export_all or not target:
                unit = xliff.add_unit(original, msgkey, None)
                unit.attributes['id'] = md5text(msgkey)
                if target:
                    unit.target = target
                # Add note
                note = transunit.get('note')
                if note:
                    unit.notes.append(XLFNote(note))

        # build the data-stucture for the File tag
        file = xliff.files[original]
        attributes = file.attributes
        attributes['original'] = original
        attributes['product-name'] = u'Localizer'
        attributes['product-version'] = u'1.1.x'
        attributes['data-type'] = u'plaintext'
        attributes['source-language'] = src_lang
        attributes['target-language'] = dst_lang
        attributes['date'] = DateTime().HTML4()

        # Serialize
        xliff = xliff.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang)
        RESPONSE.setHeader('Content-Disposition',
           'attachment; filename="%s"' % filename)
        # Ok
        return xliff


    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 = XLFFile(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.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.º 9
0
class HBTreeFolder2Base (Persistent):
    """Base for BTree-based folders.
    """

    security = ClassSecurityInfo()

    manage_options=(
        ({'label':'Contents', 'action':'manage_main',},
         ) + Folder.manage_options[1:]
        )

    security.declareProtected(view_management_screens,
                              'manage_main')
    manage_main = DTMLFile('contents', globals())

    _htree = None      # OOBTree: { id -> object }
    _count = None     # A BTrees.Length
    _v_nextid = 0     # The integer component of the next generated ID
    title = ''
    _tree_list = None


    def __init__(self, id=None):
        if id is not None:
            self.id = id
        self._initBTrees()

    def _initBTrees(self):
        self._htree = OOBTree()
        self._count = Length()
        self._tree_list = PersistentMapping()

    def _populateFromFolder(self, source):
        """Fill this folder with the contents of another folder.
        """
        for name in source.objectIds():
            value = source._getOb(name, None)
            if value is not None:
                self._setOb(name, aq_base(value))


    security.declareProtected(view_management_screens, 'manage_fixCount')
    def manage_fixCount(self):
        """Calls self._fixCount() and reports the result as text.
        """
        old, new = self._fixCount()
        path = '/'.join(self.getPhysicalPath())
        if old == new:
            return "No count mismatch detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed count mismatch in HBTreeFolder2 at %s. "
                    "Count was %d; corrected to %d" % (path, old, new))


    def _fixCount(self):
        """Checks if the value of self._count disagrees with
        len(self.objectIds()). If so, corrects self._count. Returns the
        old and new count values. If old==new, no correction was
        performed.
        """
        old = self._count()
        new = len(self.objectIds())
        if old != new:
            self._count.set(new)
        return old, new


    security.declareProtected(view_management_screens, 'manage_cleanup')
    def manage_cleanup(self):
        """Calls self._cleanup() and reports the result as text.
        """
        v = self._cleanup()
        path = '/'.join(self.getPhysicalPath())
        if v:
            return "No damage detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed HBTreeFolder2 at %s.  "
                    "See the log for more details." % path)


    def _cleanup(self):
        """Cleans up errors in the BTrees.

        Certain ZODB bugs have caused BTrees to become slightly insane.
        Fortunately, there is a way to clean up damaged BTrees that
        always seems to work: make a new BTree containing the items()
        of the old one.

        Returns 1 if no damage was detected, or 0 if damage was
        detected and fixed.
        """
        def hCheck(htree):
          """
              Recursively check the btree
          """
          check(htree)
          for key in htree.keys():
              if not htree.has_key(key):
                  raise AssertionError(
                      "Missing value for key: %s" % repr(key))
              else:
                ob = htree[key]
                if isinstance(ob, OOBTree):
                  hCheck(ob)
          return 1
        
        from BTrees.check import check
        path = '/'.join(self.getPhysicalPath())
        try:
            return hCheck(self._htree)
        except AssertionError:            
            LOG('HBTreeFolder2', WARNING,
                'Detected damage to %s. Fixing now.' % path,
                error=sys.exc_info())
            try:
                self._htree = OOBTree(self._htree) # XXX hFix needed
            except:
                LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path,
                    error=sys.exc_info())
                raise
            else:
                LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path)
            return 0

    def hashId(self, id):
        """Return a tuple of ids
        """
        # XXX: why tolerate non-string ids ?
        id_list = str(id).split(H_SEPARATOR)     # We use '-' as the separator by default
        if len(id_list) > 1:
          return tuple(id_list)
        else:
          return [id,]
    
#         try:                             # We then try int hashing
#           id_int = int(id)
#         except ValueError:
#           return id_list
#         result = []
#         while id_int:
#           result.append(id_int % MAX_OBJECT_PER_LEVEL)
#           id_int = id_int / MAX_OBJECT_PER_LEVEL
#         result.reverse()
#         return tuple(result)

    def _getOb(self, id, default=_marker):
        """
            Return the named object from the folder.
        """
        htree = self._htree
        ob = htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          if default is _marker:
            ob = ob[sub_id]
          else:
            ob = ob.get(sub_id, _marker)
            if ob is _marker:
              return default
        if default is _marker:
          ob = ob[id]
        else:
          ob = ob.get(id, _marker)
          if ob is _marker:
            return default
        return ob.__of__(self)

    def _setOb(self, id, object):
        """Store the named object in the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for idx in xrange(len(id_list) - 1):
          sub_id = id_list[idx]
          if not htree.has_key(sub_id):
            # Create a new level
            htree[sub_id] = OOBTree()
            if isinstance(sub_id, (int, long)):
              tree_id = 0
              for id in id_list[:idx+1]:
                  tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL
            else:
              tree_id = H_SEPARATOR.join(id_list[:idx+1])
            # Index newly created level
            self._tree_list[tree_id] = None
            
          htree = htree[sub_id]

        if len(id_list) == 1 and not htree.has_key(None):
            self._tree_list[None] = None
        # set object in subtree            
        ob_id = id_list[-1]
        if htree.has_key(id):
            raise KeyError('There is already an item named "%s".' % id)
        htree[id] = object
        self._count.change(1)

    def _delOb(self, id):
        """Remove the named object from the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          htree = htree[sub_id]
        del htree[id]
        self._count.change(-1)

    security.declareProtected(view_management_screens, 'getBatchObjectListing')
    def getBatchObjectListing(self, REQUEST=None):
        """Return a structure for a page template to show the list of objects.
        """
        if REQUEST is None:
            REQUEST = {}
        pref_rows = int(REQUEST.get('dtpref_rows', 20))
        b_start = int(REQUEST.get('b_start', 1))
        b_count = int(REQUEST.get('b_count', 1000))
        b_end = b_start + b_count - 1
        url = self.absolute_url() + '/manage_main'
        count = self.objectCount()

        if b_end < count:
            next_url = url + '?b_start=%d' % (b_start + b_count)
        else:
            b_end = count
            next_url = ''

        if b_start > 1:
            prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
        else:
            prev_url = ''

        formatted = [listtext0 % pref_rows]
        for optID in islice(self.objectIds(), b_start - 1, b_end):
            optID = escape(optID)
            formatted.append(listtext1 % (escape(optID, quote=1), optID))
        formatted.append(listtext2)
        return {'b_start': b_start, 'b_end': b_end,
                'prev_batch_url': prev_url,
                'next_batch_url': next_url,
                'formatted_list': ''.join(formatted)}


    security.declareProtected(view_management_screens,
                              'manage_object_workspace')
    def manage_object_workspace(self, ids=(), REQUEST=None):
        '''Redirects to the workspace of the first object in
        the list.'''
        if ids and REQUEST is not None:
            REQUEST.RESPONSE.redirect(
                '%s/%s/manage_workspace' % (
                self.absolute_url(), quote(ids[0])))
        else:
            return self.manage_main(self, REQUEST)


    security.declareProtected(access_contents_information,
                              'tpValues')
    def tpValues(self):
        """Ensures the items don't show up in the left pane.
        """
        return ()


    security.declareProtected(access_contents_information,
                              'objectCount')
    def objectCount(self):
        """Returns the number of items in the folder."""
        return self._count()


    security.declareProtected(access_contents_information, 'has_key')
    def has_key(self, id):
        """Indicates whether the folder has an item by ID.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          if not isinstance(htree, OOBTree):
            return 0
          if not htree.has_key(sub_id):
            return 0
          htree = htree[sub_id]
        if not htree.has_key(id):
          return 0
        return 1

    # Work around for the performance regression introduced in Zope 2.12.23.
    # Otherwise, we use superclass' __contains__ implementation, which uses
    # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key.
    __contains__ = has_key

    def _htree_iteritems(self, min=None):
        # BUG: Due to bad design of HBTreeFolder2, buckets other than the root
        #      one must not contain both buckets & leafs. Otherwise, this method
        #      fails.
        h = self._htree
        recurse_stack = []
        try:
          for sub_id in min and self.hashId(min) or ('',):
            if recurse_stack:
              i.next()
              if type(h) is not OOBTree:
                break
              id += H_SEPARATOR + sub_id
              if type(h.itervalues().next()) is not OOBTree:
                sub_id = id
            else:
              id = sub_id
            i = h.iteritems(sub_id)
            recurse_stack.append(i)
            h = h[sub_id]
        except (KeyError, StopIteration):
          pass
        while recurse_stack:
          i = recurse_stack.pop()
          try:
            while 1:
              id, h = i.next()
              if type(h) is OOBTree:
                recurse_stack.append(i)
                i = h.iteritems()
              else:
                yield id, h
          except StopIteration:
            pass

    security.declareProtected(access_contents_information,
                              'treeIds')
    def treeIds(self, base_id=None):
        """ Return a list of subtree ids
        """
        tree = self._getTree(base_id=base_id)
        return [k for k, v in self._htree.items() if isinstance(v, OOBTree)]


    def _getTree(self, base_id):
        """ Return the tree wich has the base_id
        """
        htree = self._htree
        id_list = self.hashId(base_id)
        for sub_id in id_list:            
          if not isinstance(htree, OOBTree):
            return None
          if not htree.has_key(sub_id):
            raise IndexError, base_id
          htree = htree[sub_id]
        return htree

    def _getTreeIdList(self, htree=None):
        """ recursively build a list of btree ids
        """
        if htree is None:
          htree = self._htree
          btree_list = []
        else:
          btree_list = []
        for obj_id in htree.keys():
          obj = htree[obj_id]
          if isinstance(obj, OOBTree):
            btree_list.extend(["%s-%s"%(obj_id, x) for x in self._getTreeIdList(htree=obj)])
            btree_list.append(obj_id)

        return btree_list 

    security.declareProtected(access_contents_information,
                              'getTreeIdList')
    def getTreeIdList(self, htree=None):
        """ Return list of all tree ids
        """
        if self._tree_list is None or len(self._tree_list.keys()) == 0:
            tree_list = self._getTreeIdList(htree=htree)
            self._tree_list = PersistentMapping()
            for tree in tree_list:                
                self._tree_list[tree] = None
        return sorted(self._tree_list.keys())

    def _checkObjectId(self, ids):
        """ test id is not in btree id list
        """
        base_id, obj_id = ids
        if base_id is not None:
            obj_id = "%s%s%s" %(base_id, H_SEPARATOR, obj_id)
        return not self._tree_list.has_key(obj_id)
        
    security.declareProtected(access_contents_information,
                              'objectValues')
    def objectValues(self, base_id=_marker):
        return HBTreeObjectValues(self, base_id)

    security.declareProtected(access_contents_information,
                              'objectIds')
    def objectIds(self, base_id=_marker):
        return HBTreeObjectIds(self, base_id)

    security.declareProtected(access_contents_information,
                              'objectItems')
    def objectItems(self, base_id=_marker):
        # Returns a list of (id, subobject) tuples of the current object.
        # If 'spec' is specified, returns only objects whose meta_type match
        # 'spec'
        return HBTreeObjectItems(self, base_id)

    # superValues() looks for the _objects attribute, but the implementation
    # would be inefficient, so superValues() support is disabled.
    _objects = ()


    security.declareProtected(access_contents_information,
                              'objectIds_d')
    def objectIds_d(self, t=None):
        return dict.fromkeys(self.objectIds(t), 1)

    def _checkId(self, id, allow_dup=0):
        if not allow_dup and self.has_key(id):
            raise BadRequestException, ('The id "%s" is invalid--'
                                        'it is already in use.' % id)


    def _setObject(self, id, object, roles=None, user=None, set_owner=1):
        v=self._checkId(id)
        if v is not None: id=v

        # If an object by the given id already exists, remove it.
        if self.has_key(id):
            self._delObject(id)

        self._setOb(id, object)
        object = self._getOb(id)

        if set_owner:
            object.manage_fixupOwnershipAfterAdd()

            # Try to give user the local role "Owner", but only if
            # no local roles have been set on the object yet.
            if hasattr(object, '__ac_local_roles__'):
                if object.__ac_local_roles__ is None:
                    user=getSecurityManager().getUser()
                    if user is not None:
                        userid=user.getId()
                        if userid is not None:
                            object.manage_setLocalRoles(userid, ['Owner'])

        object.manage_afterAdd(object, self)
        return id


    def _delObject(self, id, dp=1):
        object = self._getOb(id)
        try:
            object.manage_beforeDelete(object, self)
        except BeforeDeleteException, ob:
            raise
        except ConflictError:
            raise
Ejemplo n.º 10
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)

    security = ClassSecurityInfo()

    POLICY_ADD_FALSE = 0
    POLICY_ADD_TRUE = 1
    POLICY_ADD_LOG = 2

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

        self.title = title
        self.policy = self.POLICY_ADD_TRUE

        # 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

    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)

    def translate(self,
                  msgid,
                  mapping=None,
                  context=None,
                  target_language=None,
                  default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        # BBB support str in mapping by converting to unicode for
        # backward compatibility.
        if mapping:
            mapping = dict([to_unicode(k), to_unicode(v)]
                           for k, v in mapping.iteritems())
        return interpolate(msgstr, mapping)

    #######################################################################
    # 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]

    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')

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

    def message_exists(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        return bool(self.get_message_key(message))

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

    def message_edit(self, message, language, translation, note):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        self._messages[message][language] = translation
        self._messages[message]['note'] = note

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

    def message_del(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        del self._messages[message]

    security.declarePublic('gettext')

    def gettext(self, message, lang=None, add=None, 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, basestring):
            raise TypeError('only strings can be translated, not: %r' %
                            (message, ))

        if default is None:
            default = message

        message = message.strip()

        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or to_unicode(message)

        # Add it if it's not in the dictionary
        if add is None:
            add = getattr(self, 'policy', self.POLICY_ADD_TRUE)
        if add != self.POLICY_ADD_FALSE and not self._messages.has_key(
                message) and message:
            if add == self.POLICY_ADD_LOG:
                LOG(
                    'New entry added to message catalog %s :' % self.id, INFO,
                    '%s\n%s' %
                    (message, ''.join(format_list(extract_stack()[:-1]))))
            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

    #######################################################################
    # Management screens
    #######################################################################
    manage_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

    #######################################################################
    # 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, policy, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title
        self.policy = int(policy)

        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_policies')

    def get_policies(self):
        """ """
        if not hasattr(self, 'policy'):
            self.policy = self.POLICY_ADD_TRUE
        policies = [
            [self.POLICY_ADD_FALSE, "Never add new entries automatically"],
            [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"],
            [
                self.POLICY_ADD_LOG,
                "Add new entries automatically if missing and log the backtrace"
            ],
        ]
        return policies

    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.
        # Convert keys to unicode for proper sorting.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[to_unicode(k, encoding=charset)] = u""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                k = to_unicode(k, encoding=charset)
                d[k] = to_unicode(v.get(x, ""), encoding=charset)

        # Generate the file
        def backslashescape(x):
            x = to_str(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)

        return '\n'.join(r)

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

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

        # Load the data
        po = polib.pofile(data)
        encoding = to_str(po.encoding)
        for entry in po:
            msgid = to_unicode(entry.msgid, encoding=encoding)
            if msgid:
                msgstr = to_unicode(entry.msgstr or '', encoding=encoding)
                translation_map = messages.get(msgid)
                if translation_map is None:
                    # convert old non-unicode translations if they exist:
                    translation_map = messages.pop(self.get_message_key(msgid),
                                                   None)
                    if translation_map is None:
                        translation_map = PersistentMapping()
                    messages[msgid] = translation_map
                translation_map[lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=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())

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

    security.declarePublic('hasmsg')
    hasmsg = message_exists
    security.declarePublic('hasLS')
    hasLS = message_exists  # CMFLocalizer uses it
class NyMessageCatalog(Persistent):
    """Stores messages and their translations"""

    implements(INyTranslationCatalog)


    def __init__(self, id, title, languages=('en', )):

        self.id = id
        self.title = title

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

        # We suppose all zope/portal products are written in English
        # therefore we consider all new messages in English
        # Default language in Catalog is always 'en', thus it can be different
        # from the default language of the portal
        self._default_language = 'en' # self._languages[0]

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

    ### INyTranslationCatalog

    def edit_message(self, msgid, lang, translation):
        # language existance test **not present in Localizer**:
        if lang not in self.get_languages():
            return
        # Add-by-edit functionality **not present in Localizer**:
        if not self._message_exists(msgid):
            self.gettext(msgid, lang)
        self._messages[msgid][lang] = translation

    def del_message(self, msgid):
        """ """
        if self._messages.has_key(msgid):
            del self._messages[msgid]

    def gettext(self, msgid, lang=None, default=None):
        """Returns the corresponding translation of msgid in Catalog.
        """
        msgstr = None
        if not isinstance(msgid, basestring):
            raise TypeError('Only strings can be translated.')
        # saving everything unicode, utf-8
        elif isinstance(msgid, str):
            msgstr = msgid
            msgid = force_to_unicode(msgid)
        if not lang:
            raise ValueError("No language provided for gettext")
        msgid = msgid.strip()
        # empty message is translated as empty message, regardless of lang
        if not msgid:
            return msgid
        # default `default translation` is the msgid itself
        if default is None:
            default = msgid

        if lang not in self.get_languages():
            # we don't have that lang, thus we can't translate and won't add msg
            return default

        # Add it if it's not in the dictionary
        if not self._message_exists(msgid):
            if msgstr is not None:
                import logging
                logger = logging.getLogger(__name__)
                logger.warn('Got str "%s" in gettext, expecting unicode'
                            % msgstr)
            self._messages[msgid] = PersistentMapping()
            update_transaction_note()

        if not self._messages[msgid].has_key(self._default_language):
            self._messages[msgid][self._default_language] = default

        # translation may be blank (supposition), then-> default (usually msgid)
        in_catalog = self._messages[msgid].get(lang, '')
        return in_catalog or default

    def get_languages(self):
        """Get available languages"""
        return self._languages

    def add_language(self, lang):
        """Add language"""
        if lang not in self._languages:
            self._languages = self._languages + (lang, )

    def del_language(self, lang):
        """Delete language with corresponding messages"""
        if lang not in self.get_languages():
            return
        langlist = list(self._languages)
        langlist.pop(langlist.index(lang))
        self._languages = tuple(langlist)

    def clear(self):
        """Erase all messages"""
        self._messages.clear()

    def messages(self):
        """
        Returns a generator used for catalog entries iteration.
        """
        for (msgid, translations_dict) in self._messages.items():
            yield (msgid, translations_dict)


    def _message_exists(self, message):
        return self._messages.has_key(message)

    ### Dictionary-like API

    def __getitem__(self, key):
        return self._messages[key]

    def __delitem__(self, key):
        del self._messages[key]
class DuplicatesCriteriaManager(Implicit):
    """ An object that contains :
    _criteria {bib_type : [criteria_name]}:
              contains all possible criteria (meta-data) for each bibliography type

    duplicates_criteria {bib_type : [criteria] } :
          contains criteria to be checked
          for duplication while importing for each bibliography type

    _nonmeta_criteria = ( 'reference_type', ) : contains criteria not
         comprised in meta_data

    _criteria_names = {'publication_authors' : 'authors'} : contains
         mappings for meta_data that need a special treatement like 'authors'

    _ignored_criteria = (critrias) : contains the names of  same meta_data
         like 'id' or 'allowDiscussion' to be filtered

    IMPORTANT : only 'authors','reference_type', 'publication_year',
      'title' are fully functional for now
    """

    _nonmeta_criteria = ('reference_type', )

    _default_duplicates_criteria = (
        #FIXME - we may need a default value for these
        'authors',
        'reference_type',
        'publication_year',
        'title',
    )

    _criteria_names = {'publication_authors': 'authors'}

    _ignored_criteria = (
        'allowDiscussion',
        'id',
    )

    _www = os.path.join(os.path.dirname(__file__), 'www')

    security = ClassSecurityInfo()

    manage_options = ({
        'label': 'Criteria',
        'action': 'manageImportCriteria',
    }, )

    security.declareProtected(ManagePortal, 'manageImportCriteria')
    manageImportCriteria = PageTemplateFile('manage_criteria', _www)

    def __init__(self):
        self._criteria = PersistentMapping()
        self.duplicates_criteria = PersistentMapping()
        self.criteriaUpdated = False

    def allCriteria(self, bib_type=None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, '_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join(
                self.getId())
            self._criteria = PersistentMapping()
            self.initCriteria()
            try:
                delattr(self, '_criterias')
            except:
                pass

        # this should have been performed by __init__ but after some product migrations we might want to check it here again
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        if not shasattr(self, 'duplicates_criteria'):
            self.duplicates_criteria = PersistentMapping()

        # first call? initialize self._criteria (available duplicates criteria per reference type)
        if not self._criteria:
            self.initCriteria()

        # always init criteria, during development, schema changes, etc.
        self.initCriteria()
        if bib_type:
            try:
                self._criteria[bib_type]
            except KeyError:
                return False

            return self._criteria[bib_type]
        else:
            critKeys = self._criteria.keys()
            critKeys.sort()
            return [(key, self._criteria[key]) for key in critKeys]

    # fixing a linguistic fault-pas
    allcriterias = allCriteria

    def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'):
        """ take a list of criteria tuples (as returned by allCriteria)
            and sort it according to their i18n names. used for nice display on screen
        """
        # sorting reference types by their i18n titles
        ref_types_i18n = [(self.translate(domain='plone',
                                          msgid=criterion[0],
                                          default=criterion[0]), criterion[0])
                          for criterion in criteria]
        ref_types_i18n.sort()
        ref_types = [t[1] for t in ref_types_i18n]

        # turning criteria into a dictionary
        criteria_dict = {}
        for ref_type in ref_types:
            criteria_dict[ref_type] = [
                t[1] for t in criteria if t[0] == ref_type
            ][0]

        # sorting fields for each ref_type, standard fields always preceed non-standard fields
        standard_fields = [
            'authors',
            'publication_year',
            'title',
            'reference_type',
        ]
        for ref_type in ref_types:
            nonstandard_fields_i18n = [
                (self.translate(domain=i18n_domain,
                                msgid='label_%s' % field,
                                default=field), field)
                for field in criteria_dict[ref_type]
                if field not in standard_fields
            ]
            nonstandard_fields_i18n.sort()
            nonstandard_fields = [f[1] for f in nonstandard_fields_i18n]
            criteria_dict[ref_type] = standard_fields + nonstandard_fields

        return [(t, tuple(criteria_dict[t])) for t in ref_types]

    def initCriteria(self):
        """ initialize the dictionary of import criteria
        for each bibliography type
        """
        # this is a migration 0.8 -> 0.9 fix:
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        bib_tool = getToolByName(self, 'portal_bibliography')
        has = self._criteria_names.has_key
        for bib_type in bib_tool.getBibliographyContentTypes():
            bibname = bib_type['name']
            self._criteria[bibname] = [
                criteria for criteria in self._nonmeta_criteria
            ]
            #adds all meta_data as criteria for each bibliography type
            for field in bib_type['schema'].fields():
                field_name = field.getName()
                if field_name in self._ignored_criteria:
                    continue
                if not shasattr(field, 'is_duplicates_criterion'):
                    continue
                if not field.is_duplicates_criterion:
                    continue
                if has(field_name):
                    self._criteria[bibname].append(
                        self._criteria_names[field_name])
                else:
                    self._criteria[bibname].append(field_name)
            self._criteria[bibname].sort()
            self._criteria[bibname] = tuple(self._criteria[bibname])

    # fixing a linguistic fault-pas
    initCriterias = initCriteria

    def manage_changeCriteria(self, REQUEST):
        """Changes all criteria for a bibliography type given,
           called by management screen
         """
        reference_type = REQUEST.get('bibtype')
        has = REQUEST.has_key
        if reference_type != 'all':
            reference_types = [reference_type]
        else:
            bib_tool = getToolByName(self, 'portal_bibliography')
            reference_types = bib_tool.getReferenceTypes()

        for reference_type in reference_types:
            criteria = []
            for criterion in self._criteria[reference_type]:
                # the form bibtype_criterion is not useful for now, but it was set up
                # in case we want to use one submit input for all the bibliography types
                if has("%s_%s" % (reference_type, criterion)):
                    criteria.append(criterion)
            try:
                self.setCriteriaForType(reference_type, criteria)
            except:
                return MessageDialog(
                    title='Warning!',
                    message='Your changes have not been saved',
                    action='manageImportCriteria')

        return MessageDialog(title='Success!',
                             message='Your changes have been saved',
                             action='manageImportCriteria')

    # fixing a linguistic fault-pas
    manage_changeCriterias = manage_changeCriteria

    def setCriteriaForType(self, bib_type, criteria):
        """update criteria for a bibliography type given"""
        self.duplicates_criteria[bib_type] = PersistentList(criteria)
        #FIXME may need to check if any change has been done
        self.criteriaUpdated = True
        return True

    # fixing a linguistic fault-pas
    setCriteriasForType = setCriteriaForType

    def setCriteria(self, duplicates_criteria):
        """update criteria for all bibliography types"""
        if not duplicates_criteria:
            duplicates_criteria = {}
            bib_tool = getToolByName(self, 'portal_bibliography')
            for key in bib_tool.getReferenceTypes():
                duplicates_criteria[key] = []
        self.duplicates_criteria = PersistentMapping(duplicates_criteria)
        self.criteriaUpdated = True

    # fixing a linguistic fault-pas
    setCriterias = setCriteria

    def isCriterionSelected(self, bib_type, criterion):
        return (criterion
                in self.getSelectedCriteria(bib_type)) and True or False

    def isNonMetaCriterion(self, criterion):
        return criterion in self._nonmeta_criteria

    # fixing a linguistic fault-pas
    isCriteriaSelected = isCriterionSelected

    def getSelectedCriteria(self, bib_type=None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, 'imp_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join(
                self.getId())
            self.duplicates_criteria = PersistentMapping()
            self.duplicates_criteria = copy.deepcopy(self.imp_criterias)
            self.criteriaUpdated = self.criteriasUpdated
            try:
                delattr(self, 'imp_criterias')
            except:
                pass
            try:
                delattr(self, 'criteriasUpdated')
            except:
                pass

        # first call? initialize self.duplicates_criteria
        bib_tool = getToolByName(self, 'portal_bibliography')
        if not shasattr(self,
                        'duplicates_criteria') or not self.duplicates_criteria:

            if self.getId() == bib_tool.getId():
                for reference_type in bib_tool.getReferenceTypes():
                    self.duplicates_criteria[reference_type] = PersistentList(
                        self._default_duplicates_criteria)
                self.criteriaUpdated = True
            else:
                self.duplicates_criteria = bib_tool.getSelectedCriteria()
                self.criteriaUpdated = True

        if not shasattr(self, '_criteria') or not self._criteria:
            self.initCriteria()

        # make sure, our selected criteria are in sync with available criteria
        duplicates_criteria = {}
        for key in self._criteria.keys():
            duplicates_criteria[key] = [
                criterion for criterion in self._criteria[key]
                if self.duplicates_criteria.has_key(key) and (
                    criterion in self.duplicates_criteria[key])
            ]

        if bib_type:
            try:
                duplicates_criteria[bib_type]
            except KeyError:
                return False
            return duplicates_criteria[bib_type]
        else:
            return duplicates_criteria

    # fixing a linguistic fault-pas
    getSelectedCriterias = getSelectedCriteria
Ejemplo n.º 13
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)

    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

    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)

    def translate(self,
                  msgid,
                  mapping=None,
                  context=None,
                  target_language=None,
                  default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        return interpolate(msgstr, mapping)

    #######################################################################
    # 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]

    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')

    #######################################################################
    # 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()

        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:
            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

    #######################################################################
    # Management screens
    #######################################################################
    manage_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

    #######################################################################
    # 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 = itools.gettext.POFile(string=data)
        for msgid in po.get_msgids():
            # TODO Keep the context if any
            _context, msgid = msgid
            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
        """
        src_lang = self._default_language

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

        # Init the TMX handler
        tmx = TMXFile()
        tmx.header['creationtool'] = u'Localizer'
        tmx.header['creationtoolversion'] = u'1.x'
        tmx.header['datatype'] = u'plaintext'
        tmx.header['segtype'] = u'paragraph'
        tmx.header['adminlang'] = src_lang
        tmx.header['srclang'] = src_lang
        tmx.header['o-encoding'] = u'%s' % charset.lower()

        # handle messages
        for msgkey, transunit in self._messages.items():
            unit = TMXUnit({})
            for lang in transunit.keys():
                if lang != 'note':
                    sentence = Sentence({'lang': lang})
                    sentence.text = transunit[lang]
                    unit.msgstr[lang] = sentence

            if src_lang not in transunit.keys():
                sentence = Sentence({'lang': src_lang})
                sentence.text = msgkey
                unit.msgstr[src_lang] = sentence

            if transunit.has_key('note'):
                note = TMXNote(transunit.get('note'))
                unit.notes.append(note)
            tmx.messages[msgkey] = unit

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

        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 = TMXFile(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.messages.items():
            if not self._messages.has_key(id) and howmuch == 'existing':
                continue
            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:
            message = _(u'Imported %d messages and %d notes')
            return MessageDialog(title=_(u'Messages imported'),
                                 message=message % (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,
                     dst_lang,
                     export_all=1,
                     REQUEST=None,
                     RESPONSE=None):
        """Exports the content of the message catalog to an XLIFF file
        """
        from DateTime import DateTime

        src_lang = self._default_language
        export_all = int(export_all)

        # Init the XLIFF handler
        xliff = XLFFile()
        # Add the translation units
        original = '/%s' % self.absolute_url(1)
        for msgkey, transunit in self._messages.items():
            target = transunit.get(dst_lang, '')
            # If 'export_all' is true export all messages, otherwise export
            # only untranslated messages
            if export_all or not target:
                unit = xliff.add_unit(original, msgkey, None)
                unit.attributes['id'] = md5text(msgkey)
                if target:
                    unit.target = target
                # Add note
                note = transunit.get('note')
                if note:
                    unit.notes.append(XLFNote(note))

        # build the data-stucture for the File tag
        file = xliff.files[original]
        attributes = file.attributes
        attributes['original'] = original
        attributes['product-name'] = u'Localizer'
        attributes['product-version'] = u'1.1.x'
        attributes['data-type'] = u'plaintext'
        attributes['source-language'] = src_lang
        attributes['target-language'] = dst_lang
        attributes['date'] = DateTime().HTML4()

        # Serialize
        xliff = xliff.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang)
        RESPONSE.setHeader('Content-Disposition',
                           'attachment; filename="%s"' % filename)
        # Ok
        return xliff

    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 = XLFFile(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.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')
class DuplicatesCriteriaManager(Implicit):
    """ An object that contains :
    _criteria {bib_type : [criteria_name]}:
              contains all possible criteria (meta-data) for each bibliography type

    duplicates_criteria {bib_type : [criteria] } :
          contains criteria to be checked
          for duplication while importing for each bibliography type

    _nonmeta_criteria = ( 'reference_type', ) : contains criteria not
         comprised in meta_data

    _criteria_names = {'publication_authors' : 'authors'} : contains
         mappings for meta_data that need a special treatement like 'authors'

    _ignored_criteria = (critrias) : contains the names of  same meta_data
         like 'id' or 'allowDiscussion' to be filtered

    IMPORTANT : only 'authors','reference_type', 'publication_year',
      'title' are fully functional for now
    """

    _nonmeta_criteria = ( 'reference_type', )

    _default_duplicates_criteria  = (
        #FIXME - we may need a default value for these
                          'authors',
                          'reference_type',
                          'publication_year',
                          'title',)

    _criteria_names = {'publication_authors' : 'authors'}

    _ignored_criteria = ('allowDiscussion','id', )

    _www = os.path.join(os.path.dirname(__file__), 'www')

    security = ClassSecurityInfo()

    manage_options = (
        { 'label'  : 'Criteria',
          'action' : 'manageImportCriteria',
          },
        )

    security.declareProtected(ManagePortal,
                              'manageImportCriteria')
    manageImportCriteria = PageTemplateFile('manage_criteria', _www)

    def __init__(self):
        self._criteria = PersistentMapping()
        self.duplicates_criteria = PersistentMapping()
        self.criteriaUpdated = False

    def allCriteria(self, bib_type=None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, '_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join(self.getId())
            self._criteria = PersistentMapping()
            self.initCriteria()
            try: delattr(self, '_criterias')
            except: pass

        # this should have been performed by __init__ but after some product migrations we might want to check it here again
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        if not shasattr(self, 'duplicates_criteria'):
            self.duplicates_criteria = PersistentMapping()

        # first call? initialize self._criteria (available duplicates criteria per reference type)
        if not self._criteria:
            self.initCriteria()

        # always init criteria, during development, schema changes, etc.
        self.initCriteria()
        if bib_type:
            try:
                self._criteria[bib_type]
            except KeyError:
                return False

            return self._criteria[bib_type]
        else:
            critKeys = self._criteria.keys()
            critKeys.sort()
            return [(key, self._criteria[key]) for key in critKeys]

    # fixing a linguistic fault-pas
    allcriterias = allCriteria

    def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'):
        """ take a list of criteria tuples (as returned by allCriteria)
            and sort it according to their i18n names. used for nice display on screen
        """
        # sorting reference types by their i18n titles
        ref_types_i18n = [ (self.translate(domain='plone', msgid=criterion[0], default=criterion[0]), criterion[0]) for criterion in criteria ]
        ref_types_i18n.sort()
        ref_types = [ t[1] for t in ref_types_i18n ]

        # turning criteria into a dictionary
        criteria_dict = {}
        for ref_type in ref_types:
            criteria_dict[ref_type] = [ t[1] for t in criteria if t[0] == ref_type ][0]

        # sorting fields for each ref_type, standard fields always preceed non-standard fields
        standard_fields = ['authors', 'publication_year', 'title', 'reference_type',]
        for ref_type in ref_types:
            nonstandard_fields_i18n = [ (self.translate(domain=i18n_domain, msgid='label_%s' % field, default=field), field) for field in criteria_dict[ref_type] if field not in standard_fields ]
            nonstandard_fields_i18n.sort()
            nonstandard_fields = [ f[1] for f in nonstandard_fields_i18n ]
            criteria_dict[ref_type] = standard_fields + nonstandard_fields

        return [ (t, tuple(criteria_dict[t])) for t in ref_types ]

    def initCriteria(self):
        """ initialize the dictionary of import criteria
        for each bibliography type
        """
        # this is a migration 0.8 -> 0.9 fix:
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        bib_tool = getToolByName(self, 'portal_bibliography')
        has = self._criteria_names.has_key
        for bib_type in bib_tool.getBibliographyContentTypes():
            bibname = bib_type['name']
            self._criteria[bibname] = [criteria for criteria in self._nonmeta_criteria]
            #adds all meta_data as criteria for each bibliography type
            for field in bib_type['schema'].fields():
                field_name = field.getName()
                if field_name in self._ignored_criteria:
                    continue
                if not shasattr(field, 'is_duplicates_criterion'):
                    continue
                if not field.is_duplicates_criterion:
                    continue
                if has(field_name):
                    self._criteria[bibname].append(self._criteria_names[field_name])
                else :
                    self._criteria[bibname].append(field_name)
            self._criteria[bibname].sort()
            self._criteria[bibname] = tuple(self._criteria[bibname])

    # fixing a linguistic fault-pas
    initCriterias = initCriteria

    def manage_changeCriteria(self, REQUEST):
        """Changes all criteria for a bibliography type given,
           called by management screen
         """
        reference_type = REQUEST.get('bibtype')
        has = REQUEST.has_key
        if reference_type != 'all':
            reference_types = [reference_type]
        else:
            bib_tool = getToolByName(self, 'portal_bibliography')
            reference_types = bib_tool.getReferenceTypes()

        for reference_type in reference_types:
            criteria = []
            for criterion in self._criteria[reference_type]:
                # the form bibtype_criterion is not useful for now, but it was set up
                # in case we want to use one submit input for all the bibliography types
                if has("%s_%s" % (reference_type, criterion)):
                    criteria.append(criterion)
            try: self.setCriteriaForType(reference_type, criteria)
            except:
                return MessageDialog(
                    title  ='Warning!',
                    message='Your changes have not been saved',
                    action ='manageImportCriteria')

        return MessageDialog(
                title  ='Success!',
                message='Your changes have been saved',
                action ='manageImportCriteria'
                )

    # fixing a linguistic fault-pas
    manage_changeCriterias = manage_changeCriteria

    def setCriteriaForType(self, bib_type, criteria):
        """update criteria for a bibliography type given"""
        self.duplicates_criteria[bib_type] = PersistentList(criteria)
        #FIXME may need to check if any change has been done
        self.criteriaUpdated = True
        return True

    # fixing a linguistic fault-pas
    setCriteriasForType = setCriteriaForType

    def setCriteria(self, duplicates_criteria):
        """update criteria for all bibliography types"""
        if not duplicates_criteria:
            duplicates_criteria = {}
            bib_tool = getToolByName(self, 'portal_bibliography')
            for key in bib_tool.getReferenceTypes():
                duplicates_criteria[key] = []
        self.duplicates_criteria = PersistentMapping(duplicates_criteria)
        self.criteriaUpdated = True

    # fixing a linguistic fault-pas
    setCriterias = setCriteria

    def isCriterionSelected(self, bib_type, criterion):
        return (criterion in self.getSelectedCriteria(bib_type)) and True or False

    def isNonMetaCriterion(self, criterion):
        return criterion in self._nonmeta_criteria

    # fixing a linguistic fault-pas
    isCriteriaSelected = isCriterionSelected

    def getSelectedCriteria(self, bib_type = None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, 'imp_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join(self.getId())
            self.duplicates_criteria = PersistentMapping()
            self.duplicates_criteria = copy.deepcopy(self.imp_criterias)
            self.criteriaUpdated = self.criteriasUpdated
            try: delattr(self, 'imp_criterias')
            except: pass
            try: delattr(self, 'criteriasUpdated')
            except: pass

        # first call? initialize self.duplicates_criteria
        bib_tool = getToolByName(self, 'portal_bibliography')
        if not shasattr(self, 'duplicates_criteria') or not self.duplicates_criteria:

            if self.getId() == bib_tool.getId():
                for reference_type in bib_tool.getReferenceTypes():
                    self.duplicates_criteria[reference_type] = PersistentList(self._default_duplicates_criteria)
                self.criteriaUpdated = True
            else:
                self.duplicates_criteria = bib_tool.getSelectedCriteria()
                self.criteriaUpdated = True

        if not shasattr(self, '_criteria') or not self._criteria:
            self.initCriteria()

        # make sure, our selected criteria are in sync with available criteria
        duplicates_criteria = {}
        for key in self._criteria.keys():
            duplicates_criteria[key] = [ criterion for criterion in self._criteria[key] if self.duplicates_criteria.has_key(key) and (criterion in self.duplicates_criteria[key]) ]

        if bib_type:
            try:
                duplicates_criteria[bib_type]
            except KeyError:
                return False
            return duplicates_criteria[bib_type]
        else:
            return duplicates_criteria

    # fixing a linguistic fault-pas
    getSelectedCriterias = getSelectedCriteria
Ejemplo n.º 15
0
class MimeTypesRegistry(UniqueObject, ActionProviderBase, Folder):
    """Mimetype registry that deals with
    a) registering types
    b) wildcarding of rfc-2046 types
    c) classifying data into a given type
    """

    implements(IMimetypesRegistry, ISourceAdapter)

    id        = 'mimetypes_registry'
    meta_type = 'MimeTypes Registry'
    isPrincipiaFolderish = 1 # Show up in the ZMI

    meta_types = all_meta_types = (
        { 'name'   : 'MimeType',
          'action' : 'manage_addMimeTypeForm'},
        )

    manage_options = (
        ( { 'label'   : 'MimeTypes',
            'action' : 'manage_main'},) +
        Folder.manage_options[2:]
        )

    manage_addMimeTypeForm = PageTemplateFile('addMimeType', _www)
    manage_main = PageTemplateFile('listMimeTypes', _www)
    manage_editMimeTypeForm = PageTemplateFile('editMimeType', _www)

    security = ClassSecurityInfo()

    # FIXME
    __allow_access_to_unprotected_subobjects__ = 1

    def __init__(self, id=None):
        if id is not None:
            assert id == self.id
        self.encodings_map = encodings_map.copy()
        self.suffix_map = suffix_map.copy()
        # Major key -> minor IMimetype objects
        self._mimetypes  = PersistentMapping()
        # ext -> IMimetype mapping
        self.extensions = PersistentMapping()
        # glob -> (regex, mimetype) mapping
        self.globs = OOBTree()
        self.manage_addProperty('defaultMimetype', 'text/plain', 'string')
        self.manage_addProperty('unicodePolicies', 'strict ignore replace',
                                'tokens')
        self.manage_addProperty('unicodePolicy', 'unicodePolicies', 'selection')
        self.manage_addProperty('fallbackEncoding', 'latin1', 'string')

        # initialize mime types
        initialize(self)
        self._new_style_mtr = 1

    security.declareProtected(ManagePortal, 'register')
    def register(self, mimetype):
        """ Register a new mimetype

        mimetype must implement IMimetype
        """
        mimetype = aq_base(mimetype)
        assert IMimetype.providedBy(mimetype)
        for t in mimetype.mimetypes:
            self.register_mimetype(t, mimetype)
        for extension in mimetype.extensions:
            self.register_extension(extension, mimetype)
        for glob in mimetype.globs:
            self.register_glob(glob, mimetype)

    security.declareProtected(ManagePortal, 'register_mimetype')
    def register_mimetype(self, mt, mimetype):
        major, minor = split(mt)
        if not major or not minor or minor == '*':
            raise MimeTypeException('Can\'t register mime type %s' % mt)
        group = self._mimetypes.setdefault(major, PersistentMapping())
        if group.has_key(minor):
            if group.get(minor) != mimetype:
                log('Warning: redefining mime type %s (%s)' % (
                    mt, mimetype.__class__))
        group[minor] = mimetype

    security.declareProtected(ManagePortal, 'register_extension')
    def register_extension(self, extension, mimetype):
        """ Associate a file's extension to a IMimetype

        extension is a string representing a file extension (not
        prefixed by a dot) mimetype must implement IMimetype
        """
        mimetype = aq_base(mimetype)
        if self.extensions.has_key(extension):
            if self.extensions.get(extension) != mimetype:
                log('Warning: redefining extension %s from %s to %s' % (
                    extension, self.extensions[extension], mimetype))
        # we don't validate fmt yet, but its ["txt", "html"]
        self.extensions[extension] = mimetype

    security.declareProtected(ManagePortal, 'register_glob')
    def register_glob(self, glob, mimetype):
        """ Associate a glob to a IMimetype

        glob is a shell-like glob that will be translated to a regex
        to match against whole filename.
        mimetype must implement IMimetype.
        """
        globs = getattr(self, 'globs', None)
        if globs is None:
            self.globs = globs = OOBTree()
        mimetype = aq_base(mimetype)
        existing = globs.get(glob)
        if existing is not None:
            regex, mt = existing
            if mt != mimetype:
                log('Warning: redefining glob %s from %s to %s' % (
                    glob, mt, mimetype))
        # we don't validate fmt yet, but its ["txt", "html"]
        pattern = re.compile(fnmatch.translate(glob))
        globs[glob] = (pattern, mimetype)

    security.declareProtected(ManagePortal, 'unregister')
    def unregister(self, mimetype):
        """ Unregister a new mimetype

        mimetype must implement IMimetype
        """
        assert IMimetype.providedBy(mimetype)
        for t in mimetype.mimetypes:
            major, minor = split(t)
            group = self._mimetypes.get(major, {})
            if group.get(minor) == mimetype:
                del group[minor]
        for e in mimetype.extensions:
            if self.extensions.get(e) == mimetype:
                del self.extensions[e]
        globs = getattr(self, 'globs', None)
        if globs is not None:
            for glob in mimetype.globs:
                existing = globs.get(glob)
                if existing is None:
                    continue
                regex, mt = existing
                if mt == mimetype:
                    del globs[glob]

    security.declarePublic('mimetypes')
    def mimetypes(self):
        """Return all defined mime types, each one implements at least
        IMimetype
        """
        res = {}
        for g in self._mimetypes.values():
            for mt in g.values():
                res[mt] =1
        return [aq_base(mtitem) for mtitem in res.keys()]


    security.declarePublic('list_mimetypes')
    def list_mimetypes(self):
        """Return all defined mime types, as string"""
        return [str(mt) for mt in self.mimetypes()]

    security.declarePublic('lookup')
    def lookup(self, mimetypestring):
        """Lookup for IMimetypes object matching mimetypestring

        mimetypestring may have an empty minor part or containing a
        wildcard (*) mimetypestring may and IMimetype object (in this
        case it will be returned unchanged

        Return a list of mimetypes objects associated with the
        RFC-2046 name return an empty list if no one is known.
        """
        if IMimetype.providedBy(mimetypestring):
            return (aq_base(mimetypestring), )
        __traceback_info__ = (repr(mimetypestring), str(mimetypestring))
        major, minor = split(str(mimetypestring))
        group = self._mimetypes.get(major, {})
        if not minor or minor == '*':
            res = group.values()
        else:
            res = group.get(minor)
            if res:
                res = (res,)
            else:
                return ()
        return tuple([aq_base(mtitem) for mtitem in res])

    security.declarePublic('lookupExtension')
    def lookupExtension(self, filename):
        """Lookup for IMimetypes object matching filename

        Filename maybe a file name like 'content.txt' or an extension
        like 'rest'

        Return an IMimetype object associated with the file's
        extension or None
        """
        if filename.find('.') != -1:
            base, ext = os.path.splitext(filename)
            ext = ext[1:] # remove the dot
            while self.suffix_map.has_key(ext):
                base, ext = os.path.splitext(base + self.suffix_map[ext])
                ext = ext[1:] # remove the dot
        else:
            ext = filename
            base = None

        # XXX This code below make no sense and may break because base
        # isn't defined.
        if self.encodings_map.has_key(ext) and base:
            encoding = self.encodings_map[ext]
            base, ext = os.path.splitext(base)
            ext = ext[1:] # remove the dot
        else:
            encoding = None

        result = aq_base(self.extensions.get(ext))
        if result is None:
            result = aq_base(self.extensions.get(ext.lower()))
        return result

    security.declarePublic('globFilename')
    def globFilename(self, filename):
        """Lookup for IMimetypes object matching filename

        Filename must be a complete filename with extension.

        Return an IMimetype object associated with the glob's or None
        """
        globs = getattr(self, 'globs', None)
        if globs is None:
            return None
        for key in globs.keys():
            glob, mimetype = globs[key]
            if glob.match(filename):
                return aq_base(mimetype)
        return None

    security.declarePublic('lookupGlob')
    def lookupGlob(self, glob):
        globs = getattr(self, 'globs', None)
        if globs is None:
            return None
        return aq_base(globs.get(glob))

    def _classifiers(self):
        return [mt for mt in self.mimetypes() if IClassifier.providedBy(mt)]

    security.declarePublic('classify')
    def classify(self, data, mimetype=None, filename=None):
        """Classify works as follows:
        1) you tell me the rfc-2046 name and I give you an IMimetype
           object
        2) the filename includes an extension from which we can guess
           the mimetype
        3) we can optionally introspect the data
        4) default to self.defaultMimetype if no data was provided
           else to application/octet-stream of no filename was provided,
           else to text/plain

        Return an IMimetype object or None 
        """
        mt = None
        if mimetype:
            mt = self.lookup(mimetype)
            if mt:
                mt = mt[0]
        elif filename:
            mt = self.lookupExtension(filename)
            if mt is None:
                mt = self.globFilename(filename)
        if data and not mt:
            for c in self._classifiers():
                if c.classify(data):
                    mt = c
                    break
            if not mt:
                mstr = magic.guessMime(data)
                if mstr:
                    _mt = self.lookup(mstr)
                    if len(_mt) > 0:
                        mt = _mt[0]
        if not mt:
            if not data:
                mtlist = self.lookup(self.defaultMimetype)
            elif filename:
                mtlist = self.lookup('application/octet-stream')
            else:
                failed = 'text/x-unknown-content-type'
                filename = filename or ''
                data = data or ''
                ct, enc = guess_content_type(filename, data, None)
                if ct == failed:
                    ct = 'text/plain'
                mtlist = self.lookup(ct)
            if len(mtlist)>0:
                mt = mtlist[0]
            else:
                return None

        # Remove acquisition wrappers
        return aq_base(mt)

    def __call__(self, data, **kwargs):
        """ Return a triple (data, filename, mimetypeobject) given
        some raw data and optional paramters

        method from the isourceAdapter interface
        """
        mimetype = kwargs.get('mimetype', None)
        filename = kwargs.get('filename', None)
        encoding = kwargs.get('encoding', None)
        mt = None
        if hasattr(data, 'filename'):
            filename = os.path.basename(data.filename)
        elif hasattr(data, 'name'):
            filename = os.path.basename(data.name)

        if hasattr(data, 'read'):
            _data = data.read()
            if hasattr(data, 'seek'):
                data.seek(0)
            data = _data

        # We need to figure out if data is binary and skip encoding if
        # it is
        mt = self.classify(data, mimetype=mimetype, filename=filename)

        if not mt.binary and not type(data) is UnicodeType:
            # if no encoding specified, try to guess it from data
            if encoding is None:
                encoding = self.guess_encoding(data)

            # ugly workaround for
            # https://sourceforge.net/tracker/?func=detail&aid=1068001&group_id=75272&atid=543430
            # covered by
            # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=843590&group_id=5470
            # dont remove this code unless python is fixed.
            if encoding is "macintosh":
                encoding = 'mac_roman'

            try:
                try:
                    data = unicode(data, encoding, self.unicodePolicy)
                except (ValueError, LookupError):
                    # wrong unicodePolicy
                    data = unicode(data, encoding)
            except:
                data = unicode(data, self.fallbackEncoding)

        return (data, filename, aq_base(mt))

    security.declarePublic('guess_encoding')
    def guess_encoding(self, data):
        """ Try to guess encoding from a text value if no encoding
        guessed, used the default charset from site properties (Zope)
        with a fallback to UTF-8 (should never happen with correct
        site_properties, but always raise Attribute error without
        Zope)
        """
        if type(data) is type(u''):
            # data maybe unicode but with another encoding specified
            data = data.encode('UTF-8')
        encoding = guess_encoding(data)
        if encoding is None:
            try:
                site_props = getToolByName(self, 'portal_properties').site_properties
                encoding = site_props.getProperty('default_charset', 'UTF-8')
            except:
                encoding = 'UTF-8'
        return encoding

    security.declareProtected(ManagePortal, 'manage_delObjects')
    def manage_delObjects(self, ids, REQUEST=None):
        """ delete the selected mime types """
        for id in ids:
            self.unregister(self.lookup(id)[0])
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_addMimeType')
    def manage_addMimeType(self, id, mimetypes, extensions, icon_path,
                           binary=0, globs=None, REQUEST=None):
        """add a mime type to the tool"""
        mt = MimeTypeItem(id, mimetypes, extensions=extensions,
                          binary=binary, icon_path=icon_path, globs=globs)
        self.register(mt)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_editMimeType')
    def manage_editMimeType(self, name, new_name, mimetypes, extensions,
                            icon_path, binary=0, globs=None, REQUEST=None):
        """Edit a mime type by name
        """
        mt = self.lookup(name)[0]
        self.unregister(mt)
        mt.edit(new_name, mimetypes, extensions, icon_path=icon_path,
                binary=binary, globs=globs)
        self.register(mt)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')