diff --git a/product/ZLDAPConnection/CHANGES.txt b/product/ZLDAPConnection/CHANGES.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ccd3f1af4c4333197b36c23d54dcc9e4b07afc6a
--- /dev/null
+++ b/product/ZLDAPConnection/CHANGES.txt
@@ -0,0 +1,108 @@
+ZLDAPConnection CHANGES.txt
+
+ 1.1.0
+
+  Features Added
+
+   o A non beta release!  Although, this may still be a bit shaky.
+
+   o The LDAP Connection object has the ability to be
+     *non-transactional*, which is currently the preferred way of
+     using the LDAP Connection object.  There is still an outstanding
+     bug with the way the transactional behavior works when updating
+     more than one Entry object at a time.
+
+   o Entry API is vastly improved, and is not backwards compatible
+     with ZopeLDAP 1.0.  The primary advantage is that now that Python 
+     Scripts are in the core, Entry objects are easier to program
+     without having to use the 'manage_' methods (although those will
+     continue to work, and their API has not changed).
+
+   o To go along with the Entry API improvement, the Permissions of
+     Entry objects have been improved as well.
+
+   o The LDAP Connection has an Entry Factory which decides whether to 
+     use a Transactional or Non-Transactional Entry object.  The usage 
+     to the end user is indistinguishable, except that the
+     nontransactional entry objects update to the LDAP server
+     *immediately*.
+
+  Bugs Fixed
+
+   o The bug in the transaction management behavior of the Connection
+     object has not been fixed, but the new Non-Transactional ability
+     should make up for this bug in most situations.
+
+
+ 1.0b5
+
+   Bugs Fixed
+
+    o Entry objects would raise a KeyError on a failed attribute
+      access, which caused a failure in the Security Machinery
+      (non-critical - security was not affected, only attribute
+      access).  Now AttributeError is properly raised.
+
+
+ 1.0b4
+
+  Bugs Fixed
+
+    o Entry.manage_newEntry and Entry.manage_newEntryWithAttributes
+      now return the newly added Entry object.
+  
+    o Should work properly with Zope 2.2.x, and still work with Zope
+      2.1.6.
+
+ 1.0b3
+
+  Bugs Fixed
+
+    o Fixes an issue with getRawEntry() raising an IndexError
+      exception in some cases.
+
+    o LDAPConnection doesn't keep setting self.openc every time _open()
+      is called, thus avoiding constant writes to the ZODB.
+
+    o Fixed ZLDAPConnection.__init__.py to use the proper product
+      initialization code (prior versions were using an extremely
+      outdated mechanism).  *This also means registering permissions
+      which affect Entry objects so that on Folders, permissions to
+      manage entry objects are settable*.
+
+  Features Added
+  
+    o New module 'LDCAccessors.py' implements getter/setter methods for
+      all ZLDAPConnection properties.  It is mixed into ZLDAPConnection.
+
+ 1.0b2
+
+  o Updated for Zope 2.1.5/2.1.6 new security measures on method-ish
+    objects.
+
+  o Has a settable "Browsable" property on the Connection object. 
+
+ 1.0b1
+
+  o This is a pretty major overhaul of the original alpha code,
+    primarily to include some sort of pseudo-transactional safety (all 
+    done in Zope since LDAP has no concept of transactions).
+
+  o Seperated ZLDAPConnection and Entry objects.  ZLDAPConnection (the 
+    class) no longer subclasses from an Entry object.  (In old code,
+    it was the Entry object at the root of the connection.  In this
+    version, the root is gotten to by calling the method 'getRoot()'
+    on the connection).
+
+  o Seperated the Entry class into two classes:  GenericEntry and
+    Entry, found in Entry.py.  GenericEntry holds all of the Entry
+    related methods and data for communicating with its Connection
+    object; Entry has the Zope interface and implements the tree
+    protocol (tpValues, tpId, tpURL) and the objectXXX protocol
+    (objectValues, objectItems, objectIds).  These calls access the
+    Entry's sub-entry objects.
+
+  o Try to ensure some integrity with Zope transactions.  Since LDAP
+    has no concept of transactions, this is not ideal, but it's a step 
+    up from having no transaction support at all.
+
diff --git a/product/ZLDAPConnection/Entry.py b/product/ZLDAPConnection/Entry.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad642f4f3630594c9e970e40c3e99593f263311a
--- /dev/null
+++ b/product/ZLDAPConnection/Entry.py
@@ -0,0 +1,556 @@
+"""\
+LDAP Entry Objects
+"""
+
+
+__version__ = "$Revision: 1.13 $"[11:-2]
+
+import Acquisition, AccessControl, OFS, string
+from Globals import HTMLFile, MessageDialog; import Globals
+import ldap, urllib, UserList
+
+ConnectionError='ZLDAP Connection Error'
+
+def isNotBlank(s):
+    #test for non-blank strings
+    if type(s) is type('a') and s=='':
+        return 0
+    else: return 1
+
+class AttrWrap(UserList.UserList):
+    """simple attr-wrapper for LDAP attributes"""
+    def __str__(self):
+        return string.join(self.data,', ')
+
+class GenericEntry(Acquisition.Implicit):
+    """\
+    The GenericEntry class holds all the LDAP-Entry specific information.
+    """
+    __ac_permissions__ = (
+        ('Access contents information',
+         ('get',), ('Anonymous',),),
+        ('Manage Entry information',
+         ('set', 'setattrs', 'setAll','remove',),),
+        ('Create New Entry Objects',
+         ('addSubentry',),),
+        ('Delete Entry Objects',
+         ('deleteSubentry',),),
+        )
+
+    __name__ = "GenericEntry"
+
+    def __init__(self, dn, attrs=None, connection=None, isNew=0):
+        self.id = ldap.explode_dn(dn)[0] # Split the DN into a list.
+        self.dn = dn                    # Our actually unique ID in tree
+        self.__connection = None
+
+        if attrs is None and connection is not None:
+            # We have no passed in attributes, but we do have a connection
+            # to get them from.
+            self._init(connection)
+        elif attrs and connection is not None:
+            # Attributes were passed in, so we don't need to go to our
+            # connection to retrieve them
+            self._data = attrs
+            self.__connection = connection
+        else:
+            # We're totally blank and disconnected
+            self._data = {}
+
+        self._isNew = isNew
+        if isNew:
+            pass                        # XXX need to handle creation here
+        self._isDeleted = 0             # Deletion flag
+        self.__subentries = {}          # subentries
+        self._mod_delete = []
+
+
+    def _init(self, connection):
+        self.__connection = connection
+        if not self._isNew:
+            self._data = connection.getAttributes(self.dn)
+        else:
+            self._data = {}
+
+    def _reset(self):
+        if self._isNew: self._data = {}
+        else: self._data = self._connection().getAttributes(self.dn)
+
+    def __repr__(self):
+        r="<Entry instance at %s; %s>" % (id(self), self.dn)
+        return r
+
+    def _connection(self):
+        if self.__connection is None:
+            raise ConnectionError, 'No connection object for this entry'
+        else:
+            return self.__connection
+
+    #### Subentry and Attribute Access Machinery ##########
+    def __getitem__(self, key):
+        """getitem is used to get sub-entries, not attributes"""
+        if self.__subentries:
+            se=self._subentries()
+            if se.has_key(key):
+                return se[key]
+        key = '%s, %s' % (urllib.unquote(key), self.dn)
+        conn= self._connection()
+        if conn.hasEntry(key):
+            return conn.getEntry(key, self)
+        else:
+            raise IndexError, key
+
+    def __getattr__(self, attr):
+        if self._data.has_key(attr):
+            return AttrWrap(self._data[attr])
+        else:
+            raise AttributeError, attr
+
+    #### Direct access for setting/getting/unsetting attributes
+    def get(self, attr):
+        if self._data.has_key(attr):
+            return self._data[attr]
+        else:
+            raise AttributeError, attr    
+
+    def set(self, key, value):
+        """ Sets individual items """
+        self.setattrs({ key:value })
+
+    def setattrs(self, kwdict={}, **kw):
+        """ Sets one or more attributes on the entry object, taking in
+        both a dictionary AND\OR keywork arguments """
+        kwdict.update(kw)
+        data = self._data
+        for attr, value in kwdict.items():
+            if type(value) is type(''):
+                value = [value]
+            data[attr] = value
+
+        self._modify()
+
+    def setAll(self, kwdict={}, **kw):
+        """ The dictionary\keywords passed in become ALL of the new
+        attributes for the Entry (old data is lost) """
+        kwdict.update(kw)               # Merge passed in dict with keywords
+        self._data = {}                 # Clear our Entry attributes
+        self.setattrs(kwdict)           # And call self.setattrs to do the work
+
+    def remove(self, attr):
+        """ Remove the attribute\attribute list """
+        if type(attr) is type(''):
+            attr = (attr,)
+
+        data = self._data
+        mod_d = self._mod_delete
+        for item in attr:
+            if data.has_key(item):
+                del data[attr]
+                mod_d.append(attr)
+
+        self._modify()                  # Send the changes to LDAP
+
+    ### These methods actually change the object.  In the Generic Model,
+    ### a .set calls this directly, while in the TransactionalModel this
+    ### gets called by the Transaction system at commit time.
+    def _modify(self):
+	modlist = []
+
+	for attribute, values in self._data.items():
+	    modlist.append((ldap.MOD_REPLACE, attribute, values))
+        for attribute in self._mod_delete:
+            modlist.append((ldap.MOD_DELETE, attribute, None))
+
+	self._connection()._modifyEntry(self.dn, modlist)
+        self._mod_delete=[]
+        self.__subentries={}
+
+    #### Get the ZLDAPConnection object.
+    def _connection(self):
+        if self.__connection is None: 
+            raise ConnectionError, 'Cannot Get Connection'
+        else:
+            return self.__connection
+
+    def _setConnection(self, connection):
+        self.__connection = connection
+
+    ### Subentries
+    def _subentries(self):
+        if not self.__subentries:
+            # self.__subentries is empty, look up our subentries
+            # in the connection object and then set self.__subentries
+            r = {}
+            se = self._connection().getSubEntries(self.dn,self)
+            for subentry in se:
+                r[subentry.id] = subentry
+            self.__subentries = r
+            
+        return self.__subentries
+
+    def _clearSubentries(self):
+        self.__subentries = {}
+
+    def _setSubentry(self, entryid, entry):
+        self.__subentries[entryid] = entry
+
+    def _delSubentry(self, entryid):
+        subs = self.__subentries
+        if subs.has_key(entryid): del self.__subentries[entryid]
+
+    ### Deleting Subentries
+    def _beforeDelete(self, **ignored):
+        """ Go through all the subentries and delete them too """
+        conn = self._connection()
+        for entry in self._subentries().values():
+            entry._beforeDelete()
+            conn._deleteEntry(entry.dn) # Delete from the server
+            self._delSubentry(entry.id) # Delete our own reference
+
+    def _delete(self, entry):
+        conn = self._connection()
+
+        entry._beforeDelete()
+        conn._deleteEntry(entry.dn)
+        entry._isDeleted = 1
+        self._delSubentry(entry.id)
+
+    def _delete_dn(self, rdn):
+        """ Delete by relative dn, ( - entry._delete_dn('sn=Shell') - ) """
+        entry = self[rdn]               # Get the subentry
+        self._delete(entry)
+
+    ### Adding subentries
+    def addSubentry(self, rdn, attrs={}, **kw):
+        """ Create a new subentry of myself """
+        conn = self._connection()
+        nkw = {}
+        nkw.update(attrs); nkw.update(kw)
+        attrs = nkw
+
+        # Create the full new DN (Distinguished Name) for the new subentry
+        # and verify that it doesn't already exist
+        dn = "%s,%s" % (string.strip(rdn), self.dn)
+        if conn.hasEntry(dn):           # Check the LDAP server directly
+            raise KeyError, "DN '%s' already exists" % dn
+
+        # Now split out the first attr based on the RDN (ie 'cn=bob') and
+        # turn it into one of our attributes (ie attr[cn] = 'bob')
+        key, value = map(string.strip, string.split(rdn,'='))
+        attrs[key] = value
+
+        # If the objectclass is not already set in the attrs, set it now
+        if not attrs.has_key('objectclass'):
+            attrs['objectclass'] = ['top']
+
+        # Instantiate the instance based on the connections EntryFactory
+        Entry = conn._EntryFactory()
+        entry = Entry(dn, attrs, conn, isNew=1).__of__(self)
+        conn._addEntry(dn, attrs.items()) # Physically add the new entry
+        self._setSubentry(entry.id, entry)
+
+        return entry
+
+    ### Public API for deleting subentries
+    def deleteSubentry(self, entry):
+        """ Delete a subentry (may be specified either by an rdn (string)
+        or an Entry object instance """
+        if type(entry) is type(''):
+            self._delete_dn(entry)      # Delete by the RDN ('cn=...')
+        else:
+            self._delete(entry)         # Delete by Entry object itself
+        
+        
+class TransactionalEntry(GenericEntry): #Acquisition.Implicit
+    """\
+    The TransactionalEntry class holds all the LDAP-Entry specific information,
+    registers itself with the transaction manager, etc.  It's faceless.
+    All Zope UI/Management methods will be implemented in the Entry class.
+    """
+    __name__ = "TransactionalEntry"
+
+    __ac_permissions__ = (
+        ('Manage Entry information',
+         ('undelete', 'setattrs','remove'),),
+        ('Create New Entry Objects',
+         ('addSubentry',),),
+        )
+    
+    _registered=None            #denotes if we've registered with the
+                                #transaction manager
+
+    def __init__(self, dn, attrs=None, connection=None, isNew=0):
+        self.id=ldap.explode_dn(dn)[0]  #split the DN into a list.
+        self.dn=dn                      #Our actually unique ID in tree
+        self._p_jar=None                #actually, the connection
+        self._setConnection(None)
+
+        if attrs is None and connection is not None:
+            self._init(connection)
+        elif attrs and connection is not None:
+            self._data=attrs
+            self._p_jar=connection
+            self._setConnection(connection)
+        else:
+            self._data={}
+        self._isNew=isNew
+        if isNew:
+            get_transaction().register(self)
+            self._registered=1
+        self._isDeleted=0               #deletion flag
+        self._clearSubentries()
+        self._mod_delete=[]
+
+    # We override _set here because we will be physically updated by
+    # the transaction manager (we don't call self._modify(), the transaction
+    # machinery will)
+    def setattrs(self, kwdict={}, **kw):
+        """\
+        Set attributes in self._data and register ourselves with the
+        transaction machinery.  Data is not committed to LDAP when this
+        is called.
+        """ 
+        if not self._registered:
+            get_transaction().register(self)
+            self._registered=1
+            
+        kwdict.update(kw)
+        data = self._data
+        for attr, value in kwdict.items():
+            if type(value) is type(''):
+                value = [value]
+            data[attr] = value
+
+
+    # We override _remove (previously '_unSet') here because we don't call
+    # self._modify() (the transaction manager will)
+    def remove(self, attr):
+        """\
+        Unset (delete) an attribute
+        """
+        if not self._registered:
+            get_transaction().register(self)
+            self._registered=1
+
+        if type(attr) is type(''):
+            attr = (attr,)
+
+        data = self._data
+        mod_d = self._mod_delete
+        for item in attr:
+            if data.has_key(item):
+                del data[item]
+                mod_d.append(item)
+
+
+    ### Transaction Related methods
+    def _reset(self):
+        self._rollback()
+
+    def _rollback(self):
+        conn=self._connection()
+        if not self._isNew:
+            self._data=conn.getAttributes(self.dn)
+            self._clear_subentries={}
+        else:
+            self._data={}
+
+    ### Adding and Deleting sub-entries.
+    def _beforeDelete(self, **ignored):
+        c=self._connection()
+        for entry in self._subentries().values():
+            entry.manage_beforeDelete()
+            c._registerDelete(entry.dn)
+            entry._isDeleted=1
+            del self._subentries()[entry.id]
+
+    def _delete(self, o):
+        c=self._connection()
+        o._beforeDelete()
+        c._registerDelete(o.dn)
+        o._isDeleted=1
+        if not o._registered:
+            get_transaction().register(o)
+            o._registered=1
+        del self._subentries()[o.id]
+
+    def _delete_dn(self, rdn):
+        o=self[rdn]
+        self._delete(o)
+
+    def undelete(self):
+        '''undelete myself'''
+##        c=self._connection()
+##        c._unregisterDelete(self.dn)
+        self._isDeleted=0
+        
+    def addSubentry(self, rdn, attrs={}, **kw):
+        ''' create a new subentry of myself '''
+        c=self._connection()
+        nkw = {}
+        nkw.update(attrs); nkw.update(kw)
+        attrs = nkw
+        
+        #create the new full DN for new subentry and check its existance
+        dn='%s,%s' % (string.strip(rdn), self.dn)
+        if c.hasEntry(dn):
+            raise KeyError, "DN '%s' already exists" % dn
+
+        # now split out the first attr based on the rdn (ie 'cn=bob', turns
+        # into attr['cn'] = 'bob'
+        key, value = map(string.strip,string.split(rdn,'='))
+        attrs[key] = value
+
+        #if objectclass is not set in the attrs, set it now
+        if not attrs.has_key('objectclass'):
+            attrs['objectclass']=['top']
+
+        #instantiate the instance based on current instances class
+        #and register it to be added at commit time
+        Entry = c._EntryFactory()
+        entry = Entry(dn,attrs,c,isNew=1).__of__(self)
+        c._registerAdd(entry)           # Register new Entry (added by TM)
+        self._setSubentry(entry.id, entry)
+        
+        return entry
+        
+
+
+class ZopeEntry(OFS.SimpleItem.Item):
+    '''Entry Object'''
+
+    #### Initialazation Routines ##############
+
+    manage_options=(
+	{'label':'Attributes','action':'manage_attributes'},
+	)
+    
+    __ac_permissions__=(
+        ('Access contents information', ('manage_attributes',),
+         ('Manager','Anonymous',),),
+        ('Manage Entry information', ('manage_changeAttributes',
+                                      'manage_addAttribute',
+                                      'manage_editAttributes',),
+         ('Manager',),),
+        ('Create New Entry Objects',
+         ('manage_newEntry', 'manage_newEntryWithAttributes'),
+         ('Manager',),),
+        )
+    
+    manage_attributes=HTMLFile("attributes",globals())
+    manage_main=manage_attributes
+    isPrincipiaFolderish=1
+    
+
+    #### Entry & Attribute Access Machinery #####################
+
+    def attributesMap(self):
+	return self._data.items()
+
+    def __bobo_traverse__(self, REQUEST, key):
+        ' allow traversal to subentries '
+        key=urllib.unquote(key)
+        if key in self.objectIds(): return self[key]
+        else: return getattr(self,key)
+
+    ###### Tree Machinery ######
+
+    def tpValues(self):
+	return self._subentries().values()
+
+    def tpId(self):
+	return self.id
+
+    def tpURL(self):
+	"""Return string to be used as URL relative to parent."""
+	return urllib.quote(self.id)
+    
+    ### Object Manager-ish Machinery
+    def objectValues(self):
+        return self._subentries().values()
+
+    def objectIds(self):
+        return self._subentries().keys()
+
+    def objectItems(self):
+        return self._subentries().items()
+
+    ### Zope management stuff
+
+    def manage_deleteEntry(self, ids, REQUEST=None):
+	'''Delete marked Entries and all their sub-entries.'''
+
+	for rdn in ids:
+            self._delete_dn(rdn)
+
+	if REQUEST is not None:
+	    return self.manage_contents(self, REQUEST)
+
+
+    def manage_newEntry(self, rdn, REQUEST=None):
+	'''Add a new entry'''
+
+        e = self.addSubentry(rdn)
+
+	if REQUEST is not None:
+	    return self.manage_contents(self, REQUEST)
+        else:
+            return e
+
+    def manage_newEntryWithAttributes(self, rdn, attributes={}, **kw):
+        """ add a new entry with attributes """
+        attributes.update(kw)           # merge the keyword args in
+        e = self.addSubentry(rdn, attributes)
+        return e                        # return the new entry
+
+    def manage_addAttribute(self, id, values, REQUEST=None):
+	'''Add an attribute to an LDAP entry'''
+	
+        self.set(id, values)
+
+	if REQUEST is not None:
+	    return self.manage_attributes(self, REQUEST) 
+
+
+    def manage_editAttributes(self, REQUEST):
+        """Edit entry's attributes via the web."""
+	
+        for attribute in self._data.keys():
+	    values = REQUEST.get(attribute, [])
+            values = filter(isNotBlank, values)   #strip out blanks
+
+            self.set(attribute, values)
+
+        return MessageDialog(
+               title  ='Success!',
+               message='Your changes have been saved',
+               action ='manage_attributes')
+
+    def manage_changeAttributes(self, REQUEST=None,  **kw):
+        """Change existing Entry's Attributes.
+
+        Change entry's attributes by passing either a mapping object
+        of name:value pairs {'foo':6} or passing name=value parameters
+        """
+
+        if REQUEST and not kw:
+            kw=REQUEST
+
+        datakeys = self._data.keys()
+
+        if kw:
+            for name, value in kw.items():
+                if name in datakeys:
+                    self.set(name, value)
+    
+        if REQUEST is not None:
+            return MessageDialog(
+                title  ='Success!',
+                message='Your changes have been saved',
+                action ='manage_propertiesForm')
+
+
+import Globals
+for klass in (GenericEntry, TransactionalEntry, ZopeEntry):
+    Globals.default__class_init__(klass)
+
diff --git a/product/ZLDAPConnection/LDAP_conn_icon.gif b/product/ZLDAPConnection/LDAP_conn_icon.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6a5a0b98033441a55a8b018d22cbfe53365669c5
Binary files /dev/null and b/product/ZLDAPConnection/LDAP_conn_icon.gif differ
diff --git a/product/ZLDAPConnection/LDCAccessors.py b/product/ZLDAPConnection/LDCAccessors.py
new file mode 100644
index 0000000000000000000000000000000000000000..e44496819d368004931ae2a99bc241a441c06834
--- /dev/null
+++ b/product/ZLDAPConnection/LDCAccessors.py
@@ -0,0 +1,105 @@
+
+__version__="$Revision: 1.3 $"[11:-2]
+
+class LDAPConnectionAccessors:
+    """ getters / setters for LDAP Properties """
+
+    __ac_permissions__ = (
+        ('Access contents information',
+         ('getId','getTitle','getHost','getPort','getBindAs','getBoundAs',
+          'getPW','getDN','getOpenConnection','getBrowsable',
+          'shouldBeOpen','getTransactional',),),
+        ('Manage properties',
+         ('setID','setTitle','setHost','setPort', 'setBindAs','setPW',
+          'setDN','setOpenConnection','setBrowsable','setBoundAs',
+          'setTransactional',),),
+        )
+
+    def getId(self):
+        return self.id
+
+    def setId(self, id):
+        self.id = id
+
+    def getTitle(self):
+        return self.title
+
+    def setTitle(self, title):
+        self.title = title
+
+    def getHost(self):
+        """ returns the host that this connection is connected to """
+        return self.host
+
+    def setHost(self, host):
+        self.host = host
+
+    def getPort(self):
+        """ returns the port on the host that this connection connects to """
+        return self.port
+
+    def setPort(self, port):
+        self.port = port
+
+    def getBindAs(self):
+        """ return the DN that this connection is bound as """
+        return self.bind_as
+    getBoundAs = getBindAs
+
+    def setBindAs(self, bindAs):
+        self.bind_as = bindAs
+    setBoundAs = setBindAs
+
+    def getPW(self):
+        """ return the password that this connection is connected with """
+        return self.pw
+
+    def setPW(self, pw):
+        self.pw = pw
+
+    def getDN(self):
+        return self.dn
+
+    def setDN(self, dn):
+        self.dn = dn
+
+    def getOpenConnection(self):
+        """ self.openc means that the connection is open to Zope.  However,
+        the connection to the LDAP server may or may not be opened.  If
+        this returns false, we shouldn't even try connecting."""
+        return self.openc
+
+    def setOpenConnection(self, openc):
+        self._v_openc = openc
+
+    shouldBeOpen = getOpenConnection
+
+    
+    def getBrowsable(self):
+        """ if true, connection object is set to be browsable through the
+        management interface """
+        return getattr(self, '_canBrowse', 0)
+
+    def setBrowsable(self, browsable):
+        self._canBrowse = browsable
+
+    def getTransactional(self):
+        """ If transactional returns TRUE, the TransactionManager stuff
+        is used.  If FALSE, changes are sent to LDAP immediately. """
+        # Default to '1', to emulate the original behavior
+        return getattr(self, 'isTransactional', 1)
+
+
+    def setTransactional(self, transactional=1):
+        self.isTransactional = transactional
+        self._refreshEntryClass()
+        # We have a fair amount of transaction-sensitive methods that
+        # only want to run during a commit, and these are the ones that
+        # actually send the data to the LDAP server.  When in non-transactional
+        # mode, we want these things to run at any time.  In a sense, we're
+        # always committing.
+        if not transactional:
+            self._isCommitting = 1
+        
+import Globals
+Globals.default__class_init__(LDAPConnectionAccessors)
diff --git a/product/ZLDAPConnection/PROGRAMMING.txt b/product/ZLDAPConnection/PROGRAMMING.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6ad36f1a94421aec62277195968bef19b9eb8a3b
--- /dev/null
+++ b/product/ZLDAPConnection/PROGRAMMING.txt
@@ -0,0 +1,153 @@
+Programming ZopeLDAP Entry Objects
+
+ Author -- "Jeffrey P Shell", mailto:jeffrey@digicool.com
+
+ Date -- Dec 18, 2000
+
+ Revision -- For ZopeLDAP 1.1.0
+
+ 1. Getting an Entry Object and its attributes
+
+  Using an LDAP Filter object is the best way to get an Entry object.
+  LDAP Filter objects act like methods\functions and return a sequence 
+  of entry objects (so they can be used with any sort of looping
+  structure).  For example, if you had an LDAP Filter titled
+  'lookupByEmail' with the parameter 'email' with the content::
+
+       mail=<dtml-var name="mail">*
+
+  Can be used in DTML like this::
+
+       <dtml-in expr="lookupByEmail(email='jef')">
+        Surname: <dtml-var name="sn"><br />
+        Common name: <dtml-var name="cn"><br /><br />
+       </dtml-in>
+
+  Or in a Python Script like this::
+
+       entries = container.lookupByEmail(email='jef')
+
+       for entry in entries:
+        # do something here...
+
+  1.1. Notes about Attributes and Entry Objects
+
+   By default, LDAP always returns its attributes as sequences (Python 
+   lists), even if there is only one value.  ZopeLDAP Entry objects
+   use a special class, *AttrWrap*, when returning attributes accessed 
+   through normal __getattr__ (the a.b syntax).  AttrWrap behaves and
+   acts like a normal Python list with the exception that when printed 
+   as a string (ie, with 'dtml-var' or Python 'str()' or '"%s"'), it
+   printes the results as a comma seperated list.  This makes DTML
+   representations of Entry object significantly easier.  When using
+   the Entry method 'get()', the attribute is returned as a Python
+   list as returned by PythonLDAP.  But if you're wanting to do tests
+   based on attributes, you have to *remember to qualify the
+   attribute*, ie::
+
+     if entry.sn[0] == 'Shell':   # Works
+
+     if entry.sn == 'Shell':      # Won't work
+
+   And also remember that 'getattr' based access returns *AttrWrap*
+   instances, which means the following::
+
+    if entry.get('sn') == ['Shell']:   # Works
+
+    if entry.sn == ['Shell']:          # Won't work
+
+  2. Changing attribute values
+
+   Changing attributes on Entry objects is protected by the permission 
+   **Manage Entry Information**.  This permission can be set anywhere
+   in Zope, and will work best when set in the folder containing the
+   LDAP Filter used to access the Entry object.  The methods exposed
+   under this permission are:
+
+    **set(key, value)** -- example:  'entry.set("sn", "Shell")',
+    'entry.set("mail", ["foo@bar.com", "foo@baz.net"])'
+
+    **setattrs([kwdict, kw])** -- Takes either a mapping argument or
+    keyword arguments.  Or both.  Example:
+    'entry.setattrs({"sn": "Bazzo"}, mail=["foo@bar.com,
+    "foo@baz.net"])'
+
+    **setAll([kwdict, kw])** -- Same as *setattrs*, except that the
+    existing data is *replaced* by what's passed in here.
+
+    **remove(attr)** -- Deletes the attribute, example:
+    'entry.remove("comments")'
+
+   3. Accessing subentries
+
+    Attributes on Entry objects are available through the Python
+    'getattr' protocol, and subentries are available through the
+    'getitem' protocol via their Relative Distinguished Name (RDN).
+
+    Meaning if you had an Organizational Unit entry for 'Housewares'
+    and wanted to get the subentry '"Betty Ford"', it would appear like
+    this (considering 'housewares' is the Entry object)::
+
+      betty = housewares["cn=Betty Ford"]
+
+   3.1. Adding subentries
+
+    Adding subentries to an Entry object is done through the
+    'addSubentry' method.  Adding subentries is protected by the
+    permission **Create New Entry Objects**.
+
+    **addSubentry(rdn, [attrs,kw])** -- Add a new subentry identified by
+    the rdn.  The rdn **MUST** be a string in the form 'attr=value',
+    such as 'ou=Housewares' or 'cn=Davy Jones'.  The rest of the
+    signature can be a combination of a dictionary of attributes
+    passed in to 'attrs' or keyword arguments.  If no 'objectClass'
+    attribute is passed in, the default objectClass is 'top'.  The
+    newly created Entry object is returned, wrapped in the acquisition 
+    context of its parent.  Some example uses are::
+
+      betty = housewares.addSubentry("cn=Betty Ford", {
+                "objectClass": ["top", "person"],
+		"sn": "Ford",
+	      })
+
+      clinic = betty.addSubentry("sn=Clinic",
+                         objectClass=["top","place"],
+			 description="A good place to go..."
+	       )
+
+   3.2. Deleting subentries
+
+    Deleting subentries from an Entry object is done through the
+    'deleteSubentry' method.  Deleting subentries is protected by the
+    permission **Delete Entry Objects**.
+
+    **deleteSubentry(entry)** -- You can either delete a subentry by
+    its 'rdn', or by passing in the subentry object itself.  Deletion
+    is recursive and will delete everything below the specified entry
+    from the LDAP server as well as the Entry object itself.  Some
+    examples uses are::
+
+     betty.deleteSubentry("sn=Clinic")  # Delete by rdn (local id)
+
+     housewares.deleteSubentry(betty)   # Delete by entry itself
+
+ 4. Other Zope Interfaces supported
+
+  Entry objects are a combination of classes:  The basic Entry class
+  (either GenericEntry or TransactionalEntry at present), and one
+  called Entry, which implements some Zope level Interfaces (of the
+  programming kind).  These include::
+
+    - '__bobo_traverse__' -- To traverse to subentries along the URL.
+
+    - The Tree Protocol -- Allows Entry objects to be used
+      automatically with the 'dtml-tree' tag.
+
+    - 'objectValues()' -- Returns all of the subentries in a list.
+
+    - 'objectIds()' -- returns all of the RDN's of the subentries.
+
+    - 'objectItems()' -- returns a list of tuples in the form of
+      ('rdn', 'entry object').
+
+    
\ No newline at end of file
diff --git a/product/ZLDAPConnection/README.txt b/product/ZLDAPConnection/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..40e0cba62dfea00f922ebc17f1dd4f54911e6b6f
--- /dev/null
+++ b/product/ZLDAPConnection/README.txt
@@ -0,0 +1,97 @@
+ZopeLDAP README
+
+ ZopeLDAP is based off of work done by Anthony Baxter and also
+ Maurice Davice and Scott Robertson at CodeIt.  It is an attempt to
+ make LDAP behave more like Zope objects.
+
+ It needs David Leonard's ldapmodule, from http://python-ldap.sourceforge.net/
+ and the compiled module needs to be (naturally) in your PYTHONPATH
+ (or in ZLDAPConnection/).
+
+ It's been tested against the OpenLDAP stable server release, as at 
+ March 14, 1999 and Nov 2, 1999.
+
+IMPORTANT
+
+ There is a known bug in the transactional behavior of the LDAP
+ Connection object, and as of 1.1.0 this feature can be turned off.
+ The bug could put your ZODB into a nasty state due to a failed
+ transaction (usually fixed by just restarting Zope), so it is
+ recommended you run with the Transactional ability turned off.  *This 
+ bug only occurs when updating more than one Entry object in a single
+ transaction space*.
+
+Features
+
+ o Ability to browse an LDAP database like you would browse normal
+   folders.
+ 
+ o In 1.1.0, however, the Transactional behavior may be turned off.
+   This could speed things up for read-only situations, and is more
+   stable than the transactional one.
+
+ o Entry objects obey the rules of Acquisition.
+
+ o In the Zope management interface, LDAPConnections and their
+   Entries may be browsed.
+
+ o LDAP Filters provide another way of accessing Entry
+   objects.  They behave in a similar fashion to ZSQL Methods, but
+   they are *read-only*.  There is no current LDAP Spec for
+   update/insert type queries.  *see Caveats below*
+
+ o Improved Entry object API that is Python Script friendly.  For
+   updating\adding\deleting new Entry objects, LDAP Filters (to
+   retrieve entries) and Python Scripts (to update) go nicely together.
+
+
+Caveats
+
+ o Lack of stunning documentation.  
+
+ o The only way to strongly protect Entry objects from being written is
+   to use a connection name/password to the LDAP Server that does not
+   have any write permissions.  Zope security permissions can also be
+   used.
+
+ o It currently only supports simple_bind for connecting to the
+   server.
+
+ o All Entry attributes come back in the form of a list of strings.
+   This is how the LDAP Module (and presumably LDAP in general) does
+   this.  Attributes accessed through __getattr__ (like dtml-var
+   accesses) come back as an instance of AttrWrap which subclasses
+   UserList and whose str() return is a comma seperated list.  (This
+   should prevent needing to do 
+   '(dtml-in mail)(dtml-var sequence-item)(/dtml-in)' on every
+   attribute, especially where one value is expected.
+
+Known Bugs
+
+ o Transactional Behavior breaks when updating more than one Entry
+   object per LDAP Connection in a single transaction.  This behavior
+   can put the ZODB into a bad state since it fails during the
+   two-phase commit, however restarting Zope tends to return things to 
+   normal.
+
+
+Special Thanks
+
+ o Jens Vagelpohl (jens@digicool.com) for getting the pointy-hairs to
+   give me time to make 1.1.0 finally happen.
+
+ o Anthony Baxter (anthony@interlink.com.au) for most of the original work
+
+ o Scott Robertson (sropertson@codeit.com) and Maurice Davice
+   (mdavis@codeit.com) for theirs too.
+
+ o David Leonard for his LDAP Module and for keeping it pretty much
+   in alignment with the RFC (rfc1823).
+
+Author:
+  Jeffrey P Shell (jeffrey@Digicool.com)
+
+Original Authors:
+  Anthony Baxter (anthony@interlink.com.au)
+  Maurice Davice (mdavis@codeit.com)
+  Scott Robertson (srobertson@codeit.com)
diff --git a/product/ZLDAPConnection/ZLDAP.py b/product/ZLDAPConnection/ZLDAP.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfb453325d21883721988bab3e57b8b06f337aae
--- /dev/null
+++ b/product/ZLDAPConnection/ZLDAP.py
@@ -0,0 +1,502 @@
+""" 
+   An LDAP connection product.  Depends on David Leonard's ldapmodule.
+   $Id: ZLDAP.py,v 1.11 2000/12/18 22:17:50 jeffrey Exp $
+   Started by Anthony Baxter, <anthony@interlink.com.au>
+   Continued by the folks @ CodeIt <http://www.codeit.com/>
+
+   Now by Jeffrey P Shell <jeffrey@Digicool.com>.
+"""
+
+__version__ = "$Revision: 1.11 $"[11:-2]
+
+import Acquisition, AccessControl, OFS, string
+from Globals import HTMLFile, MessageDialog, Persistent
+import ldap, urllib
+
+import LDCAccessors
+from Entry import ZopeEntry, GenericEntry, TransactionalEntry
+ConnectionError='ZLDAP Connection Error'
+
+manage_addZLDAPConnectionForm = HTMLFile('add', globals())
+
+class NoBrainer:
+    """ Empty class for mixin to EntryFactory """
+
+class ZLDAPConnection(
+    Acquisition.Implicit,
+    Persistent, OFS.SimpleItem.Item,
+    LDCAccessors.LDAPConnectionAccessors,
+    AccessControl.Role.RoleManager):
+    '''LDAP Connection Object'''
+
+    isPrincipiaFolderish=1
+
+    meta_type='LDAP Connection'
+
+    manage_options=(
+        {'label':'Connection Properties','action':'manage_main'},
+        {'label':'Open/Close','action':'manage_connection'},
+        {'label':'Browse', 'action':'manage_browse'},
+        {'label':'Security','action':'manage_access'},
+        )
+
+    __ac_permissions__=(
+        ('Access contents information',
+         ('canBrowse',),),
+        ('View management screens',('manage_tabs','manage_main'),
+         ('Manager',)),
+        ('Edit connection',('manage_edit',),('Manager',)),
+        ('Change permissions',('manage_access',)),
+        ('Open/Close Connection',('manage_connection',
+                                  'manage_open','manage_close',),
+         ('Manager',)),
+        ('Browse Connection Entries', ('manage_browse',),('Manager',),),
+        )
+
+    manage_browse=HTMLFile('browse',globals())
+    manage_connection=HTMLFile('connection',globals())
+
+    ### dealing with browseability on the root.
+    def canBrowse(self):
+        """ Returns true if the connection is open *and* the '_canBrowse'
+        property is set to true """
+        return self.shouldBeOpen() and self.getBrowsable()
+
+
+    ### constructor
+    def __init__(self, id, title, host, port, basedn, bind_as, pw, openc,
+                 transactional=1):
+        "init method"
+        self._v_conn = None
+        self._v_delete = []
+        self._v_openc = openc
+        self.openc = openc
+        self.setId(id)
+        self.setTitle(title)
+        self.setHost(host)
+        self.setPort(port)
+        self.setBindAs(bind_as)
+        self.setPW(pw)
+        self.setDN(basedn)
+        self.setOpenConnection(openc)
+        self.setTransactional(transactional)
+
+        # if connection is specified to be open, open it up
+        if openc: self._open()
+
+    ### upgrade path...
+    def __setstate__(self, state):
+        # Makes sure we have an Entry class now
+        self._refreshEntryClass()
+        Persistent.__setstate__(self, state)
+
+    ### Entry Factory stuff
+    def _refreshEntryClass(self):
+        """ This class sets up the Entry class used to return results. """
+        transactional = self.getTransactional()
+        if transactional:
+            EntryBase = TransactionalEntry
+        else:
+            EntryBase = GenericEntry
+
+        class LdapEntry(EntryBase, ZopeEntry):
+            pass
+
+        self._v_entryclass = LdapEntry
+        return LdapEntry
+
+    def _EntryFactory(self):
+        """ Stamps out an Entry class to be used for every entry returned,
+        taking into account transactional versus non-transactional """
+        return getattr(self, '_v_entryclass', self._refreshEntryClass())
+
+    ### Tree stuff
+    def __bobo_traverse__(self, REQUEST, key):
+        key=urllib.unquote(key)
+        if hasattr(self, key):
+            return getattr(self, key)
+        return self.getRoot()[key]
+
+    def tpId(self):
+        return self.id
+
+    def tpURL(self):
+        return self.id
+
+    def tpValues(self):
+        if self.canBrowse():
+            return self.getRoot().tpValues()
+        else:
+            return []
+
+    #### TransactionalObjectManager stuff #####
+    def tpc_begin(self,*ignored):
+        #make sure we're open!
+        if not self.__ping():      #we're not open
+            raise (ConnectionError,
+                   'LDAP Connection is not open for commiting')
+        self._v_okobjects=[]
+
+    def commit(self, o, *ignored):
+        ' o = object to commit '
+        # check to see if object exists
+        oko=[]
+        if self.hasEntry(o.dn):
+            oko.append(o)
+        elif o._isNew or o._isDeleted:
+            oko.append(o)
+        self._v_okobjects=oko
+        
+    def tpc_finish(self, *ignored):
+        " really really commit and DON'T FAIL "
+        oko=self._v_okobjects
+        self._isCommitting=1
+        d=getattr(self,'_v_delete',[])
+
+        for deldn in d: self._deleteEntry(deldn)
+        self._v_delete=[]
+
+        for o in oko:
+            try:
+                if o._isDeleted:
+                    pass
+                    # we shouldn't need to do anything now that
+                    # the mass delete has happened
+                elif o._isNew:
+                    self._addEntry(o.dn, o._data.items())
+                    o._isNew=0
+                    del self._v_add[o.dn]
+                else:
+                    o._modify()
+                o._registered=0
+            except:
+                pass    #XXX We should log errors here
+
+        del self._v_okobjects
+        del self._isCommitting
+        self.GetConnection().destroy_cache()
+
+    def tpc_abort(self, *ignored):
+        " really really rollback and DON'T FAIL "
+        try:
+            self._abort()
+        except:
+            pass        #XXX We should also log errors here
+
+    def abort(self, o, *ignored):
+        if o.dn in getattr(self,'_v_delete',()):
+            self._v_delete.remove(o.dn)
+        if o._isDeleted: o.undelete()
+        o._rollback()
+        o._registered=0
+        if o._isNew:
+            if o.dn in getattr(self,'_v_add',{}).keys():
+                del self._v_add[o.dn]
+        self.GetConnection().destroy_cache()
+        
+    def _abort(self):
+        oko=self._v_okobjects
+        for o in oko:
+            self.abort(o)
+        self.GetConnection().destroy_cache()
+            
+    def tpc_vote(self, *ignored):
+        pass
+
+
+    ### getting entries and attributes
+
+    def hasEntry(self, dn):
+        if getattr(self, '_v_add',{}).has_key(dn):
+            #object is marked for adding
+            return 1
+        elif dn in getattr(self,'_v_delete',()):
+            #object is marked for deletion
+            return 0
+
+        try:
+            e=self._connection().search_s(dn, ldap.SCOPE_BASE,
+                                          'objectclass=*')
+            if e: return 1
+        except ldap.NO_SUCH_OBJECT:
+            return 0
+        return 0
+
+    def getRawEntry(self, dn):
+        " return raw entry from LDAP module "
+        if getattr(self, '_v_add',{}).has_key(dn):
+            return (dn, self._v_add[dn]._data)
+        elif dn in getattr(self,'_v_delete',()):
+            raise ldap.NO_SUCH_OBJECT, "Entry '%s' has been deleted" % dn
+
+        try:
+            e=self._connection().search_s(
+                dn, ldap.SCOPE_BASE, 'objectclass=*'
+                )
+            if e: return e[0]
+        except:
+            raise ldap.NO_SUCH_OBJECT, "Cannot retrieve entry '%s'" % dn
+
+
+    def getEntry(self, dn, o=None):
+        " return **unwrapped** Entry object, unless o is specified "
+        Entry = self._EntryFactory()
+
+        if getattr(self, '_v_add',{}).has_key(dn):
+            e=self._v_add[dn]
+        else:
+            e=self.getRawEntry(dn)
+            e=Entry(e[0],e[1],self)
+
+        if o is not None:
+            return e.__of__(o)
+        else:
+            return e
+
+    def getRoot(self):
+        " return root entry object "
+        return self.getEntry(self.dn, self)
+
+    def getAttributes(self, dn):
+        " get raw attributes from entry from LDAP module "
+        return self.getRawEntry(dn)[1]
+
+    ### listing subentries
+
+    def getRawSubEntries(self, dn):
+        " get the raw entry objects of entry dn's immediate children "
+        # XXX Do something soon to account for added but noncommited..?
+        if dn in getattr(self,'_v_delete',()):
+            raise ldap.NO_SUCH_OBJECT
+        results=self._connection().search_s(
+            dn, ldap.SCOPE_ONELEVEL, 'objectclass=*')
+        r=[]
+        for entry in results:
+            #make sure that the subentry isn't marked for deletion
+            if entry[0] not in getattr(self, '_v_delete',()):
+                r.append(entry)
+        return r
+
+    def getSubEntries(self, dn, o=None):
+        Entry = self._EntryFactory()
+        
+        r=[]
+        se=self.getRawSubEntries(dn)
+
+        for entry in se:
+            e=Entry(entry[0],entry[1],self)
+            if o is not None:
+                e=e.__of__(o)
+            r.append(e)
+
+        return r
+
+    ### modifying entries
+    def _modifyEntry(self, dn, modlist):
+        if not getattr(self,'_isCommitting',0):
+            raise AccessError, 'Cannot modify unless in a commit'
+            #someone's trying to be sneaky and modify an object
+            #outside of a commit.  We're not going to allow that!
+        c=self._connection()
+        c.modify_s(dn, modlist)
+        
+    ### deleting entries
+    def _registerDelete(self, dn):
+        " register DN for deletion "
+        d=getattr(self,'_v_delete',[])
+        if dn not in d:
+            d.append(dn)
+        self._v_delete=d
+
+    def _unregisterDelete(self, dn):
+        " unregister DN for deletion "
+        d=getattr(self, '_v_delete',[])
+        if dn in d: d.remove(dn)
+        self._v_delete=d
+
+        self._unregisterAdd(dn)
+
+    def _deleteEntry(self, dn):
+        if not getattr(self, '_isCommitting',0):
+            raise AccessError, 'Cannot delete unless in a commit'
+        c=self._connection()
+        c.delete_s(dn)
+
+    ### adding entries
+    def _registerAdd(self, o):
+        a=getattr(self, '_v_add',{})
+        if not a.has_key(o.dn):
+            a[o.dn]=o
+        self._v_add=a
+
+    def _unregisterAdd(self, o=None, dn=None):
+        a=getattr(self, '_v_add',{})
+        if o and o in a.values():
+            del a[o.dn]
+        elif dn and a.has_key(dn):
+            del a[dn]
+        self._v_add=a
+
+    def _addEntry(self, dn, attrs):
+        if not getattr(self, '_isCommitting',0):
+            raise AccessError, 'Cannot add unless in a commit'
+        c=self._connection()
+        c.add_s(dn, attrs)
+        
+    ### other stuff
+    def title_and_id(self):
+        "title and id, with conn state"
+        s=ZLDAPConnection.inheritedAttribute('title_and_id')(self)
+        if self.shouldBeOpen():
+            s="%s (connected)" % s
+        else:
+            s='%s (<font color="red"> not connected</font>)' % s
+        return s
+
+
+    ### connection checking stuff
+
+    def _connection(self):
+        if self.openc:
+            if not self.isOpen(): self._open()
+            return self._v_conn
+        else:
+            raise ConnectionError, 'Connection Closed'
+
+    GetConnection=_connection
+
+    def isOpen(self):
+        " quickly checks to see if the connection's open "
+        if not hasattr(self, '_v_conn'):
+            self._v_conn = None
+        if self._v_conn is None or not self.shouldBeOpen():
+            return 0
+        else:
+            return 1
+
+    def __ping(self):
+        " more expensive check on the connection and validity of conn "
+        try:
+            self._connection().search_s(self.dn,ldap.SCOPE_BASE,
+                                        'objectclass=*')
+            return 1
+        except:
+            self._close()
+            return 0
+
+    def _open(self):
+        """ open a connection """
+        try:
+            self._close()
+        except:
+            pass
+        self._v_conn = ldap.open(self.host, self.port)
+        #Nicolas the version of pythonldap doesn't use the enable_cache method
+        #self._v_conn.enable_cache()
+        try:
+            self._v_conn.simple_bind_s(self.bind_as, self.pw)
+        except ldap.NO_SUCH_OBJECT:
+            return """
+   Error: LDAP Server returned `no such object' for %s. Possibly 
+   the bind string or password are incorrect"""%(self.bind_as)
+        self._v_openc = 1
+
+    def manage_open(self, REQUEST=None):
+        """ open a connection. """
+        self.setOpenConnection(1)
+        ret = self._open()
+        if not getattr(self, '_v_openc', 0):
+            return ret
+        if REQUEST is not None:
+            m='Connection has been opened.'
+            return self.manage_connection(self,REQUEST,manage_tabs_message=m)
+
+    def _close(self):
+        """ close a connection """
+        if self.getOpenConnection() == 0:
+            #I'm already closed, but someone is still trying to close me
+            self._v_conn = None
+            self._v_openc = 0
+        else:
+            try: self._v_conn.unbind_s()
+            except AttributeError: pass
+            self._v_conn = None
+            self._v_openc = 0
+
+    def manage_close(self, REQUEST=None):
+        """ close a connection. """
+        self._close()
+        if REQUEST is not None:
+            m='Connection has been closed.'
+            return self.manage_connection(self,REQUEST,manage_tabs_message=m)
+
+    def manage_clearcache(self, REQUEST=None):
+        """ clear the cache """
+        self._connection().destroy_cache()
+        if REQUEST is not None:
+            m='Cache has been cleared.'
+            return self.manage_connection(self,REQUEST,manage_tabs_message=m)
+
+
+    manage_main=HTMLFile("edit",globals())
+
+    def manage_edit(self, title, hostport, basedn, bind_as, pw, openc=0,
+                    canBrowse=0, transactional=1, REQUEST=None):
+        """ handle changes to a connection """
+        self.title = title
+        host, port = splitHostPort(hostport)
+        if self.host != host:
+            self._close()
+            self.setHost(host)
+        if self.port != port:
+            self._close()
+            self.setPort(port)
+        if self.bind_as != bind_as:
+            self._close()
+            self.setBindAs(bind_as)
+        if self.pw != pw:
+            self._close()
+            self.setPW(pw)
+        if openc and not self.getOpenConnection():
+            self.setOpenConnection(1)
+            ret = self._open()
+            if not self._v_openc:
+                return ret
+        if not openc and self.getOpenConnection():
+            self.setOpenConnection(0)
+            self._close()
+
+        self.setBrowsable(canBrowse)
+        self.setTransactional(transactional)
+        self.setDN(basedn)
+
+        if REQUEST is not None:
+            return MessageDialog(
+                title='Edited',
+                message='<strong>%s</strong> has been edited.' % self.id,
+                action ='./manage_main',
+                )
+
+    def _isAnLDAPConnection(self):
+        return 1
+    
+def splitHostPort(hostport):
+    import string
+    l = string.split(hostport,':')
+    host = l[0]
+    if len(l) == 1:
+        port = 389
+    else:
+        port = string.atoi(l[1])
+    return host, port
+
+
+def manage_addZLDAPConnection(self, id, title, hostport,
+                              basedn, bind_as, pw, openc,
+                              REQUEST=None):
+    """create an LDAP connection and install it"""
+    host, port = splitHostPort(hostport)
+    conn = ZLDAPConnection(id, title, host, port, basedn, bind_as, pw, openc)
+    self._setObject(id, conn)
+    if REQUEST is not None:
+        return self.manage_main(self, REQUEST)
diff --git a/product/ZLDAPConnection/__init__.py b/product/ZLDAPConnection/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d4a25c04ea3a73a2035429ed2d813b8f9a85bdc
--- /dev/null
+++ b/product/ZLDAPConnection/__init__.py
@@ -0,0 +1,18 @@
+"""LDAP Server Connection Package """
+
+import ZLDAP, Entry
+__version__ = ZLDAP.__version__
+
+
+# use the propert product registration
+def initialize(context):
+    context.registerClass(
+        ZLDAP.ZLDAPConnection,
+        constructors = (ZLDAP.manage_addZLDAPConnectionForm,
+                        ZLDAP.manage_addZLDAPConnection),
+        icon = 'LDAP_conn_icon.gif',
+        permissions = ('Manage Entry information',
+                       'Create New Entry Objects',
+                       ),
+        )
+
diff --git a/product/ZLDAPConnection/add.dtml b/product/ZLDAPConnection/add.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..6c5bc783a5a702ee01cbbc962f18dda1032a527f
--- /dev/null
+++ b/product/ZLDAPConnection/add.dtml
@@ -0,0 +1,75 @@
+<html>
+  <head><title>Add LDAP connection</title></head>
+  <body bgcolor="#FFFFFF">
+  
+    <h2>Add LDAP connection</h2>
+    
+    <form action="manage_addZLDAPConnection" method="POST">
+      <table cellspacing="2">
+	
+	<tr>
+	  <th align="LEFT" valign="TOP">Id</th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="id" size="50">
+          </td>
+	</tr>
+	
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>Title</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="title" size="50">
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>LDAP Server (host[:port])</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="hostport" size="50">
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>Base DN</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="basedn" size="50">
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>Bind As</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="bind_as" size="50">
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>Bind Password</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="TEXT" name="pw" size="50">
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="LEFT" valign="TOP"><em>Open Connection?</em></th>
+	  <td align="LEFT" valign="TOP">
+            <input type="CHECKBOX" name="openc" CHECKED>
+          </td>
+	</tr>
+
+	<tr>
+	  <th align="left" valign="top"><em>Transactional?</em></th>
+	  <td align="left" valign="top">
+	   <input type="checkbox" name="transactional:int" value="1" checked>
+	   <input type="hidden" name="transactional:default:int" value="0">
+	  </td>
+	</tr>
+
+	<tr>
+	  <td></td>
+	  <td><br><input type="SUBMIT" value="Add"></td>
+	</tr>
+    
+	</table>
+    </form>
+  </body>
+</html>
diff --git a/product/ZLDAPConnection/attributes.dtml b/product/ZLDAPConnection/attributes.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..ff04eb533f08f56bde5203566af745e9cdbdcdc0
--- /dev/null
+++ b/product/ZLDAPConnection/attributes.dtml
@@ -0,0 +1,31 @@
+<html>
+ <head>
+  <title>Attributes for &dtml-dn;</title>
+ </head>
+ <body bgcolor="#FFFFFF">
+  <dtml-var manage_tabs>
+  <h3>Attributes for <em>&dtml-id;</em></h3>
+
+  <table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
+   <dtml-in attributesMap>
+    <tr valign="top">
+     <th align="left">&dtml-sequence-key;</th>
+     <td><dtml-in sequence-item>&dtml-sequence-item;<br /></dtml-in></td>
+    </tr>
+   </dtml-in>
+  </table>
+
+  <hr />
+  <h3>Subentries</h3>
+
+  <dtml-if tpValues>
+  <dtml-tree>
+   <a href="&dtml-tree-item-url;/manage_attributes"
+    title="click to view this entry">&dtml-id;</a>
+  </dtml-tree>
+  <dtml-else>
+   <p><em>No subentries for &dtml-id;</em></p>
+  </dtml-if>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/product/ZLDAPConnection/browse.dtml b/product/ZLDAPConnection/browse.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..06f48f30d155c88a7398c4e27f5d54561927b851
--- /dev/null
+++ b/product/ZLDAPConnection/browse.dtml
@@ -0,0 +1,35 @@
+<html>
+ <head>
+  <title>Browse &dtml-title_and_id;</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <dtml-var manage_tabs>
+
+ <dtml-if name="canBrowse">
+  <dtml-with name="getRoot">
+  <h3>Attributes for <em>&dtml-id;</em></h3>
+
+  <table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
+   <dtml-in name="attributesMap">
+    <tr valign="top">
+     <th align="left">&dtml-sequence-key;</th>
+     <td><dtml-in name="sequence-item">&dtml-sequence-item;<br /></dtml-in></td>
+    </tr>
+   </dtml-in>
+  </table>
+  </dtml-with>
+  <hr />
+  <h3>Subentries</h3>
+
+  <dtml-tree name="getRoot">
+   <a href="&dtml-tree-item-url;/manage_attributes"
+    title="click to view this entry">&dtml-id;</a>
+  </dtml-tree>
+
+ <dtml-else>
+  <p><em>Connection to <code>&dtml-host;:&dtml-port;</code> is
+  <dtml-if name="openc">not browsable<dtml-else>closed</dtml-if>.</em></p>
+ </dtml-if>
+
+ </body>
+</html>
diff --git a/product/ZLDAPConnection/connection.dtml b/product/ZLDAPConnection/connection.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..7175917a702d06b1b0f4e942bf23f586614e185d
--- /dev/null
+++ b/product/ZLDAPConnection/connection.dtml
@@ -0,0 +1,28 @@
+<html>
+ <head>
+  <title>Connection for &dtml-title_or_id;</title>
+  <style type="text/css"><!--
+  .open {color: blue}
+  .close {color: red}
+  --></style>
+ </head>
+ <body bgcolor="#FFFFFF">
+  <dtml-var manage_tabs>
+
+  <h2>Connection to <code>&dtml-host;:&dtml-port;</code> is
+  <dtml-if isOpen><span class="open"><em>open</em></span>
+  <dtml-else><span class="close"><em>closed</em></span>
+  </dtml-if>.</h2>
+
+  <dtml-if isOpen>
+   <form action="manage_close">
+    <input type="submit" value="Close Connection" class="close" />
+   </form>
+  <dtml-else>
+   <form action="manage_open">
+    <input type="submit" value="Open Connection" class="open" />
+   </form>
+  </dtml-if>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/product/ZLDAPConnection/contents.dtml b/product/ZLDAPConnection/contents.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..b67751718ff7cab89987119bae238a322979b1bc
--- /dev/null
+++ b/product/ZLDAPConnection/contents.dtml
@@ -0,0 +1,80 @@
+<HTML>
+<HEAD>
+<TITLE>Contents</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<!--#var manage_tabs-->
+<P>
+
+<FORM ACTION="." METHOD="POST">
+
+
+<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="2">
+<!--#in "tpValues()" -->
+<TR>
+
+  <TD ALIGN="LEFT" VALIGN="TOP" WIDTH="16">
+  <INPUT TYPE="CHECKBOX" NAME="ids:list" VALUE="<!--#var dn-->">
+  </TD>
+
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <A HREF="<!--#var id fmt=url-quote-->/manage_main">
+  <IMG SRC="<!--#var SCRIPT_NAME-->/<!--#var icon-->"
+   ALT="[Entry]" BORDER="0"></A>
+  </TD>
+
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <A HREF="<!--#var id fmt=url-quote-->/manage_main"><!--#var id--></A>
+  </TD>
+
+</TR>
+<!--#/in-->
+</TABLE>
+
+
+<TABLE BORDER="0" CELLSPACING="0" CELLPADDING=2>
+<TR>
+  <TD ALIGN="LEFT" VALIGN="TOP" WIDTH="16"></TD>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+    <!--#if "tpValues()"-->
+    <INPUT TYPE="SUBMIT" NAME="manage_deleteEntry:method" VALUE="Delete">
+  <!--#/if-->
+  </TD>
+</TR>
+</TABLE>
+
+</FORM>
+
+<a name="addentryform">
+<form action="." method="POST">
+<p>
+To add a new entry, enter an RDN (Relative Distinguished Name) for the entry
+to appear under the above DN (Distinguished Name) and click the &quot;Add New Entry&quot; 
+button. For example enter &quot;uid=spam&quot; for the RDN. An attribute of &quot;uid: spam&quot;
+will be automatically added to the entry. Other attributes may be entered by using the attribute management screens.
+</p>
+<table>
+<tr>
+  <th align="left" valign="top">RDN</th>
+  <td align="left" valign="top"><input type="text" name="rdn" size="20"></td>
+  <td align="right" valign="top">
+    <INPUT TYPE="SUBMIT" NAME="manage_newEntry:method" VALUE="Add New Entry">
+  </td>
+</tr>
+</table>
+</form>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br>
+
+
+
+
+
+
+</BODY>
+</HTML>
diff --git a/product/ZLDAPConnection/edit.dtml b/product/ZLDAPConnection/edit.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..4a2bf7468c96e5cfe3948577f2c3aa42c6e6fe38
--- /dev/null
+++ b/product/ZLDAPConnection/edit.dtml
@@ -0,0 +1,88 @@
+<html> 
+  <head><title>Edit LDAP connection</title></head> 
+  <body bgcolor="#FFFFFF"> 
+  <dtml-var name="manage_tabs"> 
+   
+    <h2>Edit LDAP connection <dtml-var name="title_and_id"></h2> 
+     
+    <form action="manage_edit" method="POST"> 
+      <table cellspacing="2"> 
+         
+        <tr> 
+          <th align="LEFT" valign="TOP">Id</th> 
+          <td align="LEFT" valign="TOP"> 
+            <dtml-var name="getId"> 
+          </td> 
+        </tr> 
+         
+        <tr> 
+          <th align="LEFT" valign="TOP"><em>Title</em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="TEXT" name="title" size="50" value="&dtml-getTitle;"> 
+          </td> 
+        </tr> 
+ 
+        <tr> 
+          <th align="LEFT" valign="TOP"><em>LDAP server (host[:port])</em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="TEXT" name="hostport" size="50" value="&dtml-getHost;:&dtml-getPort;"> 
+          </td> 
+        </tr> 
+
+        <tr> 
+          <th align="LEFT" valign="TOP"><em>Base DN</em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="TEXT" name="basedn" size="50" value="&dtml-getDN;"> 
+          </td> 
+        </tr> 
+
+ 
+        <tr> 
+          <th align="LEFT" valign="TOP"><em>Bind As</em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="TEXT" name="bind_as" size="50" value="&dtml-getBindAs;"> 
+          </td> 
+        </tr> 
+ 
+        <tr> 
+          <th align="LEFT" valign="TOP"><em>Bind Password</em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="TEXT" name="pw" size="50" value="&dtml-getPW;"> 
+          </td> 
+        </tr> 
+ 
+        <tr> 
+          <th align="LEFT" valign="TOP"><em><label for="cb-openc">Open Connection?</label></em></th> 
+          <td align="LEFT" valign="TOP"> 
+            <input type="CHECKBOX" name="openc" <dtml-if name="shouldBeOpen">CHECKED</dtml-if> id="cb-openc" value="1"> 
+          </td> 
+        </tr> 
+
+	<tr>
+	  <th align="left" valign="top"><em><label for="cb-canBrowse">Make connection object browsable?</label></em></th>
+	  <td align="left" valign="top">
+	  <input type="checkbox" name="canBrowse" value="1" <dtml-if name="getBrowsable">checked</dtml-if> id="cb-canBrowse">
+	  </td>
+	</tr>
+ 
+	<tr>
+	  <th align="left" valign="top"><em>
+	  <label for="cb-transactional">Make connection and entries 
+	    transactional?</label></em></th>
+	  <td align="left" valign="top">
+	    <input type="checkbox" name="transactional:int" value="1"
+	    <dtml-if name="getTransactional">checked</dtml-if>
+	    id="cb-transactional">
+	    <input type="hidden" name="transactional:default:int" value="0">
+	</td>
+	</tr>
+
+        <tr> 
+          <td></td> 
+          <td><br><input type="SUBMIT" value="Change"></td> 
+        </tr> 
+     
+        </table> 
+    </form> 
+  </body> 
+</html>          
diff --git a/product/ZLDAPConnection/help.dtml b/product/ZLDAPConnection/help.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..5de71b805617c75ef35d9bd4f00d528bface6c4d
--- /dev/null
+++ b/product/ZLDAPConnection/help.dtml
@@ -0,0 +1,16 @@
+<html>
+<head><title>help for LDAP connections</title></head>
+<body>
+<h1>Help for LDAP connections</h1>
+
+<b>Arguments are as follows:</b>
+<dl>
+<dt>LDAP server<dd>The hostname, and optionally the port, where the LDAP
+server is running. The standard port for LDAP is 389 - this will be used
+if no port is specified.
+<dt>Bind As<dd>Who to bind to the LDAP server as. An example:
+  "cn=Anthony, o=ekorp.com, c=AU"
+<dt>Password<dd>The password (if any) to be used to bind to the server.
+</dl>
+
+</body></html>
diff --git a/product/ZLDAPConnection/version.txt b/product/ZLDAPConnection/version.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c34818e0460a94bef4d0852597c67db7d8e23f3f
--- /dev/null
+++ b/product/ZLDAPConnection/version.txt
@@ -0,0 +1 @@
+ZopeLDAP-1-1-0
diff --git a/product/ZLDAPMethods/LDAP_Method_icon.gif b/product/ZLDAPMethods/LDAP_Method_icon.gif
new file mode 100644
index 0000000000000000000000000000000000000000..9a170fa463511d015085ad18f4a272f7a95c07cb
Binary files /dev/null and b/product/ZLDAPMethods/LDAP_Method_icon.gif differ
diff --git a/product/ZLDAPMethods/LICENSE.txt b/product/ZLDAPMethods/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5f88c3b041c519bc0219646f21eba658850262ce
--- /dev/null
+++ b/product/ZLDAPMethods/LICENSE.txt
@@ -0,0 +1,21 @@
+
+ZPL 2.1
+
+Zope Public License (ZPL) Version 2.1
+
+A copyright notice accompanies this license document that identifies the copyright holders.
+
+This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer.
+   2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
+   3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders.
+   4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders.
+   5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
+
+Disclaimer
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/product/ZLDAPMethods/LM.py b/product/ZLDAPMethods/LM.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fe93927866d5c3320eef6da3561db7214e1f27c
--- /dev/null
+++ b/product/ZLDAPMethods/LM.py
@@ -0,0 +1,551 @@
+# core of LDAP Filter Methods.
+
+
+from Globals import HTMLFile, HTML
+__version__ = "$Revision: 1.10 $"[11:-2]
+
+try:
+  import ldap
+  from ldap import modlist
+# see if it's on a regular path
+except ImportError:
+  from Products.ZLDAPConnection import ldap
+  from Products.ZLDAPConnection.ldap import modlist
+import string
+
+from Shared.DC.ZRDB import Aqueduct
+from Shared.DC.ZRDB.Aqueduct import parse, decodestring, default_input_form
+from Shared.DC.ZRDB.Results import Results
+import Acquisition, Globals, AccessControl.Role, OFS.SimpleItem
+from Globals import HTMLFile, MessageDialog, Persistent
+import DocumentTemplate
+import ExtensionClass
+import sys
+
+from zLOG import LOG
+from ldif import LDIFRecordList, is_dn, valid_changetype_dict, CHANGE_TYPES
+import ldifvar
+from AccessControl.DTML import RestrictedDTML
+try:
+    from AccessControl import getSecurityManager
+except ImportError:
+    getSecurityManager = None
+
+MODIFY_MAPPING_DICT = {'add'      : ldap.MOD_ADD,
+                       #'replace'  : ldap.MOD_REPLACE,
+                       'delete'   : ldap.MOD_DELETE}
+
+class ERP5LDIFRecordList(LDIFRecordList):
+
+  def parse(self):
+    """
+    Continously read and parse LDIF records
+    """
+    self._line = self._input_file.readline()
+
+    while self._line and \
+          (not self._max_entries or self.records_read<self._max_entries):
+
+      # Reset record
+      version = None; dn = None; changetype = None; modop = None; entry = {};
+
+      attr_type,attr_value = self._parseAttrTypeandValue()
+
+      while attr_type!=None and attr_value!=None:
+        if attr_type=='dn':
+          # attr type and value pair was DN of LDIF record
+          if dn!=None:
+            raise ValueError, 'Two lines starting with dn: in one record.'
+          if not is_dn(attr_value):
+            raise ValueError, 'No valid string-representation of distinguished name %s.' % (repr(attr_value))
+          dn = attr_value
+        elif attr_type=='version' and dn is None:
+          version = 1
+        elif attr_type=='changetype':
+          # attr type and value pair was DN of LDIF record
+          if dn is None:
+            raise ValueError, 'Read changetype: before getting valid dn: line.'
+          if changetype!=None:
+            raise ValueError, 'Two lines starting with changetype: in one record.'
+          if not valid_changetype_dict.has_key(attr_value):
+            raise ValueError, 'changetype value %s is invalid.' % (repr(attr_value))
+          changetype = attr_value
+          attr_type, attr_value = self._parseAttrTypeandValue()
+          modify_list = []
+          entry[changetype] = []
+          while (attr_type and attr_value) is not None:
+            mod_op = MODIFY_MAPPING_DICT[attr_type]
+            mod_type = attr_value
+            multivalued_list = []
+            while attr_value is not None:
+              attr_type, attr_value = self._parseAttrTypeandValue()
+              if attr_value is not None:
+                multivalued_list.append(attr_value)
+            modify_list.append((mod_op, mod_type, multivalued_list))
+            entry[changetype] = [modify_list]
+            attr_type, attr_value = self._parseAttrTypeandValue()
+          #don't add new entry for the same dn
+          break
+        elif attr_value not in (None, '') and \
+             not self._ignored_attr_types.has_key(string.lower(attr_type)):
+          # Add the attribute to the entry if not ignored attribute
+          if entry.has_key(attr_type):
+            entry[attr_type].append(attr_value)
+          else:
+            entry[attr_type]=[attr_value]
+
+        # Read the next line within an entry
+        attr_type, attr_value = self._parseAttrTypeandValue()
+
+      if entry:
+        # append entry to result list
+        self.handle(dn, entry)
+        self.records_read = self.records_read+1
+
+    return # parse()
+
+class Filter(DocumentTemplate.HTML):
+    """
+    Subclass of DocumentTemplate.HTML for Variable Interpolation.
+    Special LDAP Specific tags would go here.  Since there aren't
+    any (like ldapvar or ldaptest or whatever), we don't have to worry.
+    It's just nice to have a nice name that reflects what this is. :)
+    """
+    pass
+
+class nvLDIF(DocumentTemplate.HTML):
+    # Non-validating Ldif Template for use by LDIFFiles.
+    commands={}
+    for k, v in DocumentTemplate.HTML.commands.items(): commands[k] = v
+    commands['ldifvar' ] = ldifvar.LDIFVar
+    commands['ldifline' ] = ldifvar.LDIFLine
+
+    _proxy_roles=()
+
+class Ldif(RestrictedDTML, ExtensionClass.Base, nvLDIF):
+    pass
+
+def LDAPConnectionIDs(self):
+    """find LDAP connections in the current folder and parents
+    Returns list of ids.
+    """
+    ids={}
+    StringType = type('')
+    have_key = ids.has_key
+    while self is not None:
+        if hasattr(self, 'objectValues'):
+            for o in self.objectValues():
+                if (hasattr(o,'_isAnLDAPConnection')
+                    and o._isAnLDAPConnection() and hasattr(o,'id')):
+                    id=o.id
+                    if type(id) is not StringType: id=id()
+                    if not ids.has_key(id):
+                        if hasattr(o,'title_and_id'): o=o.title_and_id()
+                        else: o=id
+                        ids[id]=id
+        if hasattr(self, 'aq_parent'): self=self.aq_parent
+        else: self=None
+    ids=map(lambda item: (item[1], item[0]), ids.items())
+    ids.sort()
+    return ids
+
+manage_addZLDAPMethodForm = HTMLFile('add', globals())
+
+def manage_addZLDAPMethod(self, id, title, connection_id, scope, basedn, 
+                          filters, arguments, getfromconnection=0,
+                          REQUEST=None, submit=None):
+    """Add an LDAP Method """
+    l=LDAPMethod(id, title, connection_id, scope, basedn,
+                 arguments, filters)
+    self._setObject(id, l)
+    if getfromconnection:
+        getattr(self,id).recomputeBaseDN()
+
+    if REQUEST is not None:
+        u=REQUEST['URL1']
+        if submit==" Add and Edit ":
+            u="%s/%s/manage_main" % (u,id)
+        elif submit==" Add and Test ":
+            u="%s/%s/manage_testForm" % (u,id)
+        else:
+            u=u+'/manage_main'
+
+        REQUEST.RESPONSE.redirect(u)
+    return ''
+
+
+
+
+_ldapScopes = { "ONELEVEL": ldap.SCOPE_ONELEVEL,
+                "SUBTREE": ldap.SCOPE_SUBTREE,
+                "BASE": ldap.SCOPE_BASE }
+
+class LDAPMethod(Aqueduct.BaseQuery,
+    Acquisition.Implicit,
+    Globals.Persistent,
+    AccessControl.Role.RoleManager,
+    OFS.SimpleItem.Item,
+    ):
+    'LDAP Method'
+
+    meta_type = 'LDAP Method'
+
+    manage_main = HTMLFile('edit', globals())
+    manage_options = (
+        {'label':'Edit', 'action':'manage_main'},
+        {'label':'Test', 'action':'manage_testForm'},
+        {'label':'Security', 'action':'manage_access'},
+        )
+
+    __ac_permissions__=(
+        ('View management screens', ('manage_tabs','manage_main',),),
+        ('Change LDAP Methods', ('manage_edit',
+                                 'manage_testForm','manage_test')),
+        ('Use LDAP Methods',    ('__call__',''), ('Anonymous','Manager')),
+        )
+
+
+    #manage_testForm = HTMLFile("testForm", globals())
+
+    def manage_testForm(self, REQUEST):
+        " "
+        input_src=default_input_form(self.title_or_id(),
+                                     self._arg, 'manage_test',
+                                     '<!--#var manage_tabs-->')
+        return DocumentTemplate.HTML(input_src)(self,REQUEST,HTTP_REFERER='')
+
+    def __init__(self, id, title, connection_id, scope, basedn,
+                 arguments, filters):
+        """ init method """
+        self.id = id
+        self.title = title
+        self.connection_id = connection_id
+        self._scope = _ldapScopes[scope]
+        self.scope = scope
+        self.basedn = basedn
+        self.arguments_src=self.arguments=arguments
+        self._arg=parse(arguments)
+        self.filters = filters
+
+    def recomputeBaseDN(self):
+        ' recompute base DN based on connection '
+        cdn=self._connection().dn
+        if self.basedn:
+            self.basedn='%s, %s' % (self.basedn, cdn)
+        else:
+            self.basedn=cdn
+        return self.basedn
+
+    def manage_edit(self, title, connection_id, scope, basedn,
+                    arguments, filters, REQUEST=None):
+        """ commit changes """
+        self.title = title
+        self.connection_id = connection_id
+        self._scope = _ldapScopes[scope]
+        self.scope = scope
+        self.basedn = basedn
+        self.arguments_src=self.arguments=arguments
+        self._arg=parse(arguments)
+        self.filters = filters
+        if REQUEST is not None:
+            return MessageDialog(
+                title='Edited',
+                message='<strong>%s</strong> has been changed.' % self.id,
+                action ='./manage_main', )
+
+    def cleanse(self,s):
+        import string
+        # kill line breaks &c.
+        s = string.join(string.split(s))
+        return s
+
+    def _connection(self):
+        ' return actual ZLDAP Connection Object '
+        if hasattr(self,'connection_id') and hasattr(self,self.connection_id):
+            return getattr(self, self.connection_id)
+
+    def _getConn(self):
+        return self._connection().GetConnection()
+
+    # Hacky, Hacky
+    GetConnection=_getConn
+
+    def manage_test(self, REQUEST):
+        """ do the test query """
+        src="Could not render the filter template!"
+        res=()
+        t=v=tb=None
+        try:
+            try:
+                src=self(REQUEST, src__=1)
+                res=self(REQUEST, tst__=1)
+                r=self.prettyResults(res)
+            except:
+                t, v, tb = sys.exc_info()
+                r='<strong>Error, <em>%s</em>:</strong> %s' % (t,v)
+
+            report=DocumentTemplate.HTML(
+                '<html><body bgcolor="#ffffff">\n'
+                '<!--#var manage_tabs-->\n<hr>%s\n\n'
+                '<hr><strong>Filter used:</strong><br>\n<pre>\n%s\n</pre>\n<hr>\n'
+                '</body></html>' % (r, src)
+                )
+            report=apply(report,(self,REQUEST),{self.id:res})
+
+            if tb is not None:
+                self.raise_standardErrorMessage(
+                    None, REQUEST, t, v, tb, None, report)
+
+            return report
+
+        finally: tb=None
+
+    def prettyResults(self, res):
+        s = ""
+        if not res or not len(res):
+            s = "no results"
+        else:
+            for dn,attrs in res:
+                s = s + ('<ul><li><b>DN: %s</b></li>\n<ul>' % dn)
+                s = s + str(pretty_results(attrs=attrs.items()))
+                s = s + '</ul></ul>'
+        return s
+
+    def __call__(self, REQUEST=None, src__=0, tst__=0, **kw):
+        """ call the object """
+        if REQUEST is None:
+            if kw: REQUEST = kw
+            else:
+                if hasattr(self, 'REQUEST'): REQUEST=self.REQUEST
+                else: REQUEST={}
+        c = self._getConn()
+        if not c:
+            raise "LDAPError", "LDAP Connection not open"
+
+        if hasattr(self, 'aq_parent'):
+            p = self.aq_parent
+        else: p = None
+
+        argdata = self._argdata(REQUEST)  #use our BaseQuery's magic.  :)
+
+        # Also need the authenticated user.
+        auth_user = REQUEST.get('AUTHENTICATED_USER', None)
+        if auth_user is None:
+            auth_user = getattr(self, 'REQUEST', None)
+            if auth_user is not None:
+                try: auth_user = auth_user.get('AUTHENTICATED_USER', None)
+                except: auth_user = None
+
+        if auth_user is not None:
+            if getSecurityManager is None:
+                # working in a pre-Zope 2.2.x instance
+                from AccessControl.User import verify_watermark
+                verify_watermark(auth_user)
+                argdata['AUTHENTICATED_USER'] = auth_user
+
+        f = Filter(self.filters)        # make a FilterTemplate
+        f.cook()
+        if getSecurityManager is None:
+            # working in a pre-Zope 2.2 instance
+            f = apply(f, (p,argdata))       #apply the template
+        else:
+            # Working with the new security manager (Zope 2.2.x ++)
+            security = getSecurityManager()
+            security.addContext(self)
+            try:     f = apply(f, (p,), argdata)  # apply the template
+            finally: security.removeContext(self)
+
+        f = str(f)                      #ensure it's a string
+        if src__: return f              #return the rendered source
+        f = self.cleanse(f)
+        ### run the search
+        res = c.search_s(self.basedn, self._scope, f)
+        if tst__: return res            #return test-friendly data
+
+        ### instantiate Entry objects based on results
+        l = []                          #list of entries to return
+        conn=self._connection()         #ZLDAPConnection
+        Entry = conn._EntryFactory()
+        for dn, attrdict in res:
+            e = Entry(dn, attrdict, conn).__of__(self)
+            l.append(e)
+
+        return l
+
+
+
+manage_addZLDIFMethodForm = HTMLFile('addLdif', globals())
+
+def manage_addZLDIFMethod(self, id, title, connection_id, basedn, arguments, ldif, getfromconnection=0, REQUEST=None, submit=None):
+  """Add an LDIF Method """
+  l=LDIFMethod(id, title, connection_id, basedn, arguments, ldif)
+  self._setObject(id, l)
+  if getfromconnection:
+    getattr(self,id).recomputeBaseDN()
+
+  if REQUEST is not None:
+    u=REQUEST['URL1']
+    if submit == " Add and Edit ":
+        u = "%s/%s/manage_main" % (u, id)
+    elif submit == " Add and Test ":
+        u = "%s/%s/manage_testForm" % (u, id)
+    else:
+        u = u + '/manage_main'
+
+    REQUEST.RESPONSE.redirect(u)
+  return ''
+
+
+class LDIFMethod(LDAPMethod):
+  'LDIF Method'
+
+  meta_type = 'LDIF Method'
+
+  manage_main = HTMLFile('editLdif', globals())
+  manage_options = (
+      {'label':'Edit', 'action':'manage_main'},
+      {'label':'Test', 'action':'manage_testForm'},
+      {'label':'Security', 'action':'manage_access'},
+      )
+
+  __ac_permissions__=(
+      ('View management screens', ('manage_tabs', 'manage_main',),),
+      ('Change LDAP Methods', ('manage_edit',
+                                'manage_testForm', 'manage_test')),
+      ('Use LDAP Methods',    ('__call__', ''), ('Anonymous', 'Manager')),
+      )
+
+  #manage_testForm = HTMLFile("testLdifForm", globals())
+
+
+
+  def __init__(self, id, title, connection_id, basedn, arguments, ldif, **kw):
+    """ init method """
+    self.id = id
+    self.title = title
+    self.connection_id = connection_id
+    self.basedn = basedn
+    self.arguments_src=self.arguments=arguments
+    self._arg=parse(arguments)
+    self.ldif = str(ldif)
+
+
+  def manage_edit(self, title, connection_id, basedn, arguments, ldif, REQUEST=None, **kw):
+    """ commit changes """
+    self.title = title
+    self.connection_id = connection_id
+    self.basedn = basedn
+    self.arguments_src = self.arguments = arguments
+    self._arg = parse(arguments)
+    self.ldif = str(ldif)
+    if REQUEST is not None:
+      return MessageDialog(
+        title = 'Edited',
+        message = '<strong>%s</strong> has been changed.' % self.id,
+        action = './manage_main', )
+
+  def __call__(self, REQUEST=None, src__=0, tst__=0, **kw):
+    """ call the object """
+    if REQUEST is None:
+      if kw: REQUEST = kw
+      else:
+        if hasattr(self, 'REQUEST'): REQUEST = self.REQUEST
+        else: REQUEST={}
+    c = self._connection().GetConnection()
+    if not c:
+      raise "LDAPError", "LDAP Connection not open"
+
+    if hasattr(self, 'aq_parent'):
+      p = self.aq_parent
+    else: p = None
+
+    argdata = self._argdata(REQUEST)  #use our BaseQuery's magic.  :)
+
+    # Also need the authenticated user.
+    auth_user = REQUEST.get('AUTHENTICATED_USER', None)
+    if auth_user is None:
+      auth_user = getattr(self, 'REQUEST', None)
+      if auth_user is not None:
+        try: auth_user = auth_user.get('AUTHENTICATED_USER', None)
+        except: auth_user = None
+
+    if auth_user is not None:
+      if getSecurityManager is None:
+        # working in a pre-Zope 2.2.x instance
+        from AccessControl.User import verify_watermark
+        verify_watermark(auth_user)
+        argdata['AUTHENTICATED_USER'] = auth_user
+
+    ldif = Ldif(self.ldif)        # make a FilterTemplate
+    ldif.cook()
+    if getSecurityManager is None:
+      # working in a pre-Zope 2.2 instance
+      ldif = apply(ldif, (p, argdata))       #apply the template
+    else:
+      # Working with the new security manager (Zope 2.2.x ++)
+      security = getSecurityManager()
+      security.addContext(self)
+      try:     ldif = apply(ldif, (p,), argdata)  # apply the template
+      finally: security.removeContext(self)
+
+    ldif = str(ldif)                      #ensure it's a string
+    #LOG('ldif', 0, ldif)
+    if src__: return ldif              #return the rendered source
+    ### Apply Query
+    from cStringIO import StringIO
+    file = StringIO(ldif)
+    l = ERP5LDIFRecordList(file)
+    l.parse()
+    res = l.all_records
+    for record in res:
+      dn = record[0]
+      entry = record[1]
+      if type(entry) == type({}):
+        authorized_modify_key = [key for key in entry.keys() if key in CHANGE_TYPES]
+        if len(authorized_modify_key):
+          for key in authorized_modify_key:
+            tuple_list = entry[key]
+            if key == 'delete':
+              try:
+                c.delete_s(dn)
+              except ldap.NO_SUCH_OBJECT:
+                pass
+                #LOG('LDIFMethod can\'t delete NO SUCH OBJECT',0,dn)
+            else:
+              for mod_tuple in tuple_list:
+                c.modify_s(dn, mod_tuple)
+        else:
+          mod_list = modlist.addModlist(entry)
+          try:
+            c.add_s(dn, mod_list)
+          except ldap.ALREADY_EXISTS:
+            pass
+            #LOG('LDIFMethod can\'t add, entry allready exists',0,dn)
+          #except ldap.SERVER_DOWN:
+            #c = self._connection().GetConnection()
+            #try:
+              #c.add_s(dn, mod_list)
+            #except ldap.ALREADY_EXISTS:
+              #pass
+      else:
+        LOG('LDIFMethod Type unknow',0,'')
+    return res
+
+
+class LDAP(LDAPMethod):
+    "backwards compatibility.  blech. XXX Delete Me!"
+
+pretty_results=DocumentTemplate.HTML("""\
+  <table border="1" cellpadding="2" cellspacing="0" rules="rows" frame="void">
+   <dtml-in attrs>
+    <tr valign="top">
+     <th align="left">&dtml-sequence-key;</th>
+     <td><dtml-in name="sequence-item">&dtml-sequence-item;<br /></dtml-in></td>
+    </tr>
+   </dtml-in>
+  </table>""")
+
+
+import Globals
+Globals.default__class_init__(LDAPMethod)
+Globals.default__class_init__(LDIFMethod)
\ No newline at end of file
diff --git a/product/ZLDAPMethods/README.txt b/product/ZLDAPMethods/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5677a02a174b84e8cb1f508eee896d24ed4bc37a
--- /dev/null
+++ b/product/ZLDAPMethods/README.txt
@@ -0,0 +1,14 @@
+Quick README on ZLDAP Filter Methods
+
+ Current
+
+  ZLDAP Filter Methods are a way of executing LDAP Searches according
+  to RFC1558 'A String Representation of LDAP Search Filters'.  They
+  can be used like ZSQL Methods and use DTML inside of them.  For
+  example, one could do a Filter of 'uid=<dtml-var foo>'.  ZLDAP Filter 
+  Methods return Entry objects.
+
+ZLDIF Filter Methods
+  ZLDIF Filter Methods are a way to generate ldif document which are parsed to modify LDAP database.
+  This is a sample of dtml code <dtml-ldifline attr="uidNumber" expr="'03ERRRNN981'" type="string"> 
+  for generating valid LDIF message.
diff --git a/product/ZLDAPMethods/__init__.py b/product/ZLDAPMethods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f53049471d0687f14bc4ecea0778df38a7bc90cc
--- /dev/null
+++ b/product/ZLDAPMethods/__init__.py
@@ -0,0 +1,21 @@
+"""LDAP Filter Methods Package """
+
+import LM
+
+def initialize(context):
+
+    context.registerClass(
+        LM.LDAPMethod,
+        constructors = (LM.manage_addZLDAPMethodForm,
+                        LM.manage_addZLDAPMethod),
+        icon = "LDAP_Method_icon.gif",
+        legacy = (LM.LDAPConnectionIDs,), #special baby to add to ObjectManagers
+        )
+
+    context.registerClass(
+        LM.LDIFMethod,
+        constructors = (LM.manage_addZLDIFMethodForm,
+                        LM.manage_addZLDIFMethod),
+        icon = "LDAP_Method_icon.gif",
+        legacy = (LM.LDAPConnectionIDs,), #special baby to add to ObjectManagers
+        )
\ No newline at end of file
diff --git a/product/ZLDAPMethods/add.dtml b/product/ZLDAPMethods/add.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..cf3f59c062af5e7c2d42f9cb682aa8b5c130dc72
--- /dev/null
+++ b/product/ZLDAPMethods/add.dtml
@@ -0,0 +1,61 @@
+<HTML><HEAD><TITLE>New LDAP Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<H2>New LDAP Method</H2>
+
+<dtml-if LDAPConnectionIDs>
+
+<form action="manage_addZLDAPMethod" method="post">
+
+<table>     
+            
+<tr>    <th align='LEFT'>Id</th>
+        <td align='LEFT'><input name="id" size="40"></td></tr>
+<tr>	<th align='LEFT'><em>Title<em></th>
+        <td align='LEFT'><input name="title" size="40"></td></tr>
+<tr>	<th align='LEFT'>Connection id</th>
+        <td align='LEFT'><select name="connection_id">
+              <dtml-in LDAPConnectionIDs>
+                <option value="&dtml-sequence-item;"
+		><dtml-var sequence-key></option>
+              </dtml-in>
+            </select></td></tr>
+<tr>	<th align='LEFT'><em>Query Scope</em></th>
+	<td align='LEFT'><select name="scope">
+            <dtml-in expr="['SUBTREE', 'ONELEVEL', 'BASE']">
+                <option value="&dtml-sequence-item;"
+		><dtml-var sequence-item></option>
+            </dtml-in>
+
+	    </select></td></tr>
+<tr>	<th align='LEFT' valign="top"><em>Base DN</em></th>
+        <td align='LEFT'><input name="basedn" size="40"><br />
+	<input type="checkbox" name="getfromconnection:int" value="1" id="gfc">
+	<em><label for="gfc">Append Connection's Base DN</label></em></td></tr>
+
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><input name="arguments" size="40"></td></tr>
+<tr>    <td colspan=2 align='LEFT'><strong>Query Filter</strong><br>
+            <textarea name="filters:text" rows=9 cols=50>objectclass=*
+</textarea></td></tr>
+
+<tr><td colspan=2>
+<input type="hidden" name="key" value="">
+<input type="SUBMIT" name="submit" value=" Add ">
+<input type="SUBMIT" name="submit" value=" Add and Edit ">
+<input type="SUBMIT" name="submit" value=" Add and Test ">
+</td></tr>
+
+</table>
+</form>
+
+
+<dtml-else>
+<em><strong>Warning:</strong>
+There are no LDAP connections.  You need
+to add a Zope LDAP connection
+before you can use a Zope LDAP Method.
+</em>
+</dtml-if>
+
+</body></html>
diff --git a/product/ZLDAPMethods/addLdif.dtml b/product/ZLDAPMethods/addLdif.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..cb03f04a31cb19913ccc53ac66887c60ccfe4a7d
--- /dev/null
+++ b/product/ZLDAPMethods/addLdif.dtml
@@ -0,0 +1,50 @@
+<HTML><HEAD><TITLE>New LDIF Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<H2>New LDIF Method</H2>
+
+<dtml-if LDAPConnectionIDs>
+
+<form action="manage_addZLDIFMethod" method="post">
+
+<table>
+<tr>    <th align='LEFT'>Id</th>
+        <td align='LEFT'><input name="id" size="40"></td></tr>
+<tr>    <th align='LEFT'><em>Title<em></th>
+        <td align='LEFT'><input name="title" size="40"></td></tr>
+<tr>    <th align='LEFT'>Connection id</th>
+        <td align='LEFT'><select name="connection_id">
+              <dtml-in LDAPConnectionIDs>
+                <option value="&dtml-sequence-item;"><dtml-var sequence-key></option>
+              </dtml-in>
+            </select></td></tr>
+
+<tr>    <th align='LEFT' valign="top"><em>Base DN</em></th>
+        <td align='LEFT'><input name="basedn" size="40"><br />
+        <input type="checkbox" name="getfromconnection:int" value="1" id="gfc">
+        <em><label for="gfc">Append Connection's Base DN</label></em></td></tr>
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><textarea name="arguments:text" rows="4" cols="40"></textarea></td></tr>
+<tr>    <td colspan=2 align='LEFT'><strong>ldif</strong><br>
+            <textarea name="ldif:text" rows=9 cols=70></textarea></td></tr>
+
+<tr><td colspan=2>
+<input type="hidden" name="key" value="">
+<input type="SUBMIT" name="submit" value=" Add ">
+<input type="SUBMIT" name="submit" value=" Add and Edit ">
+<input type="SUBMIT" name="submit" value=" Add and Test ">
+</td></tr>
+
+</table>
+</form>
+
+
+<dtml-else>
+<em><strong>Warning:</strong>
+There are no LDAP connections.  You need
+to add a Zope LDAP connection
+before you can use a Zope LDAP Method.
+</em>
+</dtml-if>
+
+</body></html>
diff --git a/product/ZLDAPMethods/edit.dtml b/product/ZLDAPMethods/edit.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..13508ade7082139b67e25c620790942aac5abf4a
--- /dev/null
+++ b/product/ZLDAPMethods/edit.dtml
@@ -0,0 +1,58 @@
+<HTML><HEAD><TITLE>New LDAP Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<!--#var manage_tabs-->
+<H2>Edit LDAP Method</H2>
+
+<!--#if LDAPConnectionIDs-->
+
+
+<form action="manage_edit" method="post">
+
+<table>     
+            
+<tr>    <th align='LEFT'>Id</th>
+        <td align='LEFT'><!--#var id--></td></tr>
+<tr>	<th align='LEFT'><em>Title<em></th>
+        <td align='LEFT'><input name="title" size="40" value="<!--#var title-->"></td></tr>
+<tr>	<th align='LEFT'>Connection id</th>
+        <td align='LEFT'><select name="connection_id">
+              <!--#in LDAPConnectionIDs-->
+                <option value="<!--#var sequence-item-->"
+		<!--#if expr="connection_id==_vars['sequence-item']"-->
+                 SELECTED<!--#/if-->>
+                <!--#var sequence-key--></option>
+              <!--#/in-->
+            </select></td></tr>
+<tr>	<th align='LEFT'><em>Query Scope</em></th>
+	<td align='LEFT'><select name="scope">
+	    <!--#in "['SUBTREE', 'ONELEVEL', 'BASE']"-->
+		<option value="<!--#var sequence-item-->"
+		<!--#if expr="scope==_vars['sequence-item']"-->
+                 SELECTED<!--#/if-->><!--#var sequence-item--></option>
+	    <!--#/in-->
+	    </select></td></tr>
+<tr>	<th align='LEFT'><em>Base DN</em></th>
+        <td align='LEFT'><input name="basedn" size="40" value="<!--#var basedn fmt=html-quote-->"></td></tr>
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><input name="arguments" size="40" value="<!--#var arguments-->"></td></tr>
+<tr>    <td colspan=2 align='LEFT'><strong>Query Filter</strong><br>
+        <textarea name="filters:text" rows=9 cols=50><!--#var filters--></textarea>
+	</td></tr>
+
+<tr><td colspan=2>
+<input type="SUBMIT" name="submit" value="Change">
+</td></tr>
+
+</table>
+</form>
+
+
+<!--#else-->
+<em><strong>Warning:</strong>
+There are no LDAP connections.  You need
+to add a Zope LDAP connection
+before you can use a Zope LDAP Method.
+</em>
+<!--#/if-->
+</body></html>
diff --git a/product/ZLDAPMethods/editLdif.dtml b/product/ZLDAPMethods/editLdif.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..71bb2d8457aec413743fd053b0b50ddfd85d59a9
--- /dev/null
+++ b/product/ZLDAPMethods/editLdif.dtml
@@ -0,0 +1,50 @@
+<HTML><HEAD><TITLE>Edit LDIF Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<!--#var manage_tabs-->
+<H2>Edit LDIF Method</H2>
+
+<!--#if LDAPConnectionIDs-->
+
+
+<form action="manage_edit" method="post">
+
+<table>
+<tr>    <th align='LEFT'>Id</th>
+        <td align='LEFT'><!--#var id--></td></tr>
+<tr>    <th align='LEFT'><em>Title<em></th>
+        <td align='LEFT'><input name="title" size="40" value="<!--#var title-->"></td></tr>
+<tr>    <th align='LEFT'>Connection id</th>
+        <td align='LEFT'><select name="connection_id">
+              <!--#in LDAPConnectionIDs-->
+                <option value="<!--#var sequence-item-->"
+                <!--#if expr="connection_id==_vars['sequence-item']"-->
+                 SELECTED<!--#/if-->>
+                <!--#var sequence-key--></option>
+              <!--#/in-->
+            </select></td></tr>
+
+<tr>    <th align='LEFT'><em>Base DN</em></th>
+        <td align='LEFT'><input name="basedn" size="40" value="<!--#var basedn fmt=html-quote-->"></td></tr>
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><textarea name="arguments:text" rows="4" cols="50"><!--#var arguments--></textarea></td></tr>
+<tr>    <td colspan=2 align='LEFT'><strong>ldif</strong><br>
+        <textarea name="ldif:text" rows=20 cols=100><!--#var ldif--></textarea>
+        </td></tr>
+
+<tr><td colspan=2>
+<input type="SUBMIT" name="submit" value="Change">
+</td></tr>
+
+</table>
+</form>
+
+
+<!--#else-->
+<em><strong>Warning:</strong>
+There are no LDAP connections.  You need
+to add a Zope LDAP connection
+before you can use a Zope LDAP Method.
+</em>
+<!--#/if-->
+</body></html>
diff --git a/product/ZLDAPMethods/ldifvar.py b/product/ZLDAPMethods/ldifvar.py
new file mode 100644
index 0000000000000000000000000000000000000000..85299d69c35f397827fcfb4f44e3c896ce2a5b26
--- /dev/null
+++ b/product/ZLDAPMethods/ldifvar.py
@@ -0,0 +1,229 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+'''Inserting values with the 'ldifvar' tag
+
+    The 'ldifvar' tag is used to type-safely insert values into LDIF
+    text.  The 'ldifvar' tag is similar to the 'var' tag, except that
+    it replaces text formatting parameters with LDIF type information.
+
+    The sqlvar tag has the following attributes:
+
+      attr -- The name of the attribute to insert. As with other
+              DTML tags.
+
+      type -- The data type of the value to be inserted.  This
+              attribute is required and may be one of 'string',
+              'int', 'float', or 'nb'.  The 'nb' data type indicates a
+              string that must have a length that is greater than 0.
+
+      optional -- A flag indicating that a value is optional.  If a
+                  value is optional and is not provided (or is blank
+                  when a non-blank value is expected), then the string
+                  'null' is inserted.
+
+    For example, given the tag::
+
+      <dtml-ldifvar x attr=objectClass type=nb optional>
+
+    if the value of 'x' is::
+      
+      inetOrgPerson
+
+    then the text inserted is:
+
+      objectClass: inetOrgPerson
+
+    however, if x is ommitted or an empty string, then the value
+    inserted is ''.
+'''
+#__rcs_id__='$Id: sqlvar.py,v 1.14.10.2 2005/09/02 23:02:00 tseaver Exp $'
+
+############################################################################
+#     Copyright
+#
+#       Copyright 1996 Digital Creations, L.C., 910 Princess Anne
+#       Street, Suite 300, Fredericksburg, Virginia 22401 U.S.A. All
+#       rights reserved.
+#
+############################################################################
+#__version__='$Revision: 1.14.10.2 $'[11:-2]
+
+from DocumentTemplate.DT_Util import ParseError, parse_params, name_param
+from string import find, split, join, atoi, atof
+import sha
+StringType=type('')
+
+str=__builtins__['str']
+
+class LDIFVar:
+    name='ldifvar'
+
+    def __init__(self, args):
+        args = parse_params(args, name='', expr='', type=None, optional=1)
+
+        name,expr=name_param(args,'ldifvar',1)
+        if expr is None: expr=name
+        else: expr=expr.eval
+        self.__name__, self.expr = name, expr
+
+        self.args=args
+        if not args.has_key('type'):
+            raise ParseError, ('the type attribute is required', 'dtvar')
+        t=args['type']
+        if not valid_type(t):
+            raise ParseError, ('invalid type, %s' % t, 'dtvar')
+
+    def render(self, md):
+        name=self.__name__
+        args=self.args
+        t=args['type']
+        try:
+            expr=self.expr
+            if type(expr) is type(''): v=md[expr]
+            else: v=expr(md)
+        except:
+            if args.has_key('optional') and args['optional']:
+                return
+            if type(expr) is not type(''):
+                raise
+            raise ValueError, 'Missing input variable, <em>%s</em>' % name
+
+        if v is None:
+            return ''
+
+        if t=='int':
+            try:
+                if type(v) is StringType:
+                    if v[-1:]=='L':
+                        v=v[:-1]
+                    atoi(v)
+                else: v=str(int(v))
+            except:
+                if not v and args.has_key('optional') and args['optional']:
+                    return
+                raise ValueError, (
+                    'Invalid integer value for <em>%s</em>' % name)
+        elif t=='float':
+            try:
+                if type(v) is StringType:
+                    if v[-1:]=='L':
+                        v=v[:-1]
+                    atof(v)
+                else: v=str(float(v))
+            except:
+                if not v and args.has_key('optional') and args['optional']:
+                    return
+                raise ValueError, (
+                    'Invalid floating-point value for <em>%s</em>' % name)
+
+        else:
+            if not isinstance(v, (str, unicode)):
+                v=str(v)
+            if not v and t=='nb':
+                if args.has_key('optional') and args['optional']:
+                    return
+                else:
+                    raise ValueError, (
+                        'Invalid empty string value for <em>%s</em>' % name)
+
+        return v
+
+    __call__=render
+
+class LDIFLine:
+    name='ldifline'
+
+    def __init__(self, args):
+        args = parse_params(args, name='', expr='', attr='', type=None, optional=1)
+
+        name,expr=name_param(args,'ldifvar',1)
+        if expr is None: expr=name
+        else: expr=expr.eval
+        self.__name__, self.expr = name, expr
+
+        self.args=args
+        if not args.has_key('type'):
+            raise ParseError, ('the type attribute is required', 'ldifattr')
+        t=args['type']
+        if not valid_type(t):
+            raise ParseError, ('invalid type, %s' % t, 'dtvar')
+        
+        if not args.has_key('attr'):
+            raise ParseError, ('the attr attribute is required', 'ldifattr')
+        a=args['attr']
+
+    def render(self, md):
+        name=self.__name__
+        args=self.args
+        t=args['type']
+        a=args['attr']
+        default = '%s:' % (a)
+        try:
+            expr=self.expr
+            if type(expr) is type(''): v=md[expr]
+            else: v=expr(md)
+        except:
+            if args.has_key('optional') and args['optional']:
+                return default
+            if type(expr) is not type(''):
+                raise
+            raise ValueError, 'Missing input variable, <em>%s</em>' % name
+
+        if v is None:
+            if args.has_key('optional') and args['optional']:
+                return default
+            else:
+                raise ValueError, 'Missing input variable, <em>%s</em>' % name
+        if a in ['',None]:
+            return default
+
+        if t=='int':
+            try:
+                if type(v) is StringType:
+                    if v[-1:]=='L':
+                        v=v[:-1]
+                    atoi(v)
+                else: v=str(int(v))
+            except:
+                if not v and args.has_key('optional') and args['optional']:
+                    return default
+                raise ValueError, (
+                    'Invalid integer value for <em>%s</em>' % name)
+        elif t=='float':
+            try:
+                if type(v) is StringType:
+                    if v[-1:]=='L':
+                        v=v[:-1]
+                    atof(v)
+                else: v=str(float(v))
+            except:
+                if not v and args.has_key('optional') and args['optional']:
+                    return default
+                raise ValueError, (
+                    'Invalid floating-point value for <em>%s</em>' % name)
+
+        else:
+            if not isinstance(v, (str, unicode)):
+                v=str(v)
+            if not v and t=='nb':
+                if args.has_key('optional') and args['optional']:
+                    return default
+                else:
+                    raise ValueError, (
+                        'Invalid empty string value for <em>%s</em>' % name)
+
+        return '%s: %s' % (a, v)
+
+    __call__=render
+
+valid_type={'int':1, 'float':1, 'string':1, 'nb': 1}.has_key
diff --git a/product/ZLDAPMethods/testForm.dtml b/product/ZLDAPMethods/testForm.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..e9ead942a8bfa4196e3fbf4f84fffefcba76e8c7
--- /dev/null
+++ b/product/ZLDAPMethods/testForm.dtml
@@ -0,0 +1,27 @@
+<HTML><HEAD><TITLE>New LDAP Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<!--#var manage_tabs-->
+<H2>Test LDAP Method <!--#var title_or_id--></H2>
+
+<form action="./manage_test">
+<table>     
+            
+<tr>	<th align='LEFT'>Connection id</th>
+        <td align='LEFT'><!--#var connection_id--></td></tr>
+<tr>	<th align='LEFT'><em>Query Scope</em></th>
+	<td align='LEFT'><!--#var scope--></td></tr>
+<tr>	<th align='LEFT'><em>Base DN</em></th>
+        <td align='LEFT'><!--#var basedn fmt=html-quote-->
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><!--#var arguments-->
+
+<tr><td colspan=2>
+<input type="SUBMIT" name="submit" value="Test">
+</td></tr>
+
+</table>
+</form>
+
+</BODY>
+</HTML>
diff --git a/product/ZLDAPMethods/testLdifForm.dtml b/product/ZLDAPMethods/testLdifForm.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..e9ead942a8bfa4196e3fbf4f84fffefcba76e8c7
--- /dev/null
+++ b/product/ZLDAPMethods/testLdifForm.dtml
@@ -0,0 +1,27 @@
+<HTML><HEAD><TITLE>New LDAP Method</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<!--#var manage_tabs-->
+<H2>Test LDAP Method <!--#var title_or_id--></H2>
+
+<form action="./manage_test">
+<table>     
+            
+<tr>	<th align='LEFT'>Connection id</th>
+        <td align='LEFT'><!--#var connection_id--></td></tr>
+<tr>	<th align='LEFT'><em>Query Scope</em></th>
+	<td align='LEFT'><!--#var scope--></td></tr>
+<tr>	<th align='LEFT'><em>Base DN</em></th>
+        <td align='LEFT'><!--#var basedn fmt=html-quote-->
+<tr>    <th align='LEFT'><em>Arguments<em></th>
+        <td align='LEFT'><!--#var arguments-->
+
+<tr><td colspan=2>
+<input type="SUBMIT" name="submit" value="Test">
+</td></tr>
+
+</table>
+</form>
+
+</BODY>
+</HTML>