class WorklistDefinition(SimpleItem): """Worklist definiton""" meta_type = 'Worklist' security = ClassSecurityInfo() security.declareObjectProtected(ManagePortal) description = '' var_matches = None # Compared with catalog when set. actbox_name = '' actbox_url = '' actbox_icon = '' actbox_category = 'global' guard = None manage_options = ( {'label': 'Properties', 'action': 'manage_properties'}, ) def __init__(self, id): self.id = id def getGuard(self): if self.guard is not None: return self.guard else: return Guard().__of__(self) # Create a temporary guard. def getGuardSummary(self): res = None if self.guard is not None: res = self.guard.getSummary() return res def getWorkflow(self): return aq_parent(aq_inner(aq_parent(aq_inner(self)))) def getAvailableCatalogVars(self): res = [] res.append(self.getWorkflow().state_var) for id, vdef in self.getWorkflow().variables.items(): if vdef.for_catalog: res.append(id) res.sort() return res def getVarMatchKeys(self): if self.var_matches: return self.var_matches.keys() else: return [] def getVarMatch(self, id): if self.var_matches: matches = self.var_matches.get(id, ()) if not isinstance(matches, (tuple, Expression)): # Old version, convert it. matches = (matches,) self.var_matches[id] = matches return matches else: return () def getVarMatchText(self, id): values = self.getVarMatch(id) if isinstance(values, Expression): return values.text return '; '.join(values) _properties_form = DTMLFile('worklist_properties', _dtmldir) def manage_properties(self, REQUEST, manage_tabs_message=None): ''' ''' return self._properties_form(REQUEST, management_view='Properties', manage_tabs_message=manage_tabs_message, ) def setProperties(self, description, actbox_name='', actbox_url='', actbox_category='global', actbox_icon='', props=None, REQUEST=None): ''' ''' if props is None: props = REQUEST self.description = str(description) for key in self.getAvailableCatalogVars(): # Populate var_matches. fieldname = 'var_match_%s' % key v = props.get(fieldname, '') if v: if not self.var_matches: self.var_matches = PersistentMapping() if tales_re.match(v).group(1): # Found a TALES prefix self.var_matches[key] = Expression(v) else: # Falling back to formatted string v = [ var.strip() for var in v.split(';') ] self.var_matches[key] = tuple(v) else: if self.var_matches and self.var_matches.has_key(key): del self.var_matches[key] self.actbox_name = str(actbox_name) self.actbox_url = str(actbox_url) self.actbox_category = str(actbox_category) self.actbox_icon = str(actbox_icon) g = Guard() if g.changeFromProperties(props or REQUEST): self.guard = g else: self.guard = None if REQUEST is not None: return self.manage_properties(REQUEST, 'Properties changed.') def search(self, info=None, **kw): """ Perform the search corresponding to this worklist Returns sequence of ZCatalog brains - info is a mapping for resolving formatted string variable references - additional keyword/value pairs may be used to restrict the query """ if not self.var_matches: return if info is None: info = {} catalog = getToolByName(self, 'portal_catalog') criteria = {} for key, values in self.var_matches.items(): if isinstance(values, Expression): wf = self.getWorkflow() portal = wf._getPortalRoot() context = createExprContext(StateChangeInfo(portal, wf)) criteria[key] = values(context) else: criteria[key] = [x % info for x in values] criteria.update(kw) return catalog.searchResults(**criteria)
class ElementSpec(SimpleItem): """ Represent all the tool knows about a single metadata element. """ security = ClassSecurityInfo() # # Default values. # is_multi_valued = 0 def __init__(self, is_multi_valued): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[None] = self._makePolicy() # set default policy security.declarePrivate('_makePolicy') def _makePolicy(self): return MetadataElementPolicy(self.is_multi_valued) security.declareProtected(View, 'isMultiValued') def isMultiValued(self): """ Is this element multi-valued? """ return self.is_multi_valued security.declareProtected(View, 'getPolicy') def getPolicy(self, typ=None): """ Find the policy for this element for objects of the given type. o Return a default, if none found. """ try: return self.policies[typ].__of__(self) except KeyError: return self.policies[None].__of__(self) security.declareProtected(View, 'listPolicies') def listPolicies(self): """ Return a list of all policies for this element. """ res = [] for k, v in self.policies.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal, 'addPolicy') def addPolicy(self, typ): """ Add a policy to this element for objects of the given type. """ if typ is None: raise MetadataError("Can't replace default policy.") if typ in self.policies: raise MetadataError("Existing policy for content type:" + typ) self.policies[typ] = self._makePolicy() security.declareProtected(ManagePortal, 'removePolicy') def removePolicy(self, typ): """ Remove the policy from this element for objects of the given type. o Do *not* remvoe the default, however. """ if typ is None: raise MetadataError("Can't remove default policy.") del self.policies[typ]
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() POLICY_ADD_FALSE = 0 POLICY_ADD_TRUE = 1 POLICY_ADD_LOG = 2 def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title self.policy = self.POLICY_ADD_TRUE # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) # BBB support str in mapping by converting to unicode for # backward compatibility. if mapping: mapping = dict([to_unicode(k), to_unicode(v)] for k, v in mapping.iteritems()) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. return bool(self.get_message_key(message)) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=None, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, basestring): raise TypeError, 'only strings can be translated.' if default is None: default = message message = message.strip() # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or to_unicode(message) # Add it if it's not in the dictionary if add is None: add = getattr(self, 'policy', self.POLICY_ADD_TRUE) if add != self.POLICY_ADD_FALSE and not self._messages.has_key(message) and message: if add == self.POLICY_ADD_LOG: LOG('New entry added to message catalog %s :' % self.id, INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1])))) self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title self.policy = int(policy) if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_policies') def get_policies(self): """ """ if not hasattr(self, 'policy'): self.policy = self.POLICY_ADD_TRUE policies = [ [self.POLICY_ADD_FALSE, "Never add new entries automatically"], [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"], [self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace"], ] return policies security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. # Convert keys to unicode for proper sorting. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[to_unicode(k, encoding=charset)] = u"" else: filename = '%s.po' % x for k, v in self._messages.items(): k = to_unicode(k, encoding=charset) d[k] = to_unicode(v.get(x, ""), encoding=charset) # Generate the file def backslashescape(x): x = to_str(x) quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) return '\n'.join(r) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = polib.pofile(data) encoding = to_str(po.encoding) for entry in po: msgid = to_unicode(entry.msgid, encoding=encoding) if msgid: msgstr = to_unicode(entry.msgstr or '', encoding=encoding) translation_map = messages.get(msgid) if translation_map is None: # convert old non-unicode translations if they exist: translation_map = messages.pop(self.get_message_key(msgid), None) if translation_map is None: translation_map = PersistentMapping() messages[msgid] = translation_map translation_map[lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=encoding) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=1, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, (str, unicode)): raise TypeError, 'only strings can be translated.' message = message.strip() if default is None: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[k] = "" else: filename = '%s.po' % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) r2 = [] for x in r: if isinstance(x, unicode): r2.append(x.encode(charset)) else: r2.append(x) return '\n'.join(r2) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = itools.gettext.POFile(string=data) for msgid in po.get_msgids(): # TODO Keep the context if any _context, msgid = msgid if msgid: msgstr = po.get_msgstr(msgid) or '' if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) security.declareProtected('Manage messages', 'tmx_export') def tmx_export(self, REQUEST, RESPONSE=None): """Exports the content of the message catalog to a TMX file """ src_lang = self._default_language # Get the header info header = self.get_po_header(src_lang) charset = header['charset'] # Init the TMX handler tmx = TMXFile() tmx.header['creationtool'] = u'Localizer' tmx.header['creationtoolversion'] = u'1.x' tmx.header['datatype'] = u'plaintext' tmx.header['segtype'] = u'paragraph' tmx.header['adminlang'] = src_lang tmx.header['srclang'] = src_lang tmx.header['o-encoding'] = u'%s' % charset.lower() # handle messages for msgkey, transunit in self._messages.items(): unit = TMXUnit({}) for lang in transunit.keys(): if lang != 'note': sentence = Sentence({'lang': lang}) sentence.text = transunit[lang] unit.msgstr[lang] = sentence if src_lang not in transunit.keys(): sentence = Sentence({'lang': src_lang}) sentence.text = msgkey unit.msgstr[src_lang] = sentence if transunit.has_key('note'): note = TMXNote(transunit.get('note')) unit.notes.append(note) tmx.messages[msgkey] = unit if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s.tmx"' % self.id) return tmx.to_str() security.declareProtected('Manage messages', 'tmx_import') def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """Imports a TMX level 1 file. """ try: data = file.read() tmx = TMXFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = tmx.get_srclang() for id, msg in tmx.messages.items(): if not self._messages.has_key(id) and howmuch == 'existing': continue msg.msgstr.pop(self._default_language) if not self._messages.has_key(id): self._messages[id] = {} for lang in msg.msgstr.keys(): # normalize the languageTag and extract the core (core, local) = LanguageTag.decode(lang) lang = LanguageTag.encode((core, local)) if lang not in self._languages: self._languages += (lang,) if msg.msgstr[lang].text: self._messages[id][lang] = msg.msgstr[lang].text if core != lang and core != self._default_language: if core not in self._languages: self._languages += (core,) if not msg.msgstr.has_key(core): self._messages[id][core] = msg.msgstr[lang].text if msg.notes: ns = [m.text for m in msg.notes] self._messages[id]['note'] = u' '.join(ns) num_notes += 1 num_trans += 1 if REQUEST is not None: message = _(u'Imported %d messages and %d notes') return MessageDialog( title = _(u'Messages imported'), message = message % (num_trans, num_notes), action = 'manage_messages') ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected('Manage messages', 'xliff_export') def xliff_export(self, dst_lang, export_all=1, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog to an XLIFF file """ from DateTime import DateTime src_lang = self._default_language export_all = int(export_all) # Init the XLIFF handler xliff = XLFFile() # Add the translation units original = '/%s' % self.absolute_url(1) for msgkey, transunit in self._messages.items(): target = transunit.get(dst_lang, '') # If 'export_all' is true export all messages, otherwise export # only untranslated messages if export_all or not target: unit = xliff.add_unit(original, msgkey, None) unit.attributes['id'] = md5text(msgkey) if target: unit.target = target # Add note note = transunit.get('note') if note: unit.notes.append(XLFNote(note)) # build the data-stucture for the File tag file = xliff.files[original] attributes = file.attributes attributes['original'] = original attributes['product-name'] = u'Localizer' attributes['product-version'] = u'1.1.x' attributes['data-type'] = u'plaintext' attributes['source-language'] = src_lang attributes['target-language'] = dst_lang attributes['date'] = DateTime().HTML4() # Serialize xliff = xliff.to_str() # Set response headers RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8') filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang) RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s"' % filename) # Ok return xliff security.declareProtected('Manage messages', 'xliff_import') def xliff_import(self, howmuch, file, REQUEST=None): """XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ try: data = file.read() xliff = XLFFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 (file_ids, sources, targets) = xliff.get_languages() if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = sources[0] # update languages if len(sources) > 1 or sources[0] != self._default_language: return MessageDialog(title = 'Language error', message = _('incompatible language sources') , action = 'manage_Import_form',) for lang in targets: if lang != self._default_language and lang not in self._languages: self._languages += (lang,) # get messages for file in xliff.files: cur_target = file.attributes.get('target-language', '') for msg in file.body.keys(): if not self._messages.has_key(msg) and howmuch == 'existing': pass else: if not self._messages.has_key(msg): self._messages[msg] = {} if cur_target and file.body[msg].target: self._messages[msg][cur_target] = file.body[msg].target num_trans += 1 if file.body[msg].notes: ns = [n.text for n in file.body[msg].notes] comment = ' '.join(ns) self._messages[msg]['note'] = comment num_notes += 1 if REQUEST is not None: return MessageDialog( title = _(u'Messages imported'), message = (_(u'Imported %d messages and %d notes to %s') % \ (num_trans, num_notes, ' '.join(targets))), action = 'manage_messages')
class ArchetypeTool(UniqueObject, ActionProviderBase, SQLStorageConfig, Folder): """Archetypes tool, manage aspects of Archetype instances. """ id = TOOL_NAME meta_type = TOOL_NAME.title().replace('_', ' ') implements(IArchetypeTool) isPrincipiaFolderish = True # Show up in the ZMI security = ClassSecurityInfo() meta_types = all_meta_types = () manage_options = ( ( {'label': 'Types', 'action': 'manage_debugForm', }, {'label': 'Catalogs', 'action': 'manage_catalogs', }, {'label': 'Templates', 'action': 'manage_templateForm', }, {'label': 'UIDs', 'action': 'manage_uids', }, {'label': 'Update Schema', 'action': 'manage_updateSchemaForm', }, {'label': 'Migration', 'action': 'manage_migrationForm', }, ) + SQLStorageConfig.manage_options ) security.declareProtected(permissions.ManagePortal, 'manage_uids') manage_uids = PageTemplateFile('viewContents', _www) security.declareProtected(permissions.ManagePortal, 'manage_templateForm') manage_templateForm = PageTemplateFile('manageTemplates', _www) security.declareProtected(permissions.ManagePortal, 'manage_debugForm') manage_debugForm = PageTemplateFile('generateDebug', _www) security.declareProtected(permissions.ManagePortal, 'manage_updateSchemaForm') manage_updateSchemaForm = PageTemplateFile('updateSchemaForm', _www) security.declareProtected(permissions.ManagePortal, 'manage_migrationForm') manage_migrationForm = PageTemplateFile('migrationForm', _www) security.declareProtected(permissions.ManagePortal, 'manage_dumpSchemaForm') manage_dumpSchemaForm = PageTemplateFile('schema', _www) security.declareProtected(permissions.ManagePortal, 'manage_catalogs') manage_catalogs = PageTemplateFile('manage_catalogs', _www) def __init__(self): self._schemas = PersistentMapping() self._templates = PersistentMapping() self._registeredTemplates = PersistentMapping() # meta_type -> [names of CatalogTools] self.catalog_map = PersistentMapping() self.catalog_map['Reference'] = [] # References not in portal_catalog # DM (avoid persistency bug): "_types" now maps known schemas to # signatures self._types = {} security.declareProtected(permissions.ManagePortal, 'manage_dumpSchema') def manage_dumpSchema(self, REQUEST=None): """XML Dump Schema of passed in class. """ from Products.Archetypes.Schema import getSchemata package = REQUEST.get('package', '') type_name = REQUEST.get('type_name', '') spec = self.getTypeSpec(package, type_name) type = self.lookupType(package, type_name) options = {} options['classname'] = spec options['schematas'] = getSchemata(type['klass']) REQUEST.RESPONSE.setHeader('Content-Type', 'text/xml') return self.manage_dumpSchemaForm(**options) # Template Management # Views can be pretty generic by iterating the schema so we don't # register by type anymore, we just create per site selection # lists # # We keep two lists, all register templates and their # names/titles and the mapping of type to template bindings both # are persistent security.declareProtected(permissions.ManagePortal, 'registerTemplate') def registerTemplate(self, template, name=None): # Lookup the template by name if not name: obj = self.unrestrictedTraverse(template, None) try: name = obj.title_or_id() except: name = template self._registeredTemplates[template] = name security.declareProtected(permissions.View, 'lookupTemplates') def lookupTemplates(self, instance_or_portaltype=None): """Lookup templates by giving an instance or a portal_type. Returns a DisplayList. """ results = [] if not isinstance(instance_or_portaltype, basestring): portal_type = instance_or_portaltype.getTypeInfo().getId() else: portal_type = instance_or_portaltype try: templates = self._templates[portal_type] except KeyError: return DisplayList() # XXX Look this up in the types tool later # self._templates[instance] = ['base_view',] # templates = self._templates[instance] for t in templates: results.append((t, self._registeredTemplates[t])) return DisplayList(results).sortedByValue() security.declareProtected(permissions.View, 'listTemplates') def listTemplates(self): """Lists all the templates. """ return DisplayList(self._registeredTemplates.items()).sortedByValue() security.declareProtected(permissions.ManagePortal, 'bindTemplate') def bindTemplate(self, portal_type, templateList): """Creates binding between a type and its associated views. """ self._templates[portal_type] = templateList security.declareProtected(permissions.ManagePortal, 'manage_templates') def manage_templates(self, REQUEST=None): """Sets all the template/type mappings. """ prefix = 'template_names_' for key in REQUEST.form.keys(): if key.startswith(prefix): k = key[len(prefix):] v = REQUEST.form.get(key) self.bindTemplate(k, v) add = REQUEST.get('addTemplate') name = REQUEST.get('newTemplate') if add and name: self.registerTemplate(name) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_templateForm') security.declareProtected(permissions.View, 'typeImplementsInterfaces') def typeImplementsInterfaces(self, type, interfaces): """Checks if an type uses one of the given interfaces. """ if isinstance(type, dict) and 'klass' in type: type = type['klass'] for iface in interfaces: res = iface.implementedBy(type) if res: return True return False security.declareProtected(permissions.View, 'isTemplateEnabled') def isTemplateEnabled(self, type): """Checks if an type uses ITemplateMixin. """ return self.typeImplementsInterfaces(type, [ITemplateMixin]) security.declareProtected( permissions.View, 'listTemplateEnabledPortalTypes') def listTemplateEnabledPortalTypes(self): """Return a list of portal_types with ITemplateMixin """ return self.listPortalTypesWithInterfaces([ITemplateMixin]) security.declareProtected( permissions.View, 'listPortalTypesWithInterfaces') def listPortalTypesWithInterfaces(self, ifaces): """Returns a list of ftis of which the types implement one of the given interfaces. Only returns AT types. Get a list of FTIs of types implementing IReferenceable: >>> portal = layer['portal'] >>> from Products.Archetypes.config import TOOL_NAME >>> tool = getToolByName(portal, TOOL_NAME) >>> ftis = tool.listPortalTypesWithInterfaces([IReferenceable]) Sort the type ids and print them: >>> type_ids = [fti.getId() for fti in ftis] >>> type_ids.sort() >>> type_ids ['ATBIFolder', 'Collection', 'ComplexType', ...] """ pt = getToolByName(self, 'portal_types') value = [] for data in listTypes(): klass = data['klass'] for iface in ifaces: if iface.implementedBy(klass): ti = pt.getTypeInfo(data['portal_type']) if ti is not None: value.append(ti) return value # Type/Schema Management security.declareProtected(permissions.View, 'listRegisteredTypes') def listRegisteredTypes(self, inProject=False, portalTypes=False): """Return the list of sorted types. """ def type_sort(a, b): v = cmp(a['package'], b['package']) if v != False: return v c = cmp(a['klass'].__class__.__name__, b['klass'].__class__.__name__) if c == False: return cmp(a['package'], b['package']) return c values = listTypes() values.sort(type_sort) if inProject: # portal_type can change (as it does after ATCT-migration), so we # need to check against the content_meta_type of each type-info ttool = getToolByName(self, 'portal_types') types = [ti.Metatype() for ti in ttool.listTypeInfo()] if portalTypes: values = [v for v in values if v['portal_type'] in types] else: values = [v for v in values if v['meta_type'] in types] return values security.declareProtected(permissions.View, 'getTypeSpec') def getTypeSpec(self, package, type): t = self.lookupType(package, type) module = t['klass'].__module__ klass = t['name'] return '%s.%s' % (module, klass) security.declareProtected(permissions.View, 'listTypes') def listTypes(self, package=None, type=None): """Just the class. """ if type is None: return [t['klass'] for t in listTypes(package)] else: return [getType(type, package)['klass']] security.declareProtected(permissions.View, 'lookupType') def lookupType(self, package, type): types = self.listRegisteredTypes() for t in types: if t['package'] != package: continue if t['meta_type'] == type: # We have to return the schema wrapped into the acquisition of # something to allow access. Otherwise we will end up with: # Your user account is defined outside the context of the object # being accessed. t['schema'] = ImplicitAcquisitionWrapper(t['schema'], self) return t return None security.declareProtected(permissions.ManagePortal, 'manage_installType') def manage_installType(self, typeName, package=None, uninstall=None, REQUEST=None): """Un/Install a type TTW. """ typesTool = getToolByName(self, 'portal_types') try: typesTool._delObject(typeName) except (ConflictError, KeyboardInterrupt): raise except: # XXX bare exception pass if uninstall is not None: if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_debugForm') return typeinfo_name = '%s: %s' % (package, typeName) # We want to run the process/modify_fti code which might not # have been called typeDesc = getType(typeName, package) process_types([typeDesc], package) klass = typeDesc['klass'] # get the meta type of the FTI from the class, use the default FTI as # default fti_meta_type = getattr(klass, '_at_fti_meta_type', None) if fti_meta_type in (None, 'simple item'): fti_meta_type = FactoryTypeInformation.meta_type typesTool.manage_addTypeInformation(fti_meta_type, id=typeName, typeinfo_name=typeinfo_name) t = getattr(typesTool, typeName, None) if t: t.title = getattr(klass, 'archetype_name', typeDesc['portal_type']) # and update the actions as needed fixActionsForType(klass, typesTool) if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_debugForm') security.declarePublic('getSearchWidgets') def getSearchWidgets(self, package=None, type=None, context=None, nosort=None): """Empty widgets for searching. """ return self.getWidgets(package=package, type=type, context=context, mode='search', nosort=nosort) security.declarePublic('getWidgets') def getWidgets(self, instance=None, package=None, type=None, context=None, mode='edit', fields=None, schemata=None, nosort=None): """Empty widgets for standalone rendering. """ widgets = [] w_keys = {} context = context is not None and context or self instances = instance is not None and [instance] or [] f_names = fields if not instances: for t in self.listTypes(package, type): instance = t('fake_instance') instance._at_is_fake_instance = True wrapped = instance.__of__(context) wrapped.initializeArchetype() instances.append(wrapped) for instance in instances: if schemata is not None: schema = instance.Schemata()[schemata].copy() else: schema = instance.Schema().copy() fields = schema.fields() if mode == 'search': # Include only fields which have an index # XXX duplicate fieldnames may break this, # as different widgets with the same name # on different schemas means only the first # one found will be used indexes = self.portal_catalog.indexes() fields = [f for f in fields if (f.accessor and not f.accessor in w_keys and f.accessor in indexes)] if f_names is not None: fields = filter(lambda f: f.getName() in f_names, fields) for field in fields: widget = field.widget field_name = field.getName() accessor = field.getAccessor(instance) if mode == 'search': field.required = False field.addable = False # for ReferenceField if not isinstance(field.vocabulary, DisplayList): field.vocabulary = field.Vocabulary(instance) if '' not in field.vocabulary.keys(): field.vocabulary = DisplayList([('', _(u'at_search_any', default=u'<any>'))]) + \ field.vocabulary widget.populate = False field_name = field.accessor # accessor must be a method which doesn't take an argument # this lambda is facking an accessor accessor = lambda: field.getDefault(instance) w_keys[field_name] = None widgets.append((field_name, WidgetWrapper( field_name=field_name, mode=mode, widget=widget, instance=instance, field=field, accessor=accessor))) if mode == 'search' and nosort == None: widgets.sort() return [widget for name, widget in widgets] security.declarePrivate('_rawEnum') def _rawEnum(self, callback, *args, **kwargs): """Finds all object to check if they are 'referenceable'. """ catalog = getToolByName(self, 'portal_catalog') brains = catalog(dict(id=[])) for b in brains: o = b.getObject() if o is not None: if IBaseObject.providedBy(o): callback(o, *args, **kwargs) else: log('no object for brain: %s:%s' % (b, b.getURL())) security.declareProtected(permissions.View, 'enum') def enum(self, callback, *args, **kwargs): catalog = getToolByName(self, UID_CATALOG) keys = catalog.uniqueValuesFor('UID') for uid in keys: o = self.getObject(uid) if o: callback(o, *args, **kwargs) else: log('No object for %s' % uid) security.declareProtected(permissions.View, 'Content') def Content(self): """Return a list of all the content ids. """ catalog = getToolByName(self, UID_CATALOG) keys = catalog.uniqueValuesFor('UID') results = catalog(dict(UID=keys)) return results # Management Forms security.declareProtected(permissions.ManagePortal, 'manage_doGenerate') def manage_doGenerate(self, sids=(), REQUEST=None): """(Re)generate types. """ schemas = [] for sid in sids: schemas.append(self.getSchema(sid)) for s in schemas: s.generate() if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_workspace') security.declareProtected(permissions.ManagePortal, 'manage_inspect') def manage_inspect(self, UID, REQUEST=None): """Dump some things about an object hook in the debugger for now. """ object = self.getObject(UID) log("uid: %s, schema: %s" % (object, object.Schema())) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_uids') security.declareProtected(permissions.ManagePortal, 'manage_reindex') def manage_reindex(self, REQUEST=None): """Assign UIDs to all basecontent objects. """ def _index(object, archetype_tool): archetype_tool.registerContent(object) self._rawEnum(_index, self) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_uids') security.declareProtected(permissions.ManagePortal, 'index') index = manage_reindex def _listAllTypes(self): """List all types -- either currently known or known to us. """ allTypes = _types.copy() allTypes.update(self._types) return allTypes.keys() security.declareProtected(permissions.ManagePortal, 'getChangedSchema') def getChangedSchema(self): """Returns a list of tuples indicating which schema have changed. Tuples have the form (schema, changed). """ list = [] currentTypes = _types ourTypes = self._types modified = False keys = self._listAllTypes() keys.sort() for t in keys: if t not in ourTypes: # Add it ourTypes[t] = currentTypes[t]['signature'] modified = True list.append((t, 0)) elif t not in currentTypes: # Huh: what shall we do? We remove it -- this might be wrong! del ourTypes[t] modified = True # We do not add an entry because we cannot update # these objects (having no longer type information for them) else: list.append((t, ourTypes[t] != currentTypes[t]['signature'])) if modified: self._p_changed = True return list security.declareProtected(permissions.ManagePortal, 'manage_updateSchema') def manage_updateSchema(self, REQUEST=None, update_all=None, remove_instance_schemas=None): """Make sure all objects' schema are up to date. """ out = StringIO() print >> out, 'Updating schema...' update_types = [] if REQUEST is None: # DM (avoid persistency bug): avoid code duplication update_types = [ti[0] for ti in self.getChangedSchema() if ti[1]] else: # DM (avoid persistency bug): for t in self._listAllTypes(): if REQUEST.form.get(t, False): update_types.append(t) update_all = REQUEST.form.get('update_all', False) remove_instance_schemas = REQUEST.form.get( 'remove_instance_schemas', False) # XXX: Enter this block only when there are types to update! if update_types: # Use the catalog's ZopeFindAndApply method to walk through # all objects in the portal. This works much better than # relying on the catalog to find objects, because an object # may be uncatalogable because of its schema, and then you # can't update it if you require that it be in the catalog. catalog = getToolByName(self, 'portal_catalog') portal = getToolByName(self, 'portal_url').getPortalObject() meta_types = [_types[t]['meta_type'] for t in update_types] if remove_instance_schemas: func_update_changed = self._removeSchemaAndUpdateChangedObject func_update_all = self._removeSchemaAndUpdateObject else: func_update_changed = self._updateChangedObject func_update_all = self._updateObject if update_all: catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types, search_sub=True, apply_func=func_update_all) else: catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types, search_sub=True, apply_func=func_update_changed) for t in update_types: self._types[t] = _types[t]['signature'] self._p_changed = True print >> out, 'Done.' return out.getvalue() # A counter to ensure that in a given interval a subtransaction # commit is done. subtransactioncounter = 0 def _updateObject(self, o, path, remove_instance_schemas=None): o._updateSchema(remove_instance_schemas=remove_instance_schemas) # Subtransactions to avoid eating up RAM when used inside a # 'ZopeFindAndApply' like in manage_updateSchema self.subtransactioncounter += 1 # Only every 250 objects a sub-commit, otherwise it eats up all # diskspace if not self.subtransactioncounter % 250: transaction.savepoint(optimistic=True) def _updateChangedObject(self, o, path): if not o._isSchemaCurrent(): self._updateObject(o, path) def _removeSchemaAndUpdateObject(self, o, path): self._updateObject(o, path, remove_instance_schemas=True) def _removeSchemaAndUpdateChangedObject(self, o, path): if not o._isSchemaCurrent(): self._removeSchemaAndUpdateObject(o, path) security.declareProtected(permissions.ManagePortal, 'manage_updateSchema') def manage_migrate(self, REQUEST=None): """Run Extensions.migrations.migrate. """ from Products.Archetypes.Extensions.migrations import migrate out = migrate(self) self.manage_updateSchema() return out # Catalog management security.declareProtected(permissions.View, 'listCatalogs') def listCatalogs(self): """Show the catalog mapping. """ return self.catalog_map security.declareProtected(permissions.ManagePortal, 'manage_updateCatalogs') def manage_updateCatalogs(self, REQUEST=None): """Set the catalog map for meta_type to include the list catalog_names. """ prefix = 'catalog_names_' for key in REQUEST.form.keys(): if key.startswith(prefix): k = key[len(prefix):] v = REQUEST.form.get(key) self.setCatalogsByType(k, v) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_catalogs') security.declareProtected(permissions.ManagePortal, 'setCatalogsByType') def setCatalogsByType(self, portal_type, catalogList): """ associate catalogList with meta_type. (unfortunally not portal_type). catalogList is a list of strings with the ids of the catalogs. Each catalog is has to be a tool, means unique in site root. """ self.catalog_map[portal_type] = catalogList security.declareProtected(permissions.View, 'getCatalogsByType') def getCatalogsByType(self, portal_type): """Return the catalog objects assoicated with a given type. """ catalogs = [] catalog_map = getattr(self, 'catalog_map', None) if catalog_map is not None: names = self.catalog_map.get(portal_type, ['portal_catalog']) else: names = ['portal_catalog'] portal = getToolByName(self, 'portal_url').getPortalObject() for name in names: try: catalogs.append(getToolByName(portal, name)) except (ConflictError, KeyboardInterrupt): raise except Exception, E: log('No tool %s' % name, E) pass return catalogs
class Resource(Persistent): security = ClassSecurityInfo() def __init__(self, id, **kwargs): self._data = PersistentMapping() extres = id.startswith('http://') or id.startswith( 'https://') or id.startswith('//') if not extres and (id.startswith('/') or id.endswith('/') or ('//' in id)): raise ValueError("Invalid Resource ID: %s" % id) self._data['id'] = id expression = kwargs.get('expression', '') self.setExpression(expression) self._data['authenticated'] = kwargs.get('authenticated', False) self._data['enabled'] = kwargs.get('enabled', True) self._data['cookable'] = kwargs.get('cookable', True) self._data['cacheable'] = kwargs.get('cacheable', True) self._data['conditionalcomment'] = kwargs.get('conditionalcomment', '') self._data['bundle'] = kwargs.get('bundle', 'default') self.isExternal = extres if extres: self._data[ 'cacheable'] = False #External resources are NOT cacheable self._data[ 'cookable'] = False #External resources are NOT mergable def copy(self): result = self.__class__(self.getId()) for key, value in self._data.items(): if key != 'id': result._data[key] = value return result security.declarePublic('getId') def getId(self): return self._data['id'] security.declarePublic('getQuotedId') def getQuotedId(self): return quote_plus(self._data['id']) security.declareProtected(permissions.ManagePortal, '_setId') def _setId(self, id): if id.startswith('/') or id.endswith('/') or ( ('//' in id) and not self.isExternalResource()): raise ValueError("Invalid Resource ID: %s" % id) self._data['id'] = id security.declarePublic('getCookedExpression') def getCookedExpression(self): # Automatic inline migration of expressions if 'cooked_expression' not in self._data: expr = Expression(self._data['expression']) self._data['cooked_expression'] = expr return self._data['cooked_expression'] security.declarePublic('getExpression') def getExpression(self): return self._data['expression'] security.declareProtected(permissions.ManagePortal, 'setExpression') def setExpression(self, expression): # Update the cooked expression self._data['cooked_expression'] = Expression(expression) self._data['expression'] = expression security.declarePublic('getAuthenticated') def getAuthenticated(self): # Automatic inline migration if 'authenticated' not in self._data: self._data['authenticated'] = False return bool(self._data['authenticated']) security.declareProtected(permissions.ManagePortal, 'setAuthenticated') def setAuthenticated(self, authenticated): self._data['authenticated'] = authenticated security.declarePublic('getEnabled') def getEnabled(self): return bool(self._data['enabled']) security.declareProtected(permissions.ManagePortal, 'setEnabled') def setEnabled(self, enabled): self._data['enabled'] = enabled security.declarePublic('getCookable') def getCookable(self): return self._data['cookable'] security.declareProtected(permissions.ManagePortal, 'setCookable') def setCookable(self, cookable): if self.isExternalResource() and cookable: raise ValueError("External Resources cannot be merged") self._data['cookable'] = cookable security.declarePublic('getCacheable') def getCacheable(self): # as this is a new property, old instance might not have that value, so # return True as default return self._data.get('cacheable', True) security.declareProtected(permissions.ManagePortal, 'setCacheable') def setCacheable(self, cacheable): if self.isExternalResource() and cacheable: raise ValueError("External Resources are not cacheable") self._data['cacheable'] = cacheable security.declarePublic('getConditionalcomment') def getConditionalcomment(self): # New property, return blank if the old instance doesn't have that value return self._data.get('conditionalcomment', '') security.declareProtected(permissions.ManagePortal, 'setConditionalcomment') def setConditionalcomment(self, conditionalcomment): self._data['conditionalcomment'] = conditionalcomment security.declarePublic('isExternalResource') def isExternalResource(self): return getattr(self, 'isExternal', False) security.declarePublic('getBundle') def getBundle(self): return self._data.get('bundle', None) or 'default' security.declareProtected(permissions.ManagePortal, 'setBundle') def setBundle(self, bundle): self._data['bundle'] = bundle
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.private def _makePolicy(self): return MetadataElementPolicy(self.is_multi_valued) @security.protected(View) def isMultiValued(self): """ Is this element multi-valued? """ return self.is_multi_valued @security.protected(View) def getPolicy(self, typ=None): """ Find the policy for this element for objects of the given type. o Return a default, if none found. """ try: return self.policies[typ].__of__(self) except KeyError: return self.policies[None].__of__(self) @security.protected(View) 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.protected(ManagePortal) def addPolicy(self, typ): """ Add a policy to this element for objects of the given type. """ if typ is None: raise MetadataError("Can't replace default policy.") if typ in self.policies: raise MetadataError("Existing policy for content type:" + typ) self.policies[typ] = self._makePolicy() @security.protected(ManagePortal) def removePolicy(self, typ): """ Remove the policy from this element for objects of the given type. o Do *not* remvoe the default, however. """ if typ is None: raise MetadataError("Can't remove default policy.") del self.policies[typ]
class PluginRegistry(SimpleItem): """ Implement IPluginRegistry as an independent, ZMI-manageable object. o Each plugin type holds an ordered list of (id, wrapper) tuples. """ implements(IPluginRegistry, IWriteLock) security = ClassSecurityInfo() meta_type = 'Plugin Registry' _plugins = None def __init__(self, plugin_type_info=()): if isinstance(plugin_type_info, basestring): # some tool is passing us our ID. raise ValueError('Must pass a sequence of plugin info dicts!') self._plugin_types = [x[0] for x in plugin_type_info] self._plugin_type_info = PersistentMapping() for interface in plugin_type_info: self._plugin_type_info[interface[0]] = { 'id': interface[1] , 'title': interface[2] , 'description': interface[3] } # # IPluginRegistry implementation # security.declareProtected(ManageUsers, 'listPluginTypeInfo') def listPluginTypeInfo(self): """ See IPluginRegistry. """ result = [] for ptype in self._plugin_types: info = self._plugin_type_info[ptype].copy() info['interface'] = ptype info['methods'] = ptype.names() result.append(info) return result security.declareProtected(ManageUsers, 'listPlugins') def listPlugins(self, plugin_type): """ See IPluginRegistry. """ result = [] parent = aq_parent(aq_inner(self)) for plugin_id in self._getPlugins(plugin_type): plugin = parent._getOb(plugin_id) if not _satisfies(plugin, plugin_type): logger.debug('Active plugin %s no longer implements %s' % (plugin_id, plugin_type) ) else: result.append((plugin_id, plugin)) return result security.declareProtected(ManageUsers, 'getPluginInfo') def getPluginInfo(self, plugin_type): """ See IPluginRegistry. """ plugin_type = self._getInterfaceFromName(plugin_type) return self._plugin_type_info[plugin_type] security.declareProtected(ManageUsers, 'listPluginIds') def listPluginIds(self, plugin_type): """ See IPluginRegistry. """ return self._getPlugins(plugin_type) security.declareProtected(ManageUsers, 'activatePlugin') def activatePlugin(self, plugin_type, plugin_id): """ See IPluginRegistry. """ plugins = list(self._getPlugins(plugin_type)) if plugin_id in plugins: raise KeyError, 'Duplicate plugin id: %s' % plugin_id parent = aq_parent(aq_inner(self)) plugin = parent._getOb(plugin_id) if not _satisfies(plugin, plugin_type): raise ValueError, 'Plugin does not implement %s' % plugin_type plugins.append(plugin_id) self._plugins[plugin_type] = tuple(plugins) security.declareProtected(ManageUsers, 'deactivatePlugin') def deactivatePlugin(self, plugin_type, plugin_id): """ See IPluginRegistry. """ plugins = list(self._getPlugins(plugin_type)) if not plugin_id in plugins: raise KeyError, 'Invalid plugin id: %s' % plugin_id plugins = [x for x in plugins if x != plugin_id] self._plugins[plugin_type] = tuple(plugins) security.declareProtected(ManageUsers, 'movePluginsUp') def movePluginsUp(self, plugin_type, ids_to_move): """ See IPluginRegistry. """ ids = list(self._getPlugins(plugin_type)) count = len(ids) indexes = list(map(ids.index, ids_to_move)) indexes.sort() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 - 1 if i2 < 0: # i1 is already on top continue ids[i2], ids[i1] = ids[i1], ids[i2] self._plugins[plugin_type] = tuple(ids) security.declareProtected(ManageUsers, 'movePluginsDown') def movePluginsDown(self, plugin_type, ids_to_move): """ See IPluginRegistry. """ ids = list(self._getPlugins(plugin_type)) count = len(ids) indexes = list(map(ids.index, ids_to_move)) indexes.sort() indexes.reverse() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 + 1 if i2 == len(ids): # i1 is already on the bottom continue ids[i2], ids[i1] = ids[i1], ids[i2] self._plugins[plugin_type] = tuple(ids) # # ZMI # arrow_right_gif = ImageFile('www/arrow-right.gif', globals()) arrow_left_gif = ImageFile('www/arrow-left.gif', globals()) arrow_up_gif = ImageFile('www/arrow-up.gif', globals()) arrow_down_gif = ImageFile('www/arrow-down.gif', globals()) security.declareProtected(ManageUsers, 'manage_activatePlugins') def manage_activatePlugins(self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) for id in plugin_ids: self.activatePlugin(interface, id) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type) ) security.declareProtected(ManageUsers, 'manage_deactivatePlugins') def manage_deactivatePlugins(self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) for id in plugin_ids: self.deactivatePlugin(interface, id) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type) ) security.declareProtected(ManageUsers, 'manage_movePluginsUp') def manage_movePluginsUp(self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) self.movePluginsUp(interface, plugin_ids) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type) ) security.declareProtected(ManageUsers, 'manage_movePluginsDown') def manage_movePluginsDown(self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) self.movePluginsDown(interface, plugin_ids) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type) ) security.declareProtected(ManageUsers, 'getAllPlugins') def getAllPlugins(self, plugin_type): """ Return a mapping segregating active / available plugins. 'plugin_type' is the __name__ of the interface. """ interface = self._getInterfaceFromName(plugin_type) active = self._getPlugins(interface) available = [] for id, value in aq_parent(aq_inner(self)).objectItems(): if _satisfies(value, interface): if id not in active: available.append(id) return { 'active' : active, 'available' : available } security.declareProtected(ManageUsers, 'removePluginById') def removePluginById(self, plugin_id): """ Remove a plugin from any plugin types which have it configured. """ for plugin_type in self._plugin_types: if plugin_id in self._getPlugins(plugin_type): self.deactivatePlugin(plugin_type, plugin_id) security.declareProtected(ManageUsers, 'manage_plugins') manage_plugins = PageTemplateFile('plugins', _wwwdir) security.declareProtected(ManageUsers, 'manage_active') manage_active = PageTemplateFile('active_plugins', _wwwdir) manage_twoLists = PageTemplateFile('two_lists', _wwwdir) manage_options=(({ 'label' : 'Plugins' , 'action' : 'manage_plugins' # , 'help' : ('PluggableAuthService' # , 'plugins.stx') } , { 'label' : 'Active' , 'action' : 'manage_active' } ) + SimpleItem.manage_options ) if _HAS_GENERIC_SETUP: security.declareProtected(ManageUsers, 'manage_exportImportForm') manage_exportImportForm = PageTemplateFile('export_import', _wwwdir) security.declareProtected(ManageUsers, 'getConfigAsXML') def getConfigAsXML(self): """ Return XML representing the registry's configuration. """ from exportimport import PluginRegistryExporter pre = PluginRegistryExporter(self).__of__(self) return pre.generateXML() security.declareProtected(ManageUsers, 'manage_exportImport') def manage_exportImport(self, updated_xml, should_purge, RESPONSE): """ Parse XML and update the registry. """ #XXX encoding? _updatePluginRegistry(self, updated_xml, should_purge) RESPONSE.redirect('%s/manage_exportImportForm' '?manage_tabs_message=Registry+updated.' % self.absolute_url()) security.declareProtected(ManageUsers, 'manage_FTPget') def manage_FTPget(self, REQUEST, RESPONSE): """ """ return self.getConfigAsXML() security.declareProtected(ManageUsers, 'PUT') def PUT(self, REQUEST, RESPONSE): """ """ xml = REQUEST['BODYFILE'].read() _updatePluginRegistry(self, xml, True) manage_options = (manage_options[:2] + ({ 'label' : 'Export / Import' , 'action' : 'manage_exportImportForm' },) + manage_options[2:] ) # # Helper methods # security.declarePrivate('_getPlugins') def _getPlugins(self, plugin_type): parent = aq_parent(aq_inner(self)) if plugin_type not in self._plugin_types: raise KeyError, plugin_type if self._plugins is None: self._plugins = PersistentMapping() return self._plugins.setdefault(plugin_type, ()) security.declarePrivate('_getInterfaceFromName') def _getInterfaceFromName(self, plugin_type_name): """ Convert the string name to an interface. o Raise KeyError is no such interface is known. """ found = [x[0] for x in self._plugin_type_info.items() if x[1]['id'] == plugin_type_name] if not found: raise KeyError, plugin_type_name if len(found) > 1: raise KeyError, 'Waaa!: %s' % plugin_type_name return found[0]
class WorklistDefinition(SimpleItem): """Worklist definiton""" meta_type = 'Worklist' security = ClassSecurityInfo() security.declareObjectProtected(ManagePortal) description = '' var_matches = None # Compared with catalog when set. actbox_name = '' actbox_url = '' actbox_icon = '' actbox_category = 'global' guard = None manage_options = ( {'label': 'Properties', 'action': 'manage_properties'}, ) def __init__(self, id): self.id = id def getGuard(self): if self.guard is not None: return self.guard else: return Guard().__of__(self) # Create a temporary guard. def getGuardSummary(self): res = None if self.guard is not None: res = self.guard.getSummary() return res def getWorkflow(self): return aq_parent(aq_inner(aq_parent(aq_inner(self)))) def getAvailableCatalogVars(self): res = [] res.append(self.getWorkflow().state_var) for id, vdef in self.getWorkflow().variables.items(): if vdef.for_catalog: res.append(id) res.sort() return res def getVarMatchKeys(self): if self.var_matches: return self.var_matches.keys() else: return [] def getVarMatch(self, id): if self.var_matches: matches = self.var_matches.get(id, ()) if not isinstance(matches, (tuple, Expression)): # Old version, convert it. matches = (matches,) self.var_matches[id] = matches return matches else: return () def getVarMatchText(self, id): values = self.getVarMatch(id) if isinstance(values, Expression): return values.text return '; '.join(values) _properties_form = DTMLFile('worklist_properties', _dtmldir) def manage_properties(self, REQUEST, manage_tabs_message=None): ''' ''' return self._properties_form(REQUEST, management_view='Properties', manage_tabs_message=manage_tabs_message, ) def setProperties(self, description, actbox_name='', actbox_url='', actbox_category='global', actbox_icon='', props=None, REQUEST=None): ''' ''' if props is None: props = REQUEST self.description = str(description) for key in self.getAvailableCatalogVars(): # Populate var_matches. fieldname = 'var_match_%s' % key v = props.get(fieldname, '') if v: if not self.var_matches: self.var_matches = PersistentMapping() if tales_re.match(v).group(1): # Found a TALES prefix self.var_matches[key] = Expression(v) else: # Falling back to formatted string v = [ var.strip() for var in v.split(';') ] self.var_matches[key] = tuple(v) else: if self.var_matches and self.var_matches.has_key(key): del self.var_matches[key] self.actbox_name = str(actbox_name) self.actbox_url = str(actbox_url) self.actbox_category = str(actbox_category) self.actbox_icon = str(actbox_icon) g = Guard() if g.changeFromProperties(props or REQUEST): self.guard = g else: self.guard = None if REQUEST is not None: return self.manage_properties(REQUEST, 'Properties changed.') def search(self, info=None, **kw): """ Perform the search corresponding to this worklist Returns sequence of ZCatalog brains - info is a mapping for resolving formatted string variable references - additional keyword/value pairs may be used to restrict the query """ if not self.var_matches: return if info is None: info = {} criteria = {} for key, values in self.var_matches.items(): if isinstance(values, Expression): wf = self.getWorkflow() portal = wf._getPortalRoot() context = createExprContext(StateChangeInfo(portal, wf)) criteria[key] = values(context) else: criteria[key] = [x % info for x in values] criteria.update(kw) ctool = getUtility(ICatalogTool) return ctool.searchResults(**criteria)
class ArchetypeTool(UniqueObject, ActionProviderBase, \ SQLStorageConfig, Folder): """Archetypes tool, manage aspects of Archetype instances. """ id = TOOL_NAME meta_type = TOOL_NAME.title().replace('_', ' ') implements(IArchetypeTool) isPrincipiaFolderish = True # Show up in the ZMI security = ClassSecurityInfo() meta_types = all_meta_types = () manage_options = (( { 'label': 'Types', 'action': 'manage_debugForm', }, { 'label': 'Catalogs', 'action': 'manage_catalogs', }, { 'label': 'Templates', 'action': 'manage_templateForm', }, { 'label': 'UIDs', 'action': 'manage_uids', }, { 'label': 'Update Schema', 'action': 'manage_updateSchemaForm', }, { 'label': 'Migration', 'action': 'manage_migrationForm', }, ) + SQLStorageConfig.manage_options) security.declareProtected(permissions.ManagePortal, 'manage_uids') manage_uids = PageTemplateFile('viewContents', _www) security.declareProtected(permissions.ManagePortal, 'manage_templateForm') manage_templateForm = PageTemplateFile('manageTemplates', _www) security.declareProtected(permissions.ManagePortal, 'manage_debugForm') manage_debugForm = PageTemplateFile('generateDebug', _www) security.declareProtected(permissions.ManagePortal, 'manage_updateSchemaForm') manage_updateSchemaForm = PageTemplateFile('updateSchemaForm', _www) security.declareProtected(permissions.ManagePortal, 'manage_migrationForm') manage_migrationForm = PageTemplateFile('migrationForm', _www) security.declareProtected(permissions.ManagePortal, 'manage_dumpSchemaForm') manage_dumpSchemaForm = PageTemplateFile('schema', _www) security.declareProtected(permissions.ManagePortal, 'manage_catalogs') manage_catalogs = PageTemplateFile('manage_catalogs', _www) def __init__(self): self._schemas = PersistentMapping() self._templates = PersistentMapping() self._registeredTemplates = PersistentMapping() # meta_type -> [names of CatalogTools] self.catalog_map = PersistentMapping() self.catalog_map['Reference'] = [] # References not in portal_catalog # DM (avoid persistency bug): "_types" now maps known schemas to signatures self._types = {} security.declareProtected(permissions.ManagePortal, 'manage_dumpSchema') def manage_dumpSchema(self, REQUEST=None): """XML Dump Schema of passed in class. """ from Products.Archetypes.Schema import getSchemata package = REQUEST.get('package', '') type_name = REQUEST.get('type_name', '') spec = self.getTypeSpec(package, type_name) type = self.lookupType(package, type_name) options = {} options['classname'] = spec options['schematas'] = getSchemata(type['klass']) REQUEST.RESPONSE.setHeader('Content-Type', 'text/xml') return self.manage_dumpSchemaForm(**options) # Template Management # Views can be pretty generic by iterating the schema so we don't # register by type anymore, we just create per site selection # lists # # We keep two lists, all register templates and their # names/titles and the mapping of type to template bindings both # are persistent security.declareProtected(permissions.ManagePortal, 'registerTemplate') def registerTemplate(self, template, name=None): # Lookup the template by name if not name: obj = self.unrestrictedTraverse(template, None) try: name = obj.title_or_id() except: name = template self._registeredTemplates[template] = name security.declareProtected(permissions.View, 'lookupTemplates') def lookupTemplates(self, instance_or_portaltype=None): """Lookup templates by giving an instance or a portal_type. Returns a DisplayList. """ results = [] if not isinstance(instance_or_portaltype, basestring): portal_type = instance_or_portaltype.getTypeInfo().getId() else: portal_type = instance_or_portaltype try: templates = self._templates[portal_type] except KeyError: return DisplayList() # XXX Look this up in the types tool later # self._templates[instance] = ['base_view',] # templates = self._templates[instance] for t in templates: results.append((t, self._registeredTemplates[t])) return DisplayList(results).sortedByValue() security.declareProtected(permissions.View, 'listTemplates') def listTemplates(self): """Lists all the templates. """ return DisplayList(self._registeredTemplates.items()).sortedByValue() security.declareProtected(permissions.ManagePortal, 'bindTemplate') def bindTemplate(self, portal_type, templateList): """Creates binding between a type and its associated views. """ self._templates[portal_type] = templateList security.declareProtected(permissions.ManagePortal, 'manage_templates') def manage_templates(self, REQUEST=None): """Sets all the template/type mappings. """ prefix = 'template_names_' for key in REQUEST.form.keys(): if key.startswith(prefix): k = key[len(prefix):] v = REQUEST.form.get(key) self.bindTemplate(k, v) add = REQUEST.get('addTemplate') name = REQUEST.get('newTemplate') if add and name: self.registerTemplate(name) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_templateForm') security.declareProtected(permissions.View, 'typeImplementsInterfaces') def typeImplementsInterfaces(self, type, interfaces): """Checks if an type uses one of the given interfaces. """ if isinstance(type, dict) and 'klass' in type: type = type['klass'] for iface in interfaces: res = iface.implementedBy(type) if res: return True return False security.declareProtected(permissions.View, 'isTemplateEnabled') def isTemplateEnabled(self, type): """Checks if an type uses ITemplateMixin. """ return self.typeImplementsInterfaces(type, [ITemplateMixin]) security.declareProtected(permissions.View, 'listTemplateEnabledPortalTypes') def listTemplateEnabledPortalTypes(self): """Return a list of portal_types with ITemplateMixin """ return self.listPortalTypesWithInterfaces([ITemplateMixin]) security.declareProtected(permissions.View, 'listPortalTypesWithInterfaces') def listPortalTypesWithInterfaces(self, ifaces): """Returns a list of ftis of which the types implement one of the given interfaces. Only returns AT types. Get a list of FTIs of types implementing IReferenceable: >>> tool = getToolByName(self.portal, TOOL_NAME) >>> meth = tool.listPortalTypesWithInterfaces >>> ftis = tool.listPortalTypesWithInterfaces([IReferenceable]) Sort the type ids and print them: >>> type_ids = [fti.getId() for fti in ftis] >>> type_ids.sort() >>> type_ids ['ATBIFolder', 'ComplexType', ...] """ pt = getToolByName(self, 'portal_types') value = [] for data in listTypes(): klass = data['klass'] for iface in ifaces: if iface.implementedBy(klass): ti = pt.getTypeInfo(data['portal_type']) if ti is not None: value.append(ti) return value # Type/Schema Management security.declareProtected(permissions.View, 'listRegisteredTypes') def listRegisteredTypes(self, inProject=False, portalTypes=False): """Return the list of sorted types. """ def type_sort(a, b): v = cmp(a['package'], b['package']) if v != False: return v c = cmp(a['klass'].__class__.__name__, b['klass'].__class__.__name__) if c == False: return cmp(a['package'], b['package']) return c values = listTypes() values.sort(type_sort) if inProject: # portal_type can change (as it does after ATCT-migration), so we # need to check against the content_meta_type of each type-info ttool = getToolByName(self, 'portal_types') types = [ti.Metatype() for ti in ttool.listTypeInfo()] if portalTypes: values = [v for v in values if v['portal_type'] in types] else: values = [v for v in values if v['meta_type'] in types] return values security.declareProtected(permissions.View, 'getTypeSpec') def getTypeSpec(self, package, type): t = self.lookupType(package, type) module = t['klass'].__module__ klass = t['name'] return '%s.%s' % (module, klass) security.declareProtected(permissions.View, 'listTypes') def listTypes(self, package=None, type=None): """Just the class. """ if type is None: return [t['klass'] for t in listTypes(package)] else: return [getType(type, package)['klass']] security.declareProtected(permissions.View, 'lookupType') def lookupType(self, package, type): types = self.listRegisteredTypes() for t in types: if t['package'] != package: continue if t['meta_type'] == type: # We have to return the schema wrapped into the acquisition of # something to allow access. Otherwise we will end up with: # Your user account is defined outside the context of the object # being accessed. t['schema'] = ImplicitAcquisitionWrapper(t['schema'], self) return t return None security.declareProtected(permissions.ManagePortal, 'manage_installType') def manage_installType(self, typeName, package=None, uninstall=None, REQUEST=None): """Un/Install a type TTW. """ typesTool = getToolByName(self, 'portal_types') try: typesTool._delObject(typeName) except (ConflictError, KeyboardInterrupt): raise except: # XXX bare exception pass if uninstall is not None: if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_debugForm') return typeinfo_name = '%s: %s' % (package, typeName) # We want to run the process/modify_fti code which might not # have been called typeDesc = getType(typeName, package) process_types([typeDesc], package) klass = typeDesc['klass'] # get the meta type of the FTI from the class, use the default FTI as default fti_meta_type = getattr(klass, '_at_fti_meta_type', None) if fti_meta_type in (None, 'simple item'): fti_meta_type = FactoryTypeInformation.meta_type typesTool.manage_addTypeInformation(fti_meta_type, id=typeName, typeinfo_name=typeinfo_name) t = getattr(typesTool, typeName, None) if t: t.title = getattr(klass, 'archetype_name', typeDesc['portal_type']) # and update the actions as needed fixActionsForType(klass, typesTool) if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_debugForm') security.declarePublic('getSearchWidgets') def getSearchWidgets(self, package=None, type=None, context=None, nosort=None): """Empty widgets for searching. """ return self.getWidgets(package=package, type=type, context=context, mode='search', nosort=nosort) security.declarePublic('getWidgets') def getWidgets(self, instance=None, package=None, type=None, context=None, mode='edit', fields=None, schemata=None, nosort=None): """Empty widgets for standalone rendering. """ widgets = [] w_keys = {} context = context is not None and context or self instances = instance is not None and [instance] or [] f_names = fields if not instances: for t in self.listTypes(package, type): instance = t('fake_instance') instance._at_is_fake_instance = True wrapped = instance.__of__(context) wrapped.initializeArchetype() instances.append(wrapped) for instance in instances: if schemata is not None: schema = instance.Schemata()[schemata].copy() else: schema = instance.Schema().copy() fields = schema.fields() if mode == 'search': # Include only fields which have an index # XXX duplicate fieldnames may break this, # as different widgets with the same name # on different schemas means only the first # one found will be used indexes = self.portal_catalog.indexes() fields = [ f for f in fields if (f.accessor and not f.accessor in w_keys and f.accessor in indexes) ] if f_names is not None: fields = filter(lambda f: f.getName() in f_names, fields) for field in fields: widget = field.widget field_name = field.getName() accessor = field.getAccessor(instance) if mode == 'search': field.required = False field.addable = False # for ReferenceField if not isinstance(field.vocabulary, DisplayList): field.vocabulary = field.Vocabulary(instance) if '' not in field.vocabulary.keys(): field.vocabulary = DisplayList([('', _(u'at_search_any', default=u'<any>'))]) + \ field.vocabulary widget.populate = False field_name = field.accessor # accessor must be a method which doesn't take an argument # this lambda is facking an accessor accessor = lambda: field.getDefault(instance) w_keys[field_name] = None widgets.append((field_name, WidgetWrapper(field_name=field_name, mode=mode, widget=widget, instance=instance, field=field, accessor=accessor))) if mode == 'search' and nosort == None: widgets.sort() return [widget for name, widget in widgets] security.declarePrivate('_rawEnum') def _rawEnum(self, callback, *args, **kwargs): """Finds all object to check if they are 'referenceable'. """ catalog = getToolByName(self, 'portal_catalog') brains = catalog(dict(id=[])) for b in brains: o = b.getObject() if o is not None: if IBaseObject.providedBy(o): callback(o, *args, **kwargs) else: log('no object for brain: %s:%s' % (b, b.getURL())) security.declareProtected(permissions.View, 'enum') def enum(self, callback, *args, **kwargs): catalog = getToolByName(self, UID_CATALOG) keys = catalog.uniqueValuesFor('UID') for uid in keys: o = self.getObject(uid) if o: callback(o, *args, **kwargs) else: log('No object for %s' % uid) security.declareProtected(permissions.View, 'Content') def Content(self): """Return a list of all the content ids. """ catalog = getToolByName(self, UID_CATALOG) keys = catalog.uniqueValuesFor('UID') results = catalog(dict(UID=keys)) return results # Management Forms security.declareProtected(permissions.ManagePortal, 'manage_doGenerate') def manage_doGenerate(self, sids=(), REQUEST=None): """(Re)generate types. """ schemas = [] for sid in sids: schemas.append(self.getSchema(sid)) for s in schemas: s.generate() if REQUEST: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_workspace') security.declareProtected(permissions.ManagePortal, 'manage_inspect') def manage_inspect(self, UID, REQUEST=None): """Dump some things about an object hook in the debugger for now. """ object = self.getObject(UID) log("uid: %s, schema: %s" % (object, object.Schema())) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_uids') security.declareProtected(permissions.ManagePortal, 'manage_reindex') def manage_reindex(self, REQUEST=None): """Assign UIDs to all basecontent objects. """ def _index(object, archetype_tool): archetype_tool.registerContent(object) self._rawEnum(_index, self) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_uids') security.declareProtected(permissions.ManagePortal, 'index') index = manage_reindex def _listAllTypes(self): """List all types -- either currently known or known to us. """ allTypes = _types.copy() allTypes.update(self._types) return allTypes.keys() security.declareProtected(permissions.ManagePortal, 'getChangedSchema') def getChangedSchema(self): """Returns a list of tuples indicating which schema have changed. Tuples have the form (schema, changed). """ list = [] currentTypes = _types ourTypes = self._types modified = False keys = self._listAllTypes() keys.sort() for t in keys: if t not in ourTypes: # Add it ourTypes[t] = currentTypes[t]['signature'] modified = True list.append((t, 0)) elif t not in currentTypes: # Huh: what shall we do? We remove it -- this might be wrong! del ourTypes[t] modified = True # We do not add an entry because we cannot update # these objects (having no longer type information for them) else: list.append((t, ourTypes[t] != currentTypes[t]['signature'])) if modified: self._p_changed = True return list security.declareProtected(permissions.ManagePortal, 'manage_updateSchema') def manage_updateSchema(self, REQUEST=None, update_all=None, remove_instance_schemas=None): """Make sure all objects' schema are up to date. """ out = StringIO() print >> out, 'Updating schema...' update_types = [] if REQUEST is None: # DM (avoid persistency bug): avoid code duplication update_types = [ti[0] for ti in self.getChangedSchema() if ti[1]] else: # DM (avoid persistency bug): for t in self._listAllTypes(): if REQUEST.form.get(t, False): update_types.append(t) update_all = REQUEST.form.get('update_all', False) remove_instance_schemas = REQUEST.form.get( 'remove_instance_schemas', False) # XXX: Enter this block only when there are types to update! if update_types: # Use the catalog's ZopeFindAndApply method to walk through # all objects in the portal. This works much better than # relying on the catalog to find objects, because an object # may be uncatalogable because of its schema, and then you # can't update it if you require that it be in the catalog. catalog = getToolByName(self, 'portal_catalog') portal = getToolByName(self, 'portal_url').getPortalObject() meta_types = [_types[t]['meta_type'] for t in update_types] if remove_instance_schemas: func_update_changed = self._removeSchemaAndUpdateChangedObject func_update_all = self._removeSchemaAndUpdateObject else: func_update_changed = self._updateChangedObject func_update_all = self._updateObject if update_all: catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types, search_sub=True, apply_func=func_update_all) else: catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types, search_sub=True, apply_func=func_update_changed) for t in update_types: self._types[t] = _types[t]['signature'] self._p_changed = True print >> out, 'Done.' return out.getvalue() # A counter to ensure that in a given interval a subtransaction # commit is done. subtransactioncounter = 0 def _updateObject(self, o, path, remove_instance_schemas=None): o._updateSchema(remove_instance_schemas=remove_instance_schemas) # Subtransactions to avoid eating up RAM when used inside a # 'ZopeFindAndApply' like in manage_updateSchema self.subtransactioncounter += 1 # Only every 250 objects a sub-commit, otherwise it eats up all diskspace if not self.subtransactioncounter % 250: transaction.savepoint(optimistic=True) def _updateChangedObject(self, o, path): if not o._isSchemaCurrent(): self._updateObject(o, path) def _removeSchemaAndUpdateObject(self, o, path): self._updateObject(o, path, remove_instance_schemas=True) def _removeSchemaAndUpdateChangedObject(self, o, path): if not o._isSchemaCurrent(): self._removeSchemaAndUpdateObject(o, path) security.declareProtected(permissions.ManagePortal, 'manage_updateSchema') def manage_migrate(self, REQUEST=None): """Run Extensions.migrations.migrate. """ from Products.Archetypes.Extensions.migrations import migrate out = migrate(self) self.manage_updateSchema() return out # Catalog management security.declareProtected(permissions.View, 'listCatalogs') def listCatalogs(self): """Show the catalog mapping. """ return self.catalog_map security.declareProtected(permissions.ManagePortal, 'manage_updateCatalogs') def manage_updateCatalogs(self, REQUEST=None): """Set the catalog map for meta_type to include the list catalog_names. """ prefix = 'catalog_names_' for key in REQUEST.form.keys(): if key.startswith(prefix): k = key[len(prefix):] v = REQUEST.form.get(key) self.setCatalogsByType(k, v) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_catalogs') security.declareProtected(permissions.ManagePortal, 'setCatalogsByType') def setCatalogsByType(self, portal_type, catalogList): """ associate catalogList with meta_type. (unfortunally not portal_type). catalogList is a list of strings with the ids of the catalogs. Each catalog is has to be a tool, means unique in site root. """ self.catalog_map[portal_type] = catalogList security.declareProtected(permissions.View, 'getCatalogsByType') def getCatalogsByType(self, portal_type): """Return the catalog objects assoicated with a given type. """ catalogs = [] catalog_map = getattr(self, 'catalog_map', None) if catalog_map is not None: names = self.catalog_map.get(portal_type, ['portal_catalog']) else: names = ['portal_catalog'] portal = getToolByName(self, 'portal_url').getPortalObject() for name in names: try: catalogs.append(getToolByName(portal, name)) except (ConflictError, KeyboardInterrupt): raise except Exception, E: log('No tool %s' % name, E) pass return catalogs
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', WWW_DIR) @security.protected(ManagePortal) 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.protected(ManagePortal) def removeElementPolicy(self, element, content_type, REQUEST=None): # Remove 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.protected(ManagePortal) 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.protected(ManagePortal) 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.protected(ManagePortal) def getElementSpec(self, element): # Return an ElementSpec for the given 'element'. return self.element_specs[element].__of__(self) @security.protected(ManagePortal) def addElementSpec(self, element, is_multi_valued, REQUEST=None): # Add 'element' to our list of managed elements. # Don't replace. if element in self.element_specs: 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.protected(ManagePortal) 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.protected(ManagePortal) def listPolicies(self, typ=None): # Show all policies for a given content type. # 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 PluginRegistry(SimpleItem): """ Implement IPluginRegistry as an independent, ZMI-manageable object. o Each plugin type holds an ordered list of ( id, wrapper ) tuples. """ if _HAS_Z3_INTERFACES: if _HAS_Z3_DAV_INTERFACES: implements(IPluginRegistry, IWriteLock) else: implements(IPluginRegistry) __implements__ = (WriteLockInterface, ) else: __implements__ = ( IPluginRegistry, WriteLockInterface, ) security = ClassSecurityInfo() meta_type = 'Plugin Registry' _plugins = None def __init__(self, plugin_type_info): self._plugin_types = [x[0] for x in plugin_type_info] self._plugin_type_info = PersistentMapping() for interface in plugin_type_info: self._plugin_type_info[interface[0]] = { 'id': interface[1], 'title': interface[2], 'description': interface[3] } # # IPluginRegistry implementation # security.declareProtected(ManageUsers, 'listPluginTypeInfo') def listPluginTypeInfo(self): """ See IPluginRegistry. """ result = [] for ptype in self._plugin_types: info = self._plugin_type_info[ptype].copy() info['interface'] = ptype info['methods'] = ptype.names() result.append(info) return result security.declareProtected(ManageUsers, 'listPlugins') def listPlugins(self, plugin_type): """ See IPluginRegistry. """ result = [] parent = aq_parent(aq_inner(self)) for plugin_id in self._getPlugins(plugin_type): plugin = parent._getOb(plugin_id) result.append((plugin_id, plugin)) return result security.declareProtected(ManageUsers, 'getPluginInfo') def getPluginInfo(self, plugin_type): """ See IPluginRegistry. """ plugin_type = self._getInterfaceFromName(plugin_type) return self._plugin_type_info[plugin_type] security.declareProtected(ManageUsers, 'listPluginIds') def listPluginIds(self, plugin_type): """ See IPluginRegistry. """ return self._getPlugins(plugin_type) security.declareProtected(ManageUsers, 'activatePlugin') def activatePlugin(self, plugin_type, plugin_id): """ See IPluginRegistry. """ plugins = list(self._getPlugins(plugin_type)) if plugin_id in plugins: raise KeyError, 'Duplicate plugin id: %s' % plugin_id parent = aq_parent(aq_inner(self)) plugin = parent._getOb(plugin_id) satisfies = getattr(plugin_type, 'providedBy', None) if satisfies is None: satisfies = plugin_type.isImplementedBy if not satisfies(plugin): raise ValueError, 'Plugin does not implement %s' % plugin_type plugins.append(plugin_id) self._plugins[plugin_type] = tuple(plugins) security.declareProtected(ManageUsers, 'deactivatePlugin') def deactivatePlugin(self, plugin_type, plugin_id): """ See IPluginRegistry. """ plugins = list(self._getPlugins(plugin_type)) if not plugin_id in plugins: raise KeyError, 'Invalid plugin id: %s' % plugin_id plugins = [x for x in plugins if x != plugin_id] self._plugins[plugin_type] = tuple(plugins) security.declareProtected(ManageUsers, 'movePluginsUp') def movePluginsUp(self, plugin_type, ids_to_move): """ See IPluginRegistry. """ ids = list(self._getPlugins(plugin_type)) count = len(ids) indexes = list(map(ids.index, ids_to_move)) indexes.sort() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 - 1 if i2 < 0: # wrap to bottom i2 = len(ids) - 1 ids[i2], ids[i1] = ids[i1], ids[i2] self._plugins[plugin_type] = tuple(ids) security.declareProtected(ManageUsers, 'movePluginsDown') def movePluginsDown(self, plugin_type, ids_to_move): """ See IPluginRegistry. """ ids = list(self._getPlugins(plugin_type)) count = len(ids) indexes = list(map(ids.index, ids_to_move)) indexes.sort() indexes.reverse() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 + 1 if i2 == len(ids): # wrap to top i2 = 0 ids[i2], ids[i1] = ids[i1], ids[i2] self._plugins[plugin_type] = tuple(ids) # # ZMI # arrow_right_gif = ImageFile('www/arrow-right.gif', globals()) arrow_left_gif = ImageFile('www/arrow-left.gif', globals()) arrow_up_gif = ImageFile('www/arrow-up.gif', globals()) arrow_down_gif = ImageFile('www/arrow-down.gif', globals()) security.declareProtected(ManageUsers, 'manage_activatePlugins') def manage_activatePlugins(self, plugin_type, plugin_ids, RESPONSE): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) for id in plugin_ids: self.activatePlugin(interface, id) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type)) security.declareProtected(ManageUsers, 'manage_deactivatePlugins') def manage_deactivatePlugins(self, plugin_type, plugin_ids, RESPONSE): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) for id in plugin_ids: self.deactivatePlugin(interface, id) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type)) security.declareProtected(ManageUsers, 'manage_movePluginsUp') def manage_movePluginsUp(self, plugin_type, plugin_ids, RESPONSE): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) self.movePluginsUp(interface, plugin_ids) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type)) security.declareProtected(ManageUsers, 'manage_movePluginsDown') def manage_movePluginsDown(self, plugin_type, plugin_ids, RESPONSE): """ Shim into ZMI. """ interface = self._getInterfaceFromName(plugin_type) self.movePluginsDown(interface, plugin_ids) RESPONSE.redirect('%s/manage_plugins?plugin_type=%s' % (self.absolute_url(), plugin_type)) security.declareProtected(ManageUsers, 'getAllPlugins') def getAllPlugins(self, plugin_type): """ Return a mapping segregating active / available plugins. 'plugin_type' is the __name__ of the interface. """ interface = self._getInterfaceFromName(plugin_type) active = self._getPlugins(interface) available = [] satisfies = getattr(interface, 'providedBy', None) if satisfies is None: satisfies = interface.isImplementedBy for id, value in aq_parent(aq_inner(self)).objectItems(): if satisfies(value): if id not in active: available.append(id) return {'active': active, 'available': available} security.declareProtected(ManageUsers, 'removePluginById') def removePluginById(self, plugin_id): """ Remove a plugin from any plugin types which have it configured. """ for plugin_type in self._plugin_types: if plugin_id in self._getPlugins(plugin_type): self.deactivatePlugin(plugin_type, plugin_id) security.declareProtected(ManageUsers, 'manage_plugins') manage_plugins = PageTemplateFile('plugins', _wwwdir) security.declareProtected(ManageUsers, 'manage_active') manage_active = PageTemplateFile('active_plugins', _wwwdir) manage_twoLists = PageTemplateFile('two_lists', _wwwdir) manage_options = (( { 'label': 'Plugins', 'action': 'manage_plugins' # , 'help' : ( 'PluggableAuthService' # , 'plugins.stx') }, { 'label': 'Active', 'action': 'manage_active' }) + SimpleItem.manage_options) if _HAS_GENERIC_SETUP: security.declareProtected(ManageUsers, 'manage_exportImportForm') manage_exportImportForm = PageTemplateFile('export_import', _wwwdir) security.declareProtected(ManageUsers, 'getConfigAsXML') def getConfigAsXML(self): """ Return XML representing the registry's configuration. """ from exportimport import PluginRegistryExporter pre = PluginRegistryExporter(self).__of__(self) return pre.generateXML() security.declareProtected(ManageUsers, 'manage_exportImport') def manage_exportImport(self, updated_xml, should_purge, RESPONSE): """ Parse XML and update the registry. """ #XXX encoding? _updatePluginRegistry(self, updated_xml, should_purge) RESPONSE.redirect('%s/manage_exportImportForm' '?manage_tabs_message=Registry+updated.' % self.absolute_url()) security.declareProtected(ManageUsers, 'manage_FTPget') def manage_FTPget(self, REQUEST, RESPONSE): """ """ return self.getConfigAsXML() security.declareProtected(ManageUsers, 'PUT') def PUT(self, REQUEST, RESPONSE): """ """ xml = REQUEST['BODYFILE'].read() _updatePluginRegistry(self, xml, True) manage_options = (manage_options[:2] + ({ 'label': 'Export / Import', 'action': 'manage_exportImportForm' }, ) + manage_options[2:]) # # Helper methods # security.declarePrivate('_getPlugins') def _getPlugins(self, plugin_type): parent = aq_parent(aq_inner(self)) if plugin_type not in self._plugin_types: raise KeyError, plugin_type if self._plugins is None: self._plugins = PersistentMapping() return self._plugins.setdefault(plugin_type, ()) security.declarePrivate('_getInterfaceFromName') def _getInterfaceFromName(self, plugin_type_name): """ Convert the string name to an interface. o Raise KeyError is no such interface is known. """ found = [ x[0] for x in self._plugin_type_info.items() if x[1]['id'] == plugin_type_name ] if not found: raise KeyError, plugin_type_name if len(found) > 1: raise KeyError, 'Waaa!: %s' % plugin_type_name return found[0]
class PluginRegistry( SimpleItem ): """ Implement IPluginRegistry as an independent, ZMI-manageable object. o Each plugin type holds an ordered list of ( id, wrapper ) tuples. """ __implements__ = ( IPluginRegistry, ) security = ClassSecurityInfo() meta_type = 'Plugin Registry' _plugins = None def __init__( self, plugin_type_info ): self._plugin_types = [x[0] for x in plugin_type_info] self._plugin_type_info = PersistentMapping() for interface in plugin_type_info: self._plugin_type_info[interface[0]] = { 'id': interface[1] , 'title': interface[2] , 'description': interface[3] } # # IPluginRegistry implementation # security.declareProtected( ManageUsers, 'listPluginTypeInfo' ) def listPluginTypeInfo( self ): """ See IPluginRegistry. """ result = [] for ptype in self._plugin_types: info = self._plugin_type_info[ptype].copy() info['interface'] = ptype info['methods'] = ptype.names() result.append( info ) return result security.declareProtected( ManageUsers, 'listPlugins' ) def listPlugins( self, plugin_type ): """ See IPluginRegistry. """ result = [] parent = aq_parent( aq_inner( self ) ) for plugin_id in self._getPlugins( plugin_type ): plugin = parent._getOb( plugin_id ) result.append( ( plugin_id, plugin ) ) return result security.declareProtected( ManageUsers, 'getPluginInfo' ) def getPluginInfo( self, plugin_type ): """ See IPluginRegistry. """ plugin_type = self._getInterfaceFromName( plugin_type ) return self._plugin_type_info[plugin_type] security.declareProtected( ManageUsers, 'listPluginInfo' ) def listPluginIds( self, plugin_type ): """ See IPluginRegistry. """ return self._getPlugins( plugin_type ) security.declareProtected( ManageUsers, 'activatePlugin' ) def activatePlugin( self, plugin_type, plugin_id ): """ See IPluginRegistry. """ plugins = list( self._getPlugins( plugin_type ) ) if plugin_id in plugins: raise KeyError, 'Duplicate plugin id: %s' % plugin_id parent = aq_parent( aq_inner( self ) ) plugin = parent._getOb( plugin_id ) if not plugin_type.isImplementedBy(plugin): raise ValueError, 'Plugin does not implement %s' % plugin_type plugins.append( plugin_id ) self._plugins[ plugin_type ] = tuple( plugins ) security.declareProtected( ManageUsers, 'deactivatePlugin' ) def deactivatePlugin( self, plugin_type, plugin_id ): """ See IPluginRegistry. """ plugins = list( self._getPlugins( plugin_type ) ) if not plugin_id in plugins: raise KeyError, 'Invalid plugin id: %s' % plugin_id plugins = [ x for x in plugins if x != plugin_id ] self._plugins[ plugin_type ] = tuple( plugins ) security.declareProtected( ManageUsers, 'movePluginsUp' ) def movePluginsUp( self, plugin_type, ids_to_move ): """ See IPluginRegistry. """ ids = list( self._getPlugins( plugin_type ) ) count = len( ids ) indexes = list( map( ids.index, ids_to_move ) ) indexes.sort() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 - 1 if i2 < 0: # wrap to bottom i2 = len( ids ) - 1 ids[ i2 ], ids[ i1 ] = ids[ i1 ], ids[ i2 ] self._plugins[ plugin_type ] = tuple( ids ) security.declareProtected( ManageUsers, 'movePluginsDown' ) def movePluginsDown( self, plugin_type, ids_to_move ): """ See IPluginRegistry. """ ids = list( self._getPlugins( plugin_type ) ) count = len( ids ) indexes = list( map( ids.index, ids_to_move ) ) indexes.sort() indexes.reverse() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 + 1 if i2 == len( ids ): # wrap to top i2 = 0 ids[ i2 ], ids[ i1 ] = ids[ i1 ], ids[ i2 ] self._plugins[ plugin_type ] = tuple( ids ) # # ZMI # arrow_right_gif = ImageFile( 'www/arrow-right.gif', globals() ) arrow_left_gif = ImageFile( 'www/arrow-left.gif', globals() ) arrow_up_gif = ImageFile( 'www/arrow-up.gif', globals() ) arrow_down_gif = ImageFile( 'www/arrow-down.gif', globals() ) security.declareProtected( ManageUsers, 'manage_activatePlugins' ) def manage_activatePlugins( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) for id in plugin_ids: self.activatePlugin( interface, id ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_deactivatePlugins' ) def manage_deactivatePlugins( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) for id in plugin_ids: self.deactivatePlugin( interface, id ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_movePluginsUp' ) def manage_movePluginsUp( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) self.movePluginsUp( interface, plugin_ids ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_movePluginsDown' ) def manage_movePluginsDown( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) self.movePluginsDown( interface, plugin_ids ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'getAllPlugins' ) def getAllPlugins( self, plugin_type ): """ Return a mapping segregating active / available plugins. 'plugin_type' is the __name__ of the interface. """ interface = self._getInterfaceFromName( plugin_type ) active = self._getPlugins( interface ) available = [] for id, value in aq_parent( aq_inner( self ) ).objectItems(): if interface.isImplementedBy( value ): if id not in active: available.append( id ) return { 'active' : active, 'available' : available } security.declareProtected( ManageUsers, 'removePluginById' ) def removePluginById( self, plugin_id ): """ Remove a plugin from any plugin types which have it configured. """ for plugin_type in self._plugin_types: if plugin_id in self._getPlugins( plugin_type ): self.deactivatePlugin( plugin_type, plugin_id ) security.declareProtected( ManageUsers, 'manage_plugins' ) manage_plugins = PageTemplateFile( 'plugins', _wwwdir ) manage_twoLists = PageTemplateFile( 'two_lists', _wwwdir ) manage_options=( ( { 'label' : 'Plugins' , 'action' : 'manage_plugins' # , 'help' : ( 'PluggableAuthService' # , 'plugins.stx') } , ) + SimpleItem.manage_options ) # # Helper methods # security.declarePrivate( '_getPlugins' ) def _getPlugins( self, plugin_type ): parent = aq_parent( aq_inner( self ) ) if plugin_type not in self._plugin_types: raise KeyError, plugin_type if self._plugins is None: self._plugins = PersistentMapping() return self._plugins.setdefault( plugin_type, () ) security.declarePrivate( '_getInterfaceFromName' ) def _getInterfaceFromName( self, plugin_type_name ): """ Convert the string name to an interface. o Raise KeyError is no such interface is known. """ found = [ x[0] for x in self._plugin_type_info.items() if x[1][ 'id' ] == plugin_type_name ] if not found: raise KeyError, plugin_type_name if len( found ) > 1: raise KeyError, 'Waaa!: %s' % plugin_type_name return found[ 0 ]
class MetadataSchema(SimpleItem): """ Describe a metadata schema. """ security = ClassSecurityInfo() meta_type = 'Metadata Schema' publisher = '' def __init__(self, id, element_specs=()): self._setId(id) self.element_specs = PersistentMapping() for name, is_multi_valued in element_specs: self.element_specs[name] = ElementSpec(is_multi_valued) # # ZMI methods # manage_options = ( ({'label': 'Elements', 'action': 'elementPoliciesForm'},) + SimpleItem.manage_options) security.declareProtected(ManagePortal, 'elementPoliciesForm') elementPoliciesForm = DTMLFile('metadataElementPolicies', _dtmldir) security.declareProtected(ManagePortal, 'addElementPolicy') def addElementPolicy(self, element, content_type, is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary, REQUEST=None): """ Add a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec(element) spec.addPolicy(content_type) policy = spec.getPolicy(content_type) policy.edit(is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+added.') security.declareProtected(ManagePortal, 'removeElementPolicy') def removeElementPolicy(self, element, content_type, REQUEST=None): """ Remvoe a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec(element) spec.removePolicy(content_type) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+removed.') security.declareProtected(ManagePortal, 'updateElementPolicy') def updateElementPolicy(self, element, content_type, is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary, REQUEST=None): """ Update a policy for one of our elements o 'content_type' will be '<default>' when we edit the default. """ if content_type == '<default>': content_type = None spec = self.getElementSpec(element) policy = spec.getPolicy(content_type) policy.edit(is_required, supply_default, default_value, enforce_vocabulary, allowed_vocabulary) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+updated.') # # Element spec manipulation. # security.declareProtected(ManagePortal, 'listElementSpecs') def listElementSpecs(self): """ Return a list of ElementSpecs representing the elements we manage. """ res = [] for k, v in self.element_specs.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal, 'getElementSpec') def getElementSpec(self, element): """ Return an ElementSpec for the given 'element'. """ return self.element_specs[element].__of__(self) security.declareProtected(ManagePortal, 'addElementSpec') def addElementSpec(self, element, is_multi_valued, REQUEST=None): """ Add 'element' to our list of managed elements. """ # Don't replace. if element in self.element_specs: 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 PluginRegistry( SimpleItem ): """ Implement IPluginRegistry as an independent, ZMI-manageable object. o Each plugin type holds an ordered list of ( id, wrapper ) tuples. """ __implements__ = ( IPluginRegistry, ) security = ClassSecurityInfo() meta_type = 'Plugin Registry' _plugins = None def __init__( self, plugin_type_info ): self._plugin_types = [x[0] for x in plugin_type_info] self._plugin_type_info = PersistentMapping() for interface in plugin_type_info: self._plugin_type_info[interface[0]] = { 'id': interface[1] , 'title': interface[2] , 'description': interface[3] } # # IPluginRegistry implementation # security.declareProtected( ManageUsers, 'listPluginTypeInfo' ) def listPluginTypeInfo( self ): """ See IPluginRegistry. """ result = [] for ptype in self._plugin_types: info = self._plugin_type_info[ptype].copy() info['interface'] = ptype info['methods'] = ptype.names() result.append( info ) return result security.declareProtected( ManageUsers, 'listPlugins' ) def listPlugins( self, plugin_type ): """ See IPluginRegistry. """ result = [] parent = aq_parent( aq_inner( self ) ) for plugin_id in self._getPlugins( plugin_type ): plugin = parent._getOb( plugin_id ) result.append( ( plugin_id, plugin ) ) return result security.declareProtected( ManageUsers, 'getPluginInfo' ) def getPluginInfo( self, plugin_type ): """ See IPluginRegistry. """ plugin_type = self._getInterfaceFromName( plugin_type ) return self._plugin_type_info[plugin_type] security.declareProtected( ManageUsers, 'listPluginInfo' ) def listPluginIds( self, plugin_type ): """ See IPluginRegistry. """ return self._getPlugins( plugin_type ) security.declareProtected( ManageUsers, 'activatePlugin' ) def activatePlugin( self, plugin_type, plugin_id ): """ See IPluginRegistry. """ plugins = list( self._getPlugins( plugin_type ) ) if plugin_id in plugins: raise KeyError, 'Duplicate plugin id: %s' % plugin_id parent = aq_parent( aq_inner( self ) ) plugin = parent._getOb( plugin_id ) if not plugin_type.isImplementedBy(plugin): raise ValueError, 'Plugin does not implement %s' % plugin_type plugins.append( plugin_id ) self._plugins[ plugin_type ] = tuple( plugins ) security.declareProtected( ManageUsers, 'deactivatePlugin' ) def deactivatePlugin( self, plugin_type, plugin_id ): """ See IPluginRegistry. """ plugins = list( self._getPlugins( plugin_type ) ) if not plugin_id in plugins: raise KeyError, 'Invalid plugin id: %s' % plugin_id plugins = [ x for x in plugins if x != plugin_id ] self._plugins[ plugin_type ] = tuple( plugins ) security.declareProtected( ManageUsers, 'movePluginsUp' ) def movePluginsUp( self, plugin_type, ids_to_move ): """ See IPluginRegistry. """ ids = list( self._getPlugins( plugin_type ) ) count = len( ids ) indexes = list( map( ids.index, ids_to_move ) ) indexes.sort() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 - 1 if i2 < 0: # wrap to bottom i2 = len( ids ) - 1 ids[ i2 ], ids[ i1 ] = ids[ i1 ], ids[ i2 ] self._plugins[ plugin_type ] = tuple( ids ) security.declareProtected( ManageUsers, 'movePluginsDown' ) def movePluginsDown( self, plugin_type, ids_to_move ): """ See IPluginRegistry. """ ids = list( self._getPlugins( plugin_type ) ) count = len( ids ) indexes = list( map( ids.index, ids_to_move ) ) indexes.sort() indexes.reverse() for i1 in indexes: if i1 < 0 or i1 >= count: raise IndexError, i1 i2 = i1 + 1 if i2 == len( ids ): # wrap to top i2 = 0 ids[ i2 ], ids[ i1 ] = ids[ i1 ], ids[ i2 ] self._plugins[ plugin_type ] = tuple( ids ) # # ZMI # arrow_right_gif = ImageFile( 'www/arrow-right.gif', globals() ) arrow_left_gif = ImageFile( 'www/arrow-left.gif', globals() ) arrow_up_gif = ImageFile( 'www/arrow-up.gif', globals() ) arrow_down_gif = ImageFile( 'www/arrow-down.gif', globals() ) security.declareProtected( ManageUsers, 'manage_activatePlugins' ) def manage_activatePlugins( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) for id in plugin_ids: self.activatePlugin( interface, id ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_deactivatePlugins' ) def manage_deactivatePlugins( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) for id in plugin_ids: self.deactivatePlugin( interface, id ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_movePluginsUp' ) def manage_movePluginsUp( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) self.movePluginsUp( interface, plugin_ids ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'manage_movePluginsDown' ) def manage_movePluginsDown( self , plugin_type , plugin_ids , RESPONSE ): """ Shim into ZMI. """ interface = self._getInterfaceFromName( plugin_type ) self.movePluginsDown( interface, plugin_ids ) RESPONSE.redirect( '%s/manage_plugins?plugin_type=%s' % ( self.absolute_url(), plugin_type ) ) security.declareProtected( ManageUsers, 'getAllPlugins' ) def getAllPlugins( self, plugin_type ): """ Return a mapping segregating active / available plugins. 'plugin_type' is the __name__ of the interface. """ interface = self._getInterfaceFromName( plugin_type ) active = self._getPlugins( interface ) available = [] for id, value in aq_parent( aq_inner( self ) ).objectItems(): if interface.isImplementedBy( value ): if id not in active: available.append( id ) return { 'active' : active, 'available' : available } security.declareProtected( ManageUsers, 'removePluginById' ) def removePluginById( self, plugin_id ): """ Remove a plugin from any plugin types which have it configured. """ for plugin_type in self._plugin_types: if plugin_id in self._getPlugins( plugin_type ): self.deactivatePlugin( plugin_type, plugin_id ) security.declareProtected( ManageUsers, 'manage_plugins' ) manage_plugins = PageTemplateFile( 'plugins', _wwwdir ) manage_twoLists = PageTemplateFile( 'two_lists', _wwwdir ) manage_options=( ( { 'label' : 'Plugins' , 'action' : 'manage_plugins' # , 'help' : ( 'PluggableAuthService' # , 'plugins.stx') } , ) + SimpleItem.manage_options ) # # Helper methods # security.declarePrivate( '_getPlugins' ) def _getPlugins( self, plugin_type ): parent = aq_parent( aq_inner( self ) ) if plugin_type not in self._plugin_types: raise KeyError, plugin_type if self._plugins is None: self._plugins = PersistentMapping() return self._plugins.setdefault( plugin_type, () ) security.declarePrivate( '_getInterfaceFromName' ) def _getInterfaceFromName( self, plugin_type_name ): """ Convert the string name to an interface. o Raise KeyError is no such interface is known. """ found = [ x[0] for x in self._plugin_type_info.items() if x[1][ 'id' ] == plugin_type_name ] if not found: raise KeyError, plugin_type_name if len( found ) > 1: raise KeyError, 'Waaa!: %s' % plugin_type_name return found[ 0 ]
class DiscussionItemContainer(Persistent, Implicit, Traversable): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ implements(IDiscussable, ICallableOpaqueItemEvents) # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId(self): return self.id security.declareProtected(View, 'getReply') def getReply(self, reply_id): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get(reply_id).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent(aq_inner(self)) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).indexObject() security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).notifyWorkflowCreated() security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).unindexObject() # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds(self, spec=None): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r = [] a = r.append g = self._container.get for id in self.objectIds(spec): a((id, g(id))) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # IDiscussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply(self, title, text, Creator=None, text_format='structured-text'): """ Create a reply in the proper place """ id = int(DateTime().timeTime()) while self._container.get(str(id), None) is not None: id = id + 1 id = str(id) item = DiscussionItem(id, title=title, description=title) self._container[id] = item item = item.__of__(self) item.setFormat(text_format) item._edit(text) item.addCreator(Creator) item.setReplyTo(self._getDiscussable()) item.indexObject() item.notifyWorkflowCreated() return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply(self, reply_id): """ Remove a reply from this container """ if reply_id in self._container: reply = self._container.get(reply_id).__of__(self) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() self.deleteReply(my_reply_id) if hasattr(reply, 'unindexObject'): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies(self, content_obj): """ Test to see if there are any dicussion items """ outer = self._getDiscussable(outer=1) if content_obj == outer: return bool(len(self._container)) else: return bool(len(content_obj.talkback._getReplyResults())) security.declareProtected(View, 'replyCount') def replyCount(self, content_obj): """ How many replies do i have? """ outer = self._getDiscussable(outer=1) if content_obj == outer: return len(self._container) else: replies = content_obj.talkback.getReplies() return self._repcount(replies) security.declarePrivate('_repcount') def _repcount(self, replies): """counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount(replies) return count security.declareProtected(View, 'getReplies') def getReplies(self): """ Return a sequence of the IDiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a(self._container.get(id).__of__(self)) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate('_getReplyParent') def _getReplyParent(self, in_reply_to): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable(outer=1) if not in_reply_to: 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 = '' else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a((key, value)) result.sort(lambda a, b: cmp(a[1].creation_date, b[1].creation_date)) return [ x[0] for x in result ]
class NyMessageCatalog(Persistent): """Stores messages and their translations""" implements(INyTranslationCatalog) def __init__(self, id, title, languages=('en', )): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) # We suppose all zope/portal products are written in English # therefore we consider all new messages in English # Default language in Catalog is always 'en', thus it can be different # from the default language of the portal self._default_language = 'en' # self._languages[0] # Here the message translations are stored self._messages = PersistentMapping() self._po_headers = PersistentMapping() ### INyTranslationCatalog def edit_message(self, msgid, lang, translation): # language existance test **not present in Localizer**: if lang not in self.get_languages(): return # Add-by-edit functionality **not present in Localizer**: if not self._message_exists(msgid): self.gettext(msgid, lang) self._messages[msgid][lang] = translation def del_message(self, msgid): """ """ if self._messages.has_key(msgid): del self._messages[msgid] def gettext(self, msgid, lang=None, default=None): """Returns the corresponding translation of msgid in Catalog. """ msgstr = None if not isinstance(msgid, basestring): raise TypeError('Only strings can be translated.') # saving everything unicode, utf-8 elif isinstance(msgid, str): msgstr = msgid msgid = force_to_unicode(msgid) if not lang: raise ValueError("No language provided for gettext") msgid = msgid.strip() # empty message is translated as empty message, regardless of lang if not msgid: return msgid # default `default translation` is the msgid itself if default is None: default = msgid if lang not in self.get_languages(): # we don't have that lang, thus we can't translate and won't add msg return default # Add it if it's not in the dictionary if not self._message_exists(msgid): if msgstr is not None: import logging logger = logging.getLogger(__name__) logger.warn('Got str "%s" in gettext, expecting unicode' % msgstr) self._messages[msgid] = PersistentMapping() update_transaction_note() if not self._messages[msgid].has_key(self._default_language): self._messages[msgid][self._default_language] = default # translation may be blank (supposition), then-> default (usually msgid) in_catalog = self._messages[msgid].get(lang, '') return in_catalog or default def get_languages(self): """Get available languages""" return self._languages def add_language(self, lang): """Add language""" if lang not in self._languages: self._languages = self._languages + (lang, ) def del_language(self, lang): """Delete language with corresponding messages""" if lang not in self.get_languages(): return langlist = list(self._languages) langlist.pop(langlist.index(lang)) self._languages = tuple(langlist) def clear(self): """Erase all messages""" self._messages.clear() def messages(self): """ Returns a generator used for catalog entries iteration. """ for (msgid, translations_dict) in self._messages.items(): yield (msgid, translations_dict) def _message_exists(self, message): return self._messages.has_key(message) ### Dictionary-like API def __getitem__(self, key): return self._messages[key] def __delitem__(self, key): del self._messages[key]
class Resource(Persistent): security = ClassSecurityInfo() def __init__(self, id, **kwargs): self._data = PersistentMapping() extres = id.startswith("http://") or id.startswith("https://") if id.startswith("/") or id.endswith("/") or ("//" in id and not extres): raise ValueError("Invalid Resource ID: %s" % id) self._data["id"] = id expression = kwargs.get("expression", "") self.setExpression(expression) self._data["authenticated"] = kwargs.get("authenticated", False) self._data["enabled"] = kwargs.get("enabled", True) self._data["cookable"] = kwargs.get("cookable", True) self._data["cacheable"] = kwargs.get("cacheable", True) self._data["conditionalcomment"] = kwargs.get("conditionalcomment", "") self._data["bundle"] = kwargs.get("bundle", "default") self.isExternal = extres if extres: self._data["cacheable"] = False # External resources are NOT cacheable self._data["cookable"] = False # External resources are NOT mergable def copy(self): result = self.__class__(self.getId()) for key, value in self._data.items(): if key != "id": result._data[key] = value return result security.declarePublic("getId") def getId(self): return self._data["id"] security.declarePublic("getQuotedId") def getQuotedId(self): return quote_plus(self._data["id"]) security.declareProtected(permissions.ManagePortal, "_setId") def _setId(self, id): if id.startswith("/") or id.endswith("/") or (("//" in id) and not self.isExternalResource()): raise ValueError("Invalid Resource ID: %s" % id) self._data["id"] = id security.declarePublic("getCookedExpression") def getCookedExpression(self): # Automatic inline migration of expressions if "cooked_expression" not in self._data: expr = Expression(self._data["expression"]) self._data["cooked_expression"] = expr return self._data["cooked_expression"] security.declarePublic("getExpression") def getExpression(self): return self._data["expression"] security.declareProtected(permissions.ManagePortal, "setExpression") def setExpression(self, expression): # Update the cooked expression self._data["cooked_expression"] = Expression(expression) self._data["expression"] = expression security.declarePublic("getAuthenticated") def getAuthenticated(self): # Automatic inline migration if "authenticated" not in self._data: self._data["authenticated"] = False return bool(self._data["authenticated"]) security.declareProtected(permissions.ManagePortal, "setAuthenticated") def setAuthenticated(self, authenticated): self._data["authenticated"] = authenticated security.declarePublic("getEnabled") def getEnabled(self): return bool(self._data["enabled"]) security.declareProtected(permissions.ManagePortal, "setEnabled") def setEnabled(self, enabled): self._data["enabled"] = enabled security.declarePublic("getCookable") def getCookable(self): return self._data["cookable"] security.declareProtected(permissions.ManagePortal, "setCookable") def setCookable(self, cookable): if self.isExternalResource() and cookable: raise ValueError("External Resources cannot be merged") self._data["cookable"] = cookable security.declarePublic("getCacheable") def getCacheable(self): # as this is a new property, old instance might not have that value, so # return True as default return self._data.get("cacheable", True) security.declareProtected(permissions.ManagePortal, "setCacheable") def setCacheable(self, cacheable): if self.isExternalResource() and cacheable: raise ValueError("External Resources are not cacheable") self._data["cacheable"] = cacheable security.declarePublic("getConditionalcomment") def getConditionalcomment(self): # New property, return blank if the old instance doesn't have that value return self._data.get("conditionalcomment", "") security.declareProtected(permissions.ManagePortal, "setConditionalcomment") def setConditionalcomment(self, conditionalcomment): self._data["conditionalcomment"] = conditionalcomment security.declarePublic("isExternalResource") def isExternalResource(self): return getattr(self, "isExternal", False) security.declarePublic("getBundle") def getBundle(self): return self._data.get("bundle", None) or "default" security.declareProtected(permissions.ManagePortal, "setBundle") def setBundle(self, bundle): self._data["bundle"] = bundle
class TransformTool(UniqueObject, ActionProviderBase, Folder): id = 'portal_transforms' meta_type = id.title().replace('_', ' ') isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ( {'name': 'Transform', 'action': 'manage_addTransformForm'}, {'name': 'TransformsChain', 'action': 'manage_addTransformsChainForm'}, ) manage_addTransformForm = PageTemplateFile('addTransform', _www) manage_addTransformsChainForm = PageTemplateFile( 'addTransformsChain', _www) manage_cacheForm = PageTemplateFile('setCacheTime', _www) manage_editTransformationPolicyForm = PageTemplateFile( 'editTransformationPolicy', _www) manage_reloadAllTransforms = PageTemplateFile('reloadAllTransforms', _www) manage_options = ( (Folder.manage_options[0], ) + Folder.manage_options[2:] + ({'label': 'Caches', 'action': 'manage_cacheForm'}, {'label': 'Policy', 'action': 'manage_editTransformationPolicyForm'}, {'label': 'Reload transforms', 'action': 'manage_reloadAllTransforms'}, )) security = ClassSecurityInfo() def __init__(self, policies=None, max_sec_in_cache=3600): self._mtmap = PersistentMapping() self._policies = policies or PersistentMapping() self.max_sec_in_cache = max_sec_in_cache self._new_style_pt = 1 # mimetype oriented conversions (iengine interface) @security.private def unregisterTransform(self, name): """ unregister a transform name is the name of a registered transform """ self._unmapTransform(getattr(self, name)) if name in self.objectIds(): self._delObject(name) @security.public def convertTo(self, target_mimetype, orig, data=None, object=None, usedby=None, context=None, **kwargs): """Convert orig to a given mimetype * orig is an encoded string * data an optional IDataStream object. If None a new datastream will be created and returned * optional object argument is the object on which is bound the data. If present that object will be used by the engine to bound cached data. * additional arguments (kwargs) will be passed to the transformations. Some usual arguments are : filename, mimetype, encoding return an object implementing IDataStream or None if no path has been found. """ target_mimetype = str(target_mimetype) if object is not None: cache = Cache(object, context=context) data = cache.getCache(target_mimetype) if data is not None: time, data = data if self.max_sec_in_cache == 0 or time < self.max_sec_in_cache: return data if data is None: data = self._wrap(target_mimetype) registry = getToolByName(self, 'mimetypes_registry') if not getattr(aq_base(registry), 'classify', None): # avoid problems when importing a site with an old mimetype # registry return None orig_mt = registry.classify(orig, mimetype=kwargs.get('mimetype'), filename=kwargs.get('filename')) orig_mt = str(orig_mt) if not orig_mt: log('Unable to guess input mime type (filename=%s, mimetype=%s)' % (kwargs.get('mimetype'), kwargs.get('filename')), severity=DEBUG) return None target_mt = registry.lookup(target_mimetype) if target_mt: target_mt = target_mt[0] else: log('Unable to match target mime type %s' % str(target_mimetype), severity=DEBUG) return None # fastpath # If orig_mt and target_mt are the same, we only allow # a one-hop transform, a.k.a. filter. # XXX disabled filtering for now if orig_mt == str(target_mt): data.setData(orig) md = data.getMetadata() md['mimetype'] = str(orig_mt) if object is not None: cache.setCache(str(target_mimetype), data) return data # get a path to output mime type requirements = self._policies.get(str(target_mt), []) path = self._findPath(orig_mt, target_mt, list(requirements)) if not path and requirements: log('Unable to satisfy requirements %s' % ', '.join(requirements), severity=DEBUG) path = self._findPath(orig_mt, target_mt) if not path: log('NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path), severity=DEBUG) return None if len(path) > 1: # create a chain on the fly (sly) transform = chain() for t in path: transform.registerTransform(t) else: transform = path[0] result = transform.convert(orig, data, context=context, usedby=usedby, **kwargs) self._setMetaData(result, transform) # set cache if possible if object is not None and result.isCacheable(): cache.setCache(str(target_mimetype), result) # return IDataStream object return result # make sure it's not publishable (XSS risk) del convertTo.__doc__ @security.public def convertToData(self, target_mimetype, orig, data=None, object=None, usedby=None, context=None, **kwargs): # Convert to a given mimetype and return the raw data # ignoring subobjects. see convertTo for more information data = self.convertTo(target_mimetype, orig, data, object, usedby, context, **kwargs) if data: return data.getData() return None @security.public def convert(self, name, orig, data=None, context=None, **kwargs): # run a tranform of a given name on data # * name is the name of a registered transform # see convertTo docstring for more info if not data: data = self._wrap(name) try: transform = getattr(self, name) except AttributeError: raise Exception('No such transform "%s"' % name) data = transform.convert(orig, data, context=context, **kwargs) self._setMetaData(data, transform) return data def __call__(self, name, orig, data=None, context=None, **kwargs): # run a transform by its name, returning the raw data product # * name is the name of a registered transform. # return an encoded string. # see convert docstring for more info on additional arguments. data = self.convert(name, orig, data, context, **kwargs) return data.getData() # utilities ############################################################### def _setMetaData(self, datastream, transform): """set metadata on datastream according to the given transform (mime type and optionaly encoding) """ md = datastream.getMetadata() if hasattr(transform, 'output_encoding'): md['encoding'] = transform.output_encoding md['mimetype'] = transform.output def _wrap(self, name): """wrap a data object in an icache""" return datastream(name) def _unwrap(self, data): """unwrap data from an icache""" if IDataStream.providedBy(data): data = data.getData() return data def _mapTransform(self, transform): """map transform to internal structures""" registry = getToolByName(self, 'mimetypes_registry') inputs = getattr(transform, 'inputs', None) if not inputs: raise TransformException('Bad transform %s : no input MIME type' % (transform)) for i in inputs: mts = registry.lookup(i) if not mts: msg = 'Input MIME type %r for transform %s is not registered '\ 'in the MIME types registry' % (i, transform.name()) raise TransformException(msg) for mti in mts: for mt in mti.mimetypes: mt_in = self._mtmap.setdefault(mt, PersistentMapping()) output = getattr(transform, 'output', None) if not output: msg = 'Bad transform %s : no output MIME type' raise TransformException(msg % transform.name()) mto = registry.lookup(output) if not mto: msg = 'Output MIME type %r for transform %s is not '\ 'registered in the MIME types registry' % \ (output, transform.name()) raise TransformException(msg) if len(mto) > 1: msg = ("Wildcarding not allowed in transform's output " "MIME type") raise TransformException(msg) for mt2 in mto[0].mimetypes: try: if transform not in mt_in[mt2]: mt_in[mt2].append(transform) except KeyError: mt_in[mt2] = PersistentList([transform]) def _unmapTransform(self, transform): """unmap transform from internal structures""" registry = getToolByName(self, 'mimetypes_registry') for i in transform.inputs: for mti in registry.lookup(i): for mt in mti.mimetypes: mt_in = self._mtmap.get(mt, {}) output = transform.output mto = registry.lookup(output) for mt2 in mto[0].mimetypes: l = mt_in[mt2] for i in range(len(l)): if transform.name() == l[i].name(): l.pop(i) break else: log('Can\'t find transform %s from %s to %s' % ( transform.name(), mti, mt), severity=DEBUG) def _findPath(self, orig, target, required_transforms=()): """return the shortest path for transformation from orig mimetype to target mimetype """ if not self._mtmap: return None orig = str(orig) target = str(target) # First, let's deal with required transforms. if required_transforms: # Let's decompose paths, then. required_transform = required_transforms.pop(0) # The first path must lead to one of the inputs supported # by this first required transform. # Which input types are supported by this transform ? supportedInputs = {} for input, outputs in self._mtmap.items(): for output, transforms in outputs.items(): for transform in transforms: if transform.name() == required_transform: supportedInputs[input] = 'ok' # BTW, let's remember the output type transformOutput = output # and remember the transform, it is # useful later requiredTransform = transform # Which of these inputs will be reachable with the # shortest path ? shortest = 9999 # big enough, I guess shortestFirstPath = None for supportedInput in supportedInputs.keys(): # We start from orig firstOrig = orig # And want to reach supportedInput firstTarget = supportedInput # What's the shortest path ? firstPath = self._findPath(firstOrig, firstTarget) if firstPath is not None: if len(firstPath) < shortest: # Here is a path which is shorter than others # which also reach the required transform. shortest = len(firstPath) shortestFirstPath = firstPath if shortestFirstPath is None: return None # there is no path leading to this transform # Then we have to take this transform. secondPath = [requiredTransform] # From the output of this transform, we then have to # reach our target, possible through other required # transforms. thirdOrig = transformOutput thirdTarget = target thirdPath = self._findPath(thirdOrig, thirdTarget, required_transforms) if thirdPath is None: return None # no path # Final result is the concatenation of these 3 parts return shortestFirstPath + secondPath + thirdPath if orig == target: return [] # Now let's efficiently find the shortest path from orig # to target (without required transforms). # The overall idea is that we build all possible paths # starting from orig and of given length. And we increment # this length until one of these paths reaches our target or # until all reachable types have been reached. currentPathLength = 0 pathToType = {orig: []} # all paths we know, by end of path. def typesWithPathOfLength(length): '''Returns the lists of known paths of a given length''' result = [] for type_, path in pathToType.items(): if len(path) == length: result.append(type_) return result # We will start exploring paths which start from types # reachable in zero steps. That is paths which start from # orig. typesToStartFrom = typesWithPathOfLength(currentPathLength) # Explore paths while there are new paths to be explored while len(typesToStartFrom) > 0: for startingType in typesToStartFrom: # Where can we go in one step starting from here ? outputs = self._mtmap.get(startingType) if outputs: for reachedType, transforms in outputs.items(): # Does this lead to a type we never reached before ? if reachedType not in pathToType.keys() and transforms: # Yes, we did not know any path reaching this type # Let's remember the path to here pathToType[reachedType] = ( pathToType[startingType] + [transforms[0]]) if reachedType == target: # This is the first time we reach our target. # We have our shortest path to target. return pathToType[target] # We explored all possible paths of length currentPathLength # Let's increment that length. currentPathLength += 1 # What are the next types to start from ? typesToStartFrom = typesWithPathOfLength(currentPathLength) # We are done exploring paths starting from orig # and this exploration did not reach our target. # Hence there is no path from orig to target. return None def _getPaths(self, orig, target, requirements, path=None, result=None): """return some of the paths for transformation from orig mimetype to target mimetype with the guarantee that the shortest path is included. If target is the same as orig, then returns an empty path. """ shortest = 9999 if result: for okPath in result: shortest = min(shortest, len(okPath)) if orig == target: return [[]] if path is None: result = [] path = [] requirements = list(requirements) outputs = self._mtmap.get(orig) if outputs is None: return result registry = getToolByName(self, 'mimetypes_registry') mto = registry.lookup(target) # target mimetype aliases target_aliases = mto[0].mimetypes path.append(None) for o_mt, transforms in outputs.items(): for transform in transforms: required = 0 name = transform.name() if name in requirements: requirements.remove(name) required = 1 if transform in path: # avoid infinite loop... continue path[-1] = transform if o_mt in target_aliases: if not requirements: result.append(path[:]) if len(path[:]) < shortest: # here is a shorter one ! shortest = len(path) else: if len(path) < shortest: # keep exploring this path, it is still short enough self._getPaths(o_mt, target, requirements, path, result) if required: requirements.append(name) path.pop() return result @security.private def manage_afterAdd(self, item, container): """ overload manage_afterAdd to finish initialization when the transform tool is added """ Folder.manage_afterAdd(self, item, container) try: initialize(self) except TransformException: # may fail on copy or zexp import pass @security.protected(ManagePortal) def manage_addTransform(self, id, module, REQUEST=None): """ add a new transform to the tool """ transform = Transform(id, module) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_addTransformsChain(self, id, description, REQUEST=None): """ add a new transform to the tool """ transform = TransformsChain(id, description) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_setCacheValidityTime(self, seconds, REQUEST=None): """set the lifetime of cached data in seconds""" self.max_sec_in_cache = int(seconds) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def reloadTransforms(self, ids=()): """ reload transforms with the given ids if no ids, reload all registered transforms return a list of (transform_id, transform_module) describing reloaded transforms """ if not ids: ids = self.objectIds() reloaded = [] for id in ids: o = getattr(self, id) o.reload() reloaded.append((id, o.module)) return reloaded # Policy handling methods def manage_addPolicy(self, output_mimetype, required_transforms, REQUEST=None): """ add a policy for a given output mime types""" registry = getToolByName(self, 'mimetypes_registry') if not registry.lookup(output_mimetype): raise TransformException('Unknown MIME type') if output_mimetype in self._policies: msg = 'A policy for output %s is yet defined' % output_mimetype raise TransformException(msg) required_transforms = tuple(required_transforms) self._policies[output_mimetype] = required_transforms if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_editTransformationPolicyForm') def manage_delPolicies(self, outputs, REQUEST=None): """ remove policies for given output mime types""" for mimetype in outputs: del self._policies[mimetype] if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_editTransformationPolicyForm') def listPolicies(self): """ return the list of defined policies a policy is a 2-uple (output_mime_type, [list of required transforms]) """ # XXXFIXME: backward compat, should be removed latter if not hasattr(self, '_policies'): self._policies = PersistentMapping() return self._policies.items() # mimetype oriented conversions (iengine interface) @security.private def registerTransform(self, transform): """register a new transform transform isn't a Zope Transform (the wrapper) but the wrapped transform the persistence wrapper will be created here """ # needed when call from transform.transforms.initialize which # register non zope transform module = str(transform.__module__) transform = Transform(transform.name(), module, transform) if not ITransform.providedBy(transform): raise TransformException('%s does not implement ITransform' % transform) name = transform.name() __traceback_info__ = (name, transform) if name not in self.objectIds(): self._setObject(name, transform) self._mapTransform(transform) @security.protected(ManagePortal) def ZopeFind(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.ZopeFind(self, *args, **kwargs) except MissingBinary: log('ZopeFind: catched MissingBinary exception') @security.protected(View) def objectItems(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.objectItems(self, *args, **kwargs) except MissingBinary: log('objectItems: catched MissingBinary exception') return [] # available mimetypes #################################################### def listAvailableTextInputs(self): """Returns a list of mimetypes that can be used as input for textfields by building a list of the inputs beginning with "text/" of all transforms. """ available_types = [] candidate_transforms = [object[1] for object in self.objectItems()] for candidate in candidate_transforms: for input in candidate.inputs: if input.startswith("text/") and input not in available_types: available_types.append(input) return available_types
class Resource(Persistent): security = ClassSecurityInfo() def __init__(self, id, **kwargs): self._data = PersistentMapping() extres = id.startswith('http://') or id.startswith('https://') if id.startswith('/') or id.endswith('/') or ('//' in id and not extres): raise ValueError("Invalid Resource ID: %s" % id) self._data['id'] = id expression = kwargs.get('expression', '') self.setExpression(expression) self._data['authenticated'] = kwargs.get('authenticated', False) self._data['enabled'] = kwargs.get('enabled', True) self._data['cookable'] = kwargs.get('cookable', True) self._data['cacheable'] = kwargs.get('cacheable', True) self._data['conditionalcomment'] = kwargs.get('conditionalcomment','') self._data['bundle'] = kwargs.get('bundle', 'default') self.isExternal = extres if extres: self._data['cacheable'] = False #External resources are NOT cacheable self._data['cookable'] = False #External resources are NOT mergable def copy(self): result = self.__class__(self.getId()) for key, value in self._data.items(): if key != 'id': result._data[key] = value return result security.declarePublic('getId') def getId(self): return self._data['id'] security.declarePublic('getQuotedId') def getQuotedId(self): return quote_plus(self._data['id']) security.declareProtected(permissions.ManagePortal, '_setId') def _setId(self, id): if id.startswith('/') or id.endswith('/') or ( ('//' in id) and not self.isExternalResource()): raise ValueError("Invalid Resource ID: %s" %id) self._data['id'] = id security.declarePublic('getCookedExpression') def getCookedExpression(self): # Automatic inline migration of expressions if 'cooked_expression' not in self._data: expr = Expression(self._data['expression']) self._data['cooked_expression'] = expr return self._data['cooked_expression'] security.declarePublic('getExpression') def getExpression(self): return self._data['expression'] security.declareProtected(permissions.ManagePortal, 'setExpression') def setExpression(self, expression): # Update the cooked expression self._data['cooked_expression'] = Expression( expression ) self._data['expression'] = expression security.declarePublic('getAuthenticated') def getAuthenticated(self): # Automatic inline migration if 'authenticated' not in self._data: self._data['authenticated'] = False return bool(self._data['authenticated']) security.declareProtected(permissions.ManagePortal, 'setAuthenticated') def setAuthenticated(self, authenticated): self._data['authenticated'] = authenticated security.declarePublic('getEnabled') def getEnabled(self): return bool(self._data['enabled']) security.declareProtected(permissions.ManagePortal, 'setEnabled') def setEnabled(self, enabled): self._data['enabled'] = enabled security.declarePublic('getCookable') def getCookable(self): return self._data['cookable'] security.declareProtected(permissions.ManagePortal, 'setCookable') def setCookable(self, cookable): if self.isExternalResource() and cookable: raise ValueError("External Resources cannot be merged") self._data['cookable'] = cookable security.declarePublic('getCacheable') def getCacheable(self): # as this is a new property, old instance might not have that value, so # return True as default return self._data.get('cacheable', True) security.declareProtected(permissions.ManagePortal, 'setCacheable') def setCacheable(self, cacheable): if self.isExternalResource() and cacheable: raise ValueError("External Resources are not cacheable") self._data['cacheable'] = cacheable security.declarePublic('getConditionalcomment') def getConditionalcomment(self): # New property, return blank if the old instance doesn't have that value return self._data.get('conditionalcomment','') security.declareProtected(permissions.ManagePortal, 'setConditionalcomment') def setConditionalcomment(self, conditionalcomment): self._data['conditionalcomment'] = conditionalcomment security.declarePublic('isExternalResource') def isExternalResource(self): return getattr(self, 'isExternal', False) security.declarePublic('getBundle') def getBundle(self): return self._data.get('bundle', None) or 'default' security.declareProtected(permissions.ManagePortal, 'setBundle') def setBundle(self, bundle): self._data['bundle'] = bundle
class TransformTool(UniqueObject, ActionProviderBase, Folder): id = 'portal_transforms' meta_type = id.title().replace('_', ' ') isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ( { 'name': 'Transform', 'action': 'manage_addTransformForm' }, { 'name': 'TransformsChain', 'action': 'manage_addTransformsChainForm' }, ) manage_addTransformForm = PageTemplateFile('addTransform', _www) manage_addTransformsChainForm = PageTemplateFile('addTransformsChain', _www) manage_cacheForm = PageTemplateFile('setCacheTime', _www) manage_editTransformationPolicyForm = PageTemplateFile( 'editTransformationPolicy', _www) manage_reloadAllTransforms = PageTemplateFile('reloadAllTransforms', _www) manage_options = ((Folder.manage_options[0], ) + Folder.manage_options[2:] + ( { 'label': 'Caches', 'action': 'manage_cacheForm' }, { 'label': 'Policy', 'action': 'manage_editTransformationPolicyForm' }, { 'label': 'Reload transforms', 'action': 'manage_reloadAllTransforms' }, )) security = ClassSecurityInfo() def __init__(self, policies=None, max_sec_in_cache=3600): self._mtmap = PersistentMapping() self._policies = policies or PersistentMapping() self.max_sec_in_cache = max_sec_in_cache self._new_style_pt = 1 # mimetype oriented conversions (iengine interface) @security.private def unregisterTransform(self, name): """ unregister a transform name is the name of a registered transform """ self._unmapTransform(getattr(self, name)) if name in self.objectIds(): self._delObject(name) @security.public def convertTo(self, target_mimetype, orig, data=None, object=None, usedby=None, context=None, **kwargs): """Convert orig to a given mimetype * orig is a native string * data an optional IDataStream object. If None a new datastream will be created and returned * optional object argument is the object on which is bound the data. If present that object will be used by the engine to bound cached data. * additional arguments (kwargs) will be passed to the transformations. Some usual arguments are : filename, mimetype, encoding return an object implementing IDataStream or None if no path has been found. """ target_mimetype = str(target_mimetype) if object is not None: cache = Cache(object, context=context) data = cache.getCache(target_mimetype) if data is not None: time, data = data if self.max_sec_in_cache == 0 or time < self.max_sec_in_cache: return data if data is None: data = self._wrap(target_mimetype) registry = getToolByName(self, 'mimetypes_registry') if not getattr(aq_base(registry), 'classify', None): # avoid problems when importing a site with an old mimetype # registry return None orig_mt = registry.classify(orig, mimetype=kwargs.get('mimetype'), filename=kwargs.get('filename')) orig_mt = str(orig_mt) if not orig_mt: log('Unable to guess input mime type (filename=%s, mimetype=%s)' % (kwargs.get('mimetype'), kwargs.get('filename')), severity=DEBUG) return None target_mt = registry.lookup(target_mimetype) if target_mt: target_mt = target_mt[0] else: log('Unable to match target mime type %s' % str(target_mimetype), severity=DEBUG) return None # fastpath # If orig_mt and target_mt are the same, we only allow # a one-hop transform, a.k.a. filter. # XXX disabled filtering for now if orig_mt == str(target_mt): data.setData(orig) md = data.getMetadata() md['mimetype'] = str(orig_mt) if object is not None: cache.setCache(str(target_mimetype), data) return data # get a path to output mime type requirements = self._policies.get(str(target_mt), []) path = self._findPath(orig_mt, target_mt, list(requirements)) if not path and requirements: log('Unable to satisfy requirements %s' % ', '.join(requirements), severity=DEBUG) path = self._findPath(orig_mt, target_mt) if not path: log('NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path), severity=DEBUG) return None if len(path) > 1: # create a chain on the fly (sly) transform = chain() for t in path: transform.registerTransform(t) else: transform = path[0] result = transform.convert(orig, data, context=context, usedby=usedby, **kwargs) self._setMetaData(result, transform) # set cache if possible if object is not None and result.isCacheable(): cache.setCache(str(target_mimetype), result) # return IDataStream object return result # make sure it's not publishable (XSS risk) del convertTo.__doc__ @security.public def convertToData(self, target_mimetype, orig, data=None, object=None, usedby=None, context=None, **kwargs): # Convert to a given mimetype and return the raw data # ignoring subobjects. see convertTo for more information data = self.convertTo(target_mimetype, orig, data, object, usedby, context, **kwargs) if data: return data.getData() return None @security.public def convert(self, name, orig, data=None, context=None, **kwargs): # run a tranform of a given name on data # * name is the name of a registered transform # see convertTo docstring for more info if not data: data = self._wrap(name) try: transform = getattr(self, name) except AttributeError: raise Exception('No such transform "%s"' % name) data = transform.convert(orig, data, context=context, **kwargs) self._setMetaData(data, transform) return data def __call__(self, name, orig, data=None, context=None, **kwargs): # run a transform by its name, returning the raw data product # * name is the name of a registered transform. # return an encoded string. # see convert docstring for more info on additional arguments. data = self.convert(name, orig, data, context, **kwargs) return data.getData() # utilities ############################################################### def _setMetaData(self, datastream, transform): """set metadata on datastream according to the given transform (mime type and optionaly encoding) """ md = datastream.getMetadata() if hasattr(transform, 'output_encoding'): md['encoding'] = transform.output_encoding md['mimetype'] = transform.output def _wrap(self, name): """wrap a data object in an icache""" return datastream(name) def _unwrap(self, data): """unwrap data from an icache""" if IDataStream.providedBy(data): data = data.getData() return data def _mapTransform(self, transform): """map transform to internal structures""" registry = getToolByName(self, 'mimetypes_registry') inputs = getattr(transform, 'inputs', None) if not inputs: raise TransformException('Bad transform %s : no input MIME type' % (transform)) for i in inputs: mts = registry.lookup(i) if not mts: msg = 'Input MIME type %r for transform %s is not registered '\ 'in the MIME types registry' % (i, transform.name()) raise TransformException(msg) for mti in mts: for mt in mti.mimetypes: mt_in = self._mtmap.setdefault(mt, PersistentMapping()) output = getattr(transform, 'output', None) if not output: msg = 'Bad transform %s : no output MIME type' raise TransformException(msg % transform.name()) mto = registry.lookup(output) if not mto: msg = 'Output MIME type %r for transform %s is not '\ 'registered in the MIME types registry' % \ (output, transform.name()) raise TransformException(msg) if len(mto) > 1: msg = ("Wildcarding not allowed in transform's output " "MIME type") raise TransformException(msg) for mt2 in mto[0].mimetypes: try: if transform not in mt_in[mt2]: mt_in[mt2].append(transform) except KeyError: mt_in[mt2] = PersistentList([transform]) def _unmapTransform(self, transform): """unmap transform from internal structures""" registry = getToolByName(self, 'mimetypes_registry') for i in transform.inputs: for mti in registry.lookup(i): for mt in mti.mimetypes: mt_in = self._mtmap.get(mt, {}) output = transform.output mto = registry.lookup(output) for mt2 in mto[0].mimetypes: l = mt_in[mt2] for i in range(len(l)): if transform.name() == l[i].name(): l.pop(i) break else: log('Can\'t find transform %s from %s to %s' % (transform.name(), mti, mt), severity=DEBUG) def _findPath(self, orig, target, required_transforms=()): """return the shortest path for transformation from orig mimetype to target mimetype """ if not self._mtmap: return None orig = str(orig) target = str(target) # First, let's deal with required transforms. if required_transforms: # Let's decompose paths, then. required_transform = required_transforms.pop(0) # The first path must lead to one of the inputs supported # by this first required transform. # Which input types are supported by this transform ? supportedInputs = {} for input, outputs in self._mtmap.items(): for output, transforms in outputs.items(): for transform in transforms: if transform.name() == required_transform: supportedInputs[input] = 'ok' # BTW, let's remember the output type transformOutput = output # and remember the transform, it is # useful later requiredTransform = transform # Which of these inputs will be reachable with the # shortest path ? shortest = 9999 # big enough, I guess shortestFirstPath = None for supportedInput in supportedInputs.keys(): # We start from orig firstOrig = orig # And want to reach supportedInput firstTarget = supportedInput # What's the shortest path ? firstPath = self._findPath(firstOrig, firstTarget) if firstPath is not None: if len(firstPath) < shortest: # Here is a path which is shorter than others # which also reach the required transform. shortest = len(firstPath) shortestFirstPath = firstPath if shortestFirstPath is None: return None # there is no path leading to this transform # Then we have to take this transform. secondPath = [requiredTransform] # From the output of this transform, we then have to # reach our target, possible through other required # transforms. thirdOrig = transformOutput thirdTarget = target thirdPath = self._findPath(thirdOrig, thirdTarget, required_transforms) if thirdPath is None: return None # no path # Final result is the concatenation of these 3 parts return shortestFirstPath + secondPath + thirdPath if orig == target: return [] # Now let's efficiently find the shortest path from orig # to target (without required transforms). # The overall idea is that we build all possible paths # starting from orig and of given length. And we increment # this length until one of these paths reaches our target or # until all reachable types have been reached. currentPathLength = 0 pathToType = {orig: []} # all paths we know, by end of path. def typesWithPathOfLength(length): '''Returns the lists of known paths of a given length''' result = [] for type_, path in pathToType.items(): if len(path) == length: result.append(type_) return result # We will start exploring paths which start from types # reachable in zero steps. That is paths which start from # orig. typesToStartFrom = typesWithPathOfLength(currentPathLength) # Explore paths while there are new paths to be explored while len(typesToStartFrom) > 0: for startingType in typesToStartFrom: # Where can we go in one step starting from here ? outputs = self._mtmap.get(startingType) if outputs: for reachedType, transforms in outputs.items(): # Does this lead to a type we never reached before ? if reachedType not in six.iterkeys( pathToType) and transforms: # noqa # Yes, we did not know any path reaching this type # Let's remember the path to here pathToType[reachedType] = ( pathToType[startingType] + [transforms[0]]) if reachedType == target: # This is the first time we reach our target. # We have our shortest path to target. return pathToType[target] # We explored all possible paths of length currentPathLength # Let's increment that length. currentPathLength += 1 # What are the next types to start from ? typesToStartFrom = typesWithPathOfLength(currentPathLength) # We are done exploring paths starting from orig # and this exploration did not reach our target. # Hence there is no path from orig to target. return None def _getPaths(self, orig, target, requirements, path=None, result=None): """return some of the paths for transformation from orig mimetype to target mimetype with the guarantee that the shortest path is included. If target is the same as orig, then returns an empty path. """ shortest = 9999 if result: for okPath in result: shortest = min(shortest, len(okPath)) if orig == target: return [[]] if path is None: result = [] path = [] requirements = list(requirements) outputs = self._mtmap.get(orig) if outputs is None: return result registry = getToolByName(self, 'mimetypes_registry') mto = registry.lookup(target) # target mimetype aliases target_aliases = mto[0].mimetypes path.append(None) for o_mt, transforms in outputs.items(): for transform in transforms: required = 0 name = transform.name() if name in requirements: requirements.remove(name) required = 1 if transform in path: # avoid infinite loop... continue path[-1] = transform if o_mt in target_aliases: if not requirements: result.append(path[:]) if len(path[:]) < shortest: # here is a shorter one ! shortest = len(path) else: if len(path) < shortest: # keep exploring this path, it is still short enough self._getPaths(o_mt, target, requirements, path, result) if required: requirements.append(name) path.pop() return result @security.private def manage_afterAdd(self, item, container): """ overload manage_afterAdd to finish initialization when the transform tool is added """ Folder.manage_afterAdd(self, item, container) try: initialize(self) except TransformException: # may fail on copy or zexp import pass @security.protected(ManagePortal) def manage_addTransform(self, id, module, REQUEST=None): """ add a new transform to the tool """ transform = Transform(id, module) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_addTransformsChain(self, id, description, REQUEST=None): """ add a new transform to the tool """ transform = TransformsChain(id, description) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_setCacheValidityTime(self, seconds, REQUEST=None): """set the lifetime of cached data in seconds""" self.max_sec_in_cache = int(seconds) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def reloadTransforms(self, ids=()): """ reload transforms with the given ids if no ids, reload all registered transforms return a list of (transform_id, transform_module) describing reloaded transforms """ if not ids: ids = self.objectIds() reloaded = [] for id in ids: o = getattr(self, id) o.reload() reloaded.append((id, o.module)) return reloaded # Policy handling methods def manage_addPolicy(self, output_mimetype, required_transforms, REQUEST=None): """ add a policy for a given output mime types""" registry = getToolByName(self, 'mimetypes_registry') if not registry.lookup(output_mimetype): raise TransformException('Unknown MIME type') if output_mimetype in self._policies: msg = 'A policy for output %s is yet defined' % output_mimetype raise TransformException(msg) required_transforms = tuple(required_transforms) self._policies[output_mimetype] = required_transforms if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/manage_editTransformationPolicyForm') def manage_delPolicies(self, outputs, REQUEST=None): """ remove policies for given output mime types""" for mimetype in outputs: del self._policies[mimetype] if REQUEST is not None: REQUEST['RESPONSE'].redirect( self.absolute_url() + '/manage_editTransformationPolicyForm') def listPolicies(self): """ return the list of defined policies a policy is a 2-uple (output_mime_type, [list of required transforms]) """ # XXXFIXME: backward compat, should be removed latter if not hasattr(self, '_policies'): self._policies = PersistentMapping() return list(self._policies.items()) # mimetype oriented conversions (iengine interface) @security.private def registerTransform(self, transform): """register a new transform transform isn't a Zope Transform (the wrapper) but the wrapped transform the persistence wrapper will be created here """ # needed when call from transform.transforms.initialize which # register non zope transform module = str(transform.__module__) transform = Transform(transform.name(), module, transform) if not ITransform.providedBy(transform): raise TransformException('%s does not implement ITransform' % transform) name = transform.name() __traceback_info__ = (name, transform) if name not in self.objectIds(): self._setObject(name, transform) self._mapTransform(transform) @security.protected(ManagePortal) def ZopeFind(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.ZopeFind(self, *args, **kwargs) except MissingBinary: log('ZopeFind: catched MissingBinary exception') @security.protected(View) def objectItems(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.objectItems(self, *args, **kwargs) except MissingBinary: log('objectItems: catched MissingBinary exception') return [] # available mimetypes #################################################### def listAvailableTextInputs(self): """Returns a list of mimetypes that can be used as input for textfields by building a list of the inputs beginning with "text/" of all transforms. """ available_types = [] candidate_transforms = [object[1] for object in self.objectItems()] for candidate in candidate_transforms: for input in candidate.inputs: if input.startswith("text/") and input not in available_types: available_types.append(input) return available_types