class MemcachedTool(_MemcacheTool): """ Memcached interface available as a tool. """ security = ClassSecurityInfo() memcached_tool_configure = DTMLFile('memcached_tool_configure', _dtmldir) erp5_site_global_id = '' security.declareProtected(Permissions.AccessContentsInformation, 'getMemcachedDict') def getMemcachedDict(self, key_prefix, plugin_path): """ Returns an object which can be used as a dict and which gets from/stores to memcached server. key_prefix Mandatory argument allowing different tool users to share the same dictionary key namespace. plugin_path relative_url of dedicated Memcached Plugin """ memcached_plugin = self.restrictedTraverse(plugin_path, None) if memcached_plugin is None: raise ValueError('Memcached Plugin does not exists: %r' % ( plugin_path, )) global_prefix = self.erp5_site_global_id if global_prefix: key_prefix = global_prefix + '_' + key_prefix return SharedDict(memcached_plugin.getConnection(), prefix=key_prefix)
def initializeForm(field_registry, form_class=None): """Sets up ZMIForm with fields from field_registry. """ if form_class is None: form_class = ERP5Form meta_types = [] for meta_type, field in field_registry.get_field_classes().items(): # don't set up in form if this is a field for internal use only if field.internal_field: continue # set up individual add dictionaries for meta_types dict = { 'name': field.meta_type, 'permission': 'Add Formulator Fields', 'action': 'manage_addProduct/Formulator/manage_add%sForm' % meta_type } meta_types.append(dict) # set up add method setattr(form_class, 'manage_add%sForm' % meta_type, DTMLFile('dtml/fieldAdd', globals(), fieldname=meta_type)) # set up meta_types that can be added to form form_class._meta_types = tuple(meta_types) # set up settings form form_class.settings_form._realize_fields()
class BaseTool(UniqueObject, Folder): """ Base class for all ERP5 Tools """ id = 'portal_base_tool' # Override this meta_type = 'ERP5 Base Tool' # Override this allowed_types = () # Override this # Declarative Security security = ClassSecurityInfo() # # ZMI methods # manage_options = (({ 'label': 'Overview', 'action': 'manage_overview' }, ) + Folder.manage_options) security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainBaseTool', _dtmldir) # Filter content (ZMI)) def __init__(self, id=None): if id is None: id = self.__class__.id Folder.__init__(self, id) # Filter content (ZMI)) def filtered_meta_types(self, user=None): # Filters the list of available meta types. all = BaseTool.inheritedAttribute('filtered_meta_types')(self) meta_types = [] for meta_type in self.all_meta_types(): if meta_type['name'] in self.allowed_types: meta_types.append(meta_type) return meta_types def _fixPortalTypeBeforeMigration(self, portal_type): # Tools are causing problems: they used to have no type_class, or wrong # type_class, or sometimes have no type definitions at all. # Fix type definition if possible before any migration. from Products.ERP5.ERP5Site import getSite types_tool = getSite().portal_types type_definition = getattr(types_tool, portal_type, None) if type_definition is not None and \ type_definition.getTypeClass() in ('Folder', None): # wrong type_class, fix it manually: from Products.ERP5Type import document_class_registry try: type_definition.type_class = document_class_registry[ portal_type.replace(' ', '')] except KeyError: LOG( 'BaseTool._migratePortalType', WARNING, 'No document class could be found for portal type %r' % portal_type)
class CaptchaField(ZMIField): security = ClassSecurityInfo() meta_type = "CaptchaField" widget = CaptchaWidgetInstance validator = CaptchaValidatorInstance # methods screen security.declareProtected('View management screens', 'manage_main') manage_main = DTMLFile('dtml/captchaFieldEdit', globals()) security.declareProtected('Change Formulator Forms', 'manage_edit') def manage_edit(self, REQUEST): """ Surcharged values for the captcha provider custom fields. """ captcha_provider = CaptchaProviderFactory.getProvider( self.get_value("captcha_type")) result = {} for field in captcha_provider.getExtraPropertyList(): try: # validate the form and get results result[field.get_real_field().id] = field.get_real_field( ).validate(REQUEST) except ValidationError, err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise # Edit standards attributes # XXX It is not possible to call ZMIField.manage_edit because # it returns at the end... # we need to had a parameter to the method try: # validate the form and get results result.update(self.form.validate(REQUEST)) except ValidationError, err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise
class TestTool(BaseTool): """ This is not functional. You must install Zelenium. """ id = 'portal_tests' meta_type = 'ERP5 Test Tool' portal_type = 'Test Tool' allowed_types = ('Zuite', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainTestTool', _dtmldir)
class OrderTool(BuilderTool): """ OrderTool is a container for Order Builders. """ id = 'portal_orders' meta_type = 'ERP5 Order Tool' portal_type = 'Order Tool' allowed_types = ('ERP5 Order Buider', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainOrderTool', _dtmldir)
class DeliveryTool(BuilderTool): """ The DeliveryTool implements portal object deliveries building policies. """ id = 'portal_deliveries' meta_type = 'ERP5 Delivery Tool' portal_type = 'Delivery Tool' allowed_types = ('ERP5 Delivery Buider', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainDeliveryTool', _dtmldir)
class WebServiceTool(BaseTool): """ This tool can do all kinds of web services in all kinds of protocols. """ id = 'portal_web_services' title = 'Web Service Tool' meta_type = 'ERP5 Web Service Tool' portal_type = 'Web Service Tool' allowed_content_types = () security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainWebServiceTool', _dtmldir) security.declareProtected(Permissions.AccessContentsInformation, 'getConnectionPluginList') def getConnectionPluginList(self): """ Return list of available connection plugins """ plugin_list = connection_plugin_registry.keys() plugin_list.sort() return plugin_list security.declareProtected(Permissions.ManagePortal, 'connect') def connect(self, url, user_name=None, password=None, transport=None, transport_kw=None): """ Connect to remote instances of any kind of web service (not only ERP5) with many different kinds of transport like 'xml-rpc' or 'soap' """ if transport_kw is None: transport_kw = {} connection_handler_klass = connection_plugin_registry[transport] connection_handler = connection_handler_klass(url, user_name, password, **transport_kw) return connection_handler.connect()
class IntegrationTool(BaseTool): """ The IntegrationTool is used to exchange with the differents external management systems. """ id = 'portal_integrations' title = 'Integration Tool' meta_type = 'ERP5 Integration Tool' portal_type = 'Integration Tool' allowed_type = () # Declarative Security security = ClassSecurityInfo() # ZMI Methods security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainIntegrationTool', _dtmldir)
class TestTool (Zuite, BaseTool): """ Container for fonctionnal tests. """ id = 'portal_tests' meta_type = 'ERP5 Test Tool' portal_type = 'Test Tool' allowed_types = ('Zuite', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainTestTool', _dtmldir ) security.declarePublic('getZeleniumVersion') def getZeleniumVersion(self): """Returns the version of the zelenium product """ return self.Control_Panel.Products.Zelenium.version # Override this method to force Zuite objects are recursed. def _recurseListTestCases( self, result, prefix, ob ): for tcid, test_case in ob.objectItems(): if isinstance( test_case, Zuite ): result.extend( test_case.listTestCases( prefix=prefix + ( tcid, ) ) ) # Override this method to produce ERP5-style reports. # security.declarePublic('postResults') # def postResults(self, REQUEST): # """ Record the results of a test run. # """ # return self.TestTool_reportResult(REQUEST) # Use BaseTool class's methods for properties instead of patched PropertyManager's _propertyMap = BaseTool._propertyMap _setProperty = BaseTool._setProperty getProperty = BaseTool.getProperty hasProperty = BaseTool.hasProperty
class Interaction(ContainerTab): meta_type = 'Workflow Interaction' security = ClassSecurityInfo() security.declareObjectProtected(ManagePortal) all_meta_types = ({ 'name': InteractionDefinition.meta_type, 'action': 'addInteraction', 'permission': 'Manage portal', }, ) _manage_interaction = DTMLFile('interactions', _dtmldir) def manage_main(self, REQUEST, manage_tabs_message=None): ''' ''' return self._manage_interaction( REQUEST, management_view='Interactions', manage_tabs_message=manage_tabs_message, ) def addInteraction(self, id, REQUEST=None): ''' ''' tdef = InteractionDefinition(id) self._setObject(id, tdef) if REQUEST is not None: return self.manage_main(REQUEST, 'Interaction added.') def deleteInteractions(self, ids, REQUEST=None): ''' ''' for id in ids: self._delObject(id) if REQUEST is not None: return self.manage_main(REQUEST, 'Interaction(s) removed.')
""" self.title = str(title) self.description = str(description) self.manager_bypass = manager_bypass and 1 or 0 g = Guard() if g.changeFromProperties(props or REQUEST): self.creation_guard = g else: self.creation_guard = None if REQUEST is not None: return self.manage_properties( REQUEST, manage_tabs_message='Properties changed.') WorkflowUIMixin_class.setProperties = WorkflowUIMixin_setProperties WorkflowUIMixin_class.manage_properties = DTMLFile('workflow_properties', _dtmldir) def Guard_checkWithoutRoles(self, sm, wf_def, ob, **kw): """Checks conditions in this guard. This function is the same as Guard.check, but roles are not taken into account here (but taken into account as local roles). This version is for worklist guards. Note that this patched version is not a monkey patch on the class, because we only want this specific behaviour for worklists (Guards are also used in transitions). """ u_roles = None if wf_def.manager_bypass: # Possibly bypass.
class ProxyField(ZMIField): meta_type = "ProxyField" security = ClassSecurityInfo() widget = ProxyWidgetInstance validator = ProxyValidatorInstance delegated_list = tuple() delegated_message_list = tuple() # methods screen security.declareProtected('View management screens', 'manage_main') manage_main = DTMLFile('dtml/proxyFieldEdit', globals()) # tales screen security.declareProtected('View management screens', 'manage_talesForm') manage_talesForm = DTMLFile('dtml/proxyFieldTales', globals()) # messages screen security.declareProtected('View management screens', 'manage_messagesForm') manage_messagesForm = DTMLFile('dtml/proxyFieldMessages', globals()) # proxy field list header security.declareProtected('View management screens', 'proxyFieldListHeader') proxyFieldListHeader = DTMLFile('dtml/proxyFieldListHeader', globals()) security.declareProtected('Change Formulator Forms', 'manage_edit') def manage_edit(self, REQUEST): """ Surcharged values from proxied field. """ # Edit template field attributes template_field = self.getRecursiveTemplateField() if template_field is not None: # Check the surcharged checkboxes surcharge_list = [] for group in template_field.form.get_groups(): for field in template_field.form.get_fields_in_group(group): field_id = field.id checkbox_key = "surcharge_%s" % field_id if not REQUEST.has_key(checkbox_key): surcharge_list.append(field_id) try: # validate the form and get results result = template_field.form.validate(REQUEST) except ValidationError, err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise self._surcharged_edit(result, surcharge_list) # Edit standards attributes # XXX It is not possible to call ZMIField.manage_edit because # it returns at the end... # we need to had a parameter to the method try: # validate the form and get results result = self.form.validate(REQUEST) except ValidationError, err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.ERP5Type import WITH_LEGACY_WORKFLOW assert WITH_LEGACY_WORKFLOW # State types patch for DCWorkflow from Products.DCWorkflow.States import StateDefinition from Products.ERP5Type.Globals import DTMLFile from Products.ERP5Type import _dtmldir _properties_form = DTMLFile('state_properties', _dtmldir) def getAvailableTypeList(self): """This is a method specific to ERP5. This returns a list of state types, which are used for portal methods. """ return ( 'draft_order', 'planned_order', 'future_inventory', 'reserved_inventory', 'transit_inventory', 'current_inventory', )
class ArchiveTool(BaseTool): """ Archive Tool contains archive objects """ title = 'Archive Tool' id = 'portal_archives' meta_type = 'ERP5 Archive Tool' portal_type = 'Archive Tool' allowed_types = ('ERP5 Archive', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainArchiveTool', _dtmldir) def getSQLCatalogIdList(self): """ Wrapper to CatalogTool method """ return self.portal_catalog.getSQLCatalogIdList() def SQLConnectionIDs(self): """ Wrapper to CatalogTool method """ return self.portal_catalog.SQLConnectionIDs() def getArchiveIdList(self): """ Return list of usable archive displayed to user """ return ["%s - %s" %(x.getId(), x.getTitle()) for x in \ self.portal_catalog(portal_type="Archive", validation_state="ready")] def getCurrentArchive(self): """ Return the archive used for the current catalog """ current_catalog = self.portal_catalog.getDefaultSqlCatalogId() current_archive_list = [x.getObject() for x in self.searchFolder(validation_state="validated") \ if x.getCatalogId() == current_catalog] if len(current_archive_list) == 0: return None else: return current_archive_list[0] def getArchiveList(self): """ Return the list of archive use by catalog """ def _getArchiveList(): return [ x.getPath() for x in self.objectValues() if x.getValidationState() == "validated" ] # getArchiveList = CachingMethod(_getArchiveList, # id='getArchiveList', # cache_factory='erp5_content_short') return _getArchiveList() def manage_archive(self, destination_archive_id, archive_id, source_connection_id=None, source_deferred_connection_id=None, update_destination_sql_catalog=None, update_archive_sql_catalog=None, clear_destination_sql_catalog=None, clear_archive_sql_catalog=None, REQUEST=None, RESPONSE=None): """ This method is used to populate an archive from the current catalog It is base on hot reindexing, we start from a current catalog in order to create a new current catalog plus an archive catalog. Archives are defined in portal_archives, they are predicate thus we use test method to know in which catalog objects must go. At the end it creates inventories in order to have consistent data within the new catalog """ # First check parameter for destination catalog if destination_archive_id == archive_id: raise ValueError, "Archive and destination archive can't be the same" portal_catalog = self.portal_catalog # Guess connection id from current catalog source_catalog = portal_catalog.getSQLCatalog() source_catalog_id = source_catalog.getId() source_connection_id = source_catalog.getConnectionId() source_deferred_connection_id = source_catalog.getConnectionId( deferred=True) if source_connection_id is None or source_deferred_connection_id is None: raise ValueError, "Unable to determine connection id for the current catalog" # Get destination property from archive destination_archive_id = destination_archive_id.split(' - ')[0] destination_archive = self._getOb(destination_archive_id) destination_sql_catalog_id = destination_archive.getCatalogId() destination_connection_id = destination_archive.getConnectionId() destination_deferred_connection_id = destination_archive.getDeferredConnectionId( ) # Get archive property from archive archive_id = archive_id.split(' - ')[0] archive = self._getOb(archive_id) archive_sql_catalog_id = archive.getCatalogId() archive_connection_id = archive.getConnectionId() archive_deferred_connection_id = archive.getDeferredConnectionId() # Check we don't use same connection id for source and destination if destination_sql_catalog_id == source_catalog_id: raise ValueError, "Destination and source catalog can't be the same" if destination_connection_id == source_connection_id: raise ValueError, "Destination and source connection can't be the same" if destination_deferred_connection_id == source_deferred_connection_id: raise ValueError, "Destination and source deferred connection can't be the same" # Same for source and archive if archive_sql_catalog_id == source_catalog_id: raise ValueError, "Archive and source catalog can't be the same" if archive_connection_id == source_connection_id: raise ValueError, "Archive and source connection can't be the same" if archive_deferred_connection_id == source_deferred_connection_id: raise ValueError, "Archive and source deferred connection can't be the same" # Same for destination and archive if archive_sql_catalog_id == destination_sql_catalog_id: raise ValueError, "Archive and destination catalog can't be the same" if archive_connection_id == destination_connection_id: raise ValueError, "Archive and destination connection can't be the same" if archive_deferred_connection_id == destination_deferred_connection_id: raise ValueError, "Archive and destination deferred connection can't be the same" # Update connection id in destination and archive catalog if asked destination_sql_catalog = getattr(portal_catalog, destination_sql_catalog_id) if update_destination_sql_catalog: sql_connection_id_dict = { source_connection_id: destination_connection_id, source_deferred_connection_id: destination_deferred_connection_id } portal_catalog.changeSQLConnectionIds(destination_sql_catalog, sql_connection_id_dict) archive_sql_catalog = getattr(portal_catalog, archive_sql_catalog_id) if update_archive_sql_catalog: sql_connection_id_dict = { source_connection_id: archive_connection_id, source_deferred_connection_id: archive_deferred_connection_id } portal_catalog.changeSQLConnectionIds(archive_sql_catalog, sql_connection_id_dict) # Clear destination and archive catalog if asked if clear_destination_sql_catalog: portal_catalog.manage_catalogClear( sql_catalog_id=destination_sql_catalog_id) if clear_archive_sql_catalog: portal_catalog.manage_catalogClear( sql_catalog_id=archive_sql_catalog_id) # validate archive archive.validate() destination_archive.validate() # Call hot reindexing portal_catalog.manage_hotReindexAll(source_sql_catalog_id=source_catalog_id, destination_sql_catalog_id=destination_sql_catalog_id, archive_path=archive.getPath(), source_sql_connection_id_list=[source_connection_id, \ source_deferred_connection_id], destination_sql_connection_id_list=[destination_connection_id, \ destination_deferred_connection_id], REQUEST=REQUEST, RESPONSE=RESPONSE) # Create inventory just before finish of hot reindexing inventory_date = archive.getStopDateRangeMax() self.activate(after_method_id=('playBackRecordedObjectList'), priority=5).runInventoryMethod( archive.id, source_connection_id, destination_sql_catalog_id, inventory_date) self.activate( after_method_id=('runInventoryMethod'), after_tag="runInventoryMethod", priority=5).InventoryModule_reindexMovementList( sql_catalog_id=destination_sql_catalog_id, final_activity_tag="InventoryModule_reindexMovementList") if RESPONSE is not None: URL1 = REQUEST.get('URL1') RESPONSE.redirect( URL1 + '/portal_archives?portal_status_message=Archiving%20Started') def runInventoryMethod(self, archive_id, source_connection_id, destination_sql_catalog_id, inventory_date): """ Use a specific method to create inventory in order to use activity to execute it """ archive = self._getOb(archive_id) inventory_method_id = archive.getInventoryMethodId() inventory_method = getattr(archive, inventory_method_id, None) if inventory_method is not None: inventory_method(source_connection_id, destination_sql_catalog_id, inventory_date, tag='runInventoryMethod')
class ERP5Form(ZMIForm, ZopePageTemplate): """ A Formulator form with a built-in rendering parameter based on page templates or DTML. """ meta_type = "ERP5 Form" icon = "www/Form.png" # Declarative Security security = ClassSecurityInfo() # Tabs in ZMI manage_options = (ZMIForm.manage_options[:5] + ({'label':'Proxify', 'action':'formProxify'}, {'label':'UnProxify', 'action':'formUnProxify'}, {'label':'RelatedProxy', 'action':'formShowRelatedProxyFields'}, {'label': 'Cache', 'action': 'ZCacheable_manage', 'filter': filterCacheTab, 'help': ('OFSP', 'Cacheable-properties.stx')} )+ ZMIForm.manage_options[5:]) # Declarative properties property_sheets = ( PropertySheet.Base , PropertySheet.SimpleItem) # Constructors constructors = (manage_addForm, addERP5Form) # This is a patched dtml formOrder security.declareProtected('View management screens', 'formOrder') formOrder = DTMLFile('dtml/formOrder', globals()) # Proxify form security.declareProtected('View management screens', 'formProxify') formProxify = DTMLFile('dtml/formProxify', globals()) # Proxify form security.declareProtected('View management screens', 'formUnProxify') formUnProxify = DTMLFile('dtml/formUnProxify', globals()) # Related Proxy Fields security.declareProtected('View management screens', 'formShowRelatedProxyFields') formShowRelatedProxyFields = DTMLFile('dtml/formShowRelatedProxyFields', globals()) # Default Attributes pt = 'form_view' update_action = '' update_action_title = '' edit_order = [] # Special Settings settings_form = create_settings_form() def __init__(self, id, title, unicode_mode=0, encoding='UTF-8', stored_encoding='UTF-8'): """Initialize form. id -- id of form title -- the title of the form """ ZMIForm.inheritedAttribute('__init__')(self, "", "POST", "", id, encoding, stored_encoding, unicode_mode) self.id = id self.title = title self.row_length = 4 self.group_list = ["left", "right", "center", "bottom", "hidden"] groups = {} for group in self.group_list: groups[group] = [] self.groups = groups # Proxy method to PageTemplate def __call__(self, *args, **kwargs): # Security # # The minimal action consists in checking that # we have View permission on the current object # before rendering a form. Otherwise, object with # AccessContentInformation can be viewed by invoking # a form directly. # # What would be better is to prevent calling certain # forms to render objects. This can not be done # through actions since we are using sometimes forms # to render the results of a report dialog form. # An a appropriate solutions could consist in adding # a permission field to the form. Another solutions # is the use of REFERER in the rendering process. # # Both solutions are not perfect if the goal is, for # example, to prevent displaying private information of # staff. The only real solution is to use a special # permission (ex. AccessPrivateInformation) for those # properties which are sensitive. kwargs.setdefault('args', args) key_prefix = kwargs.pop('key_prefix', None) obj = getattr(self, 'aq_parent', None) if obj is not None: container = obj.aq_inner.aq_parent if not _checkPermission(Permissions.View, obj): raise AccessControl_Unauthorized('This document is not authorized for view.') else: container = None pt = getattr(self,self.pt) extra_context = dict( container=container, template=self, form=self, key_prefix=key_prefix, options=kwargs, here=obj, context=obj, ) return pt.pt_render(extra_context=extra_context) def _exec(self, bound_names, args, kw): pt = getattr(self,self.pt) return pt._exec(self, bound_names, args, kw) def manage_renameObject(self, id, new_id, REQUEST=None): # overriden to keep the order of a field after rename groups = deepcopy(self.groups) ret = ZMIForm.manage_renameObject(self, id, new_id, REQUEST=REQUEST) for group_id, field_id_list in groups.items(): if id in field_id_list: index = field_id_list.index(id) field_id_list.pop(index) field_id_list.insert(index, new_id) groups[group_id] = field_id_list self.groups = groups return ret # Utilities def ErrorFields(self, validation_errors): """ Create a dictionnary of validation_errors with field id as key """ ef = {} for e in validation_errors.errors: ef[e.field_id] = e return ef def om_icons(self): """Return a list of icon URLs to be displayed by an ObjectManager""" icons = ({'path': 'misc_/ERP5Form/Form.png', 'alt': self.meta_type, 'title': self.meta_type},) return icons # Pached validate_all to support ListBox validation security.declareProtected('View', 'validate_all') def validate_all(self, REQUEST, key_prefix=None): """Validate all enabled fields in this form, catch any ValidationErrors if they occur and raise a FormValidationError in the end if any Validation Errors occured. """ result = {} errors = [] for group in self.get_groups(): if group.lower() == 'hidden': continue for field in self.get_fields_in_group(group): # skip any field we don't need to validate if not field.need_validate(REQUEST, key_prefix=key_prefix): continue if not (field.get_value('editable',REQUEST=REQUEST)): continue try: value = field.validate(REQUEST, key_prefix=key_prefix) # store under id result[field.id] = value # store as alternate name as well if necessary alternate_name = field.get_value('alternate_name') if alternate_name: result[alternate_name] = value except FormValidationError, e: # XXX JPS Patch for listbox errors.extend(e.errors) result.update(e.result) except ValidationError, err: errors.append(err) except KeyError, err: LOG('ERP5Form/Form.py:validate_all', 0, 'KeyError : %s' % (err, ))
class AlarmTool(TimerServiceMixin, BaseTool): """ This tool manages alarms. It is used as a central managment point for all alarms. Inside this tool we have a way to retrieve all reports coming from Alarms,... """ id = 'portal_alarms' meta_type = 'ERP5 Alarm Tool' portal_type = 'Alarm Tool' # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainAlarmTool', _dtmldir) security.declareProtected(Permissions.ManagePortal, 'manageAlarmNode') manageAlarmNode = DTMLFile('manageAlarmNode', _dtmldir) manage_options = (( { 'label': 'Overview', 'action': 'manage_overview' }, { 'label': 'Alarm Node', 'action': 'manageAlarmNode' }, ) + Folder.manage_options) _properties = ({ 'id': 'interval', 'type': 'int', 'mode': 'w', }, ) interval = 60 # Default interval for alarms is 60 seconds # alarmNode possible values: # '' Bootstraping. The first node to call process_timer will cause this # value to be set to its node id. # (other) Node id matching this value will be the alarmNode. # Those values were chosen for backward compatibility with sites having an # alarmNode set to '' but expecting alarms to be executed. Use None to # disable alarm processing (see setAlarmNode). alarmNode = '' # API to manage alarms # Aim of this API: #-- see all alarms stored everywhere #-- defines global alarms #-- activate an alarm #-- see reports #-- see active alarms #-- retrieve all alarms security.declareProtected(Permissions.ModifyPortalContent, 'getAlarmList') def getAlarmList(self, to_active=0): """ We retrieve thanks to the catalog the full list of alarms """ if to_active: now = DateTime() catalog_search = self.portal_catalog.unrestrictedSearchResults( portal_type=self.getPortalAlarmTypeList(), alarm_date={ 'query': now, 'range': 'ngt' }) # check again the alarm date in case the alarm was not yet reindexed alarm_list = [] for x in catalog_search: alarm = x.getObject() alarm_date = alarm.getAlarmDate() if alarm_date is not None and alarm_date <= now: alarm_list.append(alarm) else: catalog_search = self.portal_catalog.unrestrictedSearchResults( portal_type=self.getPortalAlarmTypeList()) alarm_list = [x.getObject() for x in catalog_search] return alarm_list security.declareProtected(Permissions.ModifyPortalContent, 'tic') def tic(self): """ We will look at all alarms and see if they should be activated, if so then we will activate them. """ security_manager = getSecurityManager() try: for alarm in self.getAlarmList(to_active=1): if alarm is not None: user = alarm.getWrappedOwner() newSecurityManager(self.REQUEST, user) if alarm.isActive() or not alarm.isEnabled(): # do nothing if already active, or not enabled continue alarm.activeSense() finally: setSecurityManager(security_manager) security.declarePrivate('process_timer') def process_timer(self, interval, tick, prev="", next=""): """ Call tic() every x seconds. x is defined in self.interval This method is called by TimerService in the interval given in zope.conf. The Default is every 5 seconds. """ if not last_tic_lock.acquire(0): return try: # make sure our skin is set-up. On CMF 1.5 it's setup by acquisition, # but on 2.2 it's by traversal, and our site probably wasn't traversed # by the timerserver request, which goes into the Zope Control_Panel # calling it a second time is a harmless and cheap no-op. # both setupCurrentSkin and REQUEST are acquired from containers. self.setupCurrentSkin(self.REQUEST) # only start when we are the alarmNode alarmNode = self.getAlarmNode() current_node = self.getCurrentNode() if alarmNode == '': self.setAlarmNode(current_node) alarmNode = current_node if alarmNode == current_node: global last_tic now = tick.timeTime() if now - last_tic >= self.interval: self.tic() last_tic = now finally: last_tic_lock.release() security.declarePublic('getAlarmNode') def getAlarmNode(self): """ Return the alarmNode """ return self.alarmNode security.declareProtected(Permissions.ManageProperties, 'setAlarmNode') def setAlarmNode(self, alarm_node): """ When alarm_node evaluates to false, set a None value: Its meaning is that alarm processing is disabled. This avoids an empty string to make the system re-enter boostrap mode. """ if alarm_node: self.alarmNode = alarm_node else: self.alarmNode = None security.declareProtected(Permissions.ManageProperties, 'manage_setAlarmNode') def manage_setAlarmNode(self, alarmNode, REQUEST=None): """ set the alarm node """ if not alarmNode or self._isValidNodeName(alarmNode): self.setAlarmNode(alarmNode) if REQUEST is not None: REQUEST.RESPONSE.redirect( REQUEST.URL1 + '/manageAlarmNode?manage_tabs_message=' + urllib.quote("Distributing Node successfully changed.")) else: if REQUEST is not None: REQUEST.RESPONSE.redirect( REQUEST.URL1 + '/manageAlarmNode?manage_tabs_message=' + urllib.quote("Malformed Distributing Node."))
# values from request if (self.has_value('unicode') and self.get_value('unicode') and type(value) == type('')): return unicode(value, self.get_form_encoding()) else: return value # Dynamic Patch Field.get_value = get_value Field._get_default = _get_default Field.om_icons = om_icons # Constructors manage_addForm = DTMLFile("dtml/form_add", globals()) def addERP5Form(self, id, title="", REQUEST=None): """Add form to folder. id -- the id of the new form to add title -- the title of the form to add Result -- empty string """ # add actual object id = self._setObject(id, ERP5Form(id, title)) # respond to the add_and_edit button if necessary add_and_edit(self, id, REQUEST) return '' def add_and_edit(self, id, REQUEST): """Helper method to point to the object's management screen if
class TrashTool(BaseTool): """ TrashTool contains objects removed/replaced during installation of business templates. """ title = 'Trash Tool' id = 'portal_trash' meta_type = 'ERP5 Trash Tool' portal_type = 'Trash Tool' allowed_types = ('ERP5 Trash Bin', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainTrashTool', _dtmldir) security.declarePrivate('backupObject') def backupObject(self, trashbin, container_path, object_id, save, **kw): """ Backup an object in a trash bin """ # LOG('Trash : backup object', 0, str((container_path, object_id))) if save: # recreate path of the backup object if necessary backup_object_container = trashbin for path in container_path: if 'portal' in path: path += '_items' if path not in backup_object_container.objectIds(): if not hasattr(aq_base(backup_object_container), "newContent"): backup_object_container.manage_addFolder(id=path, ) backup_object_container = backup_object_container._getOb( path) else: backup_object_container = backup_object_container.newContent( portal_type='Trash Folder', id=path, is_indexable=0) backup_object_container.edit(isHidden=1) else: backup_object_container = backup_object_container._getOb( path) # backup the object # here we choose export/import to copy because cut/paste # do too many things and check for what we want to do object_path = container_path + [object_id] obj = self.unrestrictedTraverse(object_path, None) if obj is not None: connection = obj._p_jar o = obj while connection is None: o = o.aq_parent connection = o._p_jar if obj._p_oid is None: LOG("Trash Tool backupObject", WARNING, "Trying to backup uncommitted object %s" % object_path) return {} if isinstance(obj, Broken): LOG("Trash Tool backupObject", WARNING, "Can't backup broken object %s" % object_path) klass = obj.__class__ if klass.__module__[:27] in ('Products.ERP5Type.Document.', 'erp5.portal_type'): # meta_type is required so that a broken object # can be removed properly from a BTreeFolder2 # (unfortunately, we can only guess it) klass.meta_type = 'ERP5' + re.subn( '(?=[A-Z])', ' ', klass.__name__)[0] return {} copy = connection.exportFile(obj._p_oid) # import object in trash connection = backup_object_container._p_jar o = backup_object_container while connection is None: o = o.aq_parent connection = o._p_jar copy.seek(0) try: backup = connection.importFile(copy) backup.isIndexable = ConstantGetter('isIndexable', value=False) # the isIndexable setting above avoids the recursion of # manage_afterAdd on # Products.ERP5Type.CopySupport.CopySupport.manage_afterAdd() # but not on event subscribers, so we need to suppress_events, # otherwise subobjects will be reindexed backup_object_container._setObject(object_id, backup, suppress_events=True) except (AttributeError, ImportError): # XXX we can go here due to formulator because attribute # field_added doesn't not exists on parent if it is a Trash # Folder and not a Form, or a module for the old object is # already removed, and we cannot backup the object LOG("Trash Tool backupObject", WARNING, "Can't backup object %s" % object_path) return {} keep_sub = kw.get('keep_subobjects', 0) subobjects_dict = {} if not keep_sub: # export subobjects if save: obj = backup_object_container._getOb(object_id, None) else: object_path = container_path + [object_id] obj = self.unrestrictedTraverse(object_path, None) if obj is not None: for subobject_id in list(obj.objectIds()): subobject = obj[subobject_id] subobjects_dict[ subobject_id] = subobject._p_jar.exportFile( subobject._p_oid, StringIO()) if save: # remove subobjecs from backup object obj._delObject(subobject_id) if subobject_id in obj.objectIds(): LOG('Products.ERP5.Tool.TrashTool', WARNING, 'Cleaning corrupted BTreeFolder2 object at %r.' % \ (subobject.getRelativeUrl(),)) obj._cleanup() return subobjects_dict security.declarePrivate('newTrashBin') def newTrashBin(self, bt_title='trash', bt=None): """ Create a new trash bin at upgrade of bt """ # construct date date = DateTime() start_date = date.strftime('%Y-%m-%d') def getBaseTrashId(): ''' A little function to get an id without leading underscore ''' base_id = '%s' % start_date if bt_title not in ('', None): base_id = '%s_%s' % (bt_title, base_id) return base_id # generate id trash_ids = self.objectIds() n = 0 new_trash_id = getBaseTrashId() while new_trash_id in trash_ids: n += 1 new_trash_id = '%s_%s' % (getBaseTrashId(), n) # create trash bin trashbin = self.newContent(portal_type='Trash Bin', id=new_trash_id, title=bt_title, start_date=start_date, causality_value=bt) return trashbin security.declareProtected(Permissions.ManagePortal, 'getTrashBinObjectsList') def getTrashBinObjectsList(self, trashbin): """ Return a list of trash objects for a given trash bin """ def getChildObjects(obj): object_list = [] if hasattr(aq_base(obj), 'objectValues'): childObjects = obj.objectValues() if hasattr(aq_base(obj), 'isHidden'): if not obj.isHidden: object_list.append(obj) if len(childObjects) > 0: for o in childObjects: object_list.extend(getChildObjects(o)) else: object_list.append(obj) return object_list list = getChildObjects(trashbin) list.sort() return list
class CaptchaField(ZMIField): security = ClassSecurityInfo() meta_type = "CaptchaField" widget = CaptchaWidgetInstance validator = CaptchaValidatorInstance # methods screen security.declareProtected('View management screens', 'manage_main') manage_main = DTMLFile('dtml/captchaFieldEdit', globals()) security.declareProtected('Change Formulator Forms', 'manage_edit') def manage_edit(self, REQUEST): """ Surcharged values for the captcha provider custom fields. """ captcha_provider = CaptchaProviderFactory.getProvider( self.get_value("captcha_type")) result = {} for field in captcha_provider.getExtraPropertyList(): try: # validate the form and get results result[field.get_real_field().id] = field.get_real_field( ).validate(REQUEST) except ValidationError as err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise # Edit standards attributes # XXX It is not possible to call ZMIField.manage_edit because # it returns at the end... # we need to had a parameter to the method try: # validate the form and get results result.update(self.form.validate(REQUEST)) except ValidationError as err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_main(self, REQUEST, manage_tabs_message=message) else: raise self.values.update(result) # finally notify field of all changed values if necessary for key in result: method_name = "on_value_%s_changed" % key if hasattr(self, method_name): getattr(self, method_name)(result[key]) if REQUEST: message = "Content changed." return self.manage_main(self, REQUEST, manage_tabs_message=message) security.declareProtected('Access contents information', 'get_value') def get_value(self, id, **kw): if id in self.getCaptchaCustomPropertyList(): return self.values[id] return ZMIField.get_value(self, id, **kw) def getCaptchaCustomPropertyList(self): captcha_type = ZMIField.get_value(self, "captcha_type") captcha_provider = CaptchaProviderFactory.getProvider(captcha_type) extraPropertyList = captcha_provider.getExtraPropertyList() return extraPropertyList security.declareProtected('View management screens', 'manage_talesForm') manage_talesForm = DTMLFile('dtml/captchaFieldTales', globals()) security.declareProtected('Change Formulator Forms', 'manage_tales') def manage_tales(self, REQUEST): """Change TALES expressions. """ result = {} # add dynamic form fields for captcha #captcha_provider = CaptchaProviderFactory.getProvider(self.get_value("captcha_type")) for field in self.getCaptchaCustomTalesPropertyList(): try: # validate the form and get results result[field.id] = field.validate(REQUEST) except ValidationError as err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_talesForm(self, REQUEST, manage_tabs_message=message) else: raise # standard tales form fields try: # validate the form and get results result.update(self.tales_form.validate(REQUEST)) except ValidationError as err: if REQUEST: message = "Error: %s - %s" % (err.field.get_value('title'), err.error_text) return self.manage_talesForm(self, REQUEST, manage_tabs_message=message) else: raise self._edit_tales(result) if REQUEST: message = "Content changed." return self.manage_talesForm(self, REQUEST, manage_tabs_message=message) def getCaptchaCustomTalesPropertyList(self): captcha_type = ZMIField.get_value(self, "captcha_type") captcha_provider = CaptchaProviderFactory.getProvider(captcha_type) extraPropertyList = captcha_provider.getExtraTalesPropertyList() return extraPropertyList
class ERP5CatalogTool(BaseTool, CMFCore_CatalogTool): id = 'portal_catalog' title = 'Catalog Tool' meta_type = 'Catalog Tool' portal_type = 'Catalog Tool' allowed_types = ('Catalog', ) # Declarative security security = ClassSecurityInfo() # Explicitly add tabs for manage_options manage_options = ( { 'label': 'Contents', 'action': 'manage_main' }, { 'label': 'View', 'action': 'view' }, { 'label': 'Security', 'action': 'manage_access' }, { 'label': 'Undo', 'action': 'manage_UndoForm' }, { 'label': 'Ownership', 'action': 'manage_owner' }, { 'label': 'Interfaces', 'action': 'manage_interfaces' }, { 'label': 'Find', 'action': 'manage_findForm' }, { 'label': 'History', 'action': 'manage_change_history_page' }, { 'label': 'Workflows', 'action': 'manage_workflowsTab' }, ) property_sheets = (PropertySheet.Base, PropertySheet.SimpleItem, PropertySheet.Folder, PropertySheet.CategoryCore, PropertySheet.CatalogTool) # Use reindexObject method from BaseTool class and declare it public reindexObject = BaseTool.reindexObject security.declarePublic('reindexObject') # Explicit Inheritance __url = CMFCoreCatalogTool._CatalogTool__url unindexObject = CMFCore_CatalogTool.unindexObject __call__ = CMFCore_CatalogTool.__call__ _aq_dynamic = CMFCore_CatalogTool._aq_dynamic ZopeFindAndApply = CMFCore_CatalogTool.ZopeFindAndApply #_checkId = CMFCore_CatalogTool._checkId listDAVObjects = CMFCore_CatalogTool.listDAVObjects __class_init__ = CMFCore_CatalogTool.__class_init__ security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('dtml/explainCatalogTool', globals()) # IMPORTANT:Solve inheritance conflict, this is necessary as getObject from # Base gives the current object, which migth be harmful for CatalogTool as # we use this function here to sometimes get objects to delete which if # not solved of inheritance conflict might lead to catalog deletion. getObject = ZCatalog.getObject default_erp5_catalog_id = None def __init__(self, id=''): ZCatalog.__init__(self, self.getId()) BaseTool.__init__(self, self.getId()) def _isBootstrapRequired(self): return False def _bootstrap(self): pass def getDefaultSqlCatalogId(self): return self.default_erp5_catalog_id def _setDefaultSqlCatalogId(self, value): """ Function to maintain compatibility between ZSQLCatalog and ERP5CatalogTool Notice that we update the attribute `default_erp5_catalog_id` here and not the property. This is because there maybe cases(migration) whern we don't have accessors defined and there we'll need the attribute. """ self.default_erp5_catalog_id = value # Filter content (ZMI)) def filtered_meta_types(self, user=None): # Filters the list of available meta types for CatalogTool meta_types = [] for meta_type in ERP5CatalogTool.inheritedAttribute( 'filtered_meta_types')(self): if meta_type['name'] in self.allowed_types: meta_types.append(meta_type) return meta_types allowedContentTypes = BaseTool.allowedContentTypes getVisibleAllowedContentTypeList = BaseTool.getVisibleAllowedContentTypeList # The functions 'getERP5CatalogIdList' and 'getERP5Catalog' are meant to # be used in restricted environment, cause the reason they were created is # the transition of Catalog from SQLCatalog to ERP5Catalog, which basically # means Catalog is going to be an ERP5 object, which is why we need these # functions to be declared public. security.declarePublic('getERP5CatalogIdList') def getERP5CatalogIdList(self): """ Get ERP5 Catalog Ids """ return list(self.objectIds(spec=('ERP5 Catalog', ))) security.declarePublic('getERP5Catalog') def getERP5Catalog(self, id=None, default_value=None): """ Get current ERP5 Catalog """ if id is None: if not self.default_erp5_catalog_id: id_list = self.getERP5CatalogIdList() if len(id_list) > 0: self.default_erp5_catalog_id = id_list[0] else: return default_value id = self.default_erp5_catalog_id return self._getOb(id, default_value) security.declarePublic('getSQLCatalog') getSQLCatalog = getERP5Catalog # For compatibilty security.declarePrivate('reindexCatalogObject') def reindexCatalogObject(self, object, idxs=None, sql_catalog_id=None, **kw): '''Update catalog after object data has changed. The optional idxs argument is a list of specific indexes to update (all of them by default). ''' if idxs is None: idxs = [] url = self.__url(object) self.catalog_object(object, url, idxs=idxs, sql_catalog_id=sql_catalog_id, **kw) security.declareProtected(Permissions.View, 'index_html') def index_html(self): """ Override index_html to display the view for Catalog Tool """ return self.view() security.declarePrivate('getCatalogUrl') def getCatalogUrl(self, object): return self.__url(object) def _redirectHotReindexAll(self, REQUEST, RESPONSE): ''' Override this function from ZSQLCatalog as here we want to redirect to the view for ERP5CatalogTool. ''' if not RESPONSE: try: RESPONSE = REQUEST.RESPONSE except AttributeError: return if RESPONSE is not None: url = self.absolute_url() + '/view' \ + '?portal_status_message=HotReindexing%20Started' return RESPONSE.redirect(url)
class ContributionTool(BaseTool): """ ContributionTool provides an abstraction layer to unify the contribution of documents into an ERP5 Site. ContributionTool needs to be configured in portal_types (allowed contents) so that it can store Text, Spreadsheet, PDF, etc. The main method of ContributionTool is newContent. This method can be provided various parameters from which the portal type and document metadata can be derived. Configuration Scripts: - ContributionTool_getPropertyDictFromFilename: receives file name and a dict derived from filename by regular expression, and does any necesary operations (e.g. mapping document type id onto a real portal_type). Problems which are not solved - handling of relative links in HTML contents (or others...) some text rewriting is necessary. """ title = 'Contribution Tool' id = 'portal_contributions' meta_type = 'ERP5 Contribution Tool' portal_type = 'Contribution Tool' # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainContributionTool', _dtmldir) security.declareProtected(Permissions.AddPortalContent, 'newContent') @fill_args_from_request('data', 'filename', 'portal_type', 'container_path', 'discover_metadata', 'temp_object', 'reference') def newContent(self, REQUEST=None, **kw): """ The newContent method is overriden to implement smart content creation by detecting the portal type based on whatever information was provided and finding out the most appropriate module to store the content. explicit named parameters was: id - id of document portal_type - explicit portal_type parameter, must be honoured url - Identifier of external resource. Content will be downloaded from it container - if specified, it is possible to define where to contribute the content. Else, ContributionTool tries to guess. container_path - if specified, defines the container path and has precedence over container discover_metadata - Enable metadata extraction and discovery (default True) temp_object - build tempObject or not (default False) user_login - is the name under which the content will be created XXX - this is a security hole which needs to be fixed by making sure only Manager can use this parameter data - Binary representation of content filename - explicit filename of content """ # Useful for metadata discovery, keep it as it as been provided input_parameter_dict = kw.copy() # But file and data are exceptions. # They are potentialy too big to be keept into memory. # We want to keep only one reference of thoses values # on futur created document only ! if 'file' in input_parameter_dict: del input_parameter_dict['file'] if 'data' in input_parameter_dict: del input_parameter_dict['data'] if 'container' in input_parameter_dict: # Container is a persistent object # keep only its path in container_path key container = input_parameter_dict.pop('container') input_parameter_dict['container_path'] = container.getPath() # pop: remove keys which are not document properties url = kw.pop('url', None) container = kw.pop('container', None) container_path = kw.pop('container_path', None) discover_metadata = kw.pop('discover_metadata', True) user_login = kw.pop('user_login', None) document_id = kw.pop('id', None) # check file_name argument for backward compatibility. if 'file_name' in kw: if 'filename' not in kw: kw['filename'] = kw['file_name'] del (kw['file_name']) filename = kw.get('filename', None) temp_object = kw.get('temp_object', False) document = None portal = self.getPortalObject() if container is None and container_path: # Get persistent object from its path. # Container may disappear, be smoother by passing default value container = portal.restrictedTraverse(container_path, None) # Try to find the filename if not url: # check if file was provided file_object = kw.get('file') if file_object is not None: if not filename: filename = getattr(file_object, 'filename', None) else: # some channels supply data and file-name separately # this is the case for example for email ingestion # in this case, we build a file wrapper for it try: data = kw.pop('data') except KeyError: raise ValueError('data must be provided') if data is not None: file_object = cStringIO.StringIO() file_object.write(data) file_object.seek(0) kw['file'] = file_object content_type = kw.pop('content_type', None) else: file_object, filename, content_type = self._openURL(url) content_type = kw.pop('content_type', None) or content_type kw['file'] = file_object if not filename and url is None: raise ValueError('filename must be provided') if not content_type: # fallback to a default content_type according provided # filename content_type = self.guessMimeTypeFromFilename(filename) if content_type: kw['content_type'] = content_type portal_type = kw.pop('portal_type', None) if not portal_type: # Guess it with help of portal_contribution_registry portal_type = portal.portal_contribution_registry.findPortalTypeName( filename=filename, content_type=content_type) if not (container is None or container.isModuleType() or container.getTypeInfo().allowType(portal_type)): portal_type = 'Embedded File' if container is None: # If the portal_type was provided, we can go faster if portal_type: # We know the portal_type, let us find the default module # and use it as container try: container = portal.getDefaultModule(portal_type) except ValueError: pass elif not url: # Simplify things here and return a document immediately # XXX Nicolas: This will break support of WebDAV # if _setObject is not called document = container.newContent(document_id, portal_type, **kw) if discover_metadata: document.activate(after_path_and_method_id=(document.getPath(), ('convertToBaseFormat', 'Document_tryToConvertToBaseFormat')))\ .discoverMetadata(filename=filename, user_login=user_login, input_parameter_dict=input_parameter_dict) if REQUEST is not None: response = REQUEST.RESPONSE response.setHeader('X-Location', document.absolute_url()) return response.redirect(self.absolute_url()) return document # # Check if same file is already exists. if it exists, then update it. # property_dict = self.getMatchedFilenamePatternDict(filename) reference = property_dict.get('reference', None) version = property_dict.get('version', None) language = property_dict.get('language', None) if portal_type and reference and version and language: portal_catalog = portal.portal_catalog document = portal_catalog.getResultValue(portal_type=portal_type, reference=reference, version=version, language=language) if document is not None: # document is already uploaded. So overrides file. if not _checkPermission(Permissions.ModifyPortalContent, document): raise Unauthorized, "[DMS] You are not allowed to update the existing document which has the same coordinates (id %s)" % document.getId( ) document.edit(file=kw['file']) return document # Temp objects use the standard newContent from Folder if temp_object: # For temp_object creation, use the standard method return BaseTool.newContent(self, portal_type=portal_type, **kw) # Then put the file inside ourselves for a short while document = self._setObject(document_id, None, portal_type=portal_type, user_login=user_login, container=container, discover_metadata=discover_metadata, filename=filename, input_parameter_dict=input_parameter_dict) object_id = document.getId() document = self[object_id] # Call __getitem__ to purge cache kw['filename'] = filename # Override filename property # Then edit the document contents (so that upload can happen) document._edit(**kw) if url: document.fromURL(url) # Allow reindexing, reindex it and return the document try: del document.isIndexable except AttributeError: # Document does not have such attribute pass document.reindexObject() if REQUEST is not None: return REQUEST.RESPONSE.redirect(self.absolute_url()) return document security.declareProtected(Permissions.AddPortalContent, 'newXML') def newXML(self, xml): """ Create a new content based on XML data. This is intended for contributing to ERP5 from another application. """ pass security.declareProtected(Permissions.ModifyPortalContent, 'getMatchedFilenamePatternDict') def getMatchedFilenamePatternDict(self, filename): """ Get matched group dict of file name parsing regular expression. """ property_dict = {} if filename is None: return property_dict regex_text = self.portal_preferences.\ getPreferredDocumentFilenameRegularExpression() if regex_text in ('', None): return property_dict if regex_text: pattern = re.compile(regex_text) if pattern is not None: try: property_dict = pattern.match(filename).groupdict() except AttributeError: # no match pass return property_dict # backward compatibility security.declareProtected(Permissions.ModifyPortalContent, 'getMatchedFileNamePatternDict') def getMatchedFileNamePatternDict(self, filename): """ (deprecated) use getMatchedFilenamePatternDict() instead. """ warnings.warn('getMatchedFileNamePatternDict() is deprecated. ' 'use getMatchedFilenamePatternDict() instead.') return self.getMatchedFilenamePatternDict(filename) security.declareProtected(Permissions.ModifyPortalContent, 'getPropertyDictFromFilename') def getPropertyDictFromFilename(self, filename): """ Gets properties from filename. File name is parsed with a regular expression set in preferences. The regexp should contain named groups. """ if filename is None: return {} property_dict = self.getMatchedFilenamePatternDict(filename) try: method = self._getTypeBasedMethod( 'getPropertyDictFromFilename', fallback_script_id= 'ContributionTool_getPropertyDictFromFilename') except AttributeError: # Try to use previous naming convention method = self._getTypeBasedMethod( 'getPropertyDictFromFileName', fallback_script_id= 'ContributionTool_getPropertyDictFromFileName') property_dict = method(filename, property_dict) return property_dict # backward compatibility security.declareProtected(Permissions.ModifyPortalContent, 'getPropertyDictFromFileName') def getPropertyDictFromFileName(self, filename): """ (deprecated) use getPropertyDictFromFilename() instead. """ warnings.warn('getPropertyDictFromFileName() is deprecated. ' 'use getPropertyDictFromFilename() instead.') return self.getPropertyDictFromFilename(filename) # WebDAV virtual folder support def _setObject(self, id, ob, portal_type=None, user_login=None, container=None, discover_metadata=True, filename=None, input_parameter_dict=None): """ portal_contribution_registry will find appropriate portal type name by filename and content itself. The ContributionTool instance must be configured in such way that _verifyObjectPaste will return TRUE. """ # _setObject is called by constructInstance at a time # when the object has no portal_type defined yet. It # will be removed later on. We can safely store the # document inside us at this stage. Else we # must find out where to store it. if ob is not None: # Called from webdav API # Object is already created by PUT_factory # fill the volatile cache _v_document_cache # then return the document document = ob else: if not portal_type: document = BaseTool.newContent(self, id=id, portal_type=portal_type, is_indexable=0) elif ob is None: # We give the system a last chance to analyse the # portal_type based on the document content # (ex. a Memo is a kind of Text which can be identified # by the fact it includes some specific content) # Now we know the portal_type, let us find the module # to which we should move the document to if container is None: module = self.getDefaultModule(portal_type) else: module = container # There is no preexisting document - we can therefore # set the new object new_content_kw = { 'portal_type': portal_type, 'is_indexable': False } if id is not None: new_content_kw['id'] = id document = module.newContent(**new_content_kw) # We can now discover metadata if discover_metadata: # Metadata disovery is done as an activity by default # If we need to discoverMetadata synchronously, it must # be for user interface and should thus be handled by # ZODB scripts document.activate(after_path_and_method_id=( document.getPath(), ('convertToBaseFormat', 'Document_tryToConvertToBaseFormat'), ), ).discoverMetadata( filename=filename, user_login=user_login, input_parameter_dict=input_parameter_dict) # Keep the document close to us - this is only useful for # file upload from webdav volatile_cache = getattr(self, '_v_document_cache', None) if volatile_cache is None: self._v_document_cache = {} volatile_cache = self._v_document_cache volatile_cache[document.getId()] = document.getRelativeUrl() # Return document to newContent method return document def __getitem__(self, id): # Use the document cache if possible and return result immediately # this is only useful for webdav volatile_cache = getattr(self, '_v_document_cache', None) if volatile_cache is not None: document_url = volatile_cache.get(id) if document_url is not None: del volatile_cache[id] return self.getPortalObject().unrestrictedTraverse( document_url) # Try first to return the real object inside # This is much safer than trying to access objects displayed by listDAVObjects # because the behaviour of catalog is unpredicatble if a string is passed # for a UID. For example # select path from catalog where uid = "001193.html"; # will return the same as # select path from catalog where uid = 1193; # This was the source of an error in which the contribution tool # was creating a web page and was returning a Base Category # when # o = folder._getOb(id) # was called in DocumentConstructor if id in self: # NOTE: used 'id in self' instead of catching KeyError because # __getitem__ can return NullResource return super(ContributionTool, self).__getitem__(id) # Return an object listed by listDAVObjects # ids are concatenation of uid + '-' + standard file name of documents # get the uid uid = str(id).split('-', 1)[0] object = self.getPortalObject( ).portal_catalog.unrestrictedGetResultValue(uid=uid) if object is not None: return object.getObject( ) # Make sure this does not break security. XXX # Overriden method can return a NullResource or raise a KeyError: return super(ContributionTool, self).__getitem__(id) def listDAVObjects(self): """ Get all contents contributed by the current user. This is delegated to a script in order to help customisation. XXX Killer feature, it is not scalable """ method = getattr(self, 'ContributionTool_getMyContentList', None) if method is not None: object_list = method() else: sm = getSecurityManager() user = sm.getUser() object_list = self.portal_catalog( portal_type=self.getPortalMyDocumentTypeList(), owner=user.getIdOrUserName()) def wrapper(o_list): for o in o_list: o = o.getObject() id = '%s-%s' % ( o.getUid(), o.getStandardFilename(), ) yield o.asContext(id=id) return wrapper(object_list) security.declareProtected(Permissions.AddPortalContent, 'crawlContent') def crawlContent(self, content, container=None): """ Analyses content and download linked pages XXX: missing is the conversion of content local href to something valid. """ portal = self.getPortalObject() url_registry_tool = portal.portal_url_registry depth = content.getCrawlingDepth() if depth < 0: # Do nothing if crawling depth is reached # (this is not a duplicate code but a way to prevent # calling isIndexContent unnecessarily) return if not content.isIndexContent( ): # Decrement depth only if it is a content document depth = depth - 1 if depth < 0: # Do nothing if crawling depth is reached return for url in set(content.getContentNormalisedURLList()): # LOG('trying to crawl', 0, url) # Some url protocols should not be crawled if urlparse.urlsplit(url)[0] in no_crawl_protocol_list: continue if container is None: #if content.getParentValue() # in place of not ? container = content.getParentValue() try: url_registry_tool.getReferenceFromURL(url, context=container) except KeyError: pass else: # url already crawled continue # XXX - This call is not working due to missing group_method_id # therefore, multiple call happen in parallel and eventually fail # (the same URL is created multiple times) # LOG('activate newContentFromURL', 0, url) self.activate(activity="SQLQueue").newContentFromURL( container_path=container.getRelativeUrl(), url=url, crawling_depth=depth) # Url is not known yet but register right now to avoid # creation of duplicated crawled content # An activity will later setup the good reference for it. url_registry_tool.registerURL(url, None, context=container) security.declareProtected(Permissions.AddPortalContent, 'updateContentFromURL') def updateContentFromURL(self, content, repeat=MAX_REPEAT, crawling_depth=0, repeat_interval=1, batch_mode=True): """ Updates an existing content. """ # First, test if the document is updatable according to # its workflow states (if it has a workflow associated with) if content.isUpdatable(): # Step 0: update crawling_depth if required if crawling_depth > content.getCrawlingDepth(): content._setCrawlingDepth(crawling_depth) # Step 1: download new content try: url = content.asURL() file_object, filename, content_type = self._openURL(url) except urllib2.URLError, error: if repeat == 0 or not batch_mode: # XXX - Call the extendBadURLList method,--NOT Implemented-- raise content.activate(at_date=DateTime() + repeat_interval).updateContentFromURL( repeat=repeat - 1) return content._edit(file=file_object, content_type=content_type) # Please make sure that if content is the same # we do not update it # This feature must be implemented by Base or File # not here (look at _edit in Base) # Step 2: convert to base format if content.isSupportBaseDataConversion(): content.activate().Document_tryToConvertToBaseFormat() # Step 3: run discoverMetadata content.activate(after_path_and_method_id=(content.getPath(), ('convertToBaseFormat', 'Document_tryToConvertToBaseFormat'))) \ .discoverMetadata(filename=filename) # Step 4: activate populate (unless interaction workflow does it) content.activate().populateContent() # Step 5: activate crawlContent depth = content.getCrawlingDepth() if depth > 0: content.activate().crawlContent()
class InteractionDefinition(SimpleItem): meta_type = 'Workflow Interaction' security = ClassSecurityInfo() security.declareObjectProtected(ManagePortal) title = '' description = '' new_state_id = '' trigger_type = TRIGGER_WORKFLOW_METHOD guard = None actbox_name = '' actbox_url = '' actbox_category = 'workflow' var_exprs = None # A mapping. script_name = () # Executed before transition after_script_name = () # Executed after transition before_commit_script_name = () #Executed Before Commit Transaction activate_script_name = () # Executed as activity method_id = () portal_type_filter = None portal_type_group_filter = None once_per_transaction = False temporary_document_disallowed = False manage_options = ( { 'label': 'Properties', 'action': 'manage_properties' }, { 'label': 'Variables', 'action': 'manage_variables' }, ) def __init__(self, id): self.id = id def getId(self): return self.id def getGuardSummary(self): res = None if self.guard is not None: res = self.guard.getSummary() return res def getGuard(self): if self.guard is not None: return self.guard else: return Guard().__of__(self) # Create a temporary guard. def getVarExprText(self, id): if not self.var_exprs: return '' else: expr = self.var_exprs.get(id, None) if expr is not None: return expr.text else: return '' def getWorkflow(self): return aq_parent(aq_inner(aq_parent(aq_inner(self)))) def getAvailableStateIds(self): return self.getWorkflow().states.keys() def getAvailableScriptIds(self): return self.getWorkflow().scripts.keys() def getAvailableVarIds(self): return self.getWorkflow().variables.keys() _properties_form = DTMLFile('interaction_properties', _dtmldir) def manage_properties(self, REQUEST, manage_tabs_message=None): ''' ''' return self._properties_form( REQUEST, management_view='Properties', manage_tabs_message=manage_tabs_message, ) def setProperties(self, title, portal_type_filter=None, portal_type_group_filter=None, trigger_type=TRIGGER_WORKFLOW_METHOD, once_per_transaction=False, temporary_document_disallowed=False, script_name=(), after_script_name=(), before_commit_script_name=(), activate_script_name=(), actbox_name='', actbox_url='', actbox_category='workflow', method_id=(), props=None, REQUEST=None, description=''): """ Update transition properties XXX - then make sure that method_id is WorkflowMethod for portal_type_filter XXX - this will likely require dynamic """ if type(method_id) is type(''): self.method_id = method_id.split() else: self.method_id = method_id if portal_type_filter is not None and 'None' in portal_type_filter: portal_type_filter = None if portal_type_group_filter is not None and 'None' in portal_type_group_filter: portal_type_group_filter = None if 'None' in after_script_name: after_script_name = () if 'None' in activate_script_name: activate_script_name = () if 'None' in script_name: script_name = () if 'None' in before_commit_script_name: before_commit_script_name = () self.portal_type_filter = portal_type_filter self.portal_type_group_filter = portal_type_group_filter self.title = str(title) self.description = str(description) self.trigger_type = int(trigger_type) self.once_per_transaction = bool(once_per_transaction) self.temporary_document_disallowed = bool( temporary_document_disallowed) self.script_name = script_name self.after_script_name = after_script_name self.before_commit_script_name = before_commit_script_name self.activate_script_name = activate_script_name g = Guard() if g.changeFromProperties(props or REQUEST): self.guard = g else: self.guard = None self.actbox_name = str(actbox_name) self.actbox_url = str(actbox_url) self.actbox_category = str(actbox_category) # reset cached methods self.getPortalObject().portal_types.resetDynamicDocuments() if REQUEST is not None: return self.manage_properties(REQUEST, 'Properties changed.') _variables_form = DTMLFile('interaction_variables', _dtmldir) def manage_variables(self, REQUEST, manage_tabs_message=None): ''' ''' return self._variables_form( REQUEST, management_view='Variables', manage_tabs_message=manage_tabs_message, ) def getVariableExprs(self): ''' get variable exprs for management UI ''' ve = self.var_exprs if ve is None: return [] else: ret = [] for key in ve.keys(): ret.append((key, self.getVarExprText(key))) return ret def getWorkflowVariables(self): ''' get all variables that are available form workflow and not handled yet. ''' wf_vars = self.getAvailableVarIds() if self.var_exprs is None: return wf_vars ret = [] for vid in wf_vars: if not self.var_exprs.has_key(vid): ret.append(vid) return ret def addVariable(self, id, text, REQUEST=None): ''' Add a variable expression. ''' if self.var_exprs is None: self.var_exprs = PersistentMapping() expr = None if text: expr = Expression(str(text)) self.var_exprs[id] = expr if REQUEST is not None: return self.manage_variables(REQUEST, 'Variable added.') def deleteVariables(self, ids=[], REQUEST=None): ''' delete a WorkflowVariable from State ''' ve = self.var_exprs for id in ids: if ve.has_key(id): del ve[id] if REQUEST is not None: return self.manage_variables(REQUEST, 'Variables deleted.') def setVariables(self, ids=[], REQUEST=None): ''' set values for Variables set by this state ''' if self.var_exprs is None: self.var_exprs = PersistentMapping() ve = self.var_exprs if REQUEST is not None: for id in ve.keys(): fname = 'varexpr_%s' % id val = REQUEST[fname] expr = None if val: expr = Expression(str(REQUEST[fname])) ve[id] = expr return self.manage_variables(REQUEST, 'Variables changed.')
class PasswordTool(BaseTool): """ PasswordTool is used to allow a user to change its password """ title = 'Password Tool' id = 'portal_password' meta_type = 'ERP5 Password Tool' portal_type = 'Password Tool' allowed_types = () # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainPasswordTool', _dtmldir) _expiration_day = 1 def __init__(self, id=None): super(PasswordTool, self).__init__(id) self._password_request_dict = OOBTree() security.declareProtected('Manage users', 'getResetPasswordKey') def getResetPasswordKey(self, user_login, expiration_date=None): if expiration_date is None: # generate expiration date expiration_date = DateTime() + self._expiration_day # generate a random string key = self._generateUUID() if isinstance(self._password_request_dict, PersistentMapping): LOG('ERP5.PasswordTool', INFO, 'Migrating password_request_dict to' ' OOBTree') self._password_request_dict = OOBTree(self._password_request_dict) # register request self._password_request_dict[key] = (user_login, expiration_date) return key security.declareProtected('Manage users', 'getResetPasswordUrl') def getResetPasswordUrl(self, user_login=None, key=None, site_url=None): if user_login is not None: # XXX Backward compatibility key = self.getResetPasswordKey(user_login) parameter = urlencode(dict(reset_key=key)) method = self._getTypeBasedMethod("getSiteUrl") if method is not None: base_url = method() else: base_url = "%s/portal_password/PasswordTool_viewResetPassword" % ( site_url, ) url = "%s?%s" % (base_url, parameter) return url security.declareProtected('Manage users', 'getResetPasswordUrl') def getExpirationDateForKey(self, key=None): return self._password_request_dict[key][1] def mailPasswordResetRequest(self, user_login=None, REQUEST=None, notification_message=None, sender=None, store_as_event=False, expiration_date=None, substitution_method_parameter_dict=None): """ Create a random string and expiration date for request Parameters: user_login -- Reference of the user to send password reset link REQUEST -- Request object notification_message -- Notification Message Document used to build the email. As default, a standard text will be used. sender -- Sender (Person or Organisation) of the email. As default, the default email address will be used store_as_event -- whenever CRM is available, store notifications as events expiration_date -- If not set, expiration date is current date + 1 day. substitution_method_parameter_dict -- additional substitution dict for creating an email. """ if REQUEST is None: REQUEST = get_request() if user_login is None: user_login = REQUEST["user_login"] site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from msg = None # check user exists, and have an email user_list = self.getPortalObject().acl_users.\ erp5_users.getUserByLogin(user_login) if len(user_list) == 0: msg = translateString("User ${user} does not exist.", mapping={'user': user_login}) else: # We use checked_permission to prevent errors when trying to acquire # email from organisation user = user_list[0] email_value = user.getDefaultEmailValue( checked_permission='Access content information') if email_value is None or not email_value.asText(): msg = translateString( "User ${user} does not have an email address, please contact site " "administrator directly", mapping={'user': user_login}) if msg: if REQUEST is not None: parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % \ (site_url, parameter) return REQUEST.RESPONSE.redirect(ret_url) return msg key = self.getResetPasswordKey(user_login=user_login, expiration_date=expiration_date) url = self.getResetPasswordUrl(key=key, site_url=site_url) # send mail message_dict = { 'instance_name': self.getPortalObject().getTitle(), 'reset_password_link': url, 'expiration_date': self.getExpirationDateForKey(key) } if substitution_method_parameter_dict is not None: message_dict.update(substitution_method_parameter_dict) if notification_message is None: subject = translateString( "[${instance_name}] Reset of your password", mapping={'instance_name': self.getPortalObject().getTitle()}) subject = subject.translate() message = translateString("\nYou requested to reset your ${instance_name}"\ " account password.\n\n" \ "Please copy and paste the following link into your browser: \n"\ "${reset_password_link}\n\n" \ "Please note that this link will be valid only one time, until "\ "${expiration_date}.\n" \ "After this date, or after having used this link, you will have to make " \ "a new request\n\n" \ "Thank you", mapping=message_dict) message = message.translate() event_keyword_argument_dict = {} message_text_format = 'text/plain' else: message_text_format = notification_message.getContentType() subject = notification_message.getTitle() if message_text_format == "text/html": message = notification_message.asEntireHTML( substitution_method_parameter_dict=message_dict) else: message = notification_message.asText( substitution_method_parameter_dict=message_dict) event_keyword_argument_dict = { 'resource': notification_message.getSpecialise(), 'language': notification_message.getLanguage(), } self.getPortalObject().portal_notifications.sendMessage( sender=sender, recipient=[ user, ], subject=subject, message=message, store_as_event=store_as_event, message_text_format=message_text_format, event_keyword_argument_dict=event_keyword_argument_dict) if REQUEST is not None: msg = translateString("An email has been sent to you.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect(ret_url) def _generateUUID(self, args=""): """ Generate a unique id that will be used as url for password """ # this code is based on # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/213761 # by Carl Free Jr # as uuid module is only available in pyhton 2.5 t = long(time.time() * 1000) r = long(random.random() * 100000000000000000L) try: a = socket.gethostbyname(socket.gethostname()) except: # if we can't get a network address, just imagine one a = random.random() * 100000000000000000L data = ' '.join((str(t), str(r), str(a), str(args))) return md5(data).hexdigest() def resetPassword(self, reset_key=None, REQUEST=None): """ """ # XXX-Aurel : is it used ? if REQUEST is None: REQUEST = get_request() user_login, expiration_date = self._password_request_dict.get( reset_key, (None, None)) site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from if reset_key is None or user_login is None: ret_url = '%s/login_form' % site_url return REQUEST.RESPONSE.redirect(ret_url) # check date current_date = DateTime() if current_date > expiration_date: msg = translateString("Date has expire.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect(ret_url) # redirect to form as all is ok REQUEST.set("password_key", reset_key) return self.reset_password_form(REQUEST=REQUEST) def removeExpiredRequests(self): """ Browse dict and remove expired request """ current_date = DateTime() password_request_dict = self._password_request_dict for key, (_, date) in password_request_dict.items(): if date < current_date: del password_request_dict[key] def changeUserPassword(self, password, password_key, password_confirm=None, user_login=None, REQUEST=None, **kw): """ Reset the password for a given login """ # BBB: password_confirm: unused argument def error(message): # BBB: should "raise Redirect" instead of just returning, simplifying # calling code and making mistakes more difficult # BBB: should probably not translate message when REQUEST is None message = translateString(message) if REQUEST is None: return message return REQUEST.RESPONSE.redirect( site_url + '/login_form?' + urlencode({ 'portal_status_message': message, })) if REQUEST is None: REQUEST = get_request() if self.getWebSiteValue(): site_url = self.getWebSiteValue().absolute_url() elif REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from else: site_url = self.getPortalObject().absolute_url() try: register_user_login, expiration_date = self._password_request_dict[ password_key] except KeyError: # XXX: incorrect grammar and not descriptive enough return error('Key not known. Please ask reset password.') if user_login is not None and register_user_login != user_login: # XXX: not descriptive enough return error("Bad login provided.") if DateTime() > expiration_date: # XXX: incorrect grammar return error("Date has expire.") del self._password_request_dict[password_key] persons = self.getPortalObject().acl_users.erp5_users.getUserByLogin( register_user_login) person = persons[0] person._forceSetPassword(password) person.reindexObject() if REQUEST is not None: return REQUEST.RESPONSE.redirect( site_url + '/login_form?' + urlencode({ 'portal_status_message': translateString("Password changed."), }))
class IdTool(BaseTool): """ This tools handles the generation of IDs. """ id = 'portal_ids' meta_type = 'ERP5 Id Tool' portal_type = 'Id Tool' title = 'Id Generators' # Declarative Security security = ClassSecurityInfo() security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainIdTool', _dtmldir ) def newContent(self, *args, **kw): """ the newContent is overriden to not use generateNewId """ if id not in kw: new_id = self._generateNextId() if new_id is not None: kw['id'] = new_id else: raise ValueError('Failed to gererate id') return BaseTool.newContent(self, *args, **kw) def _get_id(self, id): """ _get_id is overrided to not use generateNewId It is used for example when an object is cloned """ if self._getOb(id, None) is None : return id return self._generateNextId() @caching_instance_method(id='IdTool._getLatestIdGenerator', cache_factory='erp5_content_long') def _getLatestIdGenerator(self, reference): """ Tries to find the id_generator with the latest version from the current object. Use the low-level to create a site without catalog """ assert reference id_last_generator = None version_last_generator = 0 for generator in self.objectValues(): if generator.getReference() == reference: # Version Property Sheet defines 'version' property as a 'string' version = int(generator.getVersion()) if version > version_last_generator: id_last_generator = generator.getId() version_last_generator = version if id_last_generator is None: raise KeyError(repr(reference)) return id_last_generator def _getLatestGeneratorValue(self, id_generator): """ Return the last generator with the reference """ return self._getOb(self._getLatestIdGenerator(id_generator)) security.declareProtected(Permissions.AccessContentsInformation, 'generateNewId') def generateNewId(self, id_group=None, default=None, method=_marker, id_generator=None, poison=False): """ Generate the next id in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError('%r is not a valid id_group' % id_group) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'document' if method is not _marker: warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id = last_generator.generateNewId( id_group=id_group, default=default, poison=poison, ) except KeyError: # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewId', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) dict_ids = getattr(aq_base(self), 'dict_ids', None) if dict_ids is None: dict_ids = self.dict_ids = PersistentMapping() new_id = None # Getting the last id if default is None: default = 0 marker = [] new_id = dict_ids.get(id_group, marker) if method is _marker: if new_id is marker: new_id = default else: new_id = new_id + 1 else: if new_id is marker: new_id = default new_id = method(new_id) # Store the new value dict_ids[id_group] = new_id return new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewIdList') def generateNewIdList(self, id_group=None, id_count=1, default=None, store=_marker, id_generator=None, poison=False): """ Generate a list of next ids in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError('%r is not a valid id_group' % id_group) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'uid' if store is not _marker: warnings.warn("Use of 'store' argument is deprecated.", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id_list = last_generator.generateNewIdList(id_group=id_group, id_count=id_count, default=default, poison=poison) except (KeyError, ValueError): # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewIdList', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) new_id = None if default is None: default = 1 # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() try: query = portal.IdTool_zGenerateId commit = portal.IdTool_zCommit except AttributeError: portal_catalog = portal.portal_catalog.getSQLCatalog() query = portal_catalog.z_portal_ids_generate_id commit = portal_catalog.z_portal_ids_commit try: result = query(id_group=id_group, id_count=id_count, default=default) finally: commit() new_id = result[0]['LAST_INSERT_ID()'] if store: if getattr(aq_base(self), 'dict_length_ids', None) is None: # Length objects are stored in a persistent mapping: there is one # Length object per id_group. self.dict_length_ids = PersistentMapping() if self.dict_length_ids.get(id_group) is None: self.dict_length_ids[id_group] = Length(new_id) self.dict_length_ids[id_group].set(new_id) if six.PY2: new_id_list = range(new_id - id_count, new_id) else: new_id_list = list(range(new_id - id_count, new_id)) return new_id_list security.declareProtected(Permissions.ModifyPortalContent, 'initializeGenerator') def initializeGenerator(self, id_generator=None, all=False): """ Initialize generators. This is mostly used when a new ERP5 site is created. Some generators will need to do some initialization like creating SQL Database, prepare some data in ZODB, etc """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.initializeGenerator() else: # recovery all the generators and initialize them for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.initializeGenerator() security.declareProtected(Permissions.ModifyPortalContent, 'clearGenerator') def clearGenerator(self, id_generator=None, all=False): """ Clear generators data. This can be usefull when working on a development instance or in some other rare cases. This will loose data and must be use with caution This can be incompatible with some particular generator implementation, in this case a particular error will be raised (to be determined and added here) """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.clearGenerator() else: if len(self.objectValues()) == 0: # compatibility with old API self.getPortalObject().IdTool_zDropTable() self.getPortalObject().IdTool_zCreateTable() for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.clearGenerator() ## XXX Old API deprecated #backward compatibility security.declareProtected(Permissions.AccessContentsInformation, 'generateNewLengthIdList') generateNewLengthIdList = generateNewIdList security.declareProtected(Permissions.AccessContentsInformation, 'getLastLengthGeneratedId') def getLastLengthGeneratedId(self, id_group, default=None): """ Get the last length id generated """ warnings.warn('getLastLengthGeneratedId is deprecated', DeprecationWarning) # check in persistent mapping if exists if getattr(aq_base(self), 'dict_length_ids', None) is not None: last_id = self.dict_length_ids.get(id_group) if last_id is not None: return last_id.value - 1 # otherwise check in mysql # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() try: query = portal.IdTool_zGetLastId except AttributeError: query = portal.portal_catalog.getSQLCatalog().z_portal_ids_get_last_id result = query(id_group=id_group) if len(result): try: return result[0]['last_id'] except KeyError: return result[0]['LAST_INSERT_ID()'] return default security.declareProtected(Permissions.AccessContentsInformation, 'getLastGeneratedId') def getLastGeneratedId(self, id_group=None, default=None): """ Get the last id generated """ warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning) if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() last_id = None if id_group is not None and id_group != 'None': last_id = self.dict_ids.get(id_group, default) return last_id security.declareProtected(Permissions.ModifyPortalContent, 'setLastGeneratedId') def setLastGeneratedId(self, new_id, id_group=None): """ Set a new last id. This is usefull in order to reset a sequence of ids. """ if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() if id_group is not None and id_group != 'None': self.dict_ids[id_group] = new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewLengthId') def generateNewLengthId(self, id_group=None, default=None, store=_marker): """Generates an Id using a conflict free id generator. Deprecated. """ warnings.warn('generateNewLengthId is deprecated.\n' 'Use generateNewIdList with a sql id_generator', DeprecationWarning) if store is not _marker: return self.generateNewIdList(id_group=id_group, id_count=1, default=default, store=store)[0] return self.generateNewIdList(id_group=id_group, id_count=1, default=default)[0] security.declareProtected(Permissions.AccessContentsInformation, 'getDictLengthIdsItems') def getDictLengthIdsItems(self): """ Return a copy of dict_length_ids. This is a workaround to access the persistent mapping content from ZSQL method to be able to insert initial tuples in the database at creation. """ if getattr(self, 'dict_length_ids', None) is None: self.dict_length_ids = PersistentMapping() return self.dict_length_ids.items() security.declarePrivate('dumpDictLengthIdsItems') def dumpDictLengthIdsItems(self): """ Store persistently data from SQL table portal_ids. """ portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_dump') dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None) if dict_length_ids is None: dict_length_ids = self.dict_length_ids = PersistentMapping() for line in query().dictionaries(): id_group = line['id_group'] last_id = line['last_id'] stored_last_id = self.dict_length_ids.get(id_group) if stored_last_id is None: self.dict_length_ids[id_group] = Length(last_id) else: stored_last_id_value = stored_last_id() if stored_last_id_value < last_id: stored_last_id.set(last_id) else: if stored_last_id_value > last_id: LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \ 'than SQL value (%r). Keeping ZODB value untouched.' % \ (stored_last_id, id_group, last_id))
class IntrospectionTool(LogMixin, BaseTool): """ This tool provides both local and remote introspection. """ id = 'portal_introspections' title = 'Introspection Tool' meta_type = 'ERP5 Introspection Tool' portal_type = 'Introspection Tool' security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainIntrospectionTool', _dtmldir ) # # Remote menu management # security.declareProtected(Permissions.AccessContentsInformation, 'getFilteredActionDict') def getFilteredActionDict(self, user_name=_MARKER): """ Returns menu items for a given user """ portal = self.getPortalObject() is_portal_manager = portal.portal_membership.checkPermission(\ Permissions.ManagePortal, self) downgrade_authenticated_user = user_name is not _MARKER and is_portal_manager if downgrade_authenticated_user: # downgrade to desired user original_security_manager = _setSuperSecurityManager(self, user_name) # call the method implementing it erp5_menu_dict = portal.portal_actions.listFilteredActionsFor(portal) if downgrade_authenticated_user: # restore original Security Manager setSecurityManager(original_security_manager) # Unlazyfy URLs and other lazy values so that it can be marshalled result = {} for key, action_list in erp5_menu_dict.items(): result[key] = map(lambda action:dict(action), action_list) return result security.declareProtected(Permissions.AccessContentsInformation, 'getModuleItemList') def getModuleItemList(self, user_name=_MARKER): """ Returns module items for a given user """ portal = self.getPortalObject() is_portal_manager = portal.portal_membership.checkPermission( Permissions.ManagePortal, self) downgrade_authenticated_user = user_name is not _MARKER and is_portal_manager if downgrade_authenticated_user: # downgrade to desired user original_security_manager = _setSuperSecurityManager(self, user_name) # call the method implementing it erp5_module_list = portal.ERP5Site_getModuleItemList() if downgrade_authenticated_user: # restore original Security Manager setSecurityManager(original_security_manager) return erp5_module_list # # Local file access # def _getLocalFile(self, REQUEST, RESPONSE, file_path, tmp_file_path='/tmp/', compressed=1): """ It should return the local file compacted or not as tar.gz. """ if file_path.startswith('/'): raise IOError, 'The file path must be relative not absolute' instance_home = getConfiguration().instancehome file_path = os.path.join(instance_home, file_path) if not os.path.exists(file_path): raise IOError, 'The file: %s does not exist.' % file_path if compressed: tmp_file_path = tempfile.mktemp(dir=tmp_file_path) tmp_file = tarfile.open(tmp_file_path,"w:gz") try: tmp_file.add(file_path) finally: tmp_file.close() RESPONSE.setHeader('Content-type', 'application/x-tar') RESPONSE.setHeader('Content-Disposition', \ 'attachment;filename="%s.tar.gz"' % file_path.split('/')[-1]) else: RESPONSE.setHeader('Content-type', 'application/txt') RESPONSE.setHeader('Content-Disposition', \ 'attachment;filename="%s.txt"' % file_path.split('/')[-1]) tmp_file_path = file_path with open(tmp_file_path) as f: RESPONSE.setHeader('Content-Length', os.stat(tmp_file_path).st_size) for data in f: RESPONSE.write(data) if compressed: os.remove(tmp_file_path) return '' def __getEventLogPath(self): """ Get the Event Log. """ return event_log.handlers[0].baseFilename def __getAccessLogPath(self): """ Get the Event Log. """ return access_log.handlers[0].baseFilename def _tailFile(self, file_name, line_number=10): """ Do a 'tail -f -n line_number filename' """ log_file = os.path.join(getConfiguration().instancehome, file_name) if not os.path.exists(log_file): raise IOError, 'The file: %s does not exist.' % log_file char_per_line = 75 with open(log_file,'r') as tailed_file: while 1: try: tailed_file.seek(-1 * char_per_line * line_number, 2) except IOError: tailed_file.seek(0) pos = tailed_file.tell() lines = tailed_file.read().split("\n") if len(lines) > (line_number + 1) or not pos: break # The lines are bigger than we thought char_per_line *= 1.3 # Inc for retry start = max(len(lines) - line_number - 1, 0) return "\n".join(lines[start:len(lines)]) security.declareProtected(Permissions.ManagePortal, 'tailEventLog') def tailEventLog(self): """ Tail the Event Log. """ return escape(self._tailFile(self.__getEventLogPath(), 500)) security.declareProtected(Permissions.ManagePortal, 'tailAccessLog') def tailAccessLog(self): """ Tail the Event Log. """ return escape(self._tailFile(self.__getAccessLogPath(), 50)) security.declareProtected(Permissions.ManagePortal, 'getAccessLog') def getAccessLog(self, compressed=1, REQUEST=None): """ Get the Access Log. """ if REQUEST is not None: response = REQUEST.RESPONSE else: return "FAILED" return self._getLocalFile(REQUEST, response, file_path=self.__getAccessLogPath(), compressed=compressed) security.declareProtected(Permissions.ManagePortal, 'getEventLog') def getEventLog(self, compressed=1, REQUEST=None): """ Get the Event Log. """ if REQUEST is not None: response = REQUEST.RESPONSE else: return "FAILED" return self._getLocalFile(REQUEST, response, file_path=self.__getEventLogPath(), compressed=compressed) security.declareProtected(Permissions.ManagePortal, 'getDataFs') def getDataFs(self, compressed=1, REQUEST=None): """ Get the Data.fs. """ if REQUEST is not None: response = REQUEST.RESPONSE else: return "FAILED" return self._getLocalFile(REQUEST, response, file_path='var/Data.fs', compressed=compressed) # # Instance variable definition access # security.declareProtected(Permissions.ManagePortal, '_loadExternalConfig') def _loadExternalConfig(self): """ Load configuration from one external file, this configuration should be set for security reasons to prevent people access forbidden areas in the system. """ def cached_loadExternalConfig(): import ConfigParser config = ConfigParser.ConfigParser() config.readfp(open('/etc/erp5.cfg')) return config cached_loadExternalConfig = CachingMethod(cached_loadExternalConfig, id='IntrospectionTool__loadExternalConfig', cache_factory='erp5_content_long') return cached_loadExternalConfig() security.declareProtected(Permissions.ManagePortal, '_getSoftwareHome') def _getSoftwareHome(self): """ Get the value of SOFTWARE_HOME for zopectl startup script or from zope.conf (whichever is most relevant) """ return getConfiguration().softwarehome security.declareProtected(Permissions.ManagePortal, '_getPythonExecutable') def _getPythonExecutable(self): """ Get the value of PYTHON for zopectl startup script or from zope.conf (whichever is most relevant) """ return sys.executable security.declareProtected(Permissions.ManagePortal, '_getProductPathList') def _getProductPathList(self): """ Get the value of SOFTWARE_HOME for zopectl startup script or from zope.conf (whichever is most relevant) """ return getConfiguration().products security.declareProtected(Permissions.ManagePortal, '_getSystemVersionDict') def _getSystemVersionDict(self): """ Returns a dictionnary with all versions of installed libraries { 'python': '2.4.3' , 'pysvn': '1.2.3' , 'ERP5' : "5.4.3" } NOTE: consider using autoconf / automake tools ? """ def cached_getSystemVersionDict(): import pkg_resources version_dict = {} for dist in pkg_resources.working_set: version_dict[dist.key] = dist.version from Products import ERP5 as erp5_product erp5_product_path = os.path.dirname(erp5_product.__file__) try: with open(os.path.join(erp5_product_path, "VERSION.txt")) as f: erp5_version = f.read().strip().replace("ERP5 ", "") except Exception: erp5_version = None version_dict["ProductS.ERP5"] = erp5_version return version_dict get_system_version_dict = CachingMethod( cached_getSystemVersionDict, id='IntrospectionTool__getSystemVersionDict', cache_factory='erp5_content_long') return get_system_version_dict() security.declareProtected(Permissions.ManagePortal, '_getExternalConnectionDict') def _getExternalConnectionDict(self): """ Return a dictionary with all connections from ERP5 to an External Service, this may include MySQL, Memcached, Kumofs, Ldap or any other. The standard format is: {'relative_url/method_or_property_id' : method_value_output,}. """ connection_dict = {} portal = self.getPortalObject() def collect_information_by_method(document, method_id): method_object = getattr(document, method_id, None) key = "%s/%s" % (document.getRelativeUrl(), method_id) connection_dict[key] = method_object() portal = self.getPortalObject() # Collect information from portal memcached for plugin in portal.portal_memcached.objectValues(): collect_information_by_method(plugin, "getUrlString") system_preference = \ portal.portal_preferences.getActiveSystemPreference() if system_preference is not None: # Conversion Server information collect_information_by_method(system_preference, 'getPreferredOoodocServerAddress') collect_information_by_method(system_preference, 'getPreferredOoodocServerPortNumber') collect_information_by_method(system_preference, 'getPreferredDocumentConversionServerUrl') def collect_information_by_property(document, property_id): key = "%s/%s" % (document.getId(), property_id) connection_dict[key] = str(getattr(document, property_id, None)) # Collect information related to Mail Server. collect_information_by_property(self.MailHost,'smtp_host') collect_information_by_property(self.MailHost,'smtp_port') # Collect information related to Databases. ie.: MySQL, LDap? for conn in self.objectValues(["CMFActivity Database Connection", "Z MySQL Database Connection", "Z MySQL Deferred Database Connection"]): collect_information_by_property(conn,'connection_string') # collect information from certificate authority certificate_authority = getattr(portal, 'portal_certificate_authority', None) if certificate_authority is not None: collect_information_by_property(certificate_authority, 'certificate_authority_path') return connection_dict security.declareProtected(Permissions.ManagePortal, '_getBusinessTemplateRevisionDict') def _getBusinessTemplateRevisionDict(self): """ Return a Dictionary of installed business templates and their revisions """ business_template_dict = {} for installed in self.portal_templates.getInstalledBusinessTemplateList(): business_template_dict[installed.getTitle()] = installed.getRevision() return business_template_dict security.declareProtected(Permissions.ManagePortal, '_getActivityDict') def _getActivityDict(self): """ Return a Dictionary with the snapshot with the status of activities. failures (-2 and -3) and running. """ activity_dict = {} # XXX Maybe this is not so efficient check. Performance Optimization # should be consider. activity_dict['failure'] = len(self.portal_activities.getMessageList(processing_node=-2)) activity_dict['total'] = len(self.portal_activities.getMessageList()) return activity_dict security.declareProtected(Permissions.ManagePortal, 'getSystemSignatureDict') def getSystemSignatureDict(self): """ Returns a dictionary with all information related to the instance. This information can report what resources (memcache, mysql, zope, python, libraries) the instance is using. Also, what business templates are installed. Such information is usefull to detect changes in the system, into upgrader, slapos and/or to build Introspection Reports. """ business_template_repository_list = self.portal_templates.getRepositoryList() return dict( activity_dict=self._getActivityDict(), version_dict=self._getSystemVersionDict(), external_connection_dict=self._getExternalConnectionDict(), business_template_dict=self._getBusinessTemplateRevisionDict(), business_template_repository_list=business_template_repository_list) security.declareProtected(Permissions.ManagePortal, 'getSystemSignatureAsJSON') def getSystemSignatureAsJSON(self, REQUEST=None): """ Returns the information as JSON. THIS merhod could be a decorator or use a some other clever way to convert the getSystemSignatureDict """ if REQUEST is not None: REQUEST.set("Content-Type", "application/json") return json.dumps(self.getSystemSignatureDict())
import urllib2 from ZODB.POSException import ConflictError from Products.ERP5Type.Utils import UpperCase from zLOG import LOG try: from webdav.Lockable import ResourceLockedError from webdav.WriteLockInterface import WriteLockInterface SUPPORTS_WEBDAV_LOCKS = 1 except ImportError: SUPPORTS_WEBDAV_LOCKS = 0 # Constructors manage_addPDFTemplate = DTMLFile("dtml/PDFTemplate_add", globals()) def addPDFTemplate(self, id, title="", REQUEST=None): """Add form to folder. id -- the id of the new form to add title -- the title of the form to add Result -- empty string """ # add actual object id = self._setObject(id, PDFTemplate(id, title)) # respond to the add_and_edit button if necessary add_and_edit(self, id, REQUEST) return ''
class NotificationTool(BaseTool): """ This tool manages notifications. It is used as a central point to send messages from one user to one or many users. The purpose of the tool is to provide an API for sending messages which is independent on how messages are actually going to be sent and when. It is also useful to send messages without having to write always the same piece of code (eg. lookup the user, lookup its mail, etc.). This early implementation only provides asynchronous email sending. Future implementations may be able to lookup user preferences to decide how and when to send a message to each user. """ id = 'portal_notifications' meta_type = 'ERP5 Notification Tool' portal_type = 'Notification Tool' # Declarative Security security = ClassSecurityInfo() security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainNotificationTool', _dtmldir ) # low-level interface def _sendEmailMessage(self, from_url, to_url, body=None, subject=None, attachment_list=None, extra_headers=None, additional_headers=None, debug=False): portal = self.getPortalObject() mailhost = getattr(portal, 'MailHost', None) if mailhost is None: raise ValueError, "Can't find MailHost." message = buildEmailMessage(from_url, to_url, msg=body, subject=subject, attachment_list=attachment_list, extra_headers=extra_headers, additional_headers=additional_headers) if debug: return message.as_string() mailhost.send(messageText=message.as_string(), mto=to_url, mfrom=from_url) # high-level interface security.declareProtected(Permissions.UseMailhostServices, 'sendMessage') def sendMessage(self, sender=None, recipient=None, subject=None, message=None, attachment_list=None, attachment_document_list=None, notifier_list=None, priority_level=None, store_as_event=False, check_consistency=False, message_text_format='text/plain', event_keyword_argument_dict=None, portal_type_list=None): """ This method provides a common API to send messages to erp5 users from object actions of workflow scripts. Note that you can't send message to person who don't have his own Person document. This method provides only high-level functionality so that you can't use email address for sender and recipient, or raw data for attachments. sender -- a login name(reference of Person document) or a Person document recipient -- a login name(reference of Person document) or a Person document, a list of thereof subject -- the subject of the message message -- the text of the message (already translated) attachment_list -- list of dictionary which contains raw data and name and mimetype for attachment. See buildEmailMessage. attachment_document_list -- list of document (optional) which will be attachment. priority_level -- a priority level which is used to lookup user preferences and decide which notifier to use XXX Not implemented yet!! notifier_list -- a list of portal type names to use to send the event store_as_event -- whenever CRM is available, store notifications as events check_consistency -- Check that the created events match their constraints. If any of the event have an unsatisified constraint, a ValueError is raised. Note that if `store_as_event` is true, some draft events are created anyway, so caller may want to abort transaction. event_keyword_argument_dict -- additional keyword arguments which is used for constructor of event document. portal_type_list -- Portal Type of Users TODO: support default notification email """ portal = self.getPortalObject() catalog_tool = getToolByName(self, 'portal_catalog') if portal_type_list is None: portal_type_list = ('Person',) if notifier_list is None: # XXX TODO: Use priority_level. Need to implement default notifier query system. # XXX For now, we use 'Mail Message'. notifier_list = ['Mail Message'] if not isinstance(notifier_list, (tuple, list)): raise TypeError("Notifier list must be a list of portal types") if not subject: raise TypeError("subject is required") # Find "From" Person from_person = None if isinstance(sender, basestring): sender = catalog_tool.getResultValue(portal_type=portal_type_list, reference=sender) if sender is not None: email_value = sender.getDefaultEmailValue() if email_value is not None and email_value.asText(): from_person = sender # Find "To" Person list to_person_list = [] if recipient: if not isinstance(recipient, (list, tuple)): recipient = (recipient,) for person in recipient: if isinstance(person, basestring): person_value = catalog_tool.getResultValue(portal_type=portal_type_list, reference=person) if person_value is None: raise ValueError("Can't find person document which reference is '%s'" % person) person = person_value to_person_list.append(person) # prepare low-level arguments if needed. low_level_kw = {} default_from_email = portal.email_from_address default_to_email = getattr(portal, 'email_to_address', default_from_email) default_from_name = portal.title default_from_name = getattr(portal, 'email_from_name', default_from_name) if from_person is None: # when sending without sender defined compose identifiable From header low_level_kw['from_url'] = '%s <%s>' % (default_from_name, default_from_email) if not to_person_list: low_level_kw['to_url'] = default_to_email if attachment_list is not None: low_level_kw['attachment_list'] = attachment_list # Make event available_notifier_list = self.getNotifierList() event_list = [] if event_keyword_argument_dict is None: event_keyword_argument_dict = {} for notifier in notifier_list: if notifier in available_notifier_list: event = portal.getDefaultModule(notifier).newContent(portal_type=notifier, temp_object=not store_as_event, **event_keyword_argument_dict) else: # portal type does not exist, likely erp5_crm is not installed. Try to # import the class with the same name. from Products.ERP5Type import Document as document_module constructor = getattr(document_module, 'newTemp%s' % notifier.replace(' ', '')) event = constructor(self, '_', **event_keyword_argument_dict) event.setSourceValue(from_person) event.setDestinationValueList(to_person_list) event.setTitle(subject) event.setContentType(message_text_format) event.setTextContent(message) event.setAggregateValueList(attachment_document_list) event_list.append(event) if check_consistency: for event in event_list: constraint_message_list = event.checkConsistency() if constraint_message_list: raise ValueError(constraint_message_list) for event in event_list: if event.isTempObject() or (not portal.portal_workflow.isTransitionPossible(event, 'start')): event.send(**low_level_kw) else: event.start(**low_level_kw) return # Future implementation could consist in implementing # policies such as grouped notification (per hour, per day, # per week, etc.) depending on user preferences. It # also add some priority and selection of notification # tool (ex SMS vs. email) # Here is a sample code of how this implementation could look like # (pseudo code) # NOTE: this implementation should also make sure that the current # buildEmailMessage method defined here and the Event.send method # are merged once for all if self.getNotifierList(): # CRM is installed - so we can lookup notification preferences if notifier_list is None: # Find which notifier to use on preferences if priority_level not in ('low', 'medium', 'high'): # XXX Better naming required here priority_level = 'high' notifier_list = self.preferences.getPreference( 'preferred_%s_priority_nofitier_list' % priority_level) event_list = [] for notifier in notifier_list: event_module = self.getDefaultModule(notifier) new_event = event_module.newContent(portal_type=notifier, temp_object=store_as_event) event_list.append(new_event) else: # CRM is not installed - only notification by email is possible # So create a temp object directly from Products.ERP5Type.Document import newTempEvent new_event = newTempEvent(self, '_') event_list = [new_event] if event in event_list: # We try to build events using the same parameters as the one # we were provided for notification. # The handling of attachment is still an open question: # either use relation (to prevent duplication) or keep # a copy inside. It is probably a good idea to # make attachment_list polymorphic in order to be able # to provide different kinds of attachments can be provided # Either document references or binary data. event.build(sender=sender, recipient=recipient, subject=subject, message=message, attachment_list=attachment_list,) # Rename here and add whatever # parameter makes sense such # as text format event.send() # Make sure workflow transition is invoked if this is # a persistent notification # Aggregation could be handled by appending the notification # to an existing message rather than creating a new one. # Sending the message should be handled by the alarm based # on a date value stored on the event. This probably required # a new workflow state to represent events which are waiting # for being sent automatically. (ie. scheduled sending) security.declareProtected(Permissions.AccessContentsInformation, 'getNotifierList') def getNotifierList(self): """ Returns the list of available notifiers. For now we consider that any event is a potential notifier. This could change though. """ return self.getPortalEventTypeList() security.declareProtected(Permissions.AccessContentsInformation, 'getDocumentValue') def getDocumentValue(self, **kw): """ Returns the last version of a Notification Document in selected Language. """ method = self._getTypeBasedMethod('getDocumentValue') return method(**kw) def __call__(self, *args, **kw): return self.sendMessage(*args, **kw)
class WizardTool(BaseTool): """ WizardTool is able to generate custom business templates. """ id = 'portal_wizard' meta_type = 'ERP5 Wizard Tool' portal_type = 'Wizard Tool' isPortalContent = ConstantGetter('isPortalContent', value=True) property_sheets = () security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainWizardTool', _dtmldir) # Stop traversing a concatenated path after the proxy method. def __before_publishing_traverse__(self, self2, request): path = request['TraversalRequestNameStack'] if path and path[-1] == 'proxy': subpath = path[:-1] subpath.reverse() request.set('traverse_subpath', subpath) # initialize our root proxy URL which we use for a referer global referer path[:-1] = [] if referer is None: referer = '%s/portal_wizard/proxy/%s/view' % ( self.getPortalObject().absolute_url(), '/'.join( subpath[:3])) def _getProxyURL(self, subpath='', query=''): # Helper method to construct an URL appropriate for proxying a request. # This makes sure that URLs generated by absolute_url at a remote site # will be always towards the proxy method again. # # Note that the code assumes that VirtualHostBase is visible. The setting # of a front-end server must allow this. # # This should generate an URL like this: # # http://remotehost:9080/VirtualHostBase/http/localhost:8080/VirtualHostRoot/_vh_erp5/_vh_portal_wizard/_vh_proxy/erp5/person_module/2 part_list = [] server_url = self.getServerUrl().rstrip('/') part_list.append(server_url) part_list.append('VirtualHostBase') portal_url = self.getPortalObject().absolute_url() scheme, rest = urllib.splittype(portal_url) addr, path = urllib.splithost(rest) host, port = urllib.splitnport(addr, scheme == 'http' and 80 or 443) part_list.append(scheme) part_list.append('%s:%s' % (host, port)) part_list.append('VirtualHostRoot') method_path = self.absolute_url_path() + '/proxy' part_list.extend(('_vh_' + p for p in method_path.split('/') if p)) server_root = self.getServerRoot().strip('/') if isinstance(subpath, (list, tuple)): subpath = '/'.join(subpath) if not subpath.startswith(server_root): part_list.append(server_root) part_list.append(subpath) url = '/'.join((p for p in part_list if p)) if query: url = url + '?' + query return url def _getSubsribedUserAndPassword(self): """Retrieve the username and password for the subscription from the system.""" user = CachingMethod(self.getExpressConfigurationPreference, 'WizardTool_preferred_express_user_id', cache_factory='erp5_content_long')( 'preferred_express_user_id', '') pw = CachingMethod(self.getExpressConfigurationPreference, 'WizardTool_preferred_express_password', cache_factory='erp5_content_long')( 'preferred_express_password', '') return (user, pw) # This is a custom opener director for not handling redirections # and errors automatically. This is necessary because the proxy # should pass all results to a client as they are. simple_opener_director = urllib2.OpenerDirector() for name in ('ProxyHandler', 'UnknownHandler', \ 'HTTPHandler', 'FTPHandler', 'FileHandler', 'HTTPSHandler',): handler = getattr(urllib2, name, None) if handler is not None: simple_opener_director.add_handler(handler()) # add cookie support simple_opener_director.add_handler(urllib2.HTTPCookieProcessor(cookiejar)) security.declareProtected(Permissions.View, 'proxy') def proxy(self, **kw): """Proxy a request to a server.""" global cookiejar, referer, last_loggedin_user_and_password if self.REQUEST['REQUEST_METHOD'] != 'GET': # XXX this depends on the internal of HTTPRequest. pos = self.REQUEST.stdin.tell() self.REQUEST.stdin.seek(0) # XXX if filesize is too big, this might cause a problem. data = self.REQUEST.stdin.read() self.REQUEST.stdin.seek(pos) else: data = None content_type = self.REQUEST.get_header('content-type') # XXX if ":method" trick is used, then remove it from subpath. if self.REQUEST.traverse_subpath: if data is not None: user_input = data else: user_input = self.REQUEST.QUERY_STRING if user_input: mark = ':method' content_type_value = None content_type_dict = None if content_type: content_type_value, content_type_dict = cgi.parse_header( content_type) if content_type_value == 'multipart/form-data': fp = StringIO(user_input) user_input_dict = cgi.parse_multipart( fp, content_type_dict) else: user_input_dict = cgi.parse_qs(user_input) for i in user_input_dict: if i.endswith(mark): method_name = i[:-len(mark)] method_path = method_name.split('/') if self.REQUEST.traverse_subpath[ -len(method_path):] == method_path: del self.REQUEST.traverse_subpath[-len(method_path ):] break url = self._getProxyURL(self.REQUEST.traverse_subpath, self.REQUEST['QUERY_STRING']) # XXX this will send the password unconditionally! # I hope https will be good enough. header_dict = {} user_and_password = self._getSubsribedUserAndPassword() if (len(user_and_password) == 2 and user_and_password[0] and user_and_password[1]): if user_and_password != last_loggedin_user_and_password: # credentials changed we need to renew __ac cookie from server as well cookiejar.clear() # try login to server only once using cookie method if not _isUserAcknowledged(cookiejar): server_url = self.getServerUrl() f = _getAcCookieFromServer('%s/WebSite_login' % server_url, self.simple_opener_director, cookiejar, user_and_password[0], user_and_password[1]) # if server doesn't support cookie authentication try basic # authentication if not _isUserAcknowledged(cookiejar): auth = 'Basic %s' % base64.standard_b64encode( '%s:%s' % user_and_password) header_dict['Authorization'] = auth # save last credentials we passed to server last_loggedin_user_and_password = user_and_password if content_type: header_dict['Content-Type'] = content_type # send locally saved cookies to remote web server if not header_dict.has_key('Cookie'): header_dict['Cookie'] = '' for cookie in cookiejar: # unconditionally send all cookies (no matter if expired or not) as URL # is always the same header_dict['Cookie'] += '%s=%s;' % (cookie.name, cookie.value) # include cookies from local browser (like show/hide tabs) which are set # directly by client JavaScript code (i.e. not sent from server) for cookie_name, cookie_value in self.REQUEST.cookies.items(): header_dict['Cookie'] += '%s=%s;' % (cookie_name, cookie_value) # add HTTP referer (especially useful in Localizer when changing language) header_dict['REFERER'] = self.REQUEST.get('HTTP_REFERER', None) or referer request = urllib2.Request(url, data, header_dict) f = self.simple_opener_director.open(request) try: data = f.read() metadata = f.info() response = self.REQUEST.RESPONSE if f.code > 300 and f.code < 400: # adjust return url which my contain proxy URLs as arguments location = metadata.getheader('location') if location is not None: parsed_url = list(urlparse(location)) local_site_url_prefix = urllib.quote( '%s/portal_wizard/proxy' % self.getPortalObject().absolute_url()) remote_url_parsed = urlparse(self.getServerUrl()) remote_site_url_prefix = '%s://%s/kb' % ( remote_url_parsed[0], remote_url_parsed[1]) # fix arguments for returned location URL parsed_url[4] = parsed_url[4].replace( local_site_url_prefix, remote_site_url_prefix) response['location'] = urlunparse(parsed_url) response.setStatus(f.code, f.msg) response.setHeader('content-type', metadata.getheader('content-type')) # FIXME this list should be confirmed with the RFC 2616. for k in ('uri', 'cache-control', 'last-modified', 'etag', 'if-matched', 'if-none-match', 'if-range', 'content-language', 'content-range' 'content-location', 'content-md5', 'expires', 'content-encoding', 'vary', 'pragma', 'content-disposition', 'content-length', 'age'): if k in metadata: response.setHeader(k, metadata.getheader(k)) return data finally: f.close() def _getRemoteWitchTool(self, url, user_name=None, password=None): """ Return remote portal_witch tool interface. """ handle = self.getPortalObject().portal_web_services.connect( url=url, user_name=user_name, password=password, transport='xml-rpc') return handle.portal_witch def callRemoteProxyMethod(self, distant_method, server_url=None, use_cache=1, ignore_exceptions=1, **kw): """ Call proxy method on server. """ configurator_user_preferred_language = self\ .getConfiguratorUserPreferredLanguage() def wrapper(distant_method, **kw): return self._callRemoteMethod(distant_method, use_proxy=1, ignore_exceptions=ignore_exceptions, **kw)['data'] if use_cache: wrapper = CachingMethod( wrapper, id='callRemoteProxyMethod_%s_%s' % (distant_method, configurator_user_preferred_language), cache_factory='erp5_ui_medium') rc = wrapper(distant_method, **kw) return rc def _callRemoteMethod(self, distant_method, server_url=None, use_proxy=0, ignore_exceptions=1, **kw): """ Call remote method on server and get result. """ result_call = GeneratorCall() user_name = None password = None if server_url is None: # calculate it server_url = self.getServerUrl() + self.getServerRoot() # include authentication if possible user_and_password = self._getSubsribedUserAndPassword() if (len(user_and_password) == 2 and user_and_password[0] and user_and_password[1]): user_name, password = user_and_password witch_tool = self._getRemoteWitchTool(server_url, user_name, password) parameter_dict = self.REQUEST.form.copy() if use_proxy: # add remote method arguments parameter_dict['method_id'] = distant_method parameter_dict['method_kw'] = kw distant_method = 'proxyMethodHandler' ## add client arguments self._updateParameterDictWithServerInfo(parameter_dict) ## handle file upload self._updateParameterDictWithFileUpload(parameter_dict) message = None ## call remote method try: method = getattr(witch_tool, distant_method) html = method(parameter_dict) except socket.error, message: html = _generateErrorXML("""Cannot contact the server: %s. Please check your network settings.""" % server_url) zLOG.LOG('Wizard Tool socket error', zLOG.ERROR, message) result_call.update({ "command": "show", "data": html, "next": None, "previous": None }) except xmlrpclib.ProtocolError, message: html = _generateErrorXML("""The server %s refused to reply. Please contact [email protected]""" % server_url) zLOG.LOG('Wizard Tool xmlrpc protocol error', zLOG.ERROR, message) result_call.update({ "command": "show", "data": html, "next": None, "previous": None })
class DomainTool(BaseTool): """ A tool to define reusable ranges and subranges through predicate trees """ id = 'portal_domains' meta_type = 'ERP5 Domain Tool' portal_type = 'Domain Tool' allowed_types = ('ERP5 Domain', ) # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview') manage_overview = DTMLFile('explainDomainTool', _dtmldir) # XXX FIXME method should not be public # (some users are not able to see resource's price) security.declarePublic('searchPredicateList') def searchPredicateList(self, *args, **kw): return self._searchPredicateList(restricted=True, *args, **kw) def _searchPredicateList(self, context, test=1, sort_method=None, ignored_category_list=None, tested_base_category_list=None, filter_method=None, acquired=1, sort_key_method=None, query=None, restricted=False, **kw): """ Search all predicates which corresponds to this particular context. - sort_method parameter should not be used, if possible, because it can be very slow. Use sort_key_method instead. - sort_key_method parameter is passed to list.sort as key parameter if it is not None. This allows to sort the list of predicates found. The most important predicate is the first one in the list. - ignored_category_list: this is the list of category that we do not want to test. For example, we might want to not test the destination or the source of a predicate. - tested_base_category_list: this is the list of category that we do want to test. For example, we might want to test only the destination or the source of a predicate. - the acquired parameter allows to define if we want to use acquisition for categories. By default we want. """ if not kw.pop('strict', True): raise ValueError('"strict" mode cannot be disabled anymore') portal = self.getPortalObject() portal_catalog = portal.portal_catalog portal_categories = portal.portal_categories # Search the columns of the predicate table query_list = [] if query is None else [query] for column in portal_catalog.getSQLCatalog().getTableColumnList( 'predicate'): # Arbitrary suffix choice, this code expects COLUMN, COLUMN_range_min # and COLUMN_range_max to be simultaneously present for ranged # properties. Only checking one suffix simplifies the code flow. if column.endswith('_range_min'): property_name = column[:-10] # We have to check a range property equality = 'predicate.' + property_name range_min = equality + '_range_min' range_max = equality + '_range_max' value = context.getProperty(property_name) query = ComplexQuery(SimpleQuery(**{equality: None}), SimpleQuery(**{range_min: None}), SimpleQuery(**{range_max: None}), logical_operator='AND') if value is not None: query = ComplexQuery( query, SimpleQuery(**{equality: value}), ComplexQuery( SimpleQuery(comparison_operator='<=', **{range_min: value}), SimpleQuery(**{range_max: None}), logical_operator='AND', ), ComplexQuery( SimpleQuery(**{range_min: None}), SimpleQuery(comparison_operator='>=', **{range_max: value}), logical_operator='AND', ), ComplexQuery( SimpleQuery(comparison_operator='<=', **{range_min: value}), SimpleQuery(comparison_operator='>=', **{range_max: value}), logical_operator='AND', ), logical_operator='OR', ) query_list.append(query) if tested_base_category_list != []: # Add category selection if tested_base_category_list is None: if acquired: category_list = context.getAcquiredCategoryList() else: category_list = context.getCategoryList() else: if acquired: getter = context.getAcquiredCategoryMembershipList else: getter = context.getCategoryMembershipList category_list = [] extend = category_list.extend for tested_base_category in tested_base_category_list: if portal_categories.get(tested_base_category) is None: raise ValueError('Unknown base category: %r' % (tested_base_category, )) tested_category_list = getter(tested_base_category, base=1) if tested_category_list: extend(tested_category_list) else: # Developer requested specific base categories, but context do not # declare one of these. Skipping this criterion risks matching too # many predicates, breaking the system performance-wise. So let # developer know there is an unexpected situation by raising. raise ValueError('%r does not have any %r relation' % ( context.getPath(), tested_base_category, )) left_join_list = kw.get('left_join_list', [])[:] inner_join_list = kw.get('inner_join_list', [])[:] if category_list: preferred_predicate_category_list = portal.portal_preferences.getPreferredPredicateCategoryList( []) left_join_category_list = [] inner_join_category_list = [] for category in category_list: if portal_categories.getBaseCategoryId( category) in preferred_predicate_category_list: inner_join_category_list.append(category) else: left_join_category_list.append(category) def onMissing(category): # BBB: ValueError would seem more appropriate here, but original code # was raising TypeError - and this is explicitely tested for. raise TypeError('Unknown category: %r' % (category, )) def onInnerJoin(column_name): inner_join_list.append(column_name) # Base category is part of preferred predicate categories, predicates # which ignore it are indexed with category_uid=0. return SimpleQuery(**{column_name: 0}) query_list.append( portal_catalog.getCategoryParameterDict( inner_join_category_list, category_table='predicate_category', onMissing=onMissing, onJoin=onInnerJoin, )) def onLeftJoin(column_name): left_join_list.append(column_name) # Base category is not part of preferred predicate categories, # predicates which ignore it get no predicate_category row inserted # for it, so an SQL NULL appears, translating to None. return SimpleQuery(**{column_name: None}) query_list.append( portal_catalog.getCategoryParameterDict( left_join_category_list, category_table='predicate_category', onMissing=onMissing, onJoin=onLeftJoin, )) else: # No category to match against, so predicates expecting any relation # would not apply, so we can exclude these. # Note: this relies on a special indexation mechanism for predicate # categories, which inserts a base_category_uid=0 line when indexed # predicate membership_criterion_category_list is empty. base_category_uid_column = 'predicate_category.base_category_uid' kw[base_category_uid_column] = 0 inner_join_list.append(base_category_uid_column) kw['left_join_list'] = left_join_list kw['inner_join_list'] = inner_join_list if query_list: kw['query'] = ComplexQuery(logical_operator='AND', *query_list) if restricted: sql_result_list = portal_catalog.searchResults(**kw) else: sql_result_list = portal_catalog.unrestrictedSearchResults(**kw) if kw.get('src__'): return sql_result_list result_list = [] if sql_result_list: if test: cache = {} def isMemberOf(context, c, strict_membership): if c in cache: return cache[c] cache[c] = result = portal_categories.isMemberOf( context, c, strict_membership=strict_membership) return result for predicate in sql_result_list: predicate = predicate.getObject() if not test or predicate.test(context, tested_base_category_list, isMemberOf=isMemberOf): result_list.append(predicate) if filter_method is not None: result_list = filter_method(result_list) if sort_key_method is not None: result_list.sort(key=sort_key_method) elif sort_method is not None: result_list.sort(cmp=sort_method) return result_list # XXX FIXME method should not be public # (some users are not able to see resource's price) security.declarePublic('generateMappedValue') def generateMappedValue(self, context, test=1, predicate_list=None, **kw): """ We will generate a mapped value with the list of all predicates found. Let's say we have 3 predicates (in the order we want) like this: Predicate 1 [ base_price1, , , , , , ] Predicate 2 [ base_price2, quantity2 , , , , , ] Predicate 3 [ base_price3, quantity3 , , , , , ] Our generated MappedValue will have the base_price of the predicate1, and the quantity of the Predicate2, because Predicate 1 is the first one which defines a base_price and the Predicate2 is the first one wich defines a quantity. """ # First get the list of predicates if predicate_list is None: predicate_list = self.searchPredicateList(context, test=test, **kw) if len(predicate_list) == 0: # No predicate, return None mapped_value = None else: # Generate tempDeliveryCell from Products.ERP5Type.Document import newTempSupplyCell mapped_value = newTempSupplyCell(self.getPortalObject(), 'new_mapped_value') mapped_value_property_dict = {} # Look for each property the first predicate which defines the # property for predicate in predicate_list: getMappedValuePropertyList = getattr( predicate, 'getMappedValuePropertyList', None) # searchPredicateList returns a list of any kind of predicate, which # includes predicates not containing any mapped value (for exemple, # domains). In such case, it has no meaning to handle them here. # A better way would be to tell catalog not to provide us with those # extra object, but there is no simple way (many portal types inherit # from MappedValue defining the accessor). # Feel free to improve. if getMappedValuePropertyList is not None: for mapped_value_property in predicate.getMappedValuePropertyList( ): if not mapped_value_property_dict.has_key( mapped_value_property): value = predicate.getProperty( mapped_value_property) if value is not None: mapped_value_property_dict[ mapped_value_property] = value # Update mapped value mapped_value.edit(**mapped_value_property_dict) return mapped_value # XXX FIXME method should not be public # (some users are not able to see resource's price) security.declarePublic('generateMultivaluedMappedValue') def generateMultivaluedMappedValue(self, context, test=1, predicate_list=None, explanation_only=0, **kw): """ We will generate a mapped value with the list of all predicates found. Let's say we have 3 predicates (in the order we want) like this: Predicate 1 [ base_price1, , , , , , ] Predicate 2 [ base_price2, additional_price2 , , , , , ] Predicate 3 [ base_price3, additional_price3 , , , , , ] Our generated MappedValue will take all values for each property and put them in lists, unless predicates define the same list of criterion categories """ # First get the list of predicates if predicate_list is None: predicate_list = self.searchPredicateList(context, test=test, **kw) if predicate_list: from Products.ERP5Type.Document import newTempSupplyCell mapped_value_property_dict = defaultdict(list) explanation_dict = defaultdict(dict) # Look for each property the first predicate with unique criterion # categories which defines the property for predicate in predicate_list: full_prop_dict = explanation_dict[tuple( predicate.getMembershipCriterionCategoryList())] for mapped_value_property in predicate.getMappedValuePropertyList( ): if mapped_value_property in full_prop_dict: # we already have one value for this (categories, property) continue value = predicate.getProperty(mapped_value_property) if value is not None: full_prop_dict[mapped_value_property] = value mapped_value_property_dict[ mapped_value_property].append(value) if explanation_only: return dict(explanation_dict) mapped_value = newTempSupplyCell(self.getPortalObject(), 'multivalued_mapped_value') mapped_value._setMappedValuePropertyList( mapped_value_property_dict.keys()) mapped_value.__dict__.update(mapped_value_property_dict) return mapped_value security.declareProtected(Permissions.AccessContentsInformation, 'getChildDomainValueList') def getChildDomainValueList(self, parent, **kw): """ Return child domain objects already present adn thois generetaded dynamically """ # get static domain object_list = list(parent.objectValues()) # get dynamic object generated from script object_list.extend(parent.getDomainGeneratorList(**kw)) return object_list security.declareProtected(Permissions.AccessContentsInformation, 'getDomainByPath') def getDomainByPath(self, path, default=_MARKER): """ Return the domain object for a given path """ path = path.split('/') base_domain_id = path[0] if default is _MARKER: domain = self[base_domain_id] else: domain = self.get(base_domain_id, _MARKER) if domain is _MARKER: return default for depth, subdomain in enumerate(path[1:]): domain_list = self.getChildDomainValueList(domain, depth=depth) for d in domain_list: if d.getId() == subdomain: domain = d break else: if domain is _MARKER: return default raise KeyError, subdomain return domain