##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# 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
#
##############################################################################
""" Portal class

$Id$
"""

import Globals
from Globals import package_home
from time import time
#from Products.ERP5 import content_classes
from AccessControl import ClassSecurityInfo
from Products.CMFDefault.Portal import CMFSite, PortalGenerator
from Products.CMFCore.utils import getToolByName, _getAuthenticatedUser
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5Type.Document import addFolder
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
import ERP5Globals

import threading

from zLOG import LOG

import os

#factory_type_information = []
#for c in content_classes:
#  factory_type_information.append(getattr(c, 'factory_type_information', []))

# Optimized Module Menu
GLOBAL_MODULE_CACHE_DURATION = 300
cached_modules = {}
cached_modules_time = {}

# Site Creation DTML
manage_addERP5SiteForm = Globals.HTMLFile('dtml/addERP5Site', globals())
manage_addERP5SiteForm.__name__ = 'addERP5Site'

# ERP5Site Constructor
def manage_addERP5Site(self, id, title='ERP5', description='',
                         create_userfolder=1,
                         email_from_address='postmaster@localhost',
                         email_from_name='Portal Administrator',
                         validate_email=0,
                         sql_connection_type='Z MySQL Database Connection',
                         sql_connection_string='test test',
                         RESPONSE=None):
    '''
    Adds a portal instance.
    '''
    gen = ERP5Generator()
    from string import strip
    id = strip(id)
    p = gen.create(self, id, create_userfolder,sql_connection_type,sql_connection_string)
    gen.setupDefaultProperties(p, title, description,
                               email_from_address, email_from_name,
                               validate_email)
    if RESPONSE is not None:
        RESPONSE.redirect(p.absolute_url() + '/finish_portal_construction')

class ERP5Site ( CMFSite ):
    """
        The *only* function this class should have is to help in the setup
        of a new ERP5.  It should not assist in the functionality at all.
    """
    meta_type = 'ERP5 Site'
    constructors = (manage_addERP5SiteForm, manage_addERP5Site, )
    uid = 0
    last_id = 0
    icon = 'portal.gif'

    _properties = (
        {'id':'title', 'type':'string'},
        {'id':'description', 'type':'text'},
        )
    title = ''
    description = ''

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.View)

    security.declareProtected(Permissions.View, 'getUid')
    def view(self):
        """
          Returns the default view.
          Implemented for consistency
        """
        return self.index_html()

    security.declareProtected(Permissions.AccessContentsInformation, 'getUid')
    def getUid(self):
      """
        Returns the UID of the object. Eventually reindexes
        the object in order to make sure there is a UID
        (useful for import / export).

        WARNING : must be updates for circular references issues
      """
      #if not hasattr(self, 'uid'):
      #  self.reindexObject()
      return getattr(self, 'uid', 0)

    security.declareProtected(Permissions.AccessContentsInformation, 'getParentUid')
    def getParentUid(self):
      """
        A portal has no parent
      """
      return self.getUid()

    security.declareProtected(Permissions.AccessContentsInformation, 'searchFolder')
    def searchFolder(self, **kw):
      """
        Search the content of a folder by calling
        the portal_catalog.
      """
      if not kw.has_key('parent_uid'):
        kw['parent_uid'] = self.uid
      kw2 = {}
      # Remove useless matter before calling the
      # catalog. In particular, consider empty
      # strings as None values
      for cname in kw.keys():
        if kw[cname] != '' and kw[cname]!=None:
          kw2[cname] = kw[cname]
      # The method to call to search the folder
      # content has to be called z_search_folder
      method = self.portal_catalog.z_search_folder
      return method(**kw2)

    security.declareProtected(Permissions.ManagePortal, 'generateNewId')
    def generateNewId(self):
        """
          Generate a new Id which has not been taken yet in this folder.
          Eventually increment the id number until an available id
          can be found
        """
        my_id = self.last_id
        l = threading.Lock()
        l.acquire()
        try:
          while hasattr(self,str(my_id)):
            self.last_id = self.last_id + 1
            my_id = self.last_id
        finally:
          l.release()
        return str(my_id)

    # Proxy methods for security reasons
    def getOwnerInfo(self):
      return self.owner_info()

    # Make sure fixConsistency is recursive - ERROR - this creates recursion errors
    # checkConsistency = Folder.checkConsistency
    # fixConsistency = Folder.fixConsistency

    security.declareProtected(Permissions.AccessContentsInformation, 'getMovementTypeList')
    def getMovementTypeList(self):
      """
        Returns possible movements types
      """
      return ERP5Globals.movement_type_list

    security.declarePublic('getOrderedGlobalActionList')
    def getModuleList(self):
      """
         Return a list of modules - result dependent on user - result is translated and cached
      """
      # Return Cache
      user = str(_getAuthenticatedUser(self))
      if cached_modules.has_key(user):
        if time() - cached_modules_time[user] < GLOBAL_MODULE_CACHE_DURATION:
          return cached_modules[user]
      result = []
      for module in self.objectValues('ERP5 Folder'):
        # XXX Restrict access to modules to valid users
        result.append({'url': module.absolute_url(), 'id': module.getId(), 'title': self.gettext(module.getTitle())})
      result.sort(lambda x,y: cmp(x['title'], y['title']))
      cached_modules[user] = result
      cached_modules_time[user] = time()
      return cached_modules[user]

    security.declarePublic('getOrderedGlobalActionList')
    def getOrderedGlobalActionList(self, action_list):
      """
      Returns a dictionnary of actions, sorted by type of object

      This should absolutely be rewritten by using clean concepts to separate worklists XXX
      """
      #LOG("getOrderedGlobalActionList", 0, str(action_list))
      sorted_workflow_actions = {}
      sorted_global_actions = []
      other_global_actions = []
      for action in action_list:
        action['disabled'] = 0
        if action.has_key('workflow_title'):
          if not sorted_workflow_actions.has_key(action['workflow_title']):
            sorted_workflow_actions[action['workflow_title']] = []
          sorted_workflow_actions[action['workflow_title']].append(action)
        else:
          other_global_actions.append(action)
      workflow_title_list = sorted_workflow_actions.keys()
      workflow_title_list.sort()
      for key in workflow_title_list:
        sorted_global_actions.append({'title': key, 'disabled': 1})
        sorted_global_actions.extend(sorted_workflow_actions[key])
      sorted_global_actions.append({'title': 'Other', 'disabled': 1})
      sorted_global_actions.extend(other_global_actions)
      return sorted_global_actions

    def setupDefaultProperties(self, p, title, description,
                               email_from_address, email_from_name,
                               validate_email
                               ):
        CMFSite.setupDefaultProperties(self, p, title, description,
                               email_from_address, email_from_name,
                               validate_email)

Globals.InitializeClass(ERP5Site)

class ERP5Generator(PortalGenerator):

    klass = ERP5Site

    def create(self, parent, id, create_userfolder, sql_connection_type, sql_connection_string):
        id = str(id)
        portal = self.klass(id=id)
        parent._setObject(id, portal)
        # Return the fully wrapped object.
        p = parent.this()._getOb(id)
        p._setProperty('sql_connection_type', sql_connection_type, 'string')
        p._setProperty('sql_connection_string', sql_connection_string, 'string')
        self.setup(p, create_userfolder)
        return p

    def setupTools(self, p):
        """Set up initial tools"""

        PortalGenerator.setupTools(self, p)

        # Add ERP5 Tools
        addTool = p.manage_addProduct['ERP5'].manage_addTool
        #print "addTool = %s" % str(addTool)
        addTool('ERP5 Categories', None)
        addTool('ERP5 Rule Tool', None)
        addTool('ERP5 Id Tool', None)
        addTool('ERP5 Simulation Tool', None)
        addTool('ERP5 Template Tool', None)

        # Add Activity Tool
        addTool = p.manage_addProduct['CMFActivity'].manage_addTool
        #addTool('CMF Activity Tool', None) # Allow user to select active/passive

        # Add ERP5 SQL Catalog Tool
        addTool = p.manage_addProduct['ERP5Catalog'].manage_addTool
        p._delObject('portal_catalog')
        addTool('ERP5 Catalog', None)
        # Add Default SQL connection
        if p.sql_connection_type == 'Z MySQL Database Connection':
          addSQLConnectioon = p.manage_addProduct['ZSQLMethods'].manage_addZMySQLConnection
          addSQLConnectioon('erp5_sql_connection', 'ERP5 SQL Server Connection', p.sql_connection_string)
        elif p.sql_connection_type == 'Z Gadfly':
          pass
        # Create default methods in Catalog XXX
        portal_catalog = getToolByName(p, 'portal_catalog')
        addSQLMethod = portal_catalog.manage_addProduct['ZSQLMethods'].manage_addZSQLMethod
        product_path = package_home(globals())
        zsql_dir = os.path.join(product_path, 'sql')
        #print ("zsql_dir = %s" % str(zsql_dir))
        # Iterate over the sql directory. Add all sql methods in that directory.
        for entry in os.listdir(zsql_dir):
          if len(entry) > 5 and entry[-5:] == '.zsql':
            id = entry[:-5]
            # Create an empty SQL method first.
            addSQLMethod(id = id, title = '', connection_id = '', arguments = '', template = '')
            sql_method = getattr(portal_catalog, id)
            # Set parameters of the SQL method from the contents of a .zsql file.
            sql_method.fromFile(os.path.join(zsql_dir, entry))
        # Setup ZSQLCaralog properties
        portal_catalog.sql_catalog_object = ('z0_catalog_object', 'z_catalog_category', 'z_catalog_movement',
                                             'z_catalog_roles_and_users', 'z_catalog_stock', 'z_catalog_subject',)
        portal_catalog.sql_uncatalog_object = ('z0_uncatalog_category', 'z0_uncatalog_movement', 'z0_uncatalog_roles_and_users',
                                               'z0_uncatalog_stock', 'z0_uncatalog_subject', 'z_uncatalog_object', )
        portal_catalog.sql_update_object = ('z0_uncatalog_category', 'z0_uncatalog_movement', 'z0_uncatalog_roles_and_users',
                                            'z0_uncatalog_stock', 'z0_uncatalog_subject', 'z_catalog_category',
                                            'z_catalog_movement', 'z_catalog_roles_and_users', 'z_catalog_stock', 'z_catalog_subject',
                                            'z_update_object', )
        portal_catalog.sql_clear_catalog = ('z0_drop_catalog', 'z0_drop_category', 'z0_drop_movement', 'z0_drop_roles_and_users',
                                            'z0_drop_stock', 'z0_drop_subject', 'z_create_catalog',
                                            'z_create_category', 'z_create_movement', 'z_create_roles_and_users',
                                            'z_create_stock', 'z_create_subject', )
        portal_catalog.sql_search_results = 'z_search_results'
        portal_catalog.sql_count_results = 'z_count_results'
        portal_catalog.sql_getitem_by_path = 'z_getitem_by_path'
        portal_catalog.sql_getitem_by_uid = 'z_getitem_by_uid'
        portal_catalog.sql_catalog_schema = 'z_show_columns'
        portal_catalog.sql_unique_values = 'z_unique_values'
        portal_catalog.sql_catalog_paths = 'z_catalog_paths'
        portal_catalog.sql_catalog_keyword_search_keys = ('Description', 'SearchableText', 'Title', )
        portal_catalog.sql_catalog_full_text_search_keys = ('Description', 'SearchableText', 'Title', )
        portal_catalog.sql_catalog_request_keys = ()
        portal_catalog.sql_search_result_keys = ('catalog.*',)
        portal_catalog.sql_search_tables = ('catalog', 'category', 'roles_and_users', 'movement', 'subject', )
        portal_catalog.sql_catalog_tables = 'z_show_tables'

        # Clear Catalog
        portal_catalog.manage_catalogClear()

        # Add Selection Tool
        addTool = p.manage_addProduct['ERP5Form'].manage_addTool
        addTool('ERP5 Selections', None)

        # Add ERP5SyncML Tools
        addTool = p.manage_addProduct['ERP5SyncML'].manage_addTool
        addTool('ERP5 Synchronizations', None)

        # Add Message Catalog
        addMessageCatalog = p.manage_addProduct['Localizer'].manage_addMessageCatalog
        addMessageCatalog('gettext', 'ERP5 Localized Messages', ('en'))
        addMessageCatalog('translated_ui', 'ERP5 Localized Interface', ('en'))
        addMessageCatalog('translated_content', 'ERP5 Localized Content', ('en'))

        # Add Translation Service
        p.manage_addProduct['TranslationService'].addPlacefulTranslationService('translation_service')
        p.translation_service.manage_setDomainInfo(domain_0=None, path_0='gettext')
        p.translation_service.manage_addDomainInfo(domain='ui', path='translated_ui')
        p.translation_service.manage_addDomainInfo(domain='content', path='translated_content')


    def setupMembersFolder(self, p):
        """
          ERP5 is not a CMS
        """
        pass
        #from Products.CMFDefault.MembershipTool import MembershipTool
        #addFolder(p, id=MembershipTool.membersfolder_id, title='ERP5 Members')
        #member_folder = p[MembershipTool.membersfolder_id]
        #member_folder.manage_addProduct['OFSP'].manage_addDTMLMethod(
        #    'index_html', 'Member list', '<dtml-return roster>')

    def setupFrontPage(self, p):
        text = """<span  metal:define-macro="body">
<span tal:condition="python: not here.portal_membership.isAnonymousUser()">
<br/>
<br/>
<br/>
<br/>
<h3 align=center>Welcome to your new information system</h3>
<table border=1 align=center>
<tr tal:define="module_list python:here.objectValues('ERP5 Folder');
                dummy python:module_list.sort(lambda x,y: cmp(x.getTitle(), y.getTitle()));
                module_len python:len(module_list);
                col_size python:16;
                col_len python:module_len / col_size">
  <td>
   <img src="erp5_logo.png" alt="ERP5 Logo" />
  </td>
  <td tal:repeat="col_no python:range(col_len)" valign="top" class="ModuleShortcut">
    <p tal:repeat="module python:module_list[col_size*col_no:min(col_size*(col_no+1),module_len)] "><a href="composant"
       tal:content="module/title"
       tal:attributes="href module/id">Composants</a></p>
  </td>
</tr>
</table>

</span>
<span tal:condition="python: here.portal_membership.isAnonymousUser()">
<p tal:define="dummy python:request.RESPONSE.redirect('%s/login_form' % here.absolute_url())"/>
</span>
</span>
"""
        p.manage_addProduct['PageTemplates'].manage_addPageTemplate(
                  'local_pt', title='ERP5 Front Page', text=text)

    def setupDefaultSkins(self, p):
        from Products.CMFCore.DirectoryView import addDirectoryViews
        from Products.CMFDefault import cmfdefault_globals
        from Products.CMFActivity import cmfactivity_globals
        ps = getToolByName(p, 'portal_skins')
        addDirectoryViews(ps, 'skins', globals())
        addDirectoryViews(ps, 'skins', cmfdefault_globals)
        addDirectoryViews(ps, 'skins', cmfactivity_globals)
        ps.manage_addProduct['OFSP'].manage_addFolder(id='external_method')
        ps.manage_addProduct['OFSP'].manage_addFolder(id='local_pro')
        ps.manage_addProduct['OFSP'].manage_addFolder(id='local_erp5')
        ps.manage_addProduct['OFSP'].manage_addFolder(id='local_list_method')
        ps.addSkinSelection('ERP5', 'local_pro, local_erp5, local_list_method, '
                                  + 'external_method, pro, erp5, activity, '
                                  + 'zpt_topic, zpt_content, zpt_generic,'
                                  + 'zpt_control, topic, content, generic, control, Images',
                            make_default=1)
        p.setupCurrentSkin()

    def setupWorkflow(self, p):
        """
          ERP5 has no default worklow
        """
        pass

    def setupIndex(self, p):
        from Products.CMFDefault.MembershipTool import MembershipTool
        # Make sure all tools and folders have been indexed
        portal_catalog = p.portal_catalog
        portal_catalog.manage_catalogClear()
        portal_catalog.reindexObject(p)
        portal_catalog.reindexObject(p.portal_templates)
        portal_catalog.reindexObject(p.portal_categories)
        # portal_catalog.reindexObject(p.portal_activities)
        #p[MembershipTool.membersfolder_id].immediateReindexObject()

    def setup(self, p, create_userfolder):
        self.setupTools(p)
        self.setupMailHost(p)
        if int(create_userfolder) != 0:
            self.setupUserFolder(p)
        self.setupCookieAuth(p)
        self.setupRoles(p)
        self.setupPermissions(p)
        self.setupDefaultSkins(p)

        # Initialize Activities
        #p.portal_skins.activity.SQLDict_createMessageTable()

        # Finish setup
        self.setupMembersFolder(p)

        # ERP5 Design Choice is that all content should be user defined
        # Content is disseminated through business templates
        from Products.ERP5.Document.BusinessTemplate import BusinessTemplate
        from Products.ERP5Type.Document.Folder import Folder
        self.setupTypes(p, (BusinessTemplate.factory_type_information, Folder.factory_type_information))

        self.setupMimetypes(p)
        self.setupWorkflow(p)
        self.setupFrontPage(p)

        # Make sure tools are cleanly indexed with a uid before creating children
        # XXX for some strange reason, member was indexed 5 times
        self.setupIndex(p)

# Patch the standard method
CMFSite.getPhysicalPath = ERP5Site.getPhysicalPath