class UserTokens(SimpleItem): def __init__(self): self.user_tokens = PersistentMapping() def generateToken(self, userId): import random token = str(random.random()).split('.')[1] self.user_tokens[token] = userId return token def getUserId(self, userToken): if self.user_tokens.has_key(token): return self.user_tokens[token] return None def getUserToken(self, userId): for token, user_id in self.user_tokens.iteritems(): if user_id == userId: return token return None def getUserTokensList(self): tokens = [] for token, user_id in self.user_tokens.iteritems(): tokens.append({'userId' : user_id, 'userToken' : token}) return tokens def removeToken(self, userToken): """ Remove user token. """ if self.user_tokens.has_key(userToken): del self.user_tokens[userToken] else: LOG('', WARNING, 'Tried to remove unexisting token : %s' % userToken)
class Federations(SimpleItem): def __init__(self): self.federations = PersistentMapping() def getUserId(self, nameIdentifier): return self.federations.get(nameIdentifier, {}).get('user_id') def getIdentityDump(self, nameIdentifier): return self.federations.get(nameIdentifier, {}).get('identity_dump') def setFederation(self, nameIdentifier, userId, identityDump): self.federations[nameIdentifier] = {'user_id': userId, 'identity_dump': identityDump} def removeFederation(self, nameIdentifier): if self.federations.has_key(nameIdentifier): del self.federations[nameIdentifier]
class MetadataTool( UniqueObject, SimpleItem, ActionProviderBase ): __implements__ = (IMetadataTool, ActionProviderBase.__implements__) id = 'portal_metadata' meta_type = 'Default Metadata Tool' _actions = () # # Default values. # publisher = '' element_specs = None #initial_values_hook = None #validation_hook = None security = ClassSecurityInfo() def __init__( self , publisher=None #, initial_values_hook=None #, validation_hook=None , element_specs=DEFAULT_ELEMENT_SPECS ): self.editProperties( publisher #, initial_values_hook #, validation_hook ) self.element_specs = PersistentMapping() for name, is_multi_valued in element_specs: self.element_specs[ name ] = ElementSpec( is_multi_valued ) # # ZMI methods # manage_options = ( ActionProviderBase.manage_options + ( { 'label' : 'Overview' , 'action' : 'manage_overview' } , { 'label' : 'Properties' , 'action' : 'propertiesForm' } , { 'label' : 'Elements' , 'action' : 'elementPoliciesForm' } # TODO , { 'label' : 'Types' # , 'action' : 'typesForm' # } ) + SimpleItem.manage_options ) security.declareProtected(ManagePortal, 'manage_overview') manage_overview = DTMLFile( 'explainMetadataTool', _dtmldir ) security.declareProtected(ManagePortal, 'propertiesForm') propertiesForm = DTMLFile( 'metadataProperties', _dtmldir ) security.declareProtected(ManagePortal, 'editProperties') def editProperties( self , publisher=None # TODO , initial_values_hook=None # TODO , validation_hook=None , REQUEST=None ): """ Form handler for "tool-wide" properties (including list of metadata elements). """ if publisher is not None: self.publisher = publisher # TODO self.initial_values_hook = initial_values_hook # TODO self.validation_hook = validation_hook if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Tool+updated.' ) security.declareProtected(ManagePortal, 'elementPoliciesForm') elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir ) security.declareProtected(ManagePortal, 'addElementPolicy') def addElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Add a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) spec.addPolicy( content_type ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+added.' ) security.declareProtected(ManagePortal, 'removeElementPolicy') def removeElementPolicy( self , element , content_type , REQUEST=None ): """ Remvoe a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) spec.removePolicy( content_type ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+removed.' ) security.declareProtected(ManagePortal, 'updateElementPolicy') def updateElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Update a policy for one of our elements ('content_type' will be '<default>' when we edit the default). """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+updated.' ) # # Element spec manipulation. # security.declareProtected(ManagePortal, 'listElementSpecs') def listElementSpecs( self ): """ Return a list of ElementSpecs representing the elements managed by the tool. """ res = [] for k, v in self.element_specs.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal, 'getElementSpec') def getElementSpec( self, element ): """ Return an ElementSpec representing the tool's knowledge of 'element'. """ return self.element_specs[ element ].__of__( self ) security.declareProtected(ManagePortal, 'addElementSpec') def addElementSpec( self, element, is_multi_valued, REQUEST=None ): """ Add 'element' to our list of managed elements. """ # Don't replace. if self.element_specs.has_key( element ): return self.element_specs[ element ] = ElementSpec( is_multi_valued ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+added.' ) security.declareProtected(ManagePortal, 'removeElementSpec') def removeElementSpec( self, element, REQUEST=None ): """ Remove 'element' from our list of managed elements. """ del self.element_specs[ element ] if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+removed.' ) security.declareProtected(ManagePortal, 'listPolicies') def listPolicies( self, typ=None ): """ Show all policies for a given content type, or the default if None. """ result = [] for element, spec in self.listElementSpecs(): result.append( ( element, spec.getPolicy( typ ) ) ) return result # # 'portal_metadata' interface # security.declarePrivate( 'getFullName' ) def getFullName( self, userid ): """ Convert an internal userid to a "formal" name, if possible, perhaps using the 'portal_membership' tool. Used to map userid's for Creator, Contributor DCMI queries. """ return userid # TODO: do lookup here security.declarePublic( 'getPublisher' ) def getPublisher( self ): """ Return the "formal" name of the publisher of the portal. """ return self.publisher security.declarePublic( 'listAllowedVocabulary' ) def listAllowedVocabulary( self, element, content=None, content_type=None ): """ List allowed keywords for a given portal_type, or all possible keywords if none supplied. """ spec = self.getElementSpec( element ) if content_type is None and content: content_type = content.getPortalTypeName() return spec.getPolicy( content_type ).allowedVocabulary() security.declarePublic( 'listAllowedSubjects' ) def listAllowedSubjects( self, content=None, content_type=None ): """ List allowed keywords for a given portal_type, or all possible keywords if none supplied. """ return self.listAllowedVocabulary( 'Subject', content, content_type ) security.declarePublic( 'listAllowedFormats' ) def listAllowedFormats( self, content=None, content_type=None ): """ List the allowed 'Content-type' values for a particular portal_type, or all possible formats if none supplied. """ return self.listAllowedVocabulary( 'Format', content, content_type ) security.declarePublic( 'listAllowedLanguages' ) def listAllowedLanguages( self, content=None, content_type=None ): """ List the allowed language values. """ return self.listAllowedVocabulary( 'Language', content, content_type ) security.declarePublic( 'listAllowedRights' ) def listAllowedRights( self, content=None, content_type=None ): """ List the allowed values for a "Rights" selection list; this gets especially important where syndication is involved. """ return self.listAllowedVocabulary( 'Rights', content, content_type ) security.declareProtected(ModifyPortalContent, 'setInitialMetadata') def setInitialMetadata( self, content ): """ Set initial values for content metatdata, supplying any site-specific defaults. """ for element, policy in self.listPolicies(content.getPortalTypeName()): if not getattr( content, element )(): if policy.supplyDefault(): setter = getattr( content, 'set%s' % element ) setter( policy.defaultValue() ) elif policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element # TODO: Call initial_values_hook, if present security.declareProtected(View, 'validateMetadata') def validateMetadata( self, content ): """ Enforce portal-wide policies about DCI, e.g., requiring non-empty title/description, etc. Called by the CMF immediately before saving changes to the metadata of an object. """ for element, policy in self.listPolicies(content.getPortalTypeName()): value = getattr( content, element )() if not value and policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element if value and policy.enforceVocabulary(): values = policy.isMultiValued() and value or [ value ] for value in values: if not value in policy.allowedVocabulary(): raise MetadataError, \ 'Value %s is not in allowed vocabulary for ' \ 'metadata element %s.' % ( value, element )
class ElementSpec( SimpleItem ): """ Represent all the tool knows about a single metadata element. """ security = ClassSecurityInfo() # # Default values. # is_multi_valued = 0 def __init__( self, is_multi_valued ): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[ None ] = self._makePolicy() # set default policy security.declarePrivate( '_makePolicy' ) def _makePolicy( self ): return MetadataElementPolicy( self.is_multi_valued ) security.declareProtected(View , 'isMultiValued') def isMultiValued( self ): """ Is this element multi-valued? """ return self.is_multi_valued security.declareProtected(View , 'getPolicy') def getPolicy( self, typ=None ): """ Find the policy this element for objects whose type object name is 'typ'; return a default, if none found. """ try: return self.policies[ typ ].__of__(self) except KeyError: return self.policies[ None ] security.declareProtected(View , 'listPolicies') def listPolicies( self ): """ Return a list of all policies for this element. """ res = [] for k, v in self.policies.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal , 'addPolicy') def addPolicy( self, typ ): """ Add a policy to this element for objects whose type object name is 'typ'. """ if typ is None: raise MetadataError, "Can't replace default policy." if self.policies.has_key( typ ): raise MetadataError, "Existing policy for content type:" + typ self.policies[ typ ] = self._makePolicy() security.declareProtected(ManagePortal, 'removePolicy') def removePolicy( self, typ ): """ Remove the policy from this element for objects whose type object name is 'typ' (*not* the default, however). """ if typ is None: raise MetadataError, "Can't remove default policy." del self.policies[ typ ]
class DiscussionItemContainer( Persistent, Implicit, Traversable ): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ __implements__ = Discussable # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId( self ): return self.id security.declareProtected(View, 'getReply') def getReply( self, reply_id ): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get( reply_id ).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent( aq_inner( self ) ) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).manage_afterAdd(item, container) security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).manage_afterClone(item) security.declarePrivate( 'manage_beforeDelete' ) def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__( self ).manage_beforeDelete( item, container ) # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds( self, spec=None ): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r=[] a=r.append g=self._container.get for id in self.objectIds(spec): a( (id, g( id ) ) ) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # Discussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply( self, title, text, Creator=None, text_format='structured-text' ): """ Create a reply in the proper place """ container = self._container id = int(DateTime().timeTime()) while self._container.get( str(id), None ) is not None: id = id + 1 id = str( id ) item = DiscussionItem( id, title=title, description=title ) item._edit( text_format=text_format, text=text ) item.__of__(self).addCreator(Creator) item.__of__(self).indexObject() item.setReplyTo( self._getDiscussable() ) item.__of__(self).notifyWorkflowCreated() self._container[ id ] = item return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply( self, reply_id ): """ Remove a reply from this container """ if self._container.has_key( reply_id ): reply = self._container.get( reply_id ).__of__( self ) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() if hasattr( my_reply, 'unindexObject' ): my_reply.unindexObject() del self._container[my_reply_id] if hasattr( reply, 'unindexObject' ): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies( self, content_obj ): """ Test to see if there are any dicussion items """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return not not len( self._container ) else: return not not len( content_obj.talkback._getReplyResults() ) security.declareProtected(View, 'replyCount') def replyCount( self, content_obj ): """ How many replies do i have? """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return len( self._container ) else: replies = content_obj.talkback.getReplies() return self._repcount( replies ) security.declarePrivate('_repcount') def _repcount( self, replies ): """ counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount( replies ) return count security.declareProtected(View, 'getReplies') def getReplies( self ): """ Return a sequence of the DiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a( self._container.get( id ).__of__( self ) ) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate( '_getReplyParent' ) def _getReplyParent( self, in_reply_to ): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable( outer=1 ) if in_reply_to is None: return outer parent = self._container[ in_reply_to ].__of__( aq_inner( self ) ) return parent.__of__( outer ) security.declarePrivate( '_getDiscussable' ) def _getDiscussable( self, outer=0 ): """ """ tb = outer and aq_inner( self ) or self return getattr( tb, 'aq_parent', None ) security.declarePrivate( '_getReplyResults' ) def _getReplyResults( self ): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable( outer=1 ) if discussable == outer: in_reply_to = None else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a( ( key, value ) ) result.sort( lambda a, b: cmp(a[1].creation_date, b[1].creation_date) ) return [ x[0] for x in result ]
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
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_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): # Old version, convert it. matches = (matches,) self.var_matches[id] = matches return matches else: return () def getVarMatchText(self, id): values = self.getVarMatch(id) 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', 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() 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) 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.')
class ElementSpec( Persistent ): """ Represent all the tool knows about a single metadata element. """ # # Default values. # is_multi_valued = 0 def __init__( self, is_multi_valued ): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[ None ] = self._makePolicy() # set default policy def _makePolicy( self ): return MetadataElementPolicy( self.is_multi_valued ) def isMultiValued( self ): """ Is this element multi-valued? """ return self.is_multi_valued def getPolicy( self, typ=None ): """ Find the policy this element for objects whose type object name is 'typ'; return a default, if none found. """ try: return self.policies[ typ ] except KeyError: return self.policies[ None ] def listPolicies( self ): """ Return a list of all policies for this element. """ return self.policies.items() def addPolicy( self, typ ): """ Add a policy to this element for objects whose type object name is 'typ'. """ if typ is None: raise MetadataError, "Can't replace default policy." if self.policies.has_key( typ ): raise MetadataError, "Existing policy for content type:" + typ self.policies[ typ ] = self._makePolicy() def removePolicy( self, typ ): """ Remove the policy from this element for objects whose type object name is 'typ' (*not* the default, however). """ if typ is None: raise MetadataError, "Can't remove default policy." del self.policies[ typ ]
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = "MessageCatalog" security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] ####################################################################### # Public API ####################################################################### security.declarePublic("message_exists") def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected("Manage messages", "message_edit") def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]["note"] = note security.declareProtected("Manage messages", "message_del") def message_del(self, message): """ """ del self._messages[message] security.declarePublic("gettext") def gettext(self, message, lang=None, add=1, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, (str, unicode)): raise TypeError, "only strings can be translated." message = message.strip() # assume empty message is always translated as empty message if not message: return message if default is None: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: update_transaction_note() self._messages[message] = PersistentMapping() if message and not self._messages[message].has_key("en"): self._messages[message]["en"] = default # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext def translate(self, domain, msgid, *args, **kw): """This method is required to get the i18n namespace from ZPT working. """ default = kw.get("default") if default is not None and not default.strip(): default = None msgstr = self.gettext(msgid, default=default) mapping = kw.get("mapping") return interpolate(msgstr, mapping) ####################################################################### # Management screens ####################################################################### def manage_options(self): """ """ options = ( ( {"label": u"Messages", "action": "manage_messages", "help": ("Localizer", "MC_messages.stx")}, {"label": u"Properties", "action": "manage_propertiesForm"}, {"label": u"Import", "action": "manage_Import_form", "help": ("Localizer", "MC_importExport.stx")}, {"label": u"Export", "action": "manage_Export_form", "help": ("Localizer", "MC_importExport.stx")}, ) + LanguageManager.manage_options + SimpleItem.manage_options ) r = [] for option in options: option = option.copy() option["label"] = _(option["label"]) r.append(option) return r ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected("Manage messages", "manage_messages") manage_messages = LocalDTMLFile("ui/MC_messages", globals()) security.declarePublic("get_namespace") def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get("batch_start", 0) batch_size = REQUEST.get("batch_size", 15) empty = REQUEST.get("empty", 0) regex = REQUEST.get("regex", "") message = REQUEST.get("msg", None) # Build the namespace namespace = {} namespace["batch_size"] = batch_size namespace["empty"] = empty namespace["regex"] = regex # The language lang = REQUEST.get("lang", None) or languages[0]["code"] namespace["language"] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile("") messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, "").strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace["n_messages"] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace["batch_start"] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace["previous"] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace["next"] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace["message"] = message namespace["message_encoded"] = message_encoded namespace["translations"] = translations namespace["translation"] = translations.get(lang, "") namespace["note"] = translations.get("note", "") # Calculate the current message namespace["messages"] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url(REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace["messages"].append( {"message": x, "message_encoded": x_encoded, "current": x == message, "url": url} ) # The languages for language in languages: code = language["code"] language["name"] = _(language["name"], language=code) language["url"] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace["languages"] = languages return namespace security.declareProtected("Manage messages", "manage_editMessage") def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url( REQUEST.URL1 + "/manage_messages", REQUEST["batch_start"], REQUEST["batch_size"], REQUEST["regex"], REQUEST.get("lang", ""), REQUEST.get("empty", 0), msg=message_encoded, manage_tabs_message=_(u"Saved changes."), ) RESPONSE.redirect(url) security.declareProtected("Manage messages", "manage_delMessage") def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url( REQUEST.URL1 + "/manage_messages", REQUEST["batch_start"], REQUEST["batch_size"], REQUEST["regex"], REQUEST.get("lang", ""), REQUEST.get("empty", 0), manage_tabs_message=_(u"Saved changes."), ) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected("View management screens", "manage_propertiesForm") manage_propertiesForm = LocalDTMLFile("ui/MC_properties", globals()) security.declareProtected("View management screens", "manage_properties") def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title if RESPONSE is not None: RESPONSE.redirect("manage_propertiesForm") # Properties management screen security.declareProtected("View management screens", "get_po_header") def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), "_po_headers"): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected("View management screens", "update_po_header") def update_po_header( self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None, ): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header["last_translator_name"] if last_translator_email is None: last_translator_email = header["last_translator_email"] if language_team is None: language_team = header["language_team"] if charset is None: charset = header["charset"] header = { "last_translator_name": last_translator_name, "last_translator_email": last_translator_email, "language_team": language_team, "charset": charset, } self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect("manage_propertiesForm") security.declareProtected("View management screens", "manage_Import_form") manage_Import_form = LocalDTMLFile("ui/MC_Import_form", globals()) security.declarePublic("get_charsets") def get_charsets(self): """ """ return charsets[:] security.declarePublic("manage_export") def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header["last_translator_name"] last_translator_email = header["last_translator_email"] language_team = header["language_team"] charset = header["charset"] # PO file header, empty message. po_revision_date = strftime("%Y-%m-%d %H:%m+%Z", gmtime(time())) pot_creation_date = po_revision_date last_translator = "%s <%s>" % (last_translator_name, last_translator_email) if x == "locale.pot": language_team = "LANGUAGE <*****@*****.**>" else: language_team = "%s <%s>" % (x, language_team) r = [ 'msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', "", "", ] # Get the messages, and perhaps its translations. d = {} if x == "locale.pot": filename = x for k in self._messages.keys(): d[k] = "" else: filename = "%s.po" % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t")] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append("") if RESPONSE is not None: RESPONSE.setHeader("Content-type", "application/data") RESPONSE.setHeader("Content-Disposition", "inline;filename=%s" % filename) r2 = [] for x in r: if isinstance(x, unicode): r2.append(x.encode(charset)) else: r2.append(x) return "\n".join(r2) security.declareProtected("Manage messages", "po_import") def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = POFile(string=data) for msgid in po.get_msgids(): if msgid: msgstr = po.get_msgstr(msgid) or "" if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected("Manage messages", "manage_import") def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect("manage_messages") def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute("objectItems")(self, spec) return r ####################################################################### # TMX support security.declareProtected("View management screens", "manage_Export_form") manage_Export_form = LocalDTMLFile("ui/MC_Export_form", globals()) security.declareProtected("Manage messages", "tmx_export") def tmx_export(self, REQUEST, RESPONSE=None): """Exports the content of the message catalog to a TMX file """ orglang = self._default_language # Get the header info header = self.get_po_header(orglang) charset = header["charset"] # build data structure for the xml header xml_header = {} xml_header["standalone"] = -1 xml_header["xml_version"] = u"1.0" xml_header["document_type"] = (u"tmx", u"http://www.lisa.org/tmx/tmx14.dtd") # build data structure for the tmx header version = u"1.4" tmx_header = {} tmx_header["creationtool"] = u"Localizer" tmx_header["creationtoolversion"] = u"1.x" tmx_header["datatype"] = u"plaintext" tmx_header["segtype"] = u"paragraph" tmx_header["adminlang"] = u"%s" % orglang tmx_header["srclang"] = u"%s" % orglang tmx_header["o-encoding"] = u"%s" % charset.lower() # handle messages d = {} filename = "%s.tmx" % self.id for msgkey, transunit in self._messages.items(): sentences = {} for lang in transunit.keys(): if lang != "note": s = Sentence(transunit[lang], {"lang": "%s" % lang}) sentences[lang] = s if orglang not in transunit.keys(): s = Sentence(msgkey, {"lang": "%s" % orglang}) sentences[orglang] = s if transunit.has_key("note"): d[msgkey] = Message(sentences, {}, [Note(transunit.get("note"))]) else: d[msgkey] = Message(sentences) tmx = TMX() tmx.build(xml_header, version, tmx_header, d) if RESPONSE is not None: RESPONSE.setHeader("Content-type", "application/data") RESPONSE.setHeader("Content-Disposition", 'attachment; filename="%s"' % filename) return tmx.to_str() security.declareProtected("Manage messages", "tmx_import") def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """Imports a TMX level 1 file. """ try: data = file.read() tmx = TMX(string=data) except: return MessageDialog( title="Parse error", message=_("impossible to parse the file"), action="manage_Import_form" ) num_notes = 0 num_trans = 0 if howmuch == "clear": # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = tmx.get_srclang() for (id, msg) in tmx.state.messages.items(): if not self._messages.has_key(id) and howmuch == "existing": pass else: msg.msgstr.pop(self._default_language) if not self._messages.has_key(id): self._messages[id] = {} for lang in msg.msgstr.keys(): # normalize the languageTag and extract the core (core, local) = LanguageTag.decode(lang) lang = LanguageTag.encode((core, local)) if lang not in self._languages: self._languages += (lang,) if msg.msgstr[lang].text: self._messages[id][lang] = msg.msgstr[lang].text if core != lang and core != self._default_language: if core not in self._languages: self._languages += (core,) if not msg.msgstr.has_key(core): self._messages[id][core] = msg.msgstr[lang].text if msg.notes: ns = [m.text for m in msg.notes] self._messages[id]["note"] = u" ".join(ns) num_notes += 1 num_trans += 1 if REQUEST is not None: return MessageDialog( title=_(u"Messages imported"), message=_(u"Imported %d messages and %d notes") % (num_trans, num_notes), action="manage_messages", ) ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected("Manage messages", "xliff_export") def xliff_export(self, x, export_all=1, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog to an XLIFF file """ orglang = self._default_language export_all = int(export_all) from DateTime import DateTime # Generate the XLIFF file header RESPONSE.setHeader("Content-Type", "text/xml; charset=UTF-8") RESPONSE.setHeader("Content-Disposition", 'attachment; filename="%s_%s_%s.xlf"' % (self.id, orglang, x)) # build data structure for the xml header xml_header = {} xml_header["standalone"] = -1 xml_header["xml_version"] = u"1.0" xml_header["document_type"] = (u"xliff", u"http://www.oasis-open.org/committees/xliff/documents/xliff.dtd") version = u"1.0" # build the data-stucture for the File tag attributes = {} attributes["original"] = u"/%s" % self.absolute_url(1) attributes["product-name"] = u"Localizer" attributes["product-version"] = u"1.1.x" attributes["data-type"] = u"plaintext" attributes["source-language"] = orglang attributes["target-language"] = x attributes["date"] = DateTime().HTML4() # Get the messages, and perhaps its translations. d = {} for msgkey, transunit in self._messages.items(): target = transunit.get(x, "") # if export_all=1 export all messages otherwise export # only untranslated messages if export_all or not target: id = md5text(msgkey) notes = [] if transunit.has_key("note") and transunit["note"]: notes = [xliff_Note(transunit["note"])] if target: t = Translation(msgkey, target, {"id": id}, notes) else: t = Translation(msgkey, msgkey, {"id": id}, notes) d[msgkey] = t files = [xliff_File(d, attributes)] xliff = XLIFF() xliff.build(xml_header, version, files) return xliff.to_str() security.declareProtected("Manage messages", "xliff_import") def xliff_import(self, howmuch, file, REQUEST=None): """XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ try: data = file.read() xliff = XLIFF(string=data) except: return MessageDialog( title="Parse error", message=_("impossible to parse the file"), action="manage_Import_form" ) num_notes = 0 num_trans = 0 (file_ids, sources, targets) = xliff.get_languages() if howmuch == "clear": # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = sources[0] # update languages if len(sources) > 1 or sources[0] != self._default_language: return MessageDialog( title="Language error", message=_("incompatible language sources"), action="manage_Import_form" ) for lang in targets: if lang != self._default_language and lang not in self._languages: self._languages += (lang,) # get messages for file in xliff.state.files: cur_target = file.attributes.get("target-language", "") for msg in file.body.keys(): if not self._messages.has_key(msg) and howmuch == "existing": pass else: if not self._messages.has_key(msg): self._messages[msg] = {} if cur_target and file.body[msg].target: self._messages[msg][cur_target] = file.body[msg].target num_trans += 1 if file.body[msg].notes: ns = [n.text for n in file.body[msg].notes] comment = " ".join(ns) self._messages[msg]["note"] = comment num_notes += 1 if REQUEST is not None: return MessageDialog( title=_(u"Messages imported"), message=(_(u"Imported %d messages and %d notes to %s") % (num_trans, num_notes, " ".join(targets))), action="manage_messages", )
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """ Stores messages and their translations... """ meta_type = 'MessageCatalog' security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # Public API ####################################################################### security.declarePublic('message_encode') def message_encode(self, message): """ Encodes a message to an ASCII string. To be used in the user interface, to avoid problems with the encodings, HTML entities, etc.. """ if type(message) is UnicodeType: msg = 'u' + message.encode('utf8') else: msg = 'n' + message return base64.encodestring(msg) security.declarePublic('message_decode') def message_decode(self, message): """ Decodes a message from an ASCII string. To be used in the user interface, to avoid problems with the encodings, HTML entities, etc.. """ message = base64.decodestring(message) type = message[0] message = message[1:] if type == 'u': return unicode(message, 'utf8') return message security.declarePublic('message_exists') def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=1, default=_marker): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if type(message) not in (StringType, UnicodeType): raise TypeError, 'only strings can be translated.' message = message.strip() if default is _marker: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext def translate(self, domain, msgid, *args, **kw): """ This method is required to get the i18n namespace from ZPT working. """ return self.gettext(msgid) ####################################################################### # Management screens ####################################################################### def manage_options(self): """ """ options = ( {'label': N_('Messages'), 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': N_('Properties'), 'action': 'manage_propertiesForm'}, {'label': N_('Import/Export'), 'action': 'manage_importExport', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': N_('TMX'), 'action': 'manage_tmx'}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options r = [] for option in options: option = option.copy() option['label'] = _(option['label']) r.append(option) return r ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declareProtected('Manage messages', 'get_translations') def get_translations(self, message): """ """ return self._messages[message] security.declarePublic('get_url') def get_url(self, url, batch_start, batch_size, regex, lang, empty, **kw): """ """ params = [] for key, value in kw.items(): if value is not None: params.append('%s=%s' % (key, quote(value))) params.extend(['batch_start:int=%d' % batch_start, 'batch_size:int=%d' % batch_size, 'regex=%s' % quote(regex), 'empty=%s' % (empty and 'on' or '')]) if lang: params.append('lang=%s' % lang) return url + '?' + '&'.join(params) def to_unicode(self, x): """ In Zope the ISO-8859-1 encoding has an special status, normal strings are considered to be in this encoding by default. """ if type(x) is StringType: x = unicode(x, 'iso-8859-1') return x def filter_sort(self, x, y): x = self.to_unicode(x) y = self.to_unicode(y) return cmp(x, y) security.declarePublic('filter') def filter(self, message, lang, empty, regex, batch_start, batch_size=15): """ For the management interface, allows to filter the messages to show. """ # Filter the messages regex = regex.strip() try: regex = re.compile(regex) except: regex = re.compile('') messages = [] for m, t in self._messages.items(): if regex.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(self.filter_sort) # How many messages n = len(messages) # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Get the message message_encoded = None if message is None: if messages: message = messages[0] message_encoded = self.message_encode(message) else: message_encoded = message message = self.message_decode(message) # Calculate the current message aux = [] for x in messages: current = type(x) is type(message) \ and self.to_unicode(x) == self.to_unicode(message) aux.append({'message': x, 'current': current}) return {'messages': aux, 'n_messages': n, 'batch_start': batch_start, 'message': message, 'message_encoded': message_encoded} security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message.""" message_encoded = message message = self.message_decode(message_encoded) self.message_edit(message, language, translation, note) url = self.get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_('Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = self.message_decode(message) self.message_del(message) url = self.get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_('Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties.""" self.title = title if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_importExport') manage_importExport = LocalDTMLFile('ui/MC_importExport', globals()) security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """ Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = time.strftime('%Y-%m-%d %H:%m+%Z', time.gmtime(time.time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[k] = "" else: filename = '%s.po' % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = re.compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) r2 = [] for x in r: if type(x) is UnicodeType: r2.append(x.encode(charset)) else: r2.append(x) return '\n'.join(r2) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages resource = memory.File(data) po = PO.PO(resource) # Load the data for msgid in po.get_msgids(): if msgid: msgstr = po.get_msgstr(msgid) or '' if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_tmx') manage_tmx = LocalDTMLFile('ui/MC_tmx', globals()) security.declareProtected('Manage messages', 'tmx_export') def tmx_export(self, REQUEST, RESPONSE=None): """ Exports the content of the message catalog to a TMX file """ orglang = self._default_language # orglang = orglang.lower() # Get the header info header = self.get_po_header(orglang) charset = header['charset'] r = [] # Generate the TMX file header r.append('<?xml version="1.0" encoding="utf-8"?>') r.append('<!DOCTYPE tmx SYSTEM "http://www.lisa.org/tmx/tmx14.dtd">') r.append('<tmx version="1.4">') r.append('<header') r.append('creationtool="Localizer"') r.append('creationtoolversion="1.x"') r.append('datatype="plaintext"') r.append('segtype="paragraph"') r.append('adminlang="%s"' % orglang) r.append('srclang="%s"' % orglang) r.append('o-encoding="%s"' % charset.lower()) r.append('>') r.append('</header>') r.append('') # Get the messages, and perhaps its translations. d = {} filename = '%s.tmx' % self.id for msgkey, transunit in self._messages.items(): try: d[msgkey] = transunit[orglang] except KeyError: d[msgkey] = "" # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() r.append('<body>') for msgkey in dkeys: r.append('<tu>') transunit = self._messages.get(msgkey) if transunit.has_key('note') and transunit['note']: r.append('<note>%s</note>' % escape(transunit['note'])) r.append('<tuv xml:lang="%s">' % orglang) # The key is the message r.append('<seg>%s</seg></tuv>' % escape(msgkey)) for tlang in self._languages: if tlang != orglang: v = transunit.get(tlang,'') v = escape(v) r.append('<tuv xml:lang="%s">' % tlang) r.append('<seg>%s</seg></tuv>' % v) r.append('</tu>') r.append('') r.append('</body>') r.append('</tmx>') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s"' % filename) r2 = [] for x in r: if type(x) is UnicodeType: r2.append(x.encode('utf-8')) else: r2.append(x) return '\r\n'.join(r2) def _normalize_lang(self,langcode): """ Get the core language (The part before the '-') and return it in lowercase. If there is a local part, return it in uppercase. """ dash = langcode.find('-') if dash == -1: la = langcode.lower() return (la,la) else: la = langcode[:dash].lower() lo = langcode[dash+1:].upper() return (la+'-'+lo,la) def _tmx_header(self, attrs): """ Works on a header (<header>) """ if attrs.has_key('srclang'): self._v_srclang = attrs['srclang'] def _tmx_tu(self, unit): """ Works on a translation unit (<tu>) """ src_lang = self._default_language # We look up the message for the language we have chosen to be our # working language if unit.has_key(self._default_language): src_lang = self._default_language else: # This MUST exist. Otherwise the TMX file is bad src_lang = self._v_srclang key = unit[src_lang] if key == u'': return # Don't add empty messages keysum = md5text(key) # For future indexing on md5 sums messages = self._messages languages = list(self._languages) if not messages.has_key(key): if self._v_howmuch == 'clear' or self._v_howmuch == 'all': messages[key] = PersistentMapping() else: return # Don't add unknown messages self._v_num_translations = self._v_num_translations + 1 for lang in unit.keys(): # Since the messagecatalog's default language overrides our # source language anyway, we handle "*all*" correctly already. # In the test below "*all*" should not be allowed. # Languages that start with '_' are other properties if lang == '_note': messages[key]['note'] = unit[lang] self._v_num_notes = self._v_num_notes + 1 continue if lang[0] == '_': # Unknown special property continue if lang == '*all*' or lang == '*none*': lang = self._v_srclang (target_lang, core_lang) = self._normalize_lang(lang) # If the core language is not seen before then add it if core_lang != src_lang and core_lang not in languages: languages.append(core_lang) # If the language+locality is not seen before then add it if target_lang != src_lang and target_lang not in languages: languages.append(target_lang) # Add message for language+locality if target_lang != src_lang: messages[key][target_lang] = unit[target_lang] # Add message for core language if not (unit.has_key(core_lang) or core_lang == src_lang): messages[key][core_lang] = unit[target_lang] self._languages = tuple(languages) self._messages = messages security.declareProtected('Manage messages', 'tmx_import') def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """ Imports a TMX level 1 file. We use the SAX parser. It has the benefit that it internally converts everything to python unicode strings. """ if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._v_howmuch = howmuch self._v_srclang = self._default_language self._v_num_translations = 0 self._v_num_notes = 0 # Create a parser parser = make_parser() chandler = HandleTMXParsing(self._tmx_tu, self._tmx_header) # Tell the parser to use our handler parser.setContentHandler(chandler) # Don't load the DTD from the Internet parser.setFeature(handler.feature_external_ges, 0) inputsrc = InputSource() if type(file) is StringType: inputsrc.setByteStream(StringIO(file)) else: content = file.read() inputsrc.setByteStream(StringIO(content)) parser.parse(inputsrc) num_translations = self._v_num_translations num_notes = self._v_num_notes del self._v_srclang del self._v_howmuch del self._v_num_translations del self._v_num_notes if REQUEST is not None: return MessageDialog( title = _('Messages imported'), message = _('Imported %d messages and %d notes') % (num_translations, num_notes), action = 'manage_messages') ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected('Manage messages', 'xliff_export') def xliff_export(self, x, export_all=1, REQUEST=None, RESPONSE=None): """ Exports the content of the message catalog to an XLIFF file """ orglang = self._default_language from DateTime import DateTime r = [] # alias for append function. For optimization purposes r_append = r.append # Generate the XLIFF file header RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s_%s_%s.xlf"' % (self.id, orglang, x)) r_append('<?xml version="1.0" encoding="UTF-8"?>') # Version 1.1 of the DTD is not yet available - use version 1.0 r_append('<!DOCTYPE xliff SYSTEM "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd">') # Force a UTF-8 char in the start r_append(u'<!-- XLIFF Format Copyright \xa9 OASIS Open 2001-2003 -->') r_append('<xliff version="1.0">') r_append('<file') r_append('original="/%s"' % self.absolute_url(1)) r_append('product-name="Localizer"') r_append('product-version="1.1.x"') r_append('datatype="plaintext"') r_append('source-language="%s"' % orglang) r_append('target-language="%s"' % x) r_append('date="%s"' % DateTime().HTML4()) r_append('>') r_append('<header>') # r_append('<phase-group>') # r_append('<phase ') # r_append('phase-name="%s"' % REQUEST.get('phase_name', '')) # r_append('process-name="Export"') # r_append('tool="Localizer"') # r_append('date="%s"' % DateTime().HTML4()) # r_append('company-name="%s"' % REQUEST.get('company_name', '')) # r_append('job-id="%s"' % REQUEST.get('job_id', '')) # r_append('contact-name="%s"' % REQUEST.get('contact_name', '')) # r_append('contact-email="%s"' % REQUEST.get('contact_email', '')) # r_append('/>') # r_append('</phase-group>') r_append('</header>') r_append('<body>') # Get the messages, and perhaps its translations. d = {} for msgkey, transunit in self._messages.items(): try: # if export_all=1 export all messages otherwise export # only untranslated messages if int(export_all) == 1 \ or (int(export_all) == 0 and transunit[x] == ''): d[msgkey] = transunit[x] except KeyError: d[msgkey] = "" if d[msgkey] == "": d[msgkey] = msgkey # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for msgkey in dkeys: transunit = self._messages[msgkey] r_append('<trans-unit id="%s">' % md5text(msgkey)) r_append(' <source>%s</source>' % escape(msgkey)) r_append(' <target>%s</target>' % escape(d[msgkey])) if transunit.has_key('note') and transunit['note']: r_append(' <note>%s</note>' % escape(transunit['note'])) r_append('</trans-unit>') r_append('</body>') r_append('</file>') r_append('</xliff>') r2 = [] for x in r: if type(x) is UnicodeType: r2.append(x.encode('utf-8')) else: r2.append(x) return '\r\n'.join(r2) security.declareProtected('Manage messages', 'xliff_import') def xliff_import(self, file, REQUEST=None): """ XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ messages = self._messages # Build a table of messages hashed on the md5 sum of the message # This is because sometimes the xliff file has the sources translated, # not the targets md5hash = {} for mes in messages.keys(): hash = md5text(mes) md5hash[hash] = mes parser = HandleXliffParsing() # parse the xliff information chandler = parser.parseXLIFFFile(file) if chandler is None: return MessageDialog(title = 'Parse error', message = 'Unable to parse XLIFF file' , action = 'manage_main',) header_info = chandler.getFileTag() #get the target language lang = [x for x in header_info if x[0]=='target-language'][0][1] (targetlang, core_lang) = self._normalize_lang(lang) # return a dictionary {id: (source, target)} body_info = chandler.getBody() num_notes = 0 num_translations = 0 # load the data for msgkey, transunit in body_info.items(): # If message is not in catalog, then it is new in xliff file # -- not legal if md5hash.has_key(msgkey): # Normal add srcmsg = md5hash[msgkey] if transunit['note'] != messages[srcmsg].get('note',u''): messages[srcmsg]['note'] = transunit['note'] num_notes = num_notes + 1 if srcmsg == transunit['target']: # No translation was done continue num_translations = num_translations + 1 if transunit['target'] == u'' and transunit['source'] != srcmsg: # The source was translated. Happens sometimes messages[srcmsg][targetlang] = transunit['source'] else: messages[srcmsg][targetlang] = transunit['target'] if REQUEST is not None: return MessageDialog(title = _('Messages imported'), message = _('Imported %d messages and %d notes to %s') % \ (num_translations, num_notes, targetlang) , action = 'manage_messages',)
class ElementSpec(SimpleItem): """ Represent all the tool knows about a single metadata element. """ security = ClassSecurityInfo() # # Default values. # is_multi_valued = 0 def __init__(self, is_multi_valued): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[None] = self._makePolicy() # set default policy security.declarePrivate('_makePolicy') def _makePolicy(self): return MetadataElementPolicy(self.is_multi_valued) security.declareProtected(View, 'isMultiValued') def isMultiValued(self): """ Is this element multi-valued? """ return self.is_multi_valued security.declareProtected(View, 'getPolicy') def getPolicy(self, typ=None): """ Find the policy this element for objects whose type object name is 'typ'; return a default, if none found. """ try: return self.policies[typ].__of__(self) except KeyError: return self.policies[None] security.declareProtected(View, 'listPolicies') def listPolicies(self): """ Return a list of all policies for this element. """ res = [] for k, v in self.policies.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal, 'addPolicy') def addPolicy(self, typ): """ Add a policy to this element for objects whose type object name is 'typ'. """ if typ is None: raise MetadataError, "Can't replace default policy." if self.policies.has_key(typ): raise MetadataError, "Existing policy for content type:" + typ self.policies[typ] = self._makePolicy() security.declareProtected(ManagePortal, 'removePolicy') def removePolicy(self, typ): """ Remove the policy from this element for objects whose type object name is 'typ' (*not* the default, however). """ if typ is None: raise MetadataError, "Can't remove default policy." del self.policies[typ]
class DiscussionItemContainer( Persistent, Implicit, Traversable ): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ __implements__ = Discussable # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId( self ): return self.id security.declareProtected(View, 'getReply') def getReply( self, reply_id ): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get( reply_id ).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent( aq_inner( self ) ) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).manage_afterAdd(item, container) security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).manage_afterClone(item) security.declarePrivate( 'manage_beforeDelete' ) def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__( self ).manage_beforeDelete( item, container ) # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds( self, spec=None ): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r=[] a=r.append g=self._container.get for id in self.objectIds(spec): a( (id, g( id ) ) ) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # Discussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply( self, title, text, Creator=None ): """ Create a reply in the proper place """ container = self._container id = int(DateTime().timeTime()) while self._container.get( str(id), None ) is not None: id = id + 1 id = str( id ) item = DiscussionItem( id, title=title, description=title ) item._edit( text_format='structured-text', text=text ) if Creator: item.creator = Creator item.__of__( self ).indexObject() item.setReplyTo( self._getDiscussable() ) self._container[ id ] = item return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply( self, reply_id ): """ Remove a reply from this container """ if self._container.has_key( reply_id ): reply = self._container.get( reply_id ).__of__( self ) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() if hasattr( my_reply, 'unindexObject' ): my_reply.unindexObject() del self._container[my_reply_id] if hasattr( reply, 'unindexObject' ): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies( self, content_obj ): """ Test to see if there are any dicussion items """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return not not len( self._container ) else: return not not len( content_obj.talkback._getReplyResults() ) security.declareProtected(View, 'replyCount') def replyCount( self, content_obj ): """ How many replies do i have? """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return len( self._container ) else: replies = content_obj.talkback.getReplies() return self._repcount( replies ) security.declarePrivate('_repcount') def _repcount( self, replies ): """ counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount( replies ) return count security.declareProtected(View, 'getReplies') def getReplies( self ): """ Return a sequence of the DiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a( self._container.get( id ).__of__( self ) ) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate( '_getReplyParent' ) def _getReplyParent( self, in_reply_to ): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable( outer=1 ) if in_reply_to is None: return outer parent = self._container[ in_reply_to ].__of__( aq_inner( self ) ) return parent.__of__( outer ) security.declarePrivate( '_getDiscussable' ) def _getDiscussable( self, outer=0 ): """ """ tb = outer and aq_inner( self ) or self return getattr( tb, 'aq_parent', None ) security.declarePrivate( '_getReplyResults' ) def _getReplyResults( self ): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable( outer=1 ) if discussable == outer: in_reply_to = None else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a( key ) return result
class MetadataTool(UniqueObject, SimpleItem, ActionProviderBase): id = 'portal_metadata' meta_type = 'Default Metadata Tool' _actions = [] security = ClassSecurityInfo() # # Default values. # publisher = '' element_specs = None #initial_values_hook = None #validation_hook = None def __init__( self, publisher=None #, initial_values_hook=None #, validation_hook=None , element_specs=DEFAULT_ELEMENT_SPECS): self.editProperties(publisher #, initial_values_hook #, validation_hook ) self.element_specs = PersistentMapping() for name, is_multi_valued in element_specs: self.element_specs[name] = ElementSpec(is_multi_valued) # # ZMI methods # manage_options = ( ActionProviderBase.manage_options + ({ 'label': 'Overview', 'action': 'manage_overview' }, { 'label': 'Properties', 'action': 'propertiesForm' }, { 'label': 'Elements', 'action': 'elementPoliciesForm' } # TODO , { 'label' : 'Types' # , 'action' : 'typesForm' # } ) + SimpleItem.manage_options) security.declareProtected(CMFCorePermissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainMetadataTool', _dtmldir) security.declareProtected(CMFCorePermissions.ManagePortal, 'propertiesForm') propertiesForm = DTMLFile('metadataProperties', _dtmldir) security.declarePrivate('listActions') def listActions(self, info=None): """ Return actions provided via tool. """ return self._actions security.declareProtected(CMFCorePermissions.ManagePortal, 'editProperties') def editProperties( self, publisher=None # TODO , initial_values_hook=None # TODO , validation_hook=None , REQUEST=None): """ Form handler for "tool-wide" properties (including list of metadata elements). """ if publisher is not None: self.publisher = publisher # TODO self.initial_values_hook = initial_values_hook # TODO self.validation_hook = validation_hook if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Tool+updated.') security.declareProtected(CMFCorePermissions.ManagePortal, 'elementPoliciesForm') elementPoliciesForm = DTMLFile('metadataElementPolicies', _dtmldir) security.declareProtected(CMFCorePermissions.ManagePortal, 'addElementPolicy') def addElementPolicy(self, element, content_type, is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary, REQUEST=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(CMFCorePermissions.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(CMFCorePermissions.ManagePortal, 'updateElementPolicy') def updateElementPolicy(self, element, content_type, is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary, REQUEST=None): """ Update a policy for one of our elements ('content_type' will be '<default>' when we edit the default). """ if content_type == '<default>': content_type = None spec = self.getElementSpec(element) policy = spec.getPolicy(content_type) policy.edit(is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary) if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+updated.') # # Element spec manipulation. # security.declareProtected(CMFCorePermissions.ManagePortal, 'listElementSpecs') def listElementSpecs(self): """ Return a list of ElementSpecs representing the elements managed by the tool. """ return tuple(self.element_specs.items()) security.declareProtected(CMFCorePermissions.ManagePortal, 'getElementSpec') def getElementSpec(self, element): """ Return an ElementSpec representing the tool's knowledge of 'element'. """ return self.element_specs[element] security.declareProtected(CMFCorePermissions.ManagePortal, 'addElementSpec') def addElementSpec(self, element, is_multi_valued, 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(CMFCorePermissions.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(CMFCorePermissions.ManagePortal, 'listPolicies') def listPolicies(self, typ=None): """ Show all policies for a given content type, or the default if None. """ result = [] for element, spec in self.listElementSpecs(): result.append((element, spec.getPolicy(typ))) return result # # 'portal_metadata' interface # security.declarePrivate('getFullName') def getFullName(self, userid): """ Convert an internal userid to a "formal" name, if possible, perhaps using the 'portal_membership' tool. Used to map userid's for Creator, Contributor DCMI queries. """ return userid # TODO: do lookup here security.declarePublic('getPublisher') def getPublisher(self): """ Return the "formal" name of the publisher of the portal. """ return self.publisher security.declarePublic('listAllowedVocabulary') def listAllowedVocabulary(self, element, content=None): """ List allowed keywords for a given meta_type, or all possible keywords if none supplied. """ spec = self.getElementSpec(element) typ = content and content.Type() or None return spec.getPolicy(typ).allowedVocabulary() security.declarePublic('listAllowedSubjects') def listAllowedSubjects(self, content=None): """ List allowed keywords for a given meta_type, or all possible keywords if none supplied. """ return self.listAllowedVocabulary('Subject', content) security.declarePublic('listAllowedFormats') def listAllowedFormats(self, content=None): """ List the allowed 'Content-type' values for a particular meta_type, or all possible formats if none supplied. """ return self.listAllowedVocabulary('Format', content) security.declarePublic('listAllowedLanguages') def listAllowedLanguages(self, content=None): """ List the allowed language values. """ return self.listAllowedVocabulary('Language', content) security.declarePublic('listAllowedRights') def listAllowedRights(self, content=None): """ List the allowed values for a "Rights" selection list; this gets especially important where syndication is involved. """ return self.listAllowedVocabulary('Rights', content) security.declareProtected(CMFCorePermissions.ModifyPortalContent, 'setInitialMetadata') def setInitialMetadata(self, content): """ Set initial values for content metatdata, supplying any site-specific defaults. """ for element, policy in self.listPolicies(content.Type()): if not getattr(content, element)(): if policy.supplyDefault(): setter = getattr(content, 'set%s' % element) setter(policy.defaultValue()) elif policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element # TODO: Call initial_values_hook, if present security.declareProtected(CMFCorePermissions.View, 'validateMetadata') def validateMetadata(self, content): """ Enforce portal-wide policies about DCI, e.g., requiring non-empty title/description, etc. Called by the CMF immediately before saving changes to the metadata of an object. """ for element, policy in self.listPolicies(content.Type()): value = getattr(content, element)() if not value and policy.isRequired(): raise MetadataError, \ 'Metadata element %s is required.' % element if policy.enforceVocabulary(): values = policy.isMultiValued() and value or [value] for value in values: if not value in policy.allowedVocabulary(): raise MetadataError, \ 'Value %s is not in allowed vocabulary for' \ 'metadata element %s.' % ( value, element )
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) implements(IMimetypesRegistryTool) 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,): 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.isImplementedBy(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.isImplementedBy(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.isImplementedBy(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 return aq_base(self.extensions.get(ext)) 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.isImplementedBy(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)[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 = 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')
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_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, TupleType): # Old version, convert it. matches = (matches, ) self.var_matches[id] = matches return matches else: return () def getVarMatchText(self, id): values = self.getVarMatch(id) 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', 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() 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) 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.')