##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Provide management of common instance property sheets
"""
 
 
from AccessControl.Permission import pname
from AccessControl.Permissions import manage_zclasses
from AccessControl.Permissions import manage_properties
from AccessControl.Permissions import access_contents_information
from AccessControl.SecurityInfo import ClassSecurityInfo
from App.class_init import InitializeClass
from App.special_dtml import DTMLFile
from OFS.ObjectManager import ObjectManager
from OFS.PropertySheets import FixedSchema
from OFS.PropertySheets import PropertySheet
from OFS.PropertySheets import PropertySheets
from OFS.PropertySheets import View
from OFS.SimpleItem import Item
from OFS.Traversable import Traversable
from Persistence import Persistent
import transaction
 
class ClassCaretaker:
    def __init__(self, klass): self.__dict__['_k']=klass
    def __getattr__(self, name): return getattr(self._k, name)
    def __setattr__(self, name, v):
        klass=self._k
        setattr(klass, name, v)
        if not getattr(klass,'_p_changed',None) and klass._p_jar is not None:
            transaction.get().register(klass)
            klass._p_changed=1
    def __delattr__(self, name):
        klass=self._k
        delattr(klass, name)
        if not getattr(klass,'_p_changed',None) and klass._p_jar is not None:
            transaction.get().register(klass)
            klass._p_changed=1
 
 
class ZCommonSheet(PropertySheet, Item):
    "Property Sheet that has properties common to all instances"
    meta_type="Common Instance Property Sheet"
    _properties=()
 
    manage_options=(
        {'label':'Properties', 'action':'manage',
         'help':('OFSP','Properties.stx')},
        {'label':'Define Permissions', 'action':'manage_security',
         'help':('OFSP','Security_Define-Permissions.stx')},
        )
 
    security = ClassSecurityInfo()
    security.declareObjectProtected(manage_zclasses)
 
    security.declareProtected(manage_zclasses, 'manage')
 
    def __init__(self, id, title):
        self.id=id
        self.title=title
        self._md={}
 
    def manage_afterAdd(self,item,container):
        if item is not self: return
        if self._properties:
            raise ValueError, (
                'Non-empty propertysheets cannot currently be '
                'added (or copied).<p>')
 
    def v_self(self):
        klass=self.aq_inner.aq_parent.aq_parent.aq_parent._zclass_
        return ClassCaretaker(klass)
 
    def p_self(self): return self
 
    def _view_widget_for_type(self, t, id):
        if t not in ('lines', 'tokens'):
            return '<dtml-var %s>' % id
        return """
        <dtml-in %s>
           <dtml-var sequence-item>
        </dtml-in %s>
        """ % (id, id)
 
    def manage_createView(self, id, title='', ps_view_type=None, REQUEST=None):
        """Create a view of a property sheet
        """
        if ps_view_type == 'Edit':
            return self.manage_createEditor(id, title, REQUEST)
 
        r=['<dtml-var standard_html_header>',
           '<table>']
        a=r.append
        for p in self.propertyMap():
            pid=p['id']
            pid=pid[:1].upper()+pid[1:]
            a('  <tr><th align=left valign=top>%s</th>' % pid)
            a('      <td align=left valign=top>%s</td>' %
              self._view_widget_for_type(p['type'], p['id'])
              )
            a('  </tr>')
        a('</table>')
        a('<dtml-var standard_html_footer>')
        r='\n'.join(r)
        self.aq_parent.aq_parent.methods.manage_addDTMLMethod(id, title, r)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(REQUEST['URL3']+'/methods/manage')
 
    def _edit_widget_for_type(self, t, id, p):
        if t in ('int', 'long', 'float', 'date', 'string'):
            if t=='string': q=' html_quote'
            else: q=''
            return ('''
            <input name="%s:%s" size="35"
                   value="<dtml-var %s%s>">'''
                    % (id, t, id, q)
                    )
        if t=='boolean':
            return ('''
            <input type="checkbox" name="%s:boolean" size="35"
                <dtml-if %s>CHECKED</dtml-if>>'''
                    % (id, id)
                    )
 
        if t=='tokens':
            return ('''
            <input type="text" name="%s:tokens" size="35"
               value="<dtml-in %s><dtml-var sequence-item> </dtml-in>">'''
                    % (id, id)
                    )
 
        if t=='text':
            return ('''
            <textarea name="%s:text" rows="6" cols="35"><dtml-var %s
            ></textarea>'''
                    % (id, id)
                    )
 
        if t=='lines':
            return ('''
            <textarea name="%s:lines" rows="6" cols="35"><dtml-in %s
            ><dtml-var sequence-item>\n</dtml-in></textarea>'''
                    % (id, id)
                    )
 
 
        if t=='selection':
            return ('''
            <dtml-if "_.has_key('%(id)s')">
            <select name="%(id)s">
              <dtml-in "_.string.split('%(select_variable)s')">
                <option
                  <dtml-if "_['sequence-item']=='%(id)s'">
                  SELECTED</dtml-if>
                  ><dtml-var sequence-item></option>
              </dtml-in>
            </select>
            <dtml-else>
              No value for %(id)s
            </dtml-if>'''
                    % p
                    )
 
        return ''
 
    def manage_beforeDelete(self, item, container):
        if self is item:
            for d in self._properties:
                self.delClassAttr(d['id'])
 
    def manage_createEditor(self, id, title='', REQUEST=None):
        """Create an edit interface for a property sheet
        """
        r=['<html><head><title><dtml-var title_or_id></title></head>',
           '<body bgcolor="#FFFFFF" link="#000099" vlink="#555555">',
           '<dtml-var manage_tabs>',
           '<form action="propertysheets/%s/manage_editProperties"><table>'
           % self.id]
        a=r.append
        for p in self.propertyMap():
            a('  <tr><th align=left valign=top>%s</th>' % p['id'])
            a('      <td align=left valign=top>%s</td>' %
              self._edit_widget_for_type(p['type'], p['id'], p)
              )
            a('  </tr>')
        a('  <tr><td colspan=2>')
        a('    <input type=submit value=" Change ">')
        a('    <input type=reset value=" Reset ">')
        a('  </td></tr>')
        a('</table></form>')
        a('</body></html>')
        r='\n'.join(r)
        self.aq_parent.aq_parent.methods.manage_addDTMLMethod(id, title, r)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(REQUEST['URL3']+'/methods/manage')
 
    def permissionMappingPossibleValues(self):
        return self.classDefinedAndInheritedPermissions()
 
    manage_security = DTMLFile('dtml/methodAccess', globals())
    def manage_getPermissionMapping(self):
        ips=self.getClassAttr('propertysheets')
        ips=getattr(ips, self.id)
 
        # ugh
        perms={}
        for p in self.classDefinedAndInheritedPermissions():
            perms[pname(p)]=p
 
        r=[]
        for p in property_sheet_permissions:
            v=getattr(ips, pname(p))
            r.append(
                {'permission_name': p,
                 'class_permission': perms.get(v,'')
                 })
 
        return r
 
    def manage_setPermissionMapping(self, permission_names=[],
                                    class_permissions=[],
                                    REQUEST=None):
        "Change property sheet permissions"
        ips=self.getClassAttr('propertysheets')
        ips=getattr(ips, self.id)
 
        perms=self.classDefinedAndInheritedPermissions()
        for i in range(len(permission_names)):
            name=permission_names[i]
            p=class_permissions[i]
            if p and (p not in perms):
                __traceback_info__=perms, p, i
                raise ValueError, 'Invalid class permission'
 
            if name not in property_sheet_permissions: continue
 
            setattr(ips, pname(name), pname(p))
 
        if REQUEST is not None:
            return self.manage_security(
                self, REQUEST,
                manage_tabs_message='The permission mapping has been updated')
 
InitializeClass(ZCommonSheet)
 
 
property_sheet_permissions=(
    # 'Access contents information',
    'Manage properties',
    )
 
class ZInstanceSheet(FixedSchema, View):
    "Waaa this is too hard"
 
    security = ClassSecurityInfo()
    security.declareObjectProtected(access_contents_information)
 
    security.declareProtected(access_contents_information, 'hasProperty')
    security.declareProtected(access_contents_information, 'propertyIds')
    security.declareProtected(access_contents_information, 'propertyValues')
    security.declareProtected(access_contents_information, 'propertyItems')
    security.declareProtected(access_contents_information, 'propertyMap')
    security.declareProtected(manage_properties, 'manage')
    security.declareProtected(manage_properties, 'manage_addProperty')
    security.declareProtected(manage_properties, 'manage_editProperties')
    security.declareProtected(manage_properties, 'manage_delProperties')
    security.declareProtected(manage_properties, 'manage_changeProperties')
 
    _Manage_properties_Permission='_Manage_properties_Permission'
    _Access_contents_information_Permission='_View_Permission'
 
    def v_self(self):
        return self.aq_inner.aq_parent.aq_parent
 
InitializeClass(ZInstanceSheet)
 
 
def rclass(klass):
    if not getattr(klass, '_p_changed', 0) and klass._p_jar is not None:
        transaction.get().register(klass)
        klass._p_changed=1
 
class ZInstanceSheetsSheet(Traversable, View, ObjectManager):
    "Manage common property sheets"
 
    # Note that we need to make sure we add and remove
    # instance sheets.
    id='common'
    isPrincipiaFolderish=1
    icon="p_/Propertysheets_icon"
    dontAllowCopyAndPaste=1
 
    def tpURL(self): return 'propertysheets/common'
 
    def _setOb(self, id, value):
        setattr(self, id, value)
        pc=self.aq_inner.aq_parent.aq_parent._zclass_propertysheets_class
        setattr(pc,id,ZInstanceSheet(id,value))
        pc.__propset_attrs__=tuple(map(lambda o: o['id'], self._objects))
        rclass(pc)
 
 
    def _delOb(self, id):
        delattr(self, id)
        pc=self.aq_inner.aq_parent.aq_parent._zclass_propertysheets_class
        try: delattr(pc,id)
        except: pass
        pc.__propset_attrs__=tuple(map(lambda o: o['id'], self._objects))
        rclass(pc)
 
    meta_types=({'name': ZCommonSheet.meta_type,
                 'action': 'manage_addCommonSheetForm',
                },)
 
    def all_meta_types(self): return self.meta_types
 
 
    manage = DTMLFile('OFS/dtml/main', management_view='Property Sheets')
    manage_main = manage
    manage_main._setName('manage_main')
    manage_addCommonSheetForm = DTMLFile('dtml/addCommonSheet', globals())
 
    def manage_addCommonSheet(self, id, title, REQUEST=None):
        "Add a property sheet"
        o=ZCommonSheet(id, title)
        self._setObject(id, o)
        if REQUEST is not None:
            return self.manage(self, REQUEST)
 
def klass_sequence(klass,attr,result=None):
    if result is None: result={}
    if hasattr(klass,attr):
        for i in getattr(klass,attr): result[i]=1
        for klass in klass.__bases__:
            klass_sequence(klass, attr, result)
    return result
 
class ZInstanceSheets(PropertySheets, Persistent):
    " "
    __propset_attrs__=()
    _implements_the_notional_subclassable_propertysheet_class_interface=1
 
    def __propsets__(self):
        propsets=ZInstanceSheets.inheritedAttribute('__propsets__')(self)
        r=[]
        for id in klass_sequence(self.__class__,'__propset_attrs__').keys():
            r.append(getattr(self, id))
        return propsets+tuple(r)
 
InitializeClass(ZInstanceSheets)