示例#1
0
  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)
示例#2
0
文件: Form.py 项目: poses/erp5
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()
示例#3
0
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)
示例#4
0
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
示例#5
0
    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)
示例#6
0
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)
示例#7
0
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)
示例#8
0
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()
示例#9
0
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)
示例#10
0
  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
示例#11
0
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.')
示例#12
0
文件: DCWorkflow.py 项目: poses/erp5
  """
    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.
示例#13
0
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
示例#14
0
# 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',
    )

示例#15
0
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')
示例#16
0
文件: Form.py 项目: poses/erp5
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, ))
示例#17
0
文件: AlarmTool.py 项目: poses/erp5
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."))
示例#18
0
文件: Form.py 项目: poses/erp5
    # 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
示例#19
0
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
示例#20
0
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
示例#21
0
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)
示例#22
0
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()
示例#23
0
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.')
示例#24
0
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."),
                }))
示例#25
0
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))
示例#26
0
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())
示例#27
0
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 ''

示例#28
0
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)
示例#29
0
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
            })
示例#30
0
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