class MimeTypesRegistry(UniqueObject, ActionProviderBase, Folder): """Mimetype registry that deals with a) registering types b) wildcarding of rfc-2046 types c) classifying data into a given type """ id = 'mimetypes_registry' meta_type = 'MimeTypes Registry' isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ({ 'name': 'MimeType', 'action': 'manage_addMimeTypeForm' }, ) manage_options = (({ 'label': 'MimeTypes', 'action': 'manage_main' }, ) + Folder.manage_options[2:]) manage_addMimeTypeForm = PageTemplateFile('addMimeType', _www) manage_main = PageTemplateFile('listMimeTypes', _www) manage_editMimeTypeForm = PageTemplateFile('editMimeType', _www) security = ClassSecurityInfo() # FIXME __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, id=None): if id is not None: assert id == self.id self.encodings_map = encodings_map.copy() self.suffix_map = suffix_map.copy() # Major key -> minor IMimetype objects self._mimetypes = PersistentMapping() # ext -> IMimetype mapping self.extensions = PersistentMapping() # glob -> (regex, mimetype) mapping self.globs = OOBTree() self.manage_addProperty('defaultMimetype', 'text/plain', 'string') self.manage_addProperty('unicodePolicies', 'strict ignore replace', 'tokens') self.manage_addProperty('unicodePolicy', 'unicodePolicies', 'selection') self.manage_addProperty('fallbackEncoding', 'latin1', 'string') # initialize mime types initialize(self) self._new_style_mtr = 1 security.declareProtected(ManagePortal, 'register') def register(self, mimetype): """ Register a new mimetype mimetype must implement IMimetype """ mimetype = aq_base(mimetype) assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: self.register_mimetype(t, mimetype) for extension in mimetype.extensions: self.register_extension(extension, mimetype) for glob in mimetype.globs: self.register_glob(glob, mimetype) security.declareProtected(ManagePortal, 'register_mimetype') def register_mimetype(self, mt, mimetype): major, minor = split(mt) if not major or not minor or minor == '*': raise MimeTypeException('Can\'t register mime type %s' % mt) group = self._mimetypes.setdefault(major, PersistentMapping()) if minor in group: if group.get(minor) != mimetype: log('Warning: redefining mime type %s (%s)' % (mt, mimetype.__class__)) group[minor] = mimetype security.declareProtected(ManagePortal, 'register_extension') def register_extension(self, extension, mimetype): """ Associate a file's extension to a IMimetype extension is a string representing a file extension (not prefixed by a dot) mimetype must implement IMimetype """ mimetype = aq_base(mimetype) if extension in self.extensions: if self.extensions.get(extension) != mimetype: log('Warning: redefining extension %s from %s to %s' % (extension, self.extensions[extension], mimetype)) # we don't validate fmt yet, but its ["txt", "html"] self.extensions[extension] = mimetype security.declareProtected(ManagePortal, 'register_glob') def register_glob(self, glob, mimetype): """ Associate a glob to a IMimetype glob is a shell-like glob that will be translated to a regex to match against whole filename. mimetype must implement IMimetype. """ globs = getattr(self, 'globs', None) if globs is None: self.globs = globs = OOBTree() mimetype = aq_base(mimetype) existing = globs.get(glob) if existing is not None: regex, mt = existing if mt != mimetype: log('Warning: redefining glob %s from %s to %s' % (glob, mt, mimetype)) # we don't validate fmt yet, but its ["txt", "html"] pattern = re.compile(fnmatch.translate(glob)) globs[glob] = (pattern, mimetype) security.declareProtected(ManagePortal, 'unregister') def unregister(self, mimetype): """ Unregister a new mimetype mimetype must implement IMimetype """ assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: major, minor = split(t) group = self._mimetypes.get(major, {}) if group.get(minor) == mimetype: del group[minor] for e in mimetype.extensions: if self.extensions.get(e) == mimetype: del self.extensions[e] globs = getattr(self, 'globs', None) if globs is not None: for glob in mimetype.globs: existing = globs.get(glob) if existing is None: continue regex, mt = existing if mt == mimetype: del globs[glob] security.declarePublic('mimetypes') def mimetypes(self): """Return all defined mime types, each one implements at least IMimetype """ res = {} for g in self._mimetypes.values(): for mt in g.values(): res[mt] = 1 return [aq_base(mtitem) for mtitem in res.keys()] security.declarePublic('list_mimetypes') def list_mimetypes(self): """Return all defined mime types, as string""" return [str(mt) for mt in self.mimetypes()] security.declarePublic('lookup') def lookup(self, mimetypestring): """Lookup for IMimetypes object matching mimetypestring mimetypestring may have an empty minor part or containing a wildcard (*) mimetypestring may and IMimetype object (in this case it will be returned unchanged Return a list of mimetypes objects associated with the RFC-2046 name return an empty list if no one is known. """ if IMimetype.providedBy(mimetypestring): return (aq_base(mimetypestring), ) __traceback_info__ = (repr(mimetypestring), str(mimetypestring)) major, minor = split(str(mimetypestring)) group = self._mimetypes.get(major, {}) if not minor or minor == '*': res = group.values() else: res = group.get(minor) if res: res = (res, ) else: return () return tuple([aq_base(mtitem) for mtitem in res]) security.declarePublic('lookupExtension') def lookupExtension(self, filename): """Lookup for IMimetypes object matching filename Filename maybe a file name like 'content.txt' or an extension like 'rest' Return an IMimetype object associated with the file's extension or None """ if filename.find('.') != -1: base, ext = os.path.splitext(filename) ext = ext[1:] # remove the dot while ext in self.suffix_map: base, ext = os.path.splitext(base + self.suffix_map[ext]) ext = ext[1:] # remove the dot else: ext = filename base = None # XXX This code below make no sense and may break because base # isn't defined. if ext in self.encodings_map and base: base, ext = os.path.splitext(base) ext = ext[1:] # remove the dot result = aq_base(self.extensions.get(ext)) if result is None: result = aq_base(self.extensions.get(ext.lower())) return result security.declarePublic('globFilename') def globFilename(self, filename): """Lookup for IMimetypes object matching filename Filename must be a complete filename with extension. Return an IMimetype object associated with the glob's or None """ globs = getattr(self, 'globs', None) if globs is None: return None for key in globs.keys(): glob, mimetype = globs[key] if glob.match(filename): return aq_base(mimetype) return None security.declarePublic('lookupGlob') def lookupGlob(self, glob): globs = getattr(self, 'globs', None) if globs is None: return None return aq_base(globs.get(glob)) def _classifiers(self): return [mt for mt in self.mimetypes() if IClassifier.providedBy(mt)] security.declarePublic('classify') def classify(self, data, mimetype=None, filename=None): """Classify works as follows: 1) you tell me the rfc-2046 name and I give you an IMimetype object 2) the filename includes an extension from which we can guess the mimetype 3) we can optionally introspect the data 4) default to self.defaultMimetype if no data was provided else to application/octet-stream of no filename was provided, else to text/plain Return an IMimetype object or None """ mt = None if mimetype: mt = self.lookup(mimetype) if mt: mt = mt[0] elif filename: mt = self.lookupExtension(filename) if mt is None: mt = self.globFilename(filename) if data and not mt: for c in self._classifiers(): if c.classify(data): mt = c break if not mt: mstr = magic.guessMime(data) if mstr: _mt = self.lookup(mstr) if len(_mt) > 0: mt = _mt[0] if not mt: if not data: mtlist = self.lookup(self.defaultMimetype) elif filename: mtlist = self.lookup('application/octet-stream') else: failed = 'text/x-unknown-content-type' filename = filename or '' data = data or '' ct, enc = guess_content_type(filename, data, None) if ct == failed: ct = 'text/plain' mtlist = self.lookup(ct) if len(mtlist) > 0: mt = mtlist[0] else: return None # Remove acquisition wrappers return aq_base(mt) def __call__(self, data, **kwargs): """ Return a triple (data, filename, mimetypeobject) given some raw data and optional paramters method from the isourceAdapter interface """ mimetype = kwargs.get('mimetype', None) filename = kwargs.get('filename', None) encoding = kwargs.get('encoding', None) mt = None if hasattr(data, 'filename'): filename = os.path.basename(data.filename) elif hasattr(data, 'name'): filename = os.path.basename(data.name) if hasattr(data, 'read'): _data = data.read() if hasattr(data, 'seek'): data.seek(0) data = _data # We need to figure out if data is binary and skip encoding if # it is mt = self.classify(data, mimetype=mimetype, filename=filename) if not mt.binary and not isinstance(data, UnicodeType): # if no encoding specified, try to guess it from data if encoding is None: encoding = self.guess_encoding(data) # ugly workaround for # https://sourceforge.net/tracker/?func=detail&aid=1068001&group_id=75272&atid=543430 # covered by # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=843590&group_id=5470 # dont remove this code unless python is fixed. if encoding is "macintosh": encoding = 'mac_roman' try: try: data = unicode(data, encoding, self.unicodePolicy) except (ValueError, LookupError): # wrong unicodePolicy data = unicode(data, encoding) except: data = unicode(data, self.fallbackEncoding) return (data, filename, aq_base(mt)) security.declarePublic('guess_encoding') def guess_encoding(self, data): """ Try to guess encoding from a text value. If no encoding can be guessed, fall back to utf-8. """ if isinstance(data, type(u'')): # data maybe unicode but with another encoding specified data = data.encode('UTF-8') encoding = guess_encoding(data) if encoding is None: encoding = 'utf-8' return encoding security.declareProtected(ManagePortal, 'manage_delObjects') def manage_delObjects(self, ids, REQUEST=None): """ delete the selected mime types """ for id in ids: self.unregister(self.lookup(id)[0]) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') security.declareProtected(ManagePortal, 'manage_addMimeType') def manage_addMimeType(self, id, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """add a mime type to the tool""" mt = MimeTypeItem(id, mimetypes, extensions=extensions, binary=binary, icon_path=icon_path, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') security.declareProtected(ManagePortal, 'manage_editMimeType') def manage_editMimeType(self, name, new_name, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """Edit a mime type by name """ mt = self.lookup(name)[0] self.unregister(mt) mt.edit(new_name, mimetypes, extensions, icon_path=icon_path, binary=binary, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main')
class 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 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 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]
def setdefault(self, key, failobj=None): if key not in self._keys: self._keys.append(key) return PersistentMapping.setdefault(self, key, failobj)
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 TransformTool(UniqueObject, ActionProviderBase, Folder): id = 'portal_transforms' meta_type = id.title().replace('_', ' ') isPrincipiaFolderish = 1 # Show up in the ZMI implements(IPortalTransformsTool, IEngine) 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.declarePrivate('unregisterTransform') 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.declarePrivate('convertTo') 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=WARNING) 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=WARNING) 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.getRequirementListByMimetype( str(orig_mt), 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=WARNING) path = self._findPath(orig_mt, target_mt) if not path: log('NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path), severity=WARNING) 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 security.declarePrivate('getRequirementListByMimetype') def getRequirementListByMimetype(self, origin_mimetype, target_mimetype): """Return requirements only if origin_mimetype and target_mimetype are matching transform policy As an example pdf => text conversion force a transformation to intermediate HTML format, because w3m_dump is a requirement to output plain/text. But we want using pdf_to_text directly. So requirements are returned only if origin_mimetype and target_mimetype sastify the requirement: ie html_to_text is returned only if origin_mimetype == 'text/html' and target_mimetype == 'text/plain' """ result_list = [] candidate_requirement_list = self._policies.get(target_mimetype, []) for candidate_requirement in candidate_requirement_list: transform = getattr(self, candidate_requirement) if origin_mimetype in transform.inputs: result_list.append(candidate_requirement) return result_list security.declarePublic('convertToData') def convertToData(self, target_mimetype, orig, data=None, object=None, usedby=None, context=None, **kwargs): """Convert to a given mimetype and return the raw data ignoring subobjects. see convertTo for more information """ data = self.convertTo(target_mimetype, orig, data, object, usedby, context, **kwargs) if data: return data.getData() return None security.declarePublic('convert') def convert(self, name, orig, data=None, context=None, **kwargs): """run a tranform of a given name on data * name is the name of a registered transform see convertTo docstring for more info """ if not data: data = self._wrap(name) try: transform = getattr(self, name) except AttributeError: raise Exception('No such transform "%s"' % name) data = transform.convert(orig, data, context=context, **kwargs) self._setMetaData(data, transform) return data def __call__(self, name, orig, data=None, context=None, **kwargs): """run a transform by its name, returning the raw data product * name is the name of a registered transform. return an encoded string. see convert docstring for more info on additional arguments. """ data = self.convert(name, orig, data, context, **kwargs) return data.getData() # utilities ############################################################### def _setMetaData(self, datastream, transform): """set metadata on datastream according to the given transform (mime type and optionaly encoding) """ md = datastream.getMetadata() if hasattr(transform, 'output_encoding'): md['encoding'] = transform.output_encoding md['mimetype'] = transform.output def _wrap(self, name): """wrap a data object in an icache""" return datastream(name) def _unwrap(self, data): """unwrap data from an icache""" if IDataStream.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 not transform in mt_in[mt2]: mt_in[mt2].append(transform) except KeyError: mt_in[mt2] = PersistentList([transform]) def _unmapTransform(self, transform): """unmap transform from internal structures""" registry = getToolByName(self, 'mimetypes_registry') for i in transform.inputs: for mti in registry.lookup(i): for mt in mti.mimetypes: mt_in = self._mtmap.get(mt, {}) output = transform.output mto = registry.lookup(output) for mt2 in mto[0].mimetypes: l = mt_in[mt2] for i in range(len(l)): if transform.name() == l[i].name(): l.pop(i) break else: log('Can\'t find transform %s from %s to %s' % (transform.name(), mti, mt), severity=WARNING) 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 == 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.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ overload manage_afterAdd to finish initialization when the transform tool is added """ Folder.manage_afterAdd(self, item, container) try: initialize(self) except TransformException: # may fail on copy or zexp import pass security.declareProtected(ManagePortal, 'manage_addTransform') def manage_addTransform(self, id, module, REQUEST=None): """ add a new transform to the tool """ transform = Transform(id, module) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') security.declareProtected(ManagePortal, 'manage_addTransform') def manage_addTransformsChain(self, id, description, REQUEST=None): """ add a new transform to the tool """ transform = TransformsChain(id, description) self._setObject(id, transform) self._mapTransform(transform) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') security.declareProtected(ManagePortal, 'manage_addTransform') def manage_setCacheValidityTime(self, seconds, REQUEST=None): """set the lifetime of cached data in seconds""" self.max_sec_in_cache = int(seconds) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') security.declareProtected(ManagePortal, 'reloadTransforms') def reloadTransforms(self, ids=()): """ reload transforms with the given ids if no ids, reload all registered transforms return a list of (transform_id, transform_module) describing reloaded transforms """ if not ids: ids = self.objectIds() reloaded = [] for id in ids: o = getattr(self, id) o.reload() reloaded.append((id, o.module)) return reloaded # Policy handling methods def manage_addPolicy(self, output_mimetype, required_transforms, REQUEST=None): """ add a policy for a given output mime types""" registry = getToolByName(self, 'mimetypes_registry') if not registry.lookup(output_mimetype): raise TransformException('Unknown MIME type') if 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') security.declarePrivate('listPolicies') 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.declarePrivate('registerTransform') 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.declareProtected(ManagePortal, 'ZopeFind') def ZopeFind(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.ZopeFind(self, *args, **kwargs) except MissingBinary: log('ZopeFind: catched MissingBinary exception') security.declareProtected(View, 'objectItems') def objectItems(self, *args, **kwargs): """Don't break ZopeFind feature when a transform can't be loaded """ try: return Folder.objectItems(self, *args, **kwargs) except MissingBinary: log('objectItems: catched MissingBinary exception') return [] # available mimetypes #################################################### security.declarePrivate('listAvailableTextInputs') 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 MimeTypesRegistry(UniqueObject, ActionProviderBase, Folder): """Mimetype registry that deals with a) registering types b) wildcarding of rfc-2046 types c) classifying data into a given type """ id = 'mimetypes_registry' meta_type = 'MimeTypes Registry' isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ( {'name': 'MimeType', 'action': 'manage_addMimeTypeForm'}, ) manage_options = ( ({'label': 'MimeTypes', 'action': 'manage_main'},) + Folder.manage_options[2:] ) manage_addMimeTypeForm = PageTemplateFile('addMimeType', _www) manage_main = PageTemplateFile('listMimeTypes', _www) manage_editMimeTypeForm = PageTemplateFile('editMimeType', _www) security = ClassSecurityInfo() # FIXME __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, id=None): if id is not None: assert id == self.id self.encodings_map = encodings_map.copy() self.suffix_map = suffix_map.copy() # Major key -> minor IMimetype objects self._mimetypes = PersistentMapping() # ext -> IMimetype mapping self.extensions = PersistentMapping() # glob -> (regex, mimetype) mapping self.globs = OOBTree() self.manage_addProperty('defaultMimetype', 'text/plain', 'string') self.manage_addProperty('unicodePolicies', 'strict ignore replace', 'tokens') self.manage_addProperty( 'unicodePolicy', 'unicodePolicies', 'selection') self.manage_addProperty('fallbackEncoding', 'latin1', 'string') # initialize mime types initialize(self) self._new_style_mtr = 1 @security.protected(ManagePortal) def register(self, mimetype): """ Register a new mimetype mimetype must implement IMimetype """ mimetype = aq_base(mimetype) assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: self.register_mimetype(t, mimetype) for extension in mimetype.extensions: self.register_extension(extension, mimetype) for glob in mimetype.globs: self.register_glob(glob, mimetype) @security.protected(ManagePortal) def register_mimetype(self, mt, mimetype): major, minor = split(mt) if not major or not minor or minor == '*': raise MimeTypeException('Can\'t register mime type %s' % mt) group = self._mimetypes.setdefault(major, PersistentMapping()) if minor in group: if group.get(minor) != mimetype: logger.warning( 'Redefining mime type {0} ({1})'.format( mt, mimetype.__class__ ) ) group[minor] = mimetype @security.protected(ManagePortal) def register_extension(self, extension, mimetype): """ Associate a file's extension to a IMimetype extension is a string representing a file extension (not prefixed by a dot) mimetype must implement IMimetype """ mimetype = aq_base(mimetype) if extension in self.extensions: if self.extensions.get(extension) != mimetype: logger.warning( 'Redefining extension {0} from {1} to {2}'.format( extension, self.extensions[extension], mimetype ) ) # we don't validate fmt yet, but its ["txt", "html"] self.extensions[extension] = mimetype @security.protected(ManagePortal) def register_glob(self, glob, mimetype): """ Associate a glob to a IMimetype glob is a shell-like glob that will be translated to a regex to match against whole filename. mimetype must implement IMimetype. """ globs = getattr(self, 'globs', None) if globs is None: self.globs = globs = OOBTree() mimetype = aq_base(mimetype) existing = globs.get(glob) if existing is not None: regex, mt = existing if mt != mimetype: logger.warning( 'Redefining glob {0} from {1} to {2}'.format( glob, mt, mimetype ) ) # we don't validate fmt yet, but its ["txt", "html"] pattern = re.compile(fnmatch.translate(glob)) globs[glob] = (pattern, mimetype) @security.protected(ManagePortal) def unregister(self, mimetype): """ Unregister a new mimetype mimetype must implement IMimetype """ assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: major, minor = split(t) group = self._mimetypes.get(major, {}) if group.get(minor) == mimetype: del group[minor] for e in mimetype.extensions: if self.extensions.get(e) == mimetype: del self.extensions[e] globs = getattr(self, 'globs', None) if globs is not None: for glob in mimetype.globs: existing = globs.get(glob) if existing is None: continue regex, mt = existing if mt == mimetype: del globs[glob] @security.public def mimetypes(self): """Return all defined mime types, each one implements at least IMimetype """ res = {} for g in self._mimetypes.values(): for mt in g.values(): res[mt] = 1 return [aq_base(mtitem) for mtitem in res.keys()] @security.public def list_mimetypes(self): """Return all defined mime types, as string""" return [str(mt) for mt in self.mimetypes()] @security.public def lookup(self, mimetypestring): """Lookup for IMimetypes object matching mimetypestring mimetypestring may have an empty minor part or containing a wildcard (*) mimetypestring may and IMimetype object (in this case it will be returned unchanged Return a list of mimetypes objects associated with the RFC-2046 name return an empty list if no one is known. """ if IMimetype.providedBy(mimetypestring): return (aq_base(mimetypestring), ) __traceback_info__ = (repr(mimetypestring), str(mimetypestring)) major, minor = split(str(mimetypestring)) group = self._mimetypes.get(major, {}) if not minor or minor == '*': res = group.values() else: res = group.get(minor) if res: res = (res,) else: return () return tuple([aq_base(mtitem) for mtitem in res]) @security.public def lookupExtension(self, filename): """Lookup for IMimetypes object matching filename Filename maybe a file name like 'content.txt' or an extension like 'rest' Return an IMimetype object associated with the file's extension or None """ base = None if filename.find('.') != -1: base, ext = os.path.splitext(filename) ext = ext[1:] # remove the dot while ext in self.suffix_map: base, ext = os.path.splitext(base + self.suffix_map[ext]) ext = ext[1:] # remove the dot else: ext = filename if base is not None and ext in self.encodings_map: base, ext = os.path.splitext(base) ext = ext[1:] # remove the dot result = aq_base(self.extensions.get(ext)) if result is None: result = aq_base(self.extensions.get(ext.lower())) return result @security.public def globFilename(self, filename): """Lookup for IMimetypes object matching filename Filename must be a complete filename with extension. Return an IMimetype object associated with the glob's or None """ globs = getattr(self, 'globs', {}) for key in globs: glob, mimetype = globs[key] if glob.match(filename): return aq_base(mimetype) return None @security.public def lookupGlob(self, glob): globs = getattr(self, 'globs', None) if globs is not None: return aq_base(globs.get(glob)) def _classifiers(self): return [mt for mt in self.mimetypes() if IClassifier.providedBy(mt)] @security.public def classify(self, data, mimetype=None, filename=None): """Classify works as follows: 1) you tell me the rfc-2046 name and I give you an IMimetype object 2) the filename includes an extension from which we can guess the mimetype 3) we can optionally introspect the data 4) default to self.defaultMimetype if no data was provided else to application/octet-stream of no filename was provided, else to text/plain Return an IMimetype object or None """ mt = None if mimetype: mt = self.lookup(mimetype) if mt: mt = mt[0] elif filename: mt = self.lookupExtension(filename) if mt is None: mt = self.globFilename(filename) if data and not mt: for c in self._classifiers(): if c.classify(data): mt = c break if not mt: mstr = magic.guessMime(data) if mstr: _mt = self.lookup(mstr) if len(_mt) > 0: mt = _mt[0] if not mt: if not data: mtlist = self.lookup(self.defaultMimetype) elif filename: mtlist = self.lookup('application/octet-stream') else: failed = 'text/x-unknown-content-type' filename = filename or '' data = data or '' if six.PY3: data = data.encode() ct, enc = guess_content_type(filename, data, None) if ct == failed: ct = 'text/plain' mtlist = self.lookup(ct) if len(mtlist) > 0: mt = mtlist[0] else: return None # Remove acquisition wrappers return aq_base(mt) def __call__(self, data, **kwargs): """ Return a triple (data, filename, mimetypeobject) given some raw data and optional paramters method from the isourceAdapter interface """ mimetype = kwargs.get('mimetype', None) filename = kwargs.get('filename', None) encoding = kwargs.get('encoding', None) mt = None if hasattr(data, 'filename'): filename = os.path.basename(data.filename) elif hasattr(data, 'name'): filename = os.path.basename(data.name) if hasattr(data, 'read'): _data = data.read() if hasattr(data, 'seek'): data.seek(0) data = _data # We need to figure out if data is binary and skip encoding if # it is mt = self.classify(data, mimetype=mimetype, filename=filename) if not mt.binary and not isinstance(data, six.text_type): # if no encoding specified, try to guess it from data if encoding is None: encoding = self.guess_encoding(data) # ugly workaround for # https://sourceforge.net/tracker/?func=detail&aid=1068001&group_id=75272&atid=543430 # covered by # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=843590&group_id=5470 # dont remove this code unless python is fixed. if encoding is "macintosh": encoding = 'mac_roman' try: try: data = six.text_type(data, encoding, self.unicodePolicy) except (ValueError, LookupError): # wrong unicodePolicy data = six.text_type(data, encoding) except: data = six.text_type(data, self.fallbackEncoding) return (data, filename, aq_base(mt)) @security.public def guess_encoding(self, data): """ Try to guess encoding from a text value. If no encoding can be guessed, fall back to utf-8. """ if isinstance(data, six.text_type): # data maybe unicode but with another encoding specified data = data.encode('UTF-8') encoding = guess_encoding(data) if encoding is None: encoding = 'utf-8' return encoding @security.protected(ManagePortal) def manage_delObjects(self, ids, REQUEST=None): """ delete the selected mime types """ for id in ids: self.unregister(self.lookup(id)[0]) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_addMimeType(self, id, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """add a mime type to the tool""" mt = MimeTypeItem(id, mimetypes, extensions=extensions, binary=binary, icon_path=icon_path, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main') @security.protected(ManagePortal) def manage_editMimeType(self, name, new_name, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """Edit a mime type by name """ mt = self.lookup(name)[0] self.unregister(mt) mt.edit(new_name, mimetypes, extensions, icon_path=icon_path, binary=binary, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main')
class 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 ]
def setdefault(self, key, failobj=None): if key not in self._keys: self._keys.append(key) return PersistentMapping.setdefault(self, key, failobj)
class MimeTypesRegistry(UniqueObject, ActionProviderBase, Folder): """Mimetype registry that deals with a) registering types b) wildcarding of rfc-2046 types c) classifying data into a given type """ implements(IMimetypesRegistry, ISourceAdapter) id = "mimetypes_registry" meta_type = "MimeTypes Registry" isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ({"name": "MimeType", "action": "manage_addMimeTypeForm"},) manage_options = ({"label": "MimeTypes", "action": "manage_main"},) + Folder.manage_options[2:] manage_addMimeTypeForm = PageTemplateFile("addMimeType", _www) manage_main = PageTemplateFile("listMimeTypes", _www) manage_editMimeTypeForm = PageTemplateFile("editMimeType", _www) security = ClassSecurityInfo() # FIXME __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, id=None): if id is not None: assert id == self.id self.encodings_map = encodings_map.copy() self.suffix_map = suffix_map.copy() # Major key -> minor IMimetype objects self._mimetypes = PersistentMapping() # ext -> IMimetype mapping self.extensions = PersistentMapping() # glob -> (regex, mimetype) mapping self.globs = OOBTree() self.manage_addProperty("defaultMimetype", "text/plain", "string") self.manage_addProperty("unicodePolicies", "strict ignore replace", "tokens") self.manage_addProperty("unicodePolicy", "unicodePolicies", "selection") self.manage_addProperty("fallbackEncoding", "latin1", "string") # initialize mime types initialize(self) self._new_style_mtr = 1 security.declareProtected(ManagePortal, "register") def register(self, mimetype): """ Register a new mimetype mimetype must implement IMimetype """ mimetype = aq_base(mimetype) assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: self.register_mimetype(t, mimetype) for extension in mimetype.extensions: self.register_extension(extension, mimetype) for glob in mimetype.globs: self.register_glob(glob, mimetype) security.declareProtected(ManagePortal, "register_mimetype") def register_mimetype(self, mt, mimetype): major, minor = split(mt) if not major or not minor or minor == "*": raise MimeTypeException("Can't register mime type %s" % mt) group = self._mimetypes.setdefault(major, PersistentMapping()) if minor in group: if group.get(minor) != mimetype: log("Warning: redefining mime type %s (%s)" % (mt, mimetype.__class__)) group[minor] = mimetype security.declareProtected(ManagePortal, "register_extension") def register_extension(self, extension, mimetype): """ Associate a file's extension to a IMimetype extension is a string representing a file extension (not prefixed by a dot) mimetype must implement IMimetype """ mimetype = aq_base(mimetype) if extension in self.extensions: if self.extensions.get(extension) != mimetype: log( "Warning: redefining extension %s from %s to %s" % (extension, self.extensions[extension], mimetype) ) # we don't validate fmt yet, but its ["txt", "html"] self.extensions[extension] = mimetype security.declareProtected(ManagePortal, "register_glob") def register_glob(self, glob, mimetype): """ Associate a glob to a IMimetype glob is a shell-like glob that will be translated to a regex to match against whole filename. mimetype must implement IMimetype. """ globs = getattr(self, "globs", None) if globs is None: self.globs = globs = OOBTree() mimetype = aq_base(mimetype) existing = globs.get(glob) if existing is not None: regex, mt = existing if mt != mimetype: log("Warning: redefining glob %s from %s to %s" % (glob, mt, mimetype)) # we don't validate fmt yet, but its ["txt", "html"] pattern = re.compile(fnmatch.translate(glob)) globs[glob] = (pattern, mimetype) security.declareProtected(ManagePortal, "unregister") def unregister(self, mimetype): """ Unregister a new mimetype mimetype must implement IMimetype """ assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: major, minor = split(t) group = self._mimetypes.get(major, {}) if group.get(minor) == mimetype: del group[minor] for e in mimetype.extensions: if self.extensions.get(e) == mimetype: del self.extensions[e] globs = getattr(self, "globs", None) if globs is not None: for glob in mimetype.globs: existing = globs.get(glob) if existing is None: continue regex, mt = existing if mt == mimetype: del globs[glob] security.declarePublic("mimetypes") def mimetypes(self): """Return all defined mime types, each one implements at least IMimetype """ res = {} for g in self._mimetypes.values(): for mt in g.values(): res[mt] = 1 return [aq_base(mtitem) for mtitem in res.keys()] security.declarePublic("list_mimetypes") def list_mimetypes(self): """Return all defined mime types, as string""" return [str(mt) for mt in self.mimetypes()] security.declarePublic("lookup") def lookup(self, mimetypestring): """Lookup for IMimetypes object matching mimetypestring mimetypestring may have an empty minor part or containing a wildcard (*) mimetypestring may and IMimetype object (in this case it will be returned unchanged Return a list of mimetypes objects associated with the RFC-2046 name return an empty list if no one is known. """ if IMimetype.providedBy(mimetypestring): return (aq_base(mimetypestring),) __traceback_info__ = (repr(mimetypestring), str(mimetypestring)) major, minor = split(str(mimetypestring)) group = self._mimetypes.get(major, {}) if not minor or minor == "*": res = group.values() else: res = group.get(minor) if res: res = (res,) else: return () return tuple([aq_base(mtitem) for mtitem in res]) security.declarePublic("lookupExtension") def lookupExtension(self, filename): """Lookup for IMimetypes object matching filename Filename maybe a file name like 'content.txt' or an extension like 'rest' Return an IMimetype object associated with the file's extension or None """ if filename.find(".") != -1: base, ext = os.path.splitext(filename) ext = ext[1:] # remove the dot while ext in self.suffix_map: base, ext = os.path.splitext(base + self.suffix_map[ext]) ext = ext[1:] # remove the dot else: ext = filename base = None # XXX This code below make no sense and may break because base # isn't defined. if ext in self.encodings_map and base: base, ext = os.path.splitext(base) ext = ext[1:] # remove the dot result = aq_base(self.extensions.get(ext)) if result is None: result = aq_base(self.extensions.get(ext.lower())) return result security.declarePublic("globFilename") def globFilename(self, filename): """Lookup for IMimetypes object matching filename Filename must be a complete filename with extension. Return an IMimetype object associated with the glob's or None """ globs = getattr(self, "globs", None) if globs is None: return None for key in globs.keys(): glob, mimetype = globs[key] if glob.match(filename): return aq_base(mimetype) return None security.declarePublic("lookupGlob") def lookupGlob(self, glob): globs = getattr(self, "globs", None) if globs is None: return None return aq_base(globs.get(glob)) def _classifiers(self): return [mt for mt in self.mimetypes() if IClassifier.providedBy(mt)] security.declarePublic("classify") def classify(self, data, mimetype=None, filename=None): """Classify works as follows: 1) you tell me the rfc-2046 name and I give you an IMimetype object 2) the filename includes an extension from which we can guess the mimetype 3) we can optionally introspect the data 4) default to self.defaultMimetype if no data was provided else to application/octet-stream of no filename was provided, else to text/plain Return an IMimetype object or None """ mt = None if mimetype: mt = self.lookup(mimetype) if mt: mt = mt[0] elif filename: mt = self.lookupExtension(filename) if mt is None: mt = self.globFilename(filename) if data and not mt: for c in self._classifiers(): if c.classify(data): mt = c break if not mt: mstr = magic.guessMime(data) if mstr: _mt = self.lookup(mstr) if len(_mt) > 0: mt = _mt[0] if not mt: if not data: mtlist = self.lookup(self.defaultMimetype) elif filename: mtlist = self.lookup("application/octet-stream") else: failed = "text/x-unknown-content-type" filename = filename or "" data = data or "" ct, enc = guess_content_type(filename, data, None) if ct == failed: ct = "text/plain" mtlist = self.lookup(ct) if len(mtlist) > 0: mt = mtlist[0] else: return None # Remove acquisition wrappers return aq_base(mt) def __call__(self, data, **kwargs): """ Return a triple (data, filename, mimetypeobject) given some raw data and optional paramters method from the isourceAdapter interface """ mimetype = kwargs.get("mimetype", None) filename = kwargs.get("filename", None) encoding = kwargs.get("encoding", None) mt = None if hasattr(data, "filename"): filename = os.path.basename(data.filename) elif hasattr(data, "name"): filename = os.path.basename(data.name) if hasattr(data, "read"): _data = data.read() if hasattr(data, "seek"): data.seek(0) data = _data # We need to figure out if data is binary and skip encoding if # it is mt = self.classify(data, mimetype=mimetype, filename=filename) if not mt.binary and not isinstance(data, UnicodeType): # if no encoding specified, try to guess it from data if encoding is None: encoding = self.guess_encoding(data) # ugly workaround for # https://sourceforge.net/tracker/?func=detail&aid=1068001&group_id=75272&atid=543430 # covered by # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=843590&group_id=5470 # dont remove this code unless python is fixed. if encoding is "macintosh": encoding = "mac_roman" try: try: data = unicode(data, encoding, self.unicodePolicy) except (ValueError, LookupError): # wrong unicodePolicy data = unicode(data, encoding) except: data = unicode(data, self.fallbackEncoding) return (data, filename, aq_base(mt)) security.declarePublic("guess_encoding") def guess_encoding(self, data): """ Try to guess encoding from a text value if no encoding guessed, used the default charset from site properties (Zope) with a fallback to UTF-8 (should never happen with correct site_properties, but always raise Attribute error without Zope) """ if isinstance(data, type(u"")): # data maybe unicode but with another encoding specified data = data.encode("UTF-8") encoding = guess_encoding(data) if encoding is None: encoding = "utf-8" return encoding security.declareProtected(ManagePortal, "manage_delObjects") def manage_delObjects(self, ids, REQUEST=None): """ delete the selected mime types """ for id in ids: self.unregister(self.lookup(id)[0]) if REQUEST is not None: REQUEST["RESPONSE"].redirect(self.absolute_url() + "/manage_main") security.declareProtected(ManagePortal, "manage_addMimeType") def manage_addMimeType(self, id, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """add a mime type to the tool""" mt = MimeTypeItem(id, mimetypes, extensions=extensions, binary=binary, icon_path=icon_path, globs=globs) self.register(mt) if REQUEST is not None: REQUEST["RESPONSE"].redirect(self.absolute_url() + "/manage_main") security.declareProtected(ManagePortal, "manage_editMimeType") def manage_editMimeType(self, name, new_name, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """Edit a mime type by name """ mt = self.lookup(name)[0] self.unregister(mt) mt.edit(new_name, mimetypes, extensions, icon_path=icon_path, binary=binary, globs=globs) self.register(mt) if REQUEST is not None: REQUEST["RESPONSE"].redirect(self.absolute_url() + "/manage_main")