diff -Naur ldapdev/_ldap_7.x-2.0-beta3_thumbnailPhoto_1774936_13.diff ldapdevpatch/_ldap_7.x-2.0-beta3_thumbnailPhoto_1774936_13.diff
--- ldapdev/_ldap_7.x-2.0-beta3_thumbnailPhoto_1774936_13.diff	1970-01-01 01:00:00.000000000 +0100
+++ ldapdevpatch/_ldap_7.x-2.0-beta3_thumbnailPhoto_1774936_13.diff	2013-02-12 10:15:00.000000000 +0100
@@ -0,0 +1,6418 @@
+*** ldap_servers/ldap_servers.install	Fri Dec 07 05:38:15 2012
+--- ldap_servers/ldap_servers.install	Wed Feb 06 11:06:04 2013
+***************
+*** 92,98 ****
+      'unique keys' => array('name' => array('name')),
+    );
+  
+!   $fields = LdapServerAdmin::fields();
+    foreach ($fields as $name => $props) {
+      if (isset($props['schema'])) {
+        $schema['ldap_servers']['fields'][$name] = $props['schema'];
+--- 92,98 ----
+      'unique keys' => array('name' => array('name')),
+    );
+  
+!   $fields = LdapServerAdmin::fields();  
+    foreach ($fields as $name => $props) {
+      if (isset($props['schema'])) {
+        $schema['ldap_servers']['fields'][$name] = $props['schema'];
+***************
+*** 757,762 ****
+--- 757,778 ----
+  
+  }
+  
++ /**
++  * Add picture_attr field in schema
++  * 
++  * 
++  */
++ function ldap_servers_update_7204() {
++ 	db_add_field(
++ 			'ldap_servers',			
++ 			'picture_attr',
++ 			array(
++ 					'type' => 'varchar',
++ 					'length' => 255,
++ 					'not null' => FALSE,
++ 			)
++ 	);		
++ }
+  
+  function ldap_servers_install_update_schema($schema, &$change_log) {
+    foreach ($schema as $table_name => $table_schema) {
+*** ldap_servers/ldap_servers.module	Fri Dec 07 05:38:15 2012
+--- ldap_servers/ldap_servers.module	Tue Feb 05 16:46:07 2013
+***************
+*** 326,331 ****
+--- 326,334 ----
+        if ($ldap_server->mail_template) {
+          ldap_servers_token_extract_attributes($attributes,  $ldap_server->mail_template);
+        }
++       if(!isset($attributes[$ldap_server->picture_attr])){
++       	$attributes[$ldap_server->picture_attr] = ldap_servers_set_attribute_map();
++       }
+        if (!isset($attributes[$ldap_server->user_attr])) {
+          $attributes[$ldap_server->user_attr] = ldap_servers_set_attribute_map();
+        }
+***************
+*** 456,461 ****
+--- 459,478 ----
+          'config_module' => 'ldap_server',
+          'prov_module' => 'ldap_user',
+        ) + $available_user_attrs['[property.mail]'];
++       
++       if (!isset($available_user_attrs['[property.picture]']) || !is_array($available_user_attrs['[property.picture]'])) {
++       	$available_user_attrs['[property.picture]'] = array();
++       }
++       $available_user_attrs['[property.picture]'] = array(
++       		'name' => 'Property: Picture',
++       		'source' => '[' . $ldap_server->picture_attr . ']',
++       		'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER,
++       		'enabled' => TRUE,
++       		'prov_events' => array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER),
++       		'config_module' => 'ldap_server',
++       		'prov_module' => 'ldap_user',
++       ) + $available_user_attrs['[property.picture]'];
++       
+      }
+    }
+   // debug('available_user_attrs2'); debug($available_user_attrs);
+*** ldap_servers/LdapServer.class.php	Fri Dec 07 05:38:15 2012
+--- ldap_servers/LdapServer.class.php	Mon Feb 11 14:12:13 2013
+***************
+*** 15,1755 ****
+   * @todo make bindpw protected
+   */
+  class LdapServer {
+!   // LDAP Settings
+  
+!   const LDAP_CONNECT_ERROR = 0x5b;
+!   const LDAP_SUCCESS = 0x00;
+!   const LDAP_OPERATIONS_ERROR = 0x01;
+!   const LDAP_PROTOCOL_ERROR = 0x02;
+! 
+!   public $sid;
+!   public $numericSid;
+!   public $name;
+!   public $status;
+!   public $ldap_type;
+!   public $address;
+!   public $port = 389;
+!   public $tls = FALSE;
+!   public $bind_method = 0;
+!   public $basedn = array();
+!   public $binddn = FALSE; // Default to an anonymous bind.
+!   public $bindpw = FALSE; // Default to an anonymous bind.
+!   public $user_dn_expression;
+!   public $user_attr;
+!   public $account_name_attr; //lowercase
+!   public $mail_attr; //lowercase
+!   public $mail_template;
+!   public $unique_persistent_attr; //lowercase
+!   public $unique_persistent_attr_binary = FALSE;
+!   public $ldapToDrupalUserPhp;
+!   public $testingDrupalUsername;
+!   public $testingDrupalUserDn;
+!   public $detailed_watchdog_log;
+!   public $editPath;
+!   public $queriableWithoutUserCredentials = FALSE; // can this server be queried without user credentials provided?
+!   public $userAttributeNeededCache = array(); // array of attributes needed keyed on $op such as 'user_update'
+! 
+!   public $groupFunctionalityUnused = 0;
+!   public $groupObjectClass;
+!   public $groupNested = 0; // 1 | 0
+!   public $groupDeriveFromDn = FALSE;
+!   public $groupDeriveFromDnAttr = NULL; //lowercase
+!   public $groupUserMembershipsAttrExists = FALSE; // does a user attribute containing groups exist?
+!   public $groupUserMembershipsAttr = NULL;   //lowercase     // name of user attribute containing groups
+!   public $groupUserMembershipsConfigured = FALSE; // user attribute containing memberships is configured enough to use
+! 
+!   public $groupMembershipsAttr = NULL;  //lowercase // members, uniquemember, memberUid
+!   public $groupMembershipsAttrMatchingUserAttr = NULL; //lowercase // dn, cn, etc contained in groupMembershipsAttr
+!   public $groupGroupEntryMembershipsConfigured = FALSE; // are groupMembershipsAttrMatchingUserAttr and groupGroupEntryMembershipsConfigured populated
+! 
+!   public $groupTestGroupDn = NULL;
+!   public $groupTestGroupDnWriteable = NULL;
+! 
+!   private $group_properties = array(
+!     'groupObjectClass', 'groupNested', 'groupDeriveFromDn', 'groupDeriveFromDnAttr', 'groupUserMembershipsAttrExists',
+!     'groupUserMembershipsAttr', 'groupMembershipsAttrMatchingUserAttr', 'groupTestGroupDn', 'groupTestGroupDnWriteable'
+!   );
+! 
+!   public $paginationEnabled = FALSE; // (boolean)(function_exists('ldap_control_paged_result_response') && function_exists('ldap_control_paged_result'));
+!   public $searchPagination = FALSE;
+!   public $searchPageSize = 1000;
+!   public $searchPageStart = 0;
+!   public $searchPageEnd = NULL;
+! 
+!   public $inDatabase = FALSE;
+!   public $connection;
+! 
+! 
+! 
+! 
+!   // direct mapping of db to object properties
+!   public static function field_to_properties_map() {
+!     return array(
+!     'sid' => 'sid',
+!     'numeric_sid' => 'numericSid',
+!     'name'  => 'name' ,
+!     'status'  => 'status',
+!     'ldap_type'  => 'ldap_type',
+!     'address'  => 'address',
+!     'port'  => 'port',
+!     'tls'  => 'tls',
+!     'bind_method' => 'bind_method',
+!     'basedn'  => 'basedn',
+!     'binddn'  => 'binddn',
+!     'user_dn_expression' => 'user_dn_expression',
+!     'user_attr'  => 'user_attr',
+!     'account_name_attr'  => 'account_name_attr',
+!     'mail_attr'  => 'mail_attr',
+!     'mail_template'  => 'mail_template',
+!     'unique_persistent_attr' => 'unique_persistent_attr',
+!     'unique_persistent_attr_binary' => 'unique_persistent_attr_binary',
+!     'ldap_to_drupal_user'  => 'ldapToDrupalUserPhp',
+!     'testing_drupal_username'  => 'testingDrupalUsername',
+!     'testing_drupal_user_dn'  => 'testingDrupalUserDn',
+! 
+!     'grp_unused' => 'groupFunctionalityUnused',
+!     'grp_object_cat' => 'groupObjectClass',
+!     'grp_nested' => 'groupNested',
+!     'grp_user_memb_attr_exists' => 'groupUserMembershipsAttrExists',
+!     'grp_user_memb_attr' => 'groupUserMembershipsAttr',
+!     'grp_memb_attr' => 'groupMembershipsAttr',
+!     'grp_memb_attr_match_user_attr' => 'groupMembershipsAttrMatchingUserAttr',
+!     'grp_derive_from_dn' => 'groupDeriveFromDn',
+!     'grp_derive_from_dn_attr' => 'groupDeriveFromDnAttr',
+!     'grp_test_grp_dn' =>  'groupTestGroupDn',
+!     'grp_test_grp_dn_writeable' => 'groupTestGroupDnWriteable',
+! 
+!     'search_pagination' => 'searchPagination',
+!     'search_page_size' => 'searchPageSize',
+! 
+!     );
+! 
+!   }
+! 
+!   /**
+!    * Constructor Method
+!    */
+!   function __construct($sid) {
+!     if (!is_scalar($sid)) {
+!       return;
+!     }
+!     $this->detailed_watchdog_log = variable_get('ldap_help_watchdog_detail', 0);
+!     $server_record = FALSE;
+!     if (module_exists('ctools')) {
+!       ctools_include('export');
+!       $result = ctools_export_load_object('ldap_servers', 'names', array($sid));
+!       if (isset($result[$sid])) {
+!         $server_record = new stdClass();
+!         foreach ($result[$sid] as $db_field_name => $value) {
+!           $server_record->{$db_field_name} = $value;
+!         }
+!       }
+!       //debug('ctools record'); debug($server_record);
+!     }
+!     else {
+!       $select = db_select('ldap_servers')
+!         ->fields('ldap_servers')
+!         ->condition('ldap_servers.sid',  $sid)
+!         ->execute();
+!       foreach ($select as $record) {
+!         if ($record->sid == $sid) {
+!           $server_record = $record;
+!         }
+!       }
+!     //  debug('db record'); debug($server_record);
+!     }
+! 
+!     if (!$server_record) {
+!       $this->inDatabase = FALSE;
+!     }
+!     else {
+!       $this->inDatabase = TRUE;
+!       $this->sid = $sid;
+!       $this->detailedWatchdogLog = variable_get('ldap_help_watchdog_detail', 0);
+!      // debug('this server_record'); debug($server_record);
+!       foreach ($this->field_to_properties_map() as $db_field_name => $property_name ) {
+!         if (isset($server_record->$db_field_name)) {
+!           $this->{$property_name} = $server_record->$db_field_name;
+!         }
+!       }
+!      // debug('this 2'); debug($this);
+!       $server_record_bindpw = property_exists($server_record, 'bindpw') ? $server_record->bindpw : '';
+!       $this->initDerivedProperties($server_record_bindpw);
+!     //  debug('this 3'); debug($this);
+!     }
+! 
+!   }
+! 
+!   /**
+!    * this method sets properties that don't directly map from db record
+!    */
+!   protected function initDerivedProperties($bindpw) {
+! 
+!    // debug('initDerivedProperties'); debug($this->basedn);
+!     if (!is_array($this->basedn)) {
+!       $basedn_unserialized = @unserialize($this->basedn);
+!      // debug('basedn_unserialized'); debug($basedn_unserialized);
+!       $this->basedn = $basedn_unserialized;
+!     }
+!    // debug('initDerivedProperties'); debug($this->basedn);
+! 
+!     if ($bindpw != '') {
+!       $this->bindpw = ldap_servers_decrypt($bindpw);
+!     }
+! 
+!     $this->paginationEnabled = (boolean)(ldap_servers_php_supports_pagination() && $this->searchPagination);
+! 
+!     $this->queriableWithoutUserCredentials = (boolean)(
+!       $this->bind_method == LDAP_SERVERS_BIND_METHOD_SERVICE_ACCT ||
+!       $this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER
+!     );
+!     $this->editPath = 'admin/config/people/ldap/servers/edit/' . $this->sid;
+! 
+!     $this->groupGroupEntryMembershipsConfigured = ($this->groupMembershipsAttrMatchingUserAttr && $this->groupMembershipsAttr);
+!     $this->groupUserMembershipsConfigured = ($this->groupUserMembershipsAttrExists && $this->groupUserMembershipsAttr);
+!   }
+!   /**
+!    * Destructor Method
+!    */
+!   function __destruct() {
+!     // Close the server connection to be sure.
+!     $this->disconnect();
+!   }
+! 
+! 
+!   /**
+!    * Invoke Method
+!    */
+!   function __invoke() {
+!     $this->connect();
+!     $this->bind();
+!   }
+! 
+! 
+! 
+!   /**
+!    * Connect Method
+!    */
+!   function connect() {
+! 
+!     if (!$con = ldap_connect($this->address, $this->port)) {
+!       watchdog('user', 'LDAP Connect failure to ' . $this->address . ':' . $this->port);
+!       return LDAP_CONNECT_ERROR;
+!     }
+! 
+!     ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
+!     ldap_set_option($con, LDAP_OPT_REFERRALS, 0);
+! 
+!     // Use TLS if we are configured and able to.
+!     if ($this->tls) {
+!       ldap_get_option($con, LDAP_OPT_PROTOCOL_VERSION, $vers);
+!       if ($vers == -1) {
+!         watchdog('user', 'Could not get LDAP protocol version.');
+!         return LDAP_PROTOCOL_ERROR;
+!       }
+!       if ($vers != 3) {
+!         watchdog('user', 'Could not start TLS, only supported by LDAP v3.');
+!         return LDAP_CONNECT_ERROR;
+!       }
+!       elseif (!function_exists('ldap_start_tls')) {
+!         watchdog('user', 'Could not start TLS. It does not seem to be supported by this PHP setup.');
+!         return LDAP_CONNECT_ERROR;
+!       }
+!       elseif (!ldap_start_tls($con)) {
+!         $msg =  t("Could not start TLS. (Error %errno: %error).", array('%errno' => ldap_errno($con), '%error' => ldap_error($con)));
+!         watchdog('user', $msg);
+!         return LDAP_CONNECT_ERROR;
+!       }
+!     }
+! 
+!   // Store the resulting resource
+!   $this->connection = $con;
+!   return LDAP_SUCCESS;
+!   }
+  
+  
+!   /**
+  	 * Bind (authenticate) against an active LDAP database.
+  	 *
+  	 * @param $userdn
+  	 *   The DN to bind against. If NULL, we use $this->binddn
+  	 * @param $pass
+  	 *   The password search base. If NULL, we use $this->bindpw
+!    *
+!    * @return
+!    *   Result of bind; TRUE if successful, FALSE otherwise.
+!    */
+!   function bind($userdn = NULL, $pass = NULL, $anon_bind = FALSE) {
+! 
+!     // Ensure that we have an active server connection.
+!     if (!$this->connection) {
+!       watchdog('ldap', "LDAP bind failure for user %user. Not connected to LDAP server.", array('%user' => $userdn));
+!       return LDAP_CONNECT_ERROR;
+!     }
+!     if ($anon_bind) {
+!       if (@!ldap_bind($this->connection)) {
+!         if ($this->detailedWatchdogLog) {
+!           watchdog('ldap', "LDAP anonymous bind error. Error %errno: %error", array('%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)));
+!         }
+!         return ldap_errno($this->connection);
+!       }
+!     }
+!     else {
+!       $userdn = ($userdn != NULL) ? $userdn : $this->binddn;
+!       $pass = ($pass != NULL) ? $pass : $this->bindpw;
+!       if (@!ldap_bind($this->connection, $userdn, $pass)) {
+!         if ($this->detailedWatchdogLog) {
+!           watchdog('ldap', "LDAP bind failure for user %user. Error %errno: %error", array('%user' => $userdn, '%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)));
+!         }
+!         return ldap_errno($this->connection);
+!       }
+!     }
+! 
+!     return LDAP_SUCCESS;
+!   }
+! 
+!   /**
+!    * Disconnect (unbind) from an active LDAP server.
+!    */
+!   function disconnect() {
+!     if (!$this->connection) {
+!       // never bound or not currently bound, so no need to disconnect
+!       //watchdog('ldap', 'LDAP disconnect failure from '. $this->server_addr . ':' . $this->port);
+!     }
+!     else {
+!       ldap_unbind($this->connection);
+!       $this->connection = NULL;
+!     }
+!   }
+! 
+!   public function connectAndBindIfNotAlready() {
+!     if (! $this->connection) {
+!       $this->connect();
+!       $this->bind();
+!     }
+!   }
+  
+  /**
+!  * does dn exist for this server?
+!  * [ ] Finished
+!  * [ ] Test Coverage.  Test ID:
+!  * [ ] Case insensitive
+!  *
+!  * @param string $dn
+!  * @param enum $return = 'boolean' or 'ldap_entry'
+!  * @param array $attributes in same form as ldap_read $attributes parameter
+   *
+!  * @param return FALSE or ldap entry array
+   */
+!   function dnExists($dn, $return = 'boolean', $attributes = NULL) {
+  
+-     $params = array(
+-       'base_dn' => $dn,
+-       'attributes' => $attributes,
+-       'attrsonly' => FALSE,
+-       'filter' => '(objectclass=*)',
+-       'sizelimit' => 0,
+-       'timelimit' => 0,
+-       'deref' => NULL,
+-     );
+- 
+-     if ($return == 'boolean' || !is_array($attributes)) {
+-       $params['attributes'] = array('objectclass');
+-     }
+-     else {
+-       $params['attributes'] = $attributes;
+-     }
+- 
+-     $result = $this->ldapQuery(LDAP_SCOPE_BASE, $params);
+-     if ($result !== FALSE) {
+-       if ($return == 'boolean') {
+-         return TRUE;
+-       }
+- 
+-       $entries = @ldap_get_entries($this->connection, $result);
+-       if ($entries !== FALSE) {
+-         return $entries[0];
+-       }
+-     }
+- 
+-     return FALSE;
+- 
+-   }
+- 
+-   /**
+-    * @param $ldap_result as ldap link identifier
+-    *
+-    * @return FALSE on error or number of entries.
+-    *   (if 0 entries will return 0)
+-    */
+-   public function countEntries($ldap_result) {
+-     return ldap_count_entries($this->connection, $ldap_result);
+-   }
+- 
+- 
+- 
+-   /**
+-    * create ldap entry.
+-    *
+-    * @param array $attributes should follow the structure of ldap_add functions
+-    *   entry array: http://us.php.net/manual/en/function.ldap-add.php
+-         $attributes["attribute1"] = "value";
+-         $attributes["attribute2"][0] = "value1";
+-         $attributes["attribute2"][1] = "value2";
+-    * @return boolean result
+-    */
+- 
+-   public function createLdapEntry($attributes, $dn = NULL) {
+-     // dpm("createLdapEntry, dn=$dn"); dpm($ldap_entry);
+-     if (!$this->connection) {
+-       $this->connect();
+-       $this->bind();
+-     }
+-     if (isset($attributes['dn'])) {
+-       $dn = $attributes['dn'];
+-       unset($attributes['dn']);
+-     }
+-     elseif (!$dn) {
+-       return FALSE;
+-     }
+- 
+-    // debug("createLdapEntry, dn=$dn, entry:"); debug($attributes);
+-     $result = @ldap_add($this->connection, $dn, $attributes);
+-     if (!$result) {
+-       $error = "LDAP Server ldap_add(%dn) Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+-       $tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+-       debug(t($error, $tokens));
+-       watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+-     }
+  
+  
+!     return $result;
+!   }
+  
+  
+  
+  /**
+!  * given 2 ldap entries, old and new, removed unchanged values to avoid security errors and incorrect date modifieds
+   *
+!  * @param ldap entry array $new_entry in form <attribute> => <value>
+!  * @param ldap entry array $old_entry in form <attribute> => array('count' => N, array(<value>,...<value>
+   *
+!  * @return ldap array with no values that have NOT changed
+   */
+  
+!   static public function removeUnchangedAttributes($new_entry, $old_entry) {
+  
+!     foreach ($new_entry as $key => $new_val) {
+!       $old_value = FALSE;
+!       $key_lcase = drupal_strtolower($key);
+!       if (isset($old_entry[$key_lcase])) {
+!         if ($old_entry[$key_lcase]['count'] == 1) {
+!           $old_value = $old_entry[$key_lcase][0];
+!           $old_value_is_scalar = TRUE;
+!         }
+!         else {
+!           unset($old_entry[$key_lcase]['count']);
+!           $old_value = $old_entry[$key_lcase];
+!           $old_value_is_scalar = FALSE;
+!         }
+!       }
+! 
+!       // identical multivalued attributes
+!       if (is_array($new_val) && is_array($old_value) && count(array_diff($new_val, $old_value)) == 0) {
+!         unset($new_entry[$key]);
+!       }
+!       elseif ($old_value_is_scalar && !is_array($new_val) && drupal_strtolower($old_value) == drupal_strtolower($new_val)) {
+!         unset($new_entry[$key]); // don't change values that aren't changing to avoid false permission constraints
+!       }
+!     }
+!     return $new_entry;
+!   }
+! 
+! 
+! 
+! 
+! 
+!   /**
+!    * modify attributes of ldap entry
+!    *
+!    * @param string $dn DN of entry
+!    * @param array $attributes should follow the structure of ldap_add functions
+!    *   entry array: http://us.php.net/manual/en/function.ldap-add.php
+!         $attributes["attribute1"] = "value";
+!         $attributes["attribute2"][0] = "value1";
+!         $attributes["attribute2"][1] = "value2";
+! 
+!     @return TRUE on success FALSE on error
+!    */
+! 
+!   function modifyLdapEntry($dn, $attributes = array(), $old_attributes = FALSE) {
+! 
+!     $this->connectAndBindIfNotAlready();
+! 
+!     if (!$old_attributes) {
+!       $result = @ldap_read($this->connection, $dn, 'objectClass=*');
+!       if (!$result) {
+!         $error = "LDAP Server ldap_read(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+!         $tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+!         watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+!         return FALSE;
+!       }
+! 
+!       $entries = ldap_get_entries($this->connection, $result);
+!       if (is_array($entries) && $entries['count'] == 1) {
+!         $old_attributes =  $entries[0];
+!       }
+!     }
+!     $attributes = $this->removeUnchangedAttributes($attributes, $old_attributes);
+! 
+!     foreach ($attributes as $key => $cur_val) {
+!       $old_value = FALSE;
+!       $key_lcase = drupal_strtolower($key);
+!       if (isset($old_attributes[$key_lcase])) {
+!         if ($old_attributes[$key_lcase]['count'] == 1) {
+!           $old_value = $old_attributes[$key_lcase][0];
+!         }
+!         else {
+!           unset($old_attributes[$key_lcase]['count']);
+!           $old_value = $old_attributes[$key_lcase];
+!         }
+!       }
+! 
+!       if ($cur_val == '' && $old_value != '') { // remove enpty attributes
+!         unset($attributes[$key]);
+!         $result = @ldap_mod_del($this->connection, $dn, array($key_lcase => $old_value));
+!         if (!$result) {
+!           $error = "LDAP Server ldap_mod_del(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+!           $tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+!           watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+!           return FALSE;
+!         }
+!       }
+!       elseif (is_array($cur_val)) {
+!         foreach ($cur_val as $mv_key => $mv_cur_val) {
+!           if ($mv_cur_val == '') {
+!             unset($attributes[$key][$mv_key]); // remove empty values in multivalues attributes
+!           }
+!           else {
+!             $attributes[$key][$mv_key] = $mv_cur_val;
+!           }
+!         }
+!       }
+!     }
+!   //  dpm('modifyLdapEntry, attributes to modify'); dpm($attributes);
+!     if (count($attributes) > 0) {
+!       $result = @ldap_modify($this->connection, $dn, $attributes);
+!       if (!$result) {
+!         $error = "LDAP Server ldap_modify(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+!         $tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+!         watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+!         return FALSE;
+!       }
+!     }
+! 
+!     return TRUE;
+! 
+!   }
+! 
+!   /**
+!    * Perform an LDAP delete.
+!    *
+!    * @param string $dn
+!    *
+!    * @return boolean result per ldap_delete
+!    */
+! 
+!   public function delete($dn) {
+!     if (!$this->connection) {
+!       $this->connect();
+!       $this->bind();
+!     }
+!     $result = @ldap_delete($this->connection, $dn);
+!     if (!$result) {
+!       $error = "LDAP Server delete(%dn) in LdapServer::delete() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+!       $tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+!       watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+!     }
+!     return $result;
+!   }
+! 
+!   /**
+!    * Perform an LDAP search on all base dns and aggregate into one result
+!    *
+!    * @param string $filter
+!    *   The search filter. such as sAMAccountName=jbarclay.  attribute values (e.g. jbarclay) should be esacaped before calling
+! 
+!    * @param array $attributes
+!    *   List of desired attributes. If omitted, we only return "dn".
+!    *
+!    * @remaining params mimick ldap_search() function params
+!    *
+!    * @return
+!    *   An array of matching entries->attributes (will have 0
+!    *   elements if search returns no results),
+!    *   or FALSE on error on any of the basedn queries
+!    */
+! 
+!   public function searchAllBaseDns(
+!     $filter,
+!     $attributes = array(),
+!     $attrsonly = 0,
+!     $sizelimit = 0,
+!     $timelimit = 0,
+!     $deref = NULL,
+!     $scope = LDAP_SCOPE_SUBTREE
+!     ) {
+!     $all_entries = array();
+!     foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+!       $entries = $this->search($base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $scope);  // no attributes, just dns needed
+!       if ($entries === FALSE) { // if error in any search, return false
+!         return FALSE;
+!       }
+!       if (count($all_entries) == 0) {
+!         $all_entries = $entries;
+!       }
+!       else {
+!         $existing_count = $all_entries['count'];
+!         unset($entries['count']);
+!         foreach ($entries as $i => $entry) {
+!           $all_entries[$existing_count + $i] = $entry;
+!         }
+!         $all_entries['count'] = count($all_entries);
+!       }
+!     }
+! 
+!     return $all_entries;
+! 
+!   }
+! 
+! 
+!   /**
+!    * Perform an LDAP search.
+!    * @param string $basedn
+!    *   The search base. If NULL, we use $this->basedn. should not be esacaped
+!    *
+!    * @param string $filter
+!    *   The search filter. such as sAMAccountName=jbarclay.  attribute values (e.g. jbarclay) should be esacaped before calling
+! 
+!    * @param array $attributes
+!    *   List of desired attributes. If omitted, we only return "dn".
+!    *
+!    * @remaining params mimick ldap_search() function params
+!    *
+!    * @return
+!    *   An array of matching entries->attributes (will have 0
+!    *   elements if search returns no results),
+!    *   or FALSE on error.
+!    */
+! 
+!   function search($base_dn = NULL, $filter, $attributes = array(),
+!     $attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = NULL, $scope = LDAP_SCOPE_SUBTREE) {
+! 
+!      /**
+!       * pagingation issues:
+!       * -- see documentation queue: http://markmail.org/message/52w24iae3g43ikix#query:+page:1+mid:bez5vpl6smgzmymy+state:results
+!       * -- wait for php 5.4? https://svn.php.net/repository/php/php-src/tags/php_5_4_0RC6/NEWS (ldap_control_paged_result
+!       * -- http://sgehrig.wordpress.com/2009/11/06/reading-paged-ldap-results-with-php-is-a-show-stopper/
+!       */
+! 
+! 
+!     if ($base_dn == NULL) {
+!       if (count($this->basedn) == 1) {
+!         $base_dn = $this->basedn[0];
+!       }
+!       else {
+!         return FALSE;
+!       }
+!     }
+! 
+!     $attr_display =  is_array($attributes) ? join(',', $attributes) : 'none';
+!     $query = 'ldap_search() call: ' . join(",\n", array(
+!       'base_dn: ' . $base_dn,
+!       'filter = ' . $filter,
+!       'attributes: ' . $attr_display,
+!       'attrsonly = ' . $attrsonly,
+!       'sizelimit = ' . $sizelimit,
+!       'timelimit = ' . $timelimit,
+!       'deref = ' . $deref,
+!       'scope = ' . $scope,
+!       )
+!     );
+!     if ($this->detailed_watchdog_log) {
+!       watchdog('ldap_server', $query, array());
+!     }
+! 
+!     // When checking multiple servers, there's a chance we might not be connected yet.
+!     if (! $this->connection) {
+!       $this->connect();
+!       $this->bind();
+!     }
+! 
+!     $ldap_query_params = array(
+!       'connection' => $this->connection,
+!       'base_dn' => $base_dn,
+!       'filter' => $filter,
+!       'attributes' => $attributes,
+!       'attrsonly' => $attrsonly,
+!       'sizelimit' => $sizelimit,
+!       'timelimit' => $timelimit,
+!       'deref' => $deref,
+!       'query_display' => $query,
+!       'scope' => $scope,
+!     );
+!    // dpm($ldap_query_params); dpm("searchPagination=" . $this->searchPagination .",paginationEnabled=". $this->paginationEnabled .", searchPageStart=" . $this->searchPageStart);
+!     if ($this->searchPagination && $this->paginationEnabled) {
+!       $aggregated_entries = $this->pagedLdapQuery($ldap_query_params);
+!       return $aggregated_entries;
+!     }
+!     else {
+!       $result = $this->ldapQuery($scope, $ldap_query_params);
+!       if ($result && ($this->countEntries($result) !== FALSE) ) {
+!         $entries = ldap_get_entries($this->connection, $result);
+!         drupal_alter('ldap_server_search_results', $entries, $ldap_query_params);
+!         return (is_array($entries)) ? $entries : FALSE;
+!       }
+!       elseif ($this->ldapErrorNumber()) {
+!         $watchdog_tokens =  array('%basedn' => $ldap_query_params['base_dn'], '%filter' => $ldap_query_params['filter'],
+!           '%attributes' => print_r($ldap_query_params['attributes'], TRUE), '%errmsg' => $this->errorMsg('ldap'),
+!           '%errno' => $this->ldapErrorNumber());
+!         watchdog('ldap', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes:
+!           %attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+!         return FALSE;
+!       }
+!       else {
+!         return FALSE;
+!       }
+!     }
+!   }
+! 
+! 
+!   /**
+!    * execute a paged ldap query and return entries as one aggregated array
+!    *
+!    * $this->searchPageStart and $this->searchPageEnd should be set before calling if
+!    *   a particular set of pages is desired
+!    *
+!    * @param array $ldap_query_params of form:
+!       'base_dn' => base_dn,
+!       'filter' =>  filter,
+!       'attributes' => attributes,
+!       'attrsonly' => attrsonly,
+!       'sizelimit' => sizelimit,
+!       'timelimit' => timelimit,
+!       'deref' => deref,
+!       'scope' => scope,
+! 
+!       (this array of parameters is primarily passed on to ldapQuery() method)
+!    *
+!    * @return array of ldap entries or FALSE on error.
+!    *
+!    */
+!   public function pagedLdapQuery($ldap_query_params) {
+! 
+!     if (!($this->searchPagination && $this->paginationEnabled)) {
+!       watchdog('ldap', "LDAP server pagedLdapQuery() called when functionality not available in php install or
+!         not enabled in ldap server configuration.  error. basedn: %basedn| filter: %filter| attributes:
+!          %attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+!       RETURN FALSE;
+!     }
+! 
+!     $paged_entries = array();
+!     $page_token = '';
+!     $page = 0;
+!     $estimated_entries = 0;
+!     $aggregated_entries = array();
+!     $aggregated_entries_count = 0;
+!     $has_page_results = FALSE;
+! 
+!     do {
+!       ldap_control_paged_result($this->connection, $this->searchPageSize, TRUE, $page_token);
+!       $result = $this->ldapQuery($ldap_query_params['scope'], $ldap_query_params);
+! 
+!       if ($page >= $this->searchPageStart) {
+!         $skipped_page = FALSE;
+!         if ($result && ($this->countEntries($result) !== FALSE) ) {
+!           $page_entries = ldap_get_entries($this->connection, $result);
+!           unset($page_entries['count']);
+!           $has_page_results = (is_array($page_entries) && count($page_entries) > 0);
+!           $aggregated_entries = array_merge($aggregated_entries, $page_entries);
+!           $aggregated_entries_count = count($aggregated_entries);
+!         }
+!         elseif ($this->ldapErrorNumber()) {
+!           $watchdog_tokens =  array('%basedn' => $ldap_query_params['base_dn'], '%filter' => $ldap_query_params['filter'],
+!             '%attributes' => print_r($ldap_query_params['attributes'], TRUE), '%errmsg' => $this->errorMsg('ldap'),
+!             '%errno' => $this->ldapErrorNumber());
+!           watchdog('ldap', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes:
+!             %attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+!           RETURN FALSE;
+!         }
+!         else {
+!           return FALSE;
+!         }
+!       }
+!       else {
+!         $skipped_page = TRUE;
+!       }
+!       @ldap_control_paged_result_response($this->connection, $result, $page_token, $estimated_entries);
+!       if ($ldap_query_params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+!         // false positive error thrown.  do not set result limit error when $sizelimit specified
+!       }
+!       elseif ($this->hasError()) {
+!         watchdog('ldap_server', 'ldap_control_paged_result_response() function error. LDAP Error: %message, ldap_list() parameters: %query',
+!           array('%message' => $this->errorMsg('ldap'), '%query' => $ldap_query_params['query_display']),
+!           WATCHDOG_ERROR);
+!       }
+! 
+!       if (isset($ldap_query_params['sizelimit']) && $ldap_query_params['sizelimit'] && $aggregated_entries_count >= $ldap_query_params['sizelimit']) {
+!         $discarded_entries = array_splice($aggregated_entries, $ldap_query_params['sizelimit']);
+!         break;
+!       }
+!       elseif ($this->searchPageEnd !== NULL && $page >= $this->searchPageEnd) { // user defined pagination has run out
+!         break;
+!       }
+!       elseif ($page_token === NULL || $page_token == '') { // ldap reference pagination has run out
+!         break;
+!       }
+!       $page++;
+!     } while ($skipped_page || $has_page_results);
+! 
+!     $aggregated_entries['count'] = count($aggregated_entries);
+!     return $aggregated_entries;
+!   }
+! 
+!   /**
+!    * execute ldap query and return ldap records
+!    *
+!    * @param scope
+!    * @params see pagedLdapQuery $params
+!    *
+!    * @return array of ldap entries
+!    */
+!   function ldapQuery($scope, $params) {
+! 
+!     $this->connectAndBindIfNotAlready();
+! 
+!     switch ($scope) {
+!       case LDAP_SCOPE_SUBTREE:
+!         $result = @ldap_search($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+!           $params['sizelimit'], $params['timelimit'], $params['deref']);
+!         if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+!           // false positive error thrown.  do not return result limit error when $sizelimit specified
+!         }
+!         elseif ($this->hasError()) {
+!           watchdog('ldap_server', 'ldap_search() function error. LDAP Error: %message, ldap_search() parameters: %query',
+!             array('%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']),
+!             WATCHDOG_ERROR);
+!         }
+!         break;
+! 
+!       case LDAP_SCOPE_BASE:
+!         $result = @ldap_read($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+!           $params['sizelimit'], $params['timelimit'], $params['deref']);
+!         if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+!           // false positive error thrown.  do not result limit error when $sizelimit specified
+!         }
+!         elseif ($this->hasError()) {
+!           watchdog('ldap_server', 'ldap_read() function error.  LDAP Error: %message, ldap_read() parameters: %query',
+!             array('%message' => $this->errorMsg('ldap'), '%query' => @$params['query_display']),
+!             WATCHDOG_ERROR);
+!         }
+!         break;
+! 
+!       case LDAP_SCOPE_ONELEVEL:
+!         $result = @ldap_list($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+!           $params['sizelimit'], $params['timelimit'], $params['deref']);
+!         if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+!           // false positive error thrown.  do not result limit error when $sizelimit specified
+!         }
+!         elseif ($this->hasError()) {
+!           watchdog('ldap_server', 'ldap_list() function error. LDAP Error: %message, ldap_list() parameters: %query',
+!             array('%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']),
+!             WATCHDOG_ERROR);
+!         }
+!         break;
+!     }
+!     return $result;
+!   }
+! 
+!   /**
+!    * @param array $dns Mixed Case
+!    * @return array $dns Lower Case
+!    */
+! 
+!   public function dnArrayToLowerCase($dns) {
+!     return array_keys(array_change_key_case(array_flip($dns), CASE_LOWER));
+!   }
+! 
+!   /**
+!    * @param binary or string $puid as returned from ldap_read or other ldap function
+!    *
+!    */
+!   public function userUserEntityFromPuid($puid) {
+! 
+!     if ($this->unique_persistent_attr_binary) {
+!       $puid = ldap_servers_binary($puid);
+!     }
+! 
+!    // list($account, $user_entity) = ldap_user_load_user_acct_and_entity('jkeats');
+!     //debug('userUserEntityFromPuid:account and user entity'); debug($account); debug($user_entity);
+!     $query = new EntityFieldQuery();
+!     $query->entityCondition('entity_type', 'user')
+!     ->fieldCondition('ldap_user_puid_sid', 'value', $this->sid, '=')
+!     ->fieldCondition('ldap_user_puid', 'value', $puid, '=')
+!     ->fieldCondition('ldap_user_puid_property', 'value', $this->unique_persistent_attr, '=')
+!     ->addMetaData('account', user_load(1)); // run the query as user 1
+! // ->entityCondition('bundle', 'user')
+!     $result = $query->execute();
+!    // debug("userUserEntityFromPuid: puid=$puid, sid=". $this->sid . "attr=" . $this->unique_persistent_attr); debug($result);
+!     if (isset($result['user'])) {
+!       $uids = array_keys($result['user']);
+!       if (count($uids) == 1) {
+!         $user = entity_load('user', array_keys($result['user']));
+!         return $user[$uids[0]];
+!       }
+!       else {
+!         $uids = join(',', $uids);
+!         $tokens = array('%uids' => $uids, '%puid' => $puid, '%sid' =>  $this->sid, '%ldap_user_puid_property' =>  $this->unique_persistent_attr);
+!         watchdog('ldap_server', 'multiple users (uids: %uids) with same puid (puid=%puid, sid=%sid, ldap_user_puid_property=%ldap_user_puid_property)', $tokens, WATCHDOG_ERROR);
+!         return FALSE;
+!       }
+!     }
+!     else {
+!       return FALSE;
+!     }
+! 
+!   }
+! 
+!   function userUsernameToLdapNameTransform($drupal_username, &$watchdog_tokens) {
+!     if ($this->ldapToDrupalUserPhp && module_exists('php')) {
+!       global $name;
+!       $old_name_value = $name;
+!       $name = $drupal_username;
+!       $code = "<?php global \$name; \n" . $this->ldapToDrupalUserPhp . "; \n ?>";
+!       $watchdog_tokens['%code'] = $this->ldapToDrupalUserPhp;
+!       $code_result = php_eval($code);
+!       $watchdog_tokens['%code_result'] = $code_result;
+!       $ldap_username = $code_result;
+!       $watchdog_tokens['%ldap_username'] = $ldap_username;
+!       $name = $old_name_value;  // important because of global scope of $name
+!       if ($this->detailedWatchdogLog) {
+!         watchdog('ldap_server', '%drupal_user_name tansformed to %ldap_username by applying code <code>%code</code>', $watchdog_tokens, WATCHDOG_DEBUG);
+!       }
+!     }
+!     else {
+!       $ldap_username = $drupal_username;
+!     }
+  
+!     return $ldap_username;
+  
+!   }
+  
+  
+!  /**
+!    * @param ldap entry array $ldap_entry
+!    *
+!    * @return string user's username value
+!    */
+!   public function userUsernameFromLdapEntry($ldap_entry) {
+! 
+!     $accountname = FALSE;
+!     if ($this->account_name_attr) {
+!       $accountname = (empty($ldap_entry[$this->user_attr][0])) ? FALSE : $ldap_entry[$this->account_name_attr][0];
+!     }
+!     elseif ($this->user_attr)  {
+!       $accountname = (empty($ldap_entry[$this->user_attr][0])) ? FALSE : $ldap_entry[$this->user_attr][0];
+!     }
+  
+!     return $accountname;
+!   }
+  
+   /**
+!    * @param string $dn ldap dn
+!    *
+!    * @return mixed string user's username value of FALSE
+!    */
+!   public function userUsernameFromDn($dn) {
+! 
+!     $ldap_entry = @$this->dnExists($dn, 'ldap_entry', array());
+!     if (!$ldap_entry || !is_array($ldap_entry)) {
+!       return FALSE;
+!     }
+!     else {
+!       return $this->userUsernameFromLdapEntry($ldap_entry);
+!     }
+! 
+!   }
+! 
+!   /**
+!    * @param ldap entry array $ldap_entry
+!    *
+!    * @return string user's mail value or FALSE if none present
+!    */
+!   public function userEmailFromLdapEntry($ldap_entry) {
+! 
+!     if ($ldap_entry && $this->mail_attr) { // not using template
+!       $mail = isset($ldap_entry[$this->mail_attr][0]) ? $ldap_entry[$this->mail_attr][0] : FALSE;
+!       return $mail;
+!     }
+!     elseif ($ldap_entry && $this->mail_template) {  // template is of form [cn]@illinois.edu
+!       ldap_servers_module_load_include('inc', 'ldap_servers', 'ldap_servers.functions');
+!       return ldap_servers_token_replace($ldap_entry, $this->mail_template, 'ldap_entry');
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+! 
+!   /**
+!    * @param ldap entry array $ldap_entry
+!    *
+!    * @return string user's PUID or permanent user id (within ldap) in native ldap format (no binary conversions applied)
+!    */
+!   public function userPuidFromLdapEntry($ldap_entry) {
+! 
+!     if ($this->unique_persistent_attr
+!         && isset($ldap_entry[$this->unique_persistent_attr][0])
+!         && is_scalar($ldap_entry[$this->unique_persistent_attr][0])
+!         ) {
+! 
+!       return $ldap_entry[$this->unique_persistent_attr][0];
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+!    /**
+!    *  @param mixed $user
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array)
+!    *    - ldap dn of user (string)
+!    *    - drupal username of user (string)
+!    *
+!    *  @return array $ldap_user_entry (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!   */
+!   public function user_lookup($user) {
+!     return $this->userUserToExistingLdapEntry($user);
+!   }
+!   public function userUserToExistingLdapEntry($user) {
+! 
+!     if (is_object($user)) {
+!       $user_ldap_entry = $this->userUserNameToExistingLdapEntry($user->name);
+!     }
+!     elseif (is_array($user)) {
+!       $user_ldap_entry = $user;
+!     }
+!     elseif (is_scalar($user)) {
+!       if (strpos($user, '=') === FALSE) { // username
+!         $user_ldap_entry = $this->userUserNameToExistingLdapEntry($user);
+!       }
+!       else {
+!         $user_ldap_entry = $this->dnExists($user, 'ldap_entry');
+!       }
+!     }
+!     return $user_ldap_entry;
+!   }
+! 
+!   /**
+!    * Queries LDAP server for the user.
+!    *
+!    * @param string $drupal_user_name
+!    *
+!    * @param string or int $prov_event
+!    *   This could be anything, particularly when used by other modules.  Other modules should use string like 'mymodule_myevent'
+!    *   LDAP_USER_EVENT_ALL signifies get all attributes needed by all other contexts/ops
+!    *
+!    * @return associative array representing ldap data of a user.  for example of returned value.
+!    *   'sid' => ldap server id
+!    *   'mail' => derived from ldap mail (not always populated).
+!    *   'dn'   => dn of user
+!    *   'attr' => single ldap entry array in form returned from ldap_search() extension, e.g.
+!    *   'dn' => dn of entry
+!    */
+!   function userUserNameToExistingLdapEntry($drupal_user_name, $ldap_context = NULL) {
+! 
+!     $watchdog_tokens = array('%drupal_user_name' => $drupal_user_name);
+!     $ldap_username = $this->userUsernameToLdapNameTransform($drupal_user_name, $watchdog_tokens);
+!     if (!$ldap_username) {
+!       return FALSE;
+!     }
+!     if (!$ldap_context) {
+!       $attributes = array();
+!     }
+!     else {
+!       $attribute_maps = ldap_servers_attributes_needed($this->sid, $ldap_context);
+!       $attributes = array_keys($attribute_maps);
+!     }
+! 
+!     $basedns = (is_array($this->basedn)) ? $this->basedn : array();
+!     foreach ($basedns as $basedn) {
+!       if (empty($basedn)) continue;
+!       $filter = '(' . $this->user_attr . '=' . ldap_server_massage_text($ldap_username, 'attr_value', LDAP_SERVER_MASSAGE_QUERY_LDAP) . ')';
+!       $result = $this->search($basedn, $filter, $attributes);
+!       if (!$result || !isset($result['count']) || !$result['count']) continue;
+! 
+!       // Must find exactly one user for authentication to work.
+! 
+!       if ($result['count'] != 1) {
+!         $count = $result['count'];
+!         watchdog('ldap_servers', "Error: !count users found with $filter under $basedn.", array('!count' => $count), WATCHDOG_ERROR);
+!         continue;
+!       }
+!       $match = $result[0];
+!       // These lines serve to fix the attribute name in case a
+!       // naughty server (i.e.: MS Active Directory) is messing the
+!       // characters' case.
+!       // This was contributed by Dan "Gribnif" Wilga, and described
+!       // here: http://drupal.org/node/87833
+!       $name_attr = $this->user_attr;
+! 
+!       if (isset($match[$name_attr][0])) {
+!         // leave name
+!       }
+!       elseif (isset($match[drupal_strtolower($name_attr)][0])) {
+!         $name_attr = drupal_strtolower($name_attr);
+! 
+!       }
+!       else {
+!         if ($this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER) {
+!           $result = array(
+!             'dn' =>  $match['dn'],
+!             'mail' => $this->userEmailFromLdapEntry($match),
+!             'attr' => $match,
+!             'sid' => $this->sid,
+!             );
+!           return $result;
+!         }
+!         else {
+!           continue;
+!         }
+!       }
+! 
+!       // Finally, we must filter out results with spaces added before
+!       // or after, which are considered OK by LDAP but are no good for us
+!       // We allow lettercase independence, as requested by Marc Galera
+!       // on http://drupal.org/node/97728
+!       //
+!       // Some setups have multiple $name_attr per entry, as pointed out by
+!       // Clarence "sparr" Risher on http://drupal.org/node/102008, so we
+!       // loop through all possible options.
+!       foreach ($match[$name_attr] as $value) {
+!         if (drupal_strtolower(trim($value)) == drupal_strtolower($ldap_username)) {
+!           $result = array(
+!             'dn' =>  $match['dn'],
+!             'mail' => $this->userEmailFromLdapEntry($match),
+!             'attr' => $match,
+!             'sid' => $this->sid,
+!           );
+!           return $result;
+!         }
+!       }
+!     }
+!   }
+! 
+!   /**
+!    * Is a user a member of group?
+!    *
+!    * @param string $group_dn MIXED CASE
+!    * @param mixed $user
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array)
+!    *    - ldap dn of user (array)
+!    *    - drupal user name (string)
+!    * @param enum $nested = NULL (default to server configuration), TRUE, or FALSE indicating to test for nested groups
+!    */
+!   public function groupIsMember($group_dn, $user, $nested = NULL) {
+! 
+!     $nested = ($nested === TRUE || $nested === FALSE) ? $nested : $this->groupNested;
+!     $group_dns = $this->groupMembershipsFromUser($user, 'group_dns', $nested);
+!     // while list of group dns is going to be in correct mixed case, $group_dn may not since it may be derived from user entered values
+!     // so make sure in_array() is case insensitive
+!     return (is_array($group_dns) && in_array(drupal_strtolower($group_dn), $this->dnArrayToLowerCase($group_dns)));
+!   }
+! 
+! 
+! 
+!   /**
+!    * NOT TESTED
+!    * add a group entry
+!    *
+!    * @param string $group_dn as ldap dn
+!    * @param array $attributes in key value form
+!    *    $attributes = array(
+!    *      "attribute1" = "value",
+!    *      "attribute2" = array("value1", "value2"),
+!    *      )
+!    * @return boolean success
+!    */
+!   public function groupAddGroup($group_dn, $attributes = array()) {
+! 
+!     //debug("this->dnExists(   $group_dn, boolean)"); debug($this->dnExists($group_dn, 'boolean'));
+!    // debug("this->dnExists(   $group_dn, boolean)"); debug($this->dnExists($group_dn));
+!     if ($this->dnExists($group_dn, 'boolean')) {
+!       return FALSE;
+!     }
+! 
+!     $attributes = array_change_key_case($attributes, CASE_LOWER);
+!     $objectclass = (empty($attributes['objectclass'])) ? $this->groupObjectClass : $attributes['objectclass'];
+!     $attributes['objectclass'] = $objectclass;
+! 
+!     /**
+!      * 2. give other modules a chance to add or alter attributes
+!      */
+!     $context = array(
+!       'action' => 'add',
+!       'corresponding_drupal_data' => array($group_dn => $attributes),
+!       'corresponding_drupal_data_type' => 'group',
+!     );
+!     $ldap_entries = array($group_dn => $attributes);
+!     drupal_alter('ldap_entry_pre_provision', $ldap_entries, $this, $context);
+!     $attributes = $ldap_entries[$group_dn];
+! 
+! 
+!      /**
+!      * 4. provision ldap entry
+!      *   @todo how is error handling done here?
+!      */
+!     $ldap_entry_created = $this->createLdapEntry($attributes, $group_dn);
+! 
+! 
+!      /**
+!      * 5. allow other modules to react to provisioned ldap entry
+!      *   @todo how is error handling done here?
+!      */
+!     if ($ldap_entry_created) {
+!       module_invoke_all('ldap_entry_post_provision', $ldap_entries, $this, $context);
+!       return TRUE;
+!     }
+!     else {
+!       return FALSE;
+!     }
+! 
+!   }
+! 
+!   /**
+!    * NOT TESTED
+!    * remove a group entry
+!    *
+!    * @param string $group_dn as ldap dn
+!    * @param boolean $only_if_group_empty
+!    *   TRUE = group should not be removed if not empty
+!    *   FALSE = groups should be deleted regardless of members
+!    */
+!   public function groupRemoveGroup($group_dn, $only_if_group_empty = TRUE) {
+! 
+!     if ($only_if_group_empty) {
+!       $members = $this->groupAllMembers($group_dn);
+!       if (is_array($members) && count($members) > 0) {
+!         return FALSE;
+!       }
+!     }
+! 
+!     return $this->delete($group_dn);
+! 
+!   }
+! 
+!   /**
+!    * NOT TESTED
+!    * add a member to a group
+!    *
+!    * @param string $ldap_user_dn as ldap dn
+!    * @param mixed $user
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    */
+!   public function groupAddMember($group_dn, $user) {
+! 
+!     $user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+!     $result = FALSE;
+!     if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) {
+!       $add = array();
+!       $add[$this->groupMembershipsAttr] = $user_ldap_entry['dn'];
+!       $this->connectAndBindIfNotAlready();
+!       $result = @ldap_mod_add($this->connection, $group_dn, $add);
+!     }
+! 
+!     return $result;
+!   }
+! 
+!   /**
+!    * NOT TESTED
+!    * remove a member from a group
+!    *
+!    * @param string $group_dn as ldap dn
+!    * @param mixed $user
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    */
+!   public function groupRemoveMember($group_dn, $user) {
+! 
+!     $user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+!     $result = FALSE;
+!     if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) {
+!       $del = array();
+!       $del[$this->groupMembershipsAttr] = $user_ldap_entry['dn'];
+!       $this->connectAndBindIfNotAlready();
+!       $result = @ldap_mod_del($this->connection, $group_dn, $del);
+!     }
+!     return $result;
+!   }
+! 
+! 
+!   /**
+!    *
+!    * @todo: NOT IMPLEMENTED: nested groups
+!    *
+!    * get all members of a group
+!    *
+!    * @param string $group_dn as ldap dn
+!    *
+!    * @return FALSE on error otherwise array of group members (could be users or groups)
+!    */
+!   public function groupAllMembers($group_dn) {
+!    // debug("groupAllMembers $group_dn, this->groupMembershipsAttr=". $this->groupMembershipsAttr . 'this->groupGroupEntryMembershipsConfigured=' . $this->groupGroupEntryMembershipsConfigured);
+!     if (!$this->groupGroupEntryMembershipsConfigured) {
+!       return FALSE;
+!     }
+!     $attributes = array($this->groupMembershipsAttr, 'cn');
+!     $group_entry = $this->dnExists($group_dn, 'ldap_entry', $attributes);
+!     if (!$group_entry) {
+!       return FALSE;
+!     }
+!     else {
+!       if (empty($group_entry['cn'])) { // if attributes weren't returned, don't give false  empty group
+!         return FALSE;
+!       }
+!       if (empty($group_entry[$this->groupMembershipsAttr])) {
+!         return array(); // if no attribute returned, no members
+!       }
+!       $members = $group_entry[$this->groupMembershipsAttr];
+!       if (isset($members['count'])) {
+!         unset($members['count']);
+!       }
+!       return $members;
+!     }
+  
+  
+  
+!     $this->groupMembersResursive($current_group_entries, $all_group_dns, $tested_group_ids, 0, $max_levels, $object_classes);
+  
+!   //  dpm('all_group_dns'); dpm($all_group_dns);
+!     return $all_group_dns;
+  
+!   }
+  
+  /**
+!    *   NOT IMPLEMENTED
+!    * recurse through all child groups and add members.
+!    *
+!    * @param array $current_group_entries of ldap group entries that are starting point.  should include at least 1 entry.
+!    * @param array $all_group_dns as array of all groups user is a member of.  MIXED CASE VALUES
+!    * @param array $tested_group_ids as array of tested group dn, cn, uid, etc.  MIXED CASE VALUES
+!    *   whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
+!    *   whatever attribute is in $this->$tested_group_ids to avoid redundant recursing
+!    * @param int $level of recursion
+!    * @param int $max_levels as max recursion allowed
+!    *
+!    */
+! 
+!   public function groupMembersResursive($current_member_entries, &$all_member_dns, &$tested_group_ids, $level, $max_levels, $object_classes = FALSE) {
+!    // dpm("group membership recursive"); dpm($current_entries);
+!     if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_member_entries) || count($current_member_entries) == 0) {
+!       return FALSE;
+!     }
+!     if (isset($current_member_entries['count'])) {
+!       unset($current_member_entries['count']);
+!     };
+! 
+!     foreach ($current_member_entries as $i => $member_entry) {
+!       //dpm("groupMembersResursive:member_entry $i, level=$level < max_levels=$max_levels"); dpm($member_entry);
+!       // 1.  Add entry itself if of the correct type to $all_member_dns
+!       $objectClassMatch = (!$object_classes || (count(array_intersect(array_values($member_entry['objectclass']), $object_classes)) > 0));
+!       $objectIsGroup = in_array($this->groupObjectClass, array_values($member_entry['objectclass']));
+!       if ($objectClassMatch && !in_array($member_entry['dn'], $all_member_dns)) { // add member
+!         $all_member_dns[] = $member_entry['dn'];
+!       }
+! 
+!       // 2. If its a group, keep recurse the group for descendants
+!       if ($objectIsGroup && $level < $max_levels) {
+!         if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+!           $group_id = $member_entry['dn'];
+!         }
+!         else {
+!           $group_id = $member_entry[$this->groupMembershipsAttrMatchingUserAttr][0];
+!         }
+!         // 3. skip any groups that have already been tested
+!         if (!in_array($group_id, $tested_group_ids)) {
+!           $tested_group_ids[] = $group_id;
+!           $member_ids = $member_entry[$this->groupMembershipsAttr];
+!           if (isset($member_ids['count'])) {
+!             unset($member_ids['count']);
+!           };
+!           $ors = array();
+!           foreach ($member_ids as $i => $member_id) {
+!             $ors[] =  $this->groupMembershipsAttr . '=' . $member_id; // @todo this would be replaced by query template
+!           }
+! 
+!           if (count($ors)) {
+!             $query_for_child_members = '(|(' . join(")(", $ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
+!             if (count($object_classes)) { // add or on object classe, otherwise get all object classes
+!               $object_classes_ors = array('(objectClass=' . $this->groupObjectClass . ')');
+!               foreach ($object_classes as $object_class) {
+!                 $object_classes_ors[] = '(objectClass=' . $object_class . ')';
+!               }
+!               $query_for_child_members = '&(|' . join($object_classes_ors) . ')(' . $query_for_child_members . ')';
+!             }
+!             foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+!               $child_member_entries = $this->search($base_dn, $query_for_child_members, array('objectclass', $this->groupMembershipsAttr, $this->groupMembershipsAttrMatchingUserAttr));
+!               if ($child_member_entries !== FALSE) {
+!                 $this->groupMembersResursive($child_member_entries, $all_member_dns, $tested_group_ids, $level + 1, $max_levels, $object_classes);
+!               }
+!             }
+!           }
+!         }
+!       }
+!     }
+!   }
+  
+  
+!  /**
+!   /**
+!    *  get list of all groups that a user is a member of.
+!    *
+!    *    If $nested = TRUE,
+!    *    list will include all parent group.  That is if user is a member of "programmer" group
+!    *    and "programmer" group is a member of "it" group, user is a member of
+!    *    both "programmer" and "it" groups.
+!    *
+!    *    If $nested = FALSE, list will only include groups user is in directly.
+!    *
+!    *  @param mixed
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    *  @param enum $return = 'group_dns'
+!    *  @param boolean $nested if groups should be recursed or not.
+!    *
+!    *  @return array of groups dns in mixed case or FALSE on error
+!    */
+! 
+!   public function groupMembershipsFromUser($user, $return = 'group_dns', $nested = NULL) {
+! 
+!     $group_dns = FALSE;
+!     $user_ldap_entry = @$this->userUserToExistingLdapEntry($user);
+!     if (!$user_ldap_entry || $this->groupFunctionalityUnused) {
+!       return FALSE;
+!     }
+!     if ($nested === NULL) {
+!       $nested = $this->groupNested;
+!     }
+! 
+!     if ($this->groupUserMembershipsConfigured) { // preferred method
+!       $group_dns = $this->groupUserMembershipsFromUserAttr($user_ldap_entry, $nested);
+!     }
+!     elseif ($this->groupGroupEntryMembershipsConfigured) {
+!       $group_dns = $this->groupUserMembershipsFromEntry($user_ldap_entry, $nested);
+!     }
+! 
+!     if ($return == 'group_dns') {
+!       return $group_dns;
+!     }
+! 
+!   }
+! 
+! 
+!   /**
+!    *  get list of all groups that a user is a member of by using memberOf attribute first,
+!    *    then if nesting is true, using group entries to find parent groups
+!    *
+!    *    If $nested = TRUE,
+!    *    list will include all parent group.  That is if user is a member of "programmer" group
+!    *    and "programmer" group is a member of "it" group, user is a member of
+!    *    both "programmer" and "it" groups.
+!    *
+!    *    If $nested = FALSE, list will only include groups user is in directly.
+!    *
+!    *  @param mixed
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    *  @param boolean $nested if groups should be recursed or not.
+!    *
+!    *  @return array of group dns
+!    */
+! 
+!   public function groupUserMembershipsFromUserAttr($user, $nested = NULL) {
+! 
+!     if (!$this->groupUserMembershipsConfigured) {
+!       return FALSE;
+!     }
+!     if ($nested === NULL) {
+!       $nested = $this->groupNested;
+!     }
+! 
+!     if (!is_array($user['attr']) && !isset($user['attr'][$this->groupUserMembershipsAttr])) {
+!       $user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+!         if (!isset($user_ldap_entry['attr'][$this->groupUserMembershipsAttr])) {
+!           return FALSE; // user's membership attribute is not present.  either misconfigured or query failed
+!         }
+!     }
+!     else {
+!       $user_ldap_entry = $user;
+!     }
+! 
+!     $all_group_dns = array();
+!     $tested_group_ids = array();
+!     $level = 0;
+! 
+!     $member_group_dns = $user_ldap_entry['attr'][$this->groupUserMembershipsAttr];
+!     if (isset($member_group_dns['count'])) {
+!       unset($member_group_dns['count']);
+!     }
+!     $ors = array();
+!     foreach ($member_group_dns as $i => $member_group_dn) {
+!       $all_group_dns[] = $member_group_dn;
+!       if ($nested) {
+!         if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+!           $member_value = $member_group_dn;
+!         }
+!         else {
+!           $member_value = ldap_servers_get_first_rdn_value_from_dn($member_group_dn, $this->groupMembershipsAttrMatchingUserAttr);
+!         }
+!         $ors[] =  $this->groupMembershipsAttr . '=' . $member_value;
+!       }
+!     }
+! 
+!     if ($nested && count($ors)) {
+!       $count = count($ors);
+!       for ($i=0; $i < $count; $i=$i+LDAP_SERVER_LDAP_QUERY_CHUNK) { // only 50 or so per query
+!         $current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK);
+!         $or = '(|(' . join(")(", $current_ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
+!         $query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')';
+! 
+!         foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+!           // debug("query for parent groups, base_dn=$base_dn, $query_for_parent_groups");
+!           $group_entries = $this->search($base_dn, $query_for_parent_groups);  // no attributes, just dns needed
+!           if ($group_entries !== FALSE  && $level < LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT) {
+!             $this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT);
+!           }
+!         }
+!       }
+!     }
+! 
+!     return $all_group_dns;
+!   }
+! 
+!   /**
+!    *  get list of all groups that a user is a member of by querying groups
+!    *
+!    *    If $nested = TRUE,
+!    *    list will include all parent group.  That is if user is a member of "programmer" group
+!    *    and "programmer" group is a member of "it" group, user is a member of
+!    *    both "programmer" and "it" groups.
+!    *
+!    *    If $nested = FALSE, list will only include groups user is in directly.
+!    *
+!    *  @param mixed
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    *  @param boolean $nested if groups should be recursed or not.
+!    *
+!    *  @return array of group dns MIXED CASE VALUES
+!    *
+!    *  @see tests/DeriveFromEntry/ldap_servers.inc for fuller notes and test example
+!    */
+!   public function groupUserMembershipsFromEntry($user, $nested = NULL) {
+! 
+!     if (!$this->groupGroupEntryMembershipsConfigured) {
+!       return FALSE;
+!     }
+!     if ($nested === NULL) {
+!       $nested = $this->groupNested;
+!     }
+! 
+!     $user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+! 
+!     $all_group_dns = array(); // MIXED CASE VALUES
+!     $tested_group_ids = array(); // array of dns already tested to avoid excess queries MIXED CASE VALUES
+!     $level = 0;
+! 
+!     if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+!       $member_value = $user_ldap_entry['dn'];
+!     }
+!     else {
+!       $member_value = $user_ldap_entry['attr'][$this->groupMembershipsAttrMatchingUserAttr][0];
+!     }
+! 
+!     $group_query = '(&(objectClass=' . $this->groupObjectClass . ')(' . $this->groupMembershipsAttr . "=$member_value))";
+! 
+!     foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+!       $group_entries = $this->search($base_dn, $group_query, array()); // only need dn, so empty array forces return of no attributes
+!       if ($group_entries !== FALSE) {
+!         $max_levels = ($nested) ? LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT : 0;
+!         $this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level, $max_levels);
+!       }
+!     }
+! 
+!     return $all_group_dns;
+!   }
+! 
+!   /**
+!    * recurse through all groups, adding parent groups to $all_group_dns array.
+!    *
+!    * @param array $current_group_entries of ldap group entries that are starting point.  should include at least 1 entry.
+!    * @param array $all_group_dns as array of all groups user is a member of.  MIXED CASE VALUES
+!    * @param array $tested_group_ids as array of tested group dn, cn, uid, etc.  MIXED CASE VALUES
+!    *   whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
+!    *   whatever attribute is in $this->$tested_group_ids to avoid redundant recursing
+!    * @param int $level of recursion
+!    * @param int $max_levels as max recursion allowed
+!    *
+!    * given set of groups entries ($current_group_entries such as it, hr, accounting),
+!    * find parent groups (such as staff, people, users) and add them to list of group memberships ($all_group_dns)
+!    *
+!    * (&(objectClass=[$this->groupObjectClass])(|([$this->groupMembershipsAttr]=groupid1)([$this->groupMembershipsAttr]=groupid2))
+!    *
+!    * @return FALSE for error or misconfiguration, otherwise TRUE.  results are passed by reference.
+!    */
+! 
+!   public function groupMembershipsFromEntryResursive($current_group_entries, &$all_group_dns, &$tested_group_ids, $level, $max_levels) {
+! 
+!     if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_group_entries) || count($current_group_entries) == 0) {
+!       return FALSE;
+!     }
+!     if (isset($current_group_entries['count'])) {
+!       unset($current_group_entries['count']);
+!     };
+! 
+!     $ors = array();
+!     foreach ($current_group_entries as $i => $group_entry) {
+!       if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+!         $member_id = $group_entry['dn'];
+!       }
+!       else {// maybe cn, uid, etc is held
+!         $member_id = ldap_servers_get_first_rdn_value_from_dn($group_entry['dn'], $this->groupMembershipsAttrMatchingUserAttr);
+!       }
+! 
+!       if ($member_id && !in_array($member_id, $tested_group_ids)) {
+!         $tested_group_ids[] = $member_id;
+!         $all_group_dns[] = $group_entry['dn'];
+!         // add $group_id (dn, cn, uid) to query
+!         $ors[] =  $this->groupMembershipsAttr . '=' . $member_id;
+!       }
+!     }
+! 
+!     if (count($ors)) {
+!       $count = count($ors);
+!       for ($i=0; $i < $count; $i=$i+LDAP_SERVER_LDAP_QUERY_CHUNK) { // only 50 or so per query
+!         $current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK);
+!         //dpm("current_ors $i," . LDAP_SERVER_LDAP_QUERY_CHUNK); dpm($current_ors);
+!         $or = '(|(' . join(")(", $current_ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
+!         $query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')';
+! 
+! 
+!         // debug('query_for_parent_groups'); debug($query_for_parent_groups);
+!         foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+!           $group_entries = $this->search($base_dn, $query_for_parent_groups);  // no attributes, just dns needed
+!           if ($group_entries !== FALSE  && $level < $max_levels) {
+!             $this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, $max_levels);
+!           }
+!         }
+!       }
+!     }
+  
+!     return TRUE;
+!   }
+  
+  
+!  /**
+!    *  get "groups" from derived from DN.  Has limited usefulness
+!    *
+!    *  @param mixed
+!    *    - drupal user object (stdClass Object)
+!    *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!    *    - ldap dn of user (array)
+!    *    - drupal username of user (string)
+!    *
+!    *  @return array of group strings
+!    */
+!   public function groupUserMembershipsFromDn($user) {
+! 
+!     if (!$this->groupDeriveFromDn || !$this->groupDeriveFromDnAttr) {
+!       return FALSE;
+!     }
+!     elseif ($user_ldap_entry = $this->userUserToExistingLdapEntry($user)) {
+!       return ldap_servers_get_all_rdn_values_from_dn($user_ldap_entry['dn'], $this->groupDeriveFromDnAttr);
+!     }
+!     else {
+!       return FALSE;
+!     }
+! 
+!   }
+!   /**
+!    * Error methods and properties.
+!    */
+! 
+!   public $detailedWatchdogLog = FALSE;
+!   protected $_errorMsg = NULL;
+!   protected $_hasError = FALSE;
+!   protected $_errorName = NULL;
+! 
+!   public function setError($_errorName, $_errorMsgText = NULL) {
+!     $this->_errorMsgText = $_errorMsgText;
+!     $this->_errorName = $_errorName;
+!     $this->_hasError = TRUE;
+!   }
+! 
+!   public function clearError() {
+!     $this->_hasError = FALSE;
+!     $this->_errorMsg = NULL;
+!     $this->_errorName = NULL;
+!   }
+! 
+!   public function hasError() {
+!     return ($this->_hasError || $this->ldapErrorNumber());
+!   }
+! 
+!   public function errorMsg($type = NULL) {
+!     if ($type == 'ldap' && $this->connection) {
+!       return ldap_err2str(ldap_errno($this->connection));
+!     }
+!     elseif ($type == NULL) {
+!       return $this->_errorMsg;
+!     }
+!     else {
+!       return NULL;
+!     }
+!   }
+! 
+!   public function errorName($type = NULL) {
+!     if ($type == 'ldap' && $this->connection) {
+!       return "LDAP Error: " . ldap_error($this->connection);
+!     }
+!     elseif ($type == NULL) {
+!       return $this->_errorName;
+!     }
+!     else {
+!       return NULL;
+!     }
+!   }
+! 
+!   public function ldapErrorNumber() {
+!     if ($this->connection && ldap_errno($this->connection)) {
+!       return ldap_errno($this->connection);
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+  
+  }
+--- 15,1825 ----
+   * @todo make bindpw protected
+   */
+  class LdapServer {
+! 	// LDAP Settings
+  
+! 	const LDAP_CONNECT_ERROR = 0x5b;
+! 	const LDAP_SUCCESS = 0x00;
+! 	const LDAP_OPERATIONS_ERROR = 0x01;
+! 	const LDAP_PROTOCOL_ERROR = 0x02;
+! 
+! 	public $sid;
+! 	public $numericSid;
+! 	public $name;
+! 	public $status;
+! 	public $ldap_type;
+! 	public $address;
+! 	public $port = 389;
+! 	public $tls = FALSE;
+! 	public $bind_method = 0;
+! 	public $basedn = array();
+! 	public $binddn = FALSE; // Default to an anonymous bind.
+! 	public $bindpw = FALSE; // Default to an anonymous bind.
+! 	public $user_dn_expression;
+! 	public $user_attr;
+! 	public $picture_attr;
+! 	public $account_name_attr; //lowercase
+! 	public $mail_attr; //lowercase
+! 	public $mail_template;
+! 	public $unique_persistent_attr; //lowercase
+! 	public $unique_persistent_attr_binary = FALSE;
+! 	public $ldapToDrupalUserPhp;
+! 	public $testingDrupalUsername;
+! 	public $testingDrupalUserDn;
+! 	public $detailed_watchdog_log;
+! 	public $editPath;
+! 	public $queriableWithoutUserCredentials = FALSE; // can this server be queried without user credentials provided?
+! 	public $userAttributeNeededCache = array(); // array of attributes needed keyed on $op such as 'user_update'
+! 
+! 	public $groupFunctionalityUnused = 0;
+! 	public $groupObjectClass;
+! 	public $groupNested = 0; // 1 | 0
+! 	public $groupDeriveFromDn = FALSE;
+! 	public $groupDeriveFromDnAttr = NULL; //lowercase
+! 	public $groupUserMembershipsAttrExists = FALSE; // does a user attribute containing groups exist?
+! 	public $groupUserMembershipsAttr = NULL;   //lowercase     // name of user attribute containing groups
+! 	public $groupUserMembershipsConfigured = FALSE; // user attribute containing memberships is configured enough to use
+! 
+! 	public $groupMembershipsAttr = NULL;  //lowercase // members, uniquemember, memberUid
+! 	public $groupMembershipsAttrMatchingUserAttr = NULL; //lowercase // dn, cn, etc contained in groupMembershipsAttr
+! 	public $groupGroupEntryMembershipsConfigured = FALSE; // are groupMembershipsAttrMatchingUserAttr and groupGroupEntryMembershipsConfigured populated
+! 
+! 	public $groupTestGroupDn = NULL;
+! 	public $groupTestGroupDnWriteable = NULL;
+! 
+! 	private $group_properties = array(
+! 			'groupObjectClass', 'groupNested', 'groupDeriveFromDn', 'groupDeriveFromDnAttr', 'groupUserMembershipsAttrExists',
+! 			'groupUserMembershipsAttr', 'groupMembershipsAttrMatchingUserAttr', 'groupTestGroupDn', 'groupTestGroupDnWriteable'
+! 	);
+! 
+! 	public $paginationEnabled = FALSE; // (boolean)(function_exists('ldap_control_paged_result_response') && function_exists('ldap_control_paged_result'));
+! 	public $searchPagination = FALSE;
+! 	public $searchPageSize = 1000;
+! 	public $searchPageStart = 0;
+! 	public $searchPageEnd = NULL;
+! 
+! 	public $inDatabase = FALSE;
+! 	public $connection;
+! 
+! 
+! 
+! 
+! 	// direct mapping of db to object properties
+! 	public static function field_to_properties_map() {
+! 		return array(
+! 				'sid' => 'sid',
+! 				'numeric_sid' => 'numericSid',
+! 				'name'  => 'name' ,
+! 				'status'  => 'status',
+! 				'ldap_type'  => 'ldap_type',
+! 				'address'  => 'address',
+! 				'port'  => 'port',
+! 				'tls'  => 'tls',
+! 				'bind_method' => 'bind_method',
+! 				'basedn'  => 'basedn',
+! 				'binddn'  => 'binddn',
+! 				'user_dn_expression' => 'user_dn_expression',
+! 				'user_attr'  => 'user_attr',
+! 				'account_name_attr'  => 'account_name_attr',
+! 				'mail_attr'  => 'mail_attr',
+! 				'picture_attr'  => 'picture_attr',
+! 				'mail_template'  => 'mail_template',
+! 				'unique_persistent_attr' => 'unique_persistent_attr',
+! 				'unique_persistent_attr_binary' => 'unique_persistent_attr_binary',
+! 				'ldap_to_drupal_user'  => 'ldapToDrupalUserPhp',
+! 				'testing_drupal_username'  => 'testingDrupalUsername',
+! 				'testing_drupal_user_dn'  => 'testingDrupalUserDn',
+! 
+! 				'grp_unused' => 'groupFunctionalityUnused',
+! 				'grp_object_cat' => 'groupObjectClass',
+! 				'grp_nested' => 'groupNested',
+! 				'grp_user_memb_attr_exists' => 'groupUserMembershipsAttrExists',
+! 				'grp_user_memb_attr' => 'groupUserMembershipsAttr',
+! 				'grp_memb_attr' => 'groupMembershipsAttr',
+! 				'grp_memb_attr_match_user_attr' => 'groupMembershipsAttrMatchingUserAttr',
+! 				'grp_derive_from_dn' => 'groupDeriveFromDn',
+! 				'grp_derive_from_dn_attr' => 'groupDeriveFromDnAttr',
+! 				'grp_test_grp_dn' =>  'groupTestGroupDn',
+! 				'grp_test_grp_dn_writeable' => 'groupTestGroupDnWriteable',
+! 
+! 				'search_pagination' => 'searchPagination',
+! 				'search_page_size' => 'searchPageSize',
+! 
+! 		);
+! 
+! 	}
+! 
+! 	/**
+! 	 * Constructor Method
+! 	 */
+! 	function __construct($sid) {
+! 		if (!is_scalar($sid)) {
+! 			return;
+! 		}
+! 		$this->detailed_watchdog_log = variable_get('ldap_help_watchdog_detail', 0);
+! 		$server_record = FALSE;
+! 		if (module_exists('ctools')) {
+! 			ctools_include('export');
+! 			$result = ctools_export_load_object('ldap_servers', 'names', array($sid));
+! 			if (isset($result[$sid])) {
+! 				$server_record = new stdClass();
+! 				foreach ($result[$sid] as $db_field_name => $value) {
+! 					$server_record->{$db_field_name} = $value;
+! 				}
+! 			}
+! 			//debug('ctools record'); debug($server_record);
+! 		}
+! 		else {
+! 			$select = db_select('ldap_servers')
+! 			->fields('ldap_servers')
+! 			->condition('ldap_servers.sid',  $sid)
+! 			->execute();
+! 			foreach ($select as $record) {
+! 				if ($record->sid == $sid) {
+! 					$server_record = $record;
+! 				}
+! 			}
+! 			//  debug('db record'); debug($server_record);
+! 		}
+! 
+! 		if (!$server_record) {
+! 			$this->inDatabase = FALSE;
+! 		}
+! 		else {
+! 			$this->inDatabase = TRUE;
+! 			$this->sid = $sid;
+! 			$this->detailedWatchdogLog = variable_get('ldap_help_watchdog_detail', 0);
+! 			// debug('this server_record'); debug($server_record);
+! 			foreach ($this->field_to_properties_map() as $db_field_name => $property_name ) {
+! 				if (isset($server_record->$db_field_name)) {
+! 					$this->{$property_name} = $server_record->$db_field_name;
+! 				}
+! 			}
+! 			// debug('this 2'); debug($this);
+! 			$server_record_bindpw = property_exists($server_record, 'bindpw') ? $server_record->bindpw : '';
+! 			$this->initDerivedProperties($server_record_bindpw);
+! 			//  debug('this 3'); debug($this);
+! 		}
+! 
+! 	}
+! 
+! 	/**
+! 	 * this method sets properties that don't directly map from db record
+! 	 */
+! 	protected function initDerivedProperties($bindpw) {
+! 
+! 		// debug('initDerivedProperties'); debug($this->basedn);
+! 		if (!is_array($this->basedn)) {
+! 			$basedn_unserialized = @unserialize($this->basedn);
+! 			// debug('basedn_unserialized'); debug($basedn_unserialized);
+! 			$this->basedn = $basedn_unserialized;
+! 		}
+! 		// debug('initDerivedProperties'); debug($this->basedn);
+! 
+! 		if ($bindpw != '') {
+! 			$this->bindpw = ldap_servers_decrypt($bindpw);
+! 		}
+! 
+! 		$this->paginationEnabled = (boolean)(ldap_servers_php_supports_pagination() && $this->searchPagination);
+! 
+! 		$this->queriableWithoutUserCredentials = (boolean)(
+! 				$this->bind_method == LDAP_SERVERS_BIND_METHOD_SERVICE_ACCT ||
+! 				$this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER
+! 		);
+! 		$this->editPath = 'admin/config/people/ldap/servers/edit/' . $this->sid;
+! 
+! 		$this->groupGroupEntryMembershipsConfigured = ($this->groupMembershipsAttrMatchingUserAttr && $this->groupMembershipsAttr);
+! 		$this->groupUserMembershipsConfigured = ($this->groupUserMembershipsAttrExists && $this->groupUserMembershipsAttr);
+! 	}
+! 	/**
+! 	 * Destructor Method
+! 	 */
+! 	function __destruct() {
+! 		// Close the server connection to be sure.
+! 		$this->disconnect();
+! 	}
+! 
+! 
+! 	/**
+! 	 * Invoke Method
+! 	 */
+! 	function __invoke() {
+! 		$this->connect();
+! 		$this->bind();
+! 	}
+! 
+! 
+! 
+! 	/**
+! 	 * Connect Method
+! 	 */
+! 	function connect() {
+! 
+! 		if (!$con = ldap_connect($this->address, $this->port)) {
+! 			watchdog('user', 'LDAP Connect failure to ' . $this->address . ':' . $this->port);
+! 			return LDAP_CONNECT_ERROR;
+! 		}
+! 
+! 		ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
+! 		ldap_set_option($con, LDAP_OPT_REFERRALS, 0);
+! 
+! 		// Use TLS if we are configured and able to.
+! 		if ($this->tls) {
+! 			ldap_get_option($con, LDAP_OPT_PROTOCOL_VERSION, $vers);
+! 			if ($vers == -1) {
+! 				watchdog('user', 'Could not get LDAP protocol version.');
+! 				return LDAP_PROTOCOL_ERROR;
+! 			}
+! 			if ($vers != 3) {
+! 				watchdog('user', 'Could not start TLS, only supported by LDAP v3.');
+! 				return LDAP_CONNECT_ERROR;
+! 			}
+! 			elseif (!function_exists('ldap_start_tls')) {
+! 				watchdog('user', 'Could not start TLS. It does not seem to be supported by this PHP setup.');
+! 				return LDAP_CONNECT_ERROR;
+! 			}
+! 			elseif (!ldap_start_tls($con)) {
+! 				$msg =  t("Could not start TLS. (Error %errno: %error).", array('%errno' => ldap_errno($con), '%error' => ldap_error($con)));
+! 				watchdog('user', $msg);
+! 				return LDAP_CONNECT_ERROR;
+! 			}
+! 		}
+! 
+! 		// Store the resulting resource
+! 		$this->connection = $con;
+! 		return LDAP_SUCCESS;
+! 	}
+  
+  
+! 	/**
+  	 * Bind (authenticate) against an active LDAP database.
+  	 *
+  	 * @param $userdn
+  	 *   The DN to bind against. If NULL, we use $this->binddn
+  	 * @param $pass
+  	 *   The password search base. If NULL, we use $this->bindpw
+! 	 *
+! 	 * @return
+! 	 *   Result of bind; TRUE if successful, FALSE otherwise.
+! 	 */
+! 	function bind($userdn = NULL, $pass = NULL, $anon_bind = FALSE) {
+! 
+! 		// Ensure that we have an active server connection.
+! 		if (!$this->connection) {
+! 			watchdog('ldap', "LDAP bind failure for user %user. Not connected to LDAP server.", array('%user' => $userdn));
+! 			return LDAP_CONNECT_ERROR;
+! 		}
+! 		if ($anon_bind) {
+! 			if (@!ldap_bind($this->connection)) {
+! 				if ($this->detailedWatchdogLog) {
+! 					watchdog('ldap', "LDAP anonymous bind error. Error %errno: %error", array('%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)));
+! 				}
+! 				return ldap_errno($this->connection);
+! 			}
+! 		}
+! 		else {
+! 			$userdn = ($userdn != NULL) ? $userdn : $this->binddn;
+! 			$pass = ($pass != NULL) ? $pass : $this->bindpw;
+! 			if (@!ldap_bind($this->connection, $userdn, $pass)) {
+! 				if ($this->detailedWatchdogLog) {
+! 					watchdog('ldap', "LDAP bind failure for user %user. Error %errno: %error", array('%user' => $userdn, '%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)));
+! 				}
+! 				return ldap_errno($this->connection);
+! 			}
+! 		}
+! 
+! 		return LDAP_SUCCESS;
+! 	}
+! 
+! 	/**
+! 	 * Disconnect (unbind) from an active LDAP server.
+! 	 */
+! 	function disconnect() {
+! 		if (!$this->connection) {
+! 			// never bound or not currently bound, so no need to disconnect
+! 			//watchdog('ldap', 'LDAP disconnect failure from '. $this->server_addr . ':' . $this->port);
+! 		}
+! 		else {
+! 			ldap_unbind($this->connection);
+! 			$this->connection = NULL;
+! 		}
+! 	}
+! 
+! 	public function connectAndBindIfNotAlready() {
+! 		if (! $this->connection) {
+! 			$this->connect();
+! 			$this->bind();
+! 		}
+! 	}
+! 
+! 	/**
+! 	 * does dn exist for this server?
+! 	 * [ ] Finished
+! 	 * [ ] Test Coverage.  Test ID:
+! 	 * [ ] Case insensitive
+! 	 *
+! 	 * @param string $dn
+! 	 * @param enum $return = 'boolean' or 'ldap_entry'
+! 	 * @param array $attributes in same form as ldap_read $attributes parameter
+! 	 *
+! 	 * @param return FALSE or ldap entry array
+! 	 */
+! 	function dnExists($dn, $return = 'boolean', $attributes = NULL) {
+! 
+! 		$params = array(
+! 				'base_dn' => $dn,
+! 				'attributes' => $attributes,
+! 				'attrsonly' => FALSE,
+! 				'filter' => '(objectclass=*)',
+! 				'sizelimit' => 0,
+! 				'timelimit' => 0,
+! 				'deref' => NULL,
+! 		);
+! 
+! 		if ($return == 'boolean' || !is_array($attributes)) {
+! 			$params['attributes'] = array('objectclass');
+! 		}
+! 		else {
+! 			$params['attributes'] = $attributes;
+! 		}
+! 
+! 		$result = $this->ldapQuery(LDAP_SCOPE_BASE, $params);
+! 		if ($result !== FALSE) {
+! 			if ($return == 'boolean') {
+! 				return TRUE;
+! 			}
+! 
+! 			$entries = @ldap_get_entries($this->connection, $result);
+! 			if ($entries !== FALSE) {
+! 				return $entries[0];
+! 			}
+! 		}
+! 
+! 		return FALSE;
+! 
+! 	}
+! 
+! 	/**
+! 	 * @param $ldap_result as ldap link identifier
+! 	 *
+! 	 * @return FALSE on error or number of entries.
+! 	 *   (if 0 entries will return 0)
+! 	 */
+! 	public function countEntries($ldap_result) {
+! 		return ldap_count_entries($this->connection, $ldap_result);
+! 	}
+! 
+! 
+! 
+! 	/**
+! 	 * create ldap entry.
+! 	 *
+! 	 * @param array $attributes should follow the structure of ldap_add functions
+! 	 *   entry array: http://us.php.net/manual/en/function.ldap-add.php
+! 	 $attributes["attribute1"] = "value";
+! 	 $attributes["attribute2"][0] = "value1";
+! 	 $attributes["attribute2"][1] = "value2";
+! 	 * @return boolean result
+! 	 */
+! 
+! 	public function createLdapEntry($attributes, $dn = NULL) {
+! 		// dpm("createLdapEntry, dn=$dn"); dpm($ldap_entry);
+! 		if (!$this->connection) {
+! 			$this->connect();
+! 			$this->bind();
+! 		}
+! 		if (isset($attributes['dn'])) {
+! 			$dn = $attributes['dn'];
+! 			unset($attributes['dn']);
+! 		}
+! 		elseif (!$dn) {
+! 			return FALSE;
+! 		}
+! 
+! 		// debug("createLdapEntry, dn=$dn, entry:"); debug($attributes);
+! 		$result = @ldap_add($this->connection, $dn, $attributes);
+! 		if (!$result) {
+! 			$error = "LDAP Server ldap_add(%dn) Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+! 			$tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+! 			debug(t($error, $tokens));
+! 			watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+! 		}
+! 
+! 
+! 		return $result;
+! 	}
+! 
+! 
+! 
+! 	/**
+! 	 * given 2 ldap entries, old and new, removed unchanged values to avoid security errors and incorrect date modifieds
+! 	 *
+! 	 * @param ldap entry array $new_entry in form <attribute> => <value>
+! 	 * @param ldap entry array $old_entry in form <attribute> => array('count' => N, array(<value>,...<value>
+! 	 *
+! 	 * @return ldap array with no values that have NOT changed
+! 	 */
+! 
+! 	static public function removeUnchangedAttributes($new_entry, $old_entry) {
+! 
+! 		foreach ($new_entry as $key => $new_val) {
+! 			$old_value = FALSE;
+! 			$key_lcase = drupal_strtolower($key);
+! 			if (isset($old_entry[$key_lcase])) {
+! 				if ($old_entry[$key_lcase]['count'] == 1) {
+! 					$old_value = $old_entry[$key_lcase][0];
+! 					$old_value_is_scalar = TRUE;
+! 				}
+! 				else {
+! 					unset($old_entry[$key_lcase]['count']);
+! 					$old_value = $old_entry[$key_lcase];
+! 					$old_value_is_scalar = FALSE;
+! 				}
+! 			}
+! 
+! 			// identical multivalued attributes
+! 			if (is_array($new_val) && is_array($old_value) && count(array_diff($new_val, $old_value)) == 0) {
+! 				unset($new_entry[$key]);
+! 			}
+! 			elseif ($old_value_is_scalar && !is_array($new_val) && drupal_strtolower($old_value) == drupal_strtolower($new_val)) {
+! 				unset($new_entry[$key]); // don't change values that aren't changing to avoid false permission constraints
+! 			}
+! 		}
+! 		return $new_entry;
+! 	}
+! 
+! 
+! 
+! 
+! 
+! 	/**
+! 	 * modify attributes of ldap entry
+! 	 *
+! 	 * @param string $dn DN of entry
+! 	 * @param array $attributes should follow the structure of ldap_add functions
+! 	 *   entry array: http://us.php.net/manual/en/function.ldap-add.php
+! 	 $attributes["attribute1"] = "value";
+! 	 $attributes["attribute2"][0] = "value1";
+! 	 $attributes["attribute2"][1] = "value2";
+! 
+! 	 @return TRUE on success FALSE on error
+! 	 */
+! 
+! 	function modifyLdapEntry($dn, $attributes = array(), $old_attributes = FALSE) {
+! 
+! 		$this->connectAndBindIfNotAlready();
+! 
+! 		if (!$old_attributes) {
+! 			$result = @ldap_read($this->connection, $dn, 'objectClass=*');
+! 			if (!$result) {
+! 				$error = "LDAP Server ldap_read(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+! 				$tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+! 				watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+! 				return FALSE;
+! 			}
+! 
+! 			$entries = ldap_get_entries($this->connection, $result);
+! 			if (is_array($entries) && $entries['count'] == 1) {
+! 				$old_attributes =  $entries[0];
+! 			}
+! 		}
+! 		$attributes = $this->removeUnchangedAttributes($attributes, $old_attributes);
+! 
+! 		foreach ($attributes as $key => $cur_val) {
+! 			$old_value = FALSE;
+! 			$key_lcase = drupal_strtolower($key);
+! 			if (isset($old_attributes[$key_lcase])) {
+! 				if ($old_attributes[$key_lcase]['count'] == 1) {
+! 					$old_value = $old_attributes[$key_lcase][0];
+! 				}
+! 				else {
+! 					unset($old_attributes[$key_lcase]['count']);
+! 					$old_value = $old_attributes[$key_lcase];
+! 				}
+! 			}
+! 
+! 			if ($cur_val == '' && $old_value != '') { // remove enpty attributes
+! 				unset($attributes[$key]);
+! 				$result = @ldap_mod_del($this->connection, $dn, array($key_lcase => $old_value));
+! 				if (!$result) {
+! 					$error = "LDAP Server ldap_mod_del(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+! 					$tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+! 					watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+! 					return FALSE;
+! 				}
+! 			}
+! 			elseif (is_array($cur_val)) {
+! 				foreach ($cur_val as $mv_key => $mv_cur_val) {
+! 					if ($mv_cur_val == '') {
+! 						unset($attributes[$key][$mv_key]); // remove empty values in multivalues attributes
+! 					}
+! 					else {
+! 						$attributes[$key][$mv_key] = $mv_cur_val;
+! 					}
+! 				}
+! 			}
+! 		}
+! 		//  dpm('modifyLdapEntry, attributes to modify'); dpm($attributes);
+! 		if (count($attributes) > 0) {
+! 			$result = @ldap_modify($this->connection, $dn, $attributes);
+! 			if (!$result) {
+! 				$error = "LDAP Server ldap_modify(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+! 				$tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+! 				watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+! 				return FALSE;
+! 			}
+! 		}
+! 
+! 		return TRUE;
+! 
+! 	}
+! 
+! 	/**
+! 	 * Perform an LDAP delete.
+! 	 *
+! 	 * @param string $dn
+! 	 *
+! 	 * @return boolean result per ldap_delete
+! 	 */
+! 
+! 	public function delete($dn) {
+! 		if (!$this->connection) {
+! 			$this->connect();
+! 			$this->bind();
+! 		}
+! 		$result = @ldap_delete($this->connection, $dn);
+! 		if (!$result) {
+! 			$error = "LDAP Server delete(%dn) in LdapServer::delete() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ";
+! 			$tokens = array('%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)));
+! 			watchdog('ldap_server', $error, $tokens, WATCHDOG_ERROR);
+! 		}
+! 		return $result;
+! 	}
+! 
+! 	/**
+! 	 * Perform an LDAP search on all base dns and aggregate into one result
+! 	 *
+! 	 * @param string $filter
+! 	 *   The search filter. such as sAMAccountName=jbarclay.  attribute values (e.g. jbarclay) should be esacaped before calling
+! 
+! 	 * @param array $attributes
+! 	 *   List of desired attributes. If omitted, we only return "dn".
+! 	 *
+! 	 * @remaining params mimick ldap_search() function params
+! 	 *
+! 	 * @return
+! 	 *   An array of matching entries->attributes (will have 0
+! 	 *   elements if search returns no results),
+! 	 *   or FALSE on error on any of the basedn queries
+! 	 */
+! 
+! 	public function searchAllBaseDns(
+! 			$filter,
+! 			$attributes = array(),
+! 			$attrsonly = 0,
+! 			$sizelimit = 0,
+! 			$timelimit = 0,
+! 			$deref = NULL,
+! 			$scope = LDAP_SCOPE_SUBTREE
+! 	) {
+! 		$all_entries = array();
+! 		foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+! 			$entries = $this->search($base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $scope);  // no attributes, just dns needed
+! 			if ($entries === FALSE) { // if error in any search, return false
+! 				return FALSE;
+! 			}
+! 			if (count($all_entries) == 0) {
+! 				$all_entries = $entries;
+! 			}
+! 			else {
+! 				$existing_count = $all_entries['count'];
+! 				unset($entries['count']);
+! 				foreach ($entries as $i => $entry) {
+! 					$all_entries[$existing_count + $i] = $entry;
+! 				}
+! 				$all_entries['count'] = count($all_entries);
+! 			}
+! 		}
+! 
+! 		return $all_entries;
+! 
+! 	}
+! 
+! 
+! 	/**
+! 	 * Perform an LDAP search.
+! 	 * @param string $basedn
+! 	 *   The search base. If NULL, we use $this->basedn. should not be esacaped
+! 	 *
+! 	 * @param string $filter
+! 	 *   The search filter. such as sAMAccountName=jbarclay.  attribute values (e.g. jbarclay) should be esacaped before calling
+! 
+! 	 * @param array $attributes
+! 	 *   List of desired attributes. If omitted, we only return "dn".
+! 	 *
+! 	 * @remaining params mimick ldap_search() function params
+! 	 *
+! 	 * @return
+! 	 *   An array of matching entries->attributes (will have 0
+! 	 *   elements if search returns no results),
+! 	 *   or FALSE on error.
+! 	 */
+! 
+! 	function search($base_dn = NULL, $filter, $attributes = array(),
+! 			$attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = NULL, $scope = LDAP_SCOPE_SUBTREE) {
+! 
+! 		/**
+! 		 * pagingation issues:
+! 		 * -- see documentation queue: http://markmail.org/message/52w24iae3g43ikix#query:+page:1+mid:bez5vpl6smgzmymy+state:results
+! 		 * -- wait for php 5.4? https://svn.php.net/repository/php/php-src/tags/php_5_4_0RC6/NEWS (ldap_control_paged_result
+! 		 * -- http://sgehrig.wordpress.com/2009/11/06/reading-paged-ldap-results-with-php-is-a-show-stopper/
+! 		 */
+! 
+! 		$filter = strtolower($filter);
+! 		if ($base_dn == NULL) {
+! 			if (count($this->basedn) == 1) {
+! 				$base_dn = $this->basedn[0];
+! 			}
+! 			else {
+! 				return FALSE;
+! 			}
+! 		}
+! 
+! 		$attr_display =  is_array($attributes) ? join(',', $attributes) : 'none';
+! 		$query = 'ldap_search() call: ' . join(",\n", array(
+! 				'base_dn: ' . $base_dn,
+! 				'filter = ' . $filter,
+! 				'attributes: ' . $attr_display,
+! 				'attrsonly = ' . $attrsonly,
+! 				'sizelimit = ' . $sizelimit,
+! 				'timelimit = ' . $timelimit,
+! 				'deref = ' . $deref,
+! 				'scope = ' . $scope,
+! 		)
+! 		);
+! 		if ($this->detailed_watchdog_log) {
+! 			watchdog('ldap_server', $query, array());
+! 		}
+! 
+! 		// When checking multiple servers, there's a chance we might not be connected yet.
+! 		if (! $this->connection) {
+! 			$this->connect();
+! 			$this->bind();
+! 		}
+! 
+! 		$ldap_query_params = array(
+! 				'connection' => $this->connection,
+! 				'base_dn' => $base_dn,
+! 				'filter' => $filter,
+! 				'attributes' => $attributes,
+! 				'attrsonly' => $attrsonly,
+! 				'sizelimit' => $sizelimit,
+! 				'timelimit' => $timelimit,
+! 				'deref' => $deref,
+! 				'query_display' => $query,
+! 				'scope' => $scope,
+! 		);
+! 		// dpm($ldap_query_params); dpm("searchPagination=" . $this->searchPagination .",paginationEnabled=". $this->paginationEnabled .", searchPageStart=" . $this->searchPageStart);
+! 		if ($this->searchPagination && $this->paginationEnabled) {
+! 			$aggregated_entries = $this->pagedLdapQuery($ldap_query_params);
+! 			return $aggregated_entries;
+! 		}
+! 		else {
+! 			$result = $this->ldapQuery($scope, $ldap_query_params);
+! 			if ($result && ($this->countEntries($result) !== FALSE) ) {
+! 				$entries = ldap_get_entries($this->connection, $result);
+! 				drupal_alter('ldap_server_search_results', $entries, $ldap_query_params);
+! 				return (is_array($entries)) ? $entries : FALSE;
+! 			}
+! 			elseif ($this->ldapErrorNumber()) {
+! 				$watchdog_tokens =  array('%basedn' => $ldap_query_params['base_dn'], '%filter' => $ldap_query_params['filter'],
+! 						'%attributes' => print_r($ldap_query_params['attributes'], TRUE), '%errmsg' => $this->errorMsg('ldap'),
+! 						'%errno' => $this->ldapErrorNumber());
+! 				watchdog('ldap', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes:
+! 						%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+! 				return FALSE;
+! 			}
+! 			else {
+! 				return FALSE;
+! 			}
+! 		}
+! 	}
+! 
+! 
+! 	/**
+! 	 * execute a paged ldap query and return entries as one aggregated array
+! 	 *
+! 	 * $this->searchPageStart and $this->searchPageEnd should be set before calling if
+! 	 *   a particular set of pages is desired
+! 	 *
+! 	 * @param array $ldap_query_params of form:
+! 	 'base_dn' => base_dn,
+! 	 'filter' =>  filter,
+! 	 'attributes' => attributes,
+! 	 'attrsonly' => attrsonly,
+! 	 'sizelimit' => sizelimit,
+! 	 'timelimit' => timelimit,
+! 	 'deref' => deref,
+! 	 'scope' => scope,
+! 
+! 	 (this array of parameters is primarily passed on to ldapQuery() method)
+! 	 *
+! 	 * @return array of ldap entries or FALSE on error.
+! 	 *
+! 	 */
+! 	public function pagedLdapQuery($ldap_query_params) {
+! 
+! 		if (!($this->searchPagination && $this->paginationEnabled)) {
+! 			watchdog('ldap', "LDAP server pagedLdapQuery() called when functionality not available in php install or
+! 					not enabled in ldap server configuration.  error. basedn: %basedn| filter: %filter| attributes:
+! 					%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+! 			RETURN FALSE;
+! 		}
+! 
+! 		$paged_entries = array();
+! 		$page_token = '';
+! 		$page = 0;
+! 		$estimated_entries = 0;
+! 		$aggregated_entries = array();
+! 		$aggregated_entries_count = 0;
+! 		$has_page_results = FALSE;
+! 
+! 		do {
+! 			ldap_control_paged_result($this->connection, $this->searchPageSize, TRUE, $page_token);
+! 			$result = $this->ldapQuery($ldap_query_params['scope'], $ldap_query_params);
+! 
+! 			if ($page >= $this->searchPageStart) {
+! 				$skipped_page = FALSE;
+! 				if ($result && ($this->countEntries($result) !== FALSE) ) {
+! 					$page_entries = ldap_get_entries($this->connection, $result);
+! 					unset($page_entries['count']);
+! 					$has_page_results = (is_array($page_entries) && count($page_entries) > 0);
+! 					$aggregated_entries = array_merge($aggregated_entries, $page_entries);
+! 					$aggregated_entries_count = count($aggregated_entries);
+! 				}
+! 				elseif ($this->ldapErrorNumber()) {
+! 					$watchdog_tokens =  array('%basedn' => $ldap_query_params['base_dn'], '%filter' => $ldap_query_params['filter'],
+! 							'%attributes' => print_r($ldap_query_params['attributes'], TRUE), '%errmsg' => $this->errorMsg('ldap'),
+! 							'%errno' => $this->ldapErrorNumber());
+! 					watchdog('ldap', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes:
+! 							%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens);
+! 					RETURN FALSE;
+! 				}
+! 				else {
+! 					return FALSE;
+! 				}
+! 			}
+! 			else {
+! 				$skipped_page = TRUE;
+! 			}
+! 			@ldap_control_paged_result_response($this->connection, $result, $page_token, $estimated_entries);
+! 			if ($ldap_query_params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+! 				// false positive error thrown.  do not set result limit error when $sizelimit specified
+! 			}
+! 			elseif ($this->hasError()) {
+! 				watchdog('ldap_server', 'ldap_control_paged_result_response() function error. LDAP Error: %message, ldap_list() parameters: %query',
+! 						array('%message' => $this->errorMsg('ldap'), '%query' => $ldap_query_params['query_display']),
+! 						WATCHDOG_ERROR);
+! 			}
+! 
+! 			if (isset($ldap_query_params['sizelimit']) && $ldap_query_params['sizelimit'] && $aggregated_entries_count >= $ldap_query_params['sizelimit']) {
+! 				$discarded_entries = array_splice($aggregated_entries, $ldap_query_params['sizelimit']);
+! 				break;
+! 			}
+! 			elseif ($this->searchPageEnd !== NULL && $page >= $this->searchPageEnd) { // user defined pagination has run out
+! 				break;
+! 			}
+! 			elseif ($page_token === NULL || $page_token == '') { // ldap reference pagination has run out
+! 				break;
+! 			}
+! 			$page++;
+! 		} while ($skipped_page || $has_page_results);
+! 
+! 		$aggregated_entries['count'] = count($aggregated_entries);
+! 		return $aggregated_entries;
+! 	}
+! 
+! 	/**
+! 	 * execute ldap query and return ldap records
+! 	 *
+! 	 * @param scope
+! 	 * @params see pagedLdapQuery $params
+! 	 *
+! 	 * @return array of ldap entries
+! 	 */
+! 	function ldapQuery($scope, $params) {
+! 
+! 		$this->connectAndBindIfNotAlready();
+! 
+! 		switch ($scope) {
+! 			case LDAP_SCOPE_SUBTREE:
+! 				$result = @ldap_search($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+! 				$params['sizelimit'], $params['timelimit'], $params['deref']);
+! 				if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+! 					// false positive error thrown.  do not return result limit error when $sizelimit specified
+! 				}
+! 				elseif ($this->hasError()) {
+! 					watchdog('ldap_server', 'ldap_search() function error. LDAP Error: %message, ldap_search() parameters: %query',
+! 							array('%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']),
+! 							WATCHDOG_ERROR);
+! 				}
+! 				break;
+! 
+! 			case LDAP_SCOPE_BASE:
+! 				$result = @ldap_read($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+! 				$params['sizelimit'], $params['timelimit'], $params['deref']);
+! 				if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+! 					// false positive error thrown.  do not result limit error when $sizelimit specified
+! 				}
+! 				elseif ($this->hasError()) {
+! 					watchdog('ldap_server', 'ldap_read() function error.  LDAP Error: %message, ldap_read() parameters: %query',
+! 							array('%message' => $this->errorMsg('ldap'), '%query' => @$params['query_display']),
+! 							WATCHDOG_ERROR);
+! 				}
+! 				break;
+! 
+! 			case LDAP_SCOPE_ONELEVEL:
+! 				$result = @ldap_list($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'],
+! 				$params['sizelimit'], $params['timelimit'], $params['deref']);
+! 				if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) {
+! 					// false positive error thrown.  do not result limit error when $sizelimit specified
+! 				}
+! 				elseif ($this->hasError()) {
+! 					watchdog('ldap_server', 'ldap_list() function error. LDAP Error: %message, ldap_list() parameters: %query',
+! 							array('%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']),
+! 							WATCHDOG_ERROR);
+! 				}
+! 				break;
+! 		}
+! 		return $result;
+! 	}
+! 
+! 	/**
+! 	 * @param array $dns Mixed Case
+! 	 * @return array $dns Lower Case
+! 	 */
+! 
+! 	public function dnArrayToLowerCase($dns) {
+! 		return array_keys(array_change_key_case(array_flip($dns), CASE_LOWER));
+! 	}
+! 
+! 	/**
+! 	 * @param binary or string $puid as returned from ldap_read or other ldap function
+! 	 *
+! 	 */
+! 	public function userUserEntityFromPuid($puid) {
+! 
+! 		if ($this->unique_persistent_attr_binary) {
+! 			$puid = ldap_servers_binary($puid);
+! 		}
+! 
+! 		// list($account, $user_entity) = ldap_user_load_user_acct_and_entity('jkeats');
+! 		//debug('userUserEntityFromPuid:account and user entity'); debug($account); debug($user_entity);
+! 		$query = new EntityFieldQuery();
+! 		$query->entityCondition('entity_type', 'user')
+! 		->fieldCondition('ldap_user_puid_sid', 'value', $this->sid, '=')
+! 		->fieldCondition('ldap_user_puid', 'value', $puid, '=')
+! 		->fieldCondition('ldap_user_puid_property', 'value', $this->unique_persistent_attr, '=')
+! 		->addMetaData('account', user_load(1)); // run the query as user 1
+! 		// ->entityCondition('bundle', 'user')
+! 		$result = $query->execute();
+! 		// debug("userUserEntityFromPuid: puid=$puid, sid=". $this->sid . "attr=" . $this->unique_persistent_attr); debug($result);
+! 		if (isset($result['user'])) {
+! 			$uids = array_keys($result['user']);
+! 			if (count($uids) == 1) {
+! 				$user = entity_load('user', array_keys($result['user']));
+! 				return $user[$uids[0]];
+! 			}
+! 			else {
+! 				$uids = join(',', $uids);
+! 				$tokens = array('%uids' => $uids, '%puid' => $puid, '%sid' =>  $this->sid, '%ldap_user_puid_property' =>  $this->unique_persistent_attr);
+! 				watchdog('ldap_server', 'multiple users (uids: %uids) with same puid (puid=%puid, sid=%sid, ldap_user_puid_property=%ldap_user_puid_property)', $tokens, WATCHDOG_ERROR);
+! 				return FALSE;
+! 			}
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 
+! 	}
+! 
+! 	function userUsernameToLdapNameTransform($drupal_username, &$watchdog_tokens) {
+! 		if ($this->ldapToDrupalUserPhp && module_exists('php')) {
+! 			global $name;
+! 			$old_name_value = $name;
+! 			$name = $drupal_username;
+! 			$code = "<?php global \$name; \n" . $this->ldapToDrupalUserPhp . "; \n ?>";
+! 			$watchdog_tokens['%code'] = $this->ldapToDrupalUserPhp;
+! 			$code_result = php_eval($code);
+! 			$watchdog_tokens['%code_result'] = $code_result;
+! 			$ldap_username = $code_result;
+! 			$watchdog_tokens['%ldap_username'] = $ldap_username;
+! 			$name = $old_name_value;  // important because of global scope of $name
+! 			if ($this->detailedWatchdogLog) {
+! 				watchdog('ldap_server', '%drupal_user_name tansformed to %ldap_username by applying code <code>%code</code>', $watchdog_tokens, WATCHDOG_DEBUG);
+! 			}
+! 		}
+! 		else {
+! 			$ldap_username = $drupal_username;
+! 		}
+! 
+! 		return $ldap_username;
+! 
+! 	}
+! 
+! 
+! 	/**
+! 	 * @param ldap entry array $ldap_entry
+! 	 *
+! 	 * @return string user's username value
+! 	 */
+! 	public function userUsernameFromLdapEntry($ldap_entry) {
+! 
+! 		$accountname = FALSE;
+! 		if ($this->account_name_attr) {
+! 			$accountname = (empty($ldap_entry[$this->user_attr][0])) ? FALSE : $ldap_entry[$this->account_name_attr][0];
+! 		}
+! 		elseif ($this->user_attr)  {
+! 			$accountname = (empty($ldap_entry[$this->user_attr][0])) ? FALSE : $ldap_entry[$this->user_attr][0];
+! 		}
+! 
+! 		return $accountname;
+! 	}
+! 
+! 	/**
+! 	 * @param string $dn ldap dn
+! 	 *
+! 	 * @return mixed string user's username value of FALSE
+! 	 */
+! 	public function userUsernameFromDn($dn) {
+! 
+! 		$ldap_entry = @$this->dnExists($dn, 'ldap_entry', array());
+! 		if (!$ldap_entry || !is_array($ldap_entry)) {
+! 			return FALSE;
+! 		}
+! 		else {
+! 			return $this->userUsernameFromLdapEntry($ldap_entry);
+! 		}
+! 
+! 	}
+! 
+! 	/**
+! 	 * @param ldap entry array $ldap_entry
+! 	 *
+! 	 * @return string user's mail value or FALSE if none present
+! 	 */
+! 	public function userEmailFromLdapEntry($ldap_entry) {
+! 
+! 		if ($ldap_entry && $this->mail_attr) { // not using template
+! 			$mail = isset($ldap_entry[$this->mail_attr][0]) ? $ldap_entry[$this->mail_attr][0] : FALSE;
+! 			return $mail;
+! 		}
+! 		elseif ($ldap_entry && $this->mail_template) {  // template is of form [cn]@illinois.edu
+! 			ldap_servers_module_load_include('inc', 'ldap_servers', 'ldap_servers.functions');
+! 			return ldap_servers_token_replace($ldap_entry, $this->mail_template, 'ldap_entry');
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 
+! 	/**
+! 	 * @param ldap entry array $ldap_entry
+! 	 *
+! 	 * @return drupal file object image user's thumbnail or FALSE if none present or ERROR happens.
+! 	 */
+! 	public function userPictureFromLdapEntry($ldap_entry) {
+! 		if ($ldap_entry && $this->picture_attr) {
+! 			//Check if ldap entry has been provisioned.
+! 			$account = user_load_by_name($ldap_entry[$this->user_attr][0]);						
+! 			$thumb = isset($ldap_entry[$this->picture_attr][0]) ? $ldap_entry[$this->picture_attr][0] : FALSE;
+! 			if(!$thumb || $account->uid == '0'){
+! 				return false;
+! 			}
+! 			//Create md5 check.						
+! 			$md5thumb = md5($thumb);
+! 			/**
+! 			 * If account already has picture check if it has changed if so remove old file and create the new one 
+! 			 * If picture is not set but account has md5 something is wrong exit. 
+! 			 */
+! 			
+! 			if(isset($account->picture)){
+! 				// Check if image has changed
+! 				if(isset($account->data['ldap_user']['init']['thumb5md']) && $md5thumb === $account->data['ldap_user']['init']['thumb5md']){
+! 					//No change return same image					
+! 					return $account->picture;					
+! 				}else{
+! 					//Image is different check wether is obj/str and remove fileobject
+! 					if(is_object($account->picture)){
+! 						file_delete($account->picture, TRUE);
+! 					}else if(is_string($account->picture)){
+! 						$file = file_load(intval($account->picture));
+! 						file_delete($file, TRUE);
+! 					}
+! 				}			
+! 			}else if(isset($account->data['ldap_user']['init']['thumb5md'])) {
+! 				watchdog('ldap_server', "Some error happened during thumbnailPhoto sync", WATCHDOG_ERROR );
+! 				return false;
+! 			}
+! 			//Create tmp file to get image format.
+! 			$filename = uniqid();
+! 			$fileuri = file_directory_temp() .'/'. $filename;
+! 			$size = file_put_contents($fileuri, $thumb);				
+! 			$info = image_get_info($fileuri);
+! 			unlink($fileuri);
+! 			// create file object
+! 			$file = file_save_data($thumb, 'public://' . variable_get('user_picture_path') .'/'. $filename .'.'. $info['extension']);
+! 			$file->md5Sum = $md5thumb;
+! 			// standard Drupal validators for user pictures
+! 			$validators = array(
+! 					'file_validate_is_image' => array(),
+! 					'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
+! 					'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
+! 			);
+! 			$errors = file_validate($file ,$validators);
+! 			if (empty($errors)) {
+! 				return $file;
+! 			}else{
+! 				foreach($errors as $err => $err_val){
+! 					watchdog('ldap_server', "Error storing picture: %$err", "%$err_val", WATCHDOG_ERROR );
+! 				}
+! 				return false;
+! 			}
+! 		}
+! 	}
+! 	
+! 
+! 
+! 
+! /**
+!  * @param ldap entry array $ldap_entry
+!  *
+!  * @return string user's PUID or permanent user id (within ldap) in native ldap format (no binary conversions applied)
+!  */
+! public function userPuidFromLdapEntry($ldap_entry) {
+! 
+! 	if ($this->unique_persistent_attr
+! 			&& isset($ldap_entry[$this->unique_persistent_attr][0])
+! 			&& is_scalar($ldap_entry[$this->unique_persistent_attr][0])
+! 	) {
+! 
+! 		return $ldap_entry[$this->unique_persistent_attr][0];
+! 	}
+! 	else {
+! 		return FALSE;
+! 	}
+! }
+! 
+! /**
+!  *  @param mixed $user
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array)
+!  *    - ldap dn of user (string)
+!  *    - drupal username of user (string)
+!  *
+!  *  @return array $ldap_user_entry (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  */
+! public function user_lookup($user) {
+! 	return $this->userUserToExistingLdapEntry($user);
+! }
+! public function userUserToExistingLdapEntry($user) {
+! 
+! 	if (is_object($user)) {
+! 		$user_ldap_entry = $this->userUserNameToExistingLdapEntry($user->name);
+! 	}
+! 	elseif (is_array($user)) {
+! 		$user_ldap_entry = $user;
+! 	}
+! 	elseif (is_scalar($user)) {
+! 		if (strpos($user, '=') === FALSE) { // username
+! 			$user_ldap_entry = $this->userUserNameToExistingLdapEntry($user);
+! 		}
+! 		else {
+! 			$user_ldap_entry = $this->dnExists($user, 'ldap_entry');
+! 		}
+! 	}
+! 	return $user_ldap_entry;
+! }
+! 
+! /**
+!  * Queries LDAP server for the user.
+!  *
+!  * @param string $drupal_user_name
+!  *
+!  * @param string or int $prov_event
+!  *   This could be anything, particularly when used by other modules.  Other modules should use string like 'mymodule_myevent'
+!  *   LDAP_USER_EVENT_ALL signifies get all attributes needed by all other contexts/ops
+!  *
+!  * @return associative array representing ldap data of a user.  for example of returned value.
+!  *   'sid' => ldap server id
+!  *   'mail' => derived from ldap mail (not always populated).
+!  *   'dn'   => dn of user
+!  *   'attr' => single ldap entry array in form returned from ldap_search() extension, e.g.
+!  *   'dn' => dn of entry
+!  */
+! function userUserNameToExistingLdapEntry($drupal_user_name, $ldap_context = NULL) {
+! 
+! 	$watchdog_tokens = array('%drupal_user_name' => $drupal_user_name);
+! 	$ldap_username = $this->userUsernameToLdapNameTransform($drupal_user_name, $watchdog_tokens);
+! 	if (!$ldap_username) {
+! 		return FALSE;
+! 	}
+! 	if (!$ldap_context) {
+! 		$attributes = array();
+! 	}
+! 	else {
+! 		$attribute_maps = ldap_servers_attributes_needed($this->sid, $ldap_context);
+! 		$attributes = array_keys($attribute_maps);
+! 	}
+! 
+! 	$basedns = (is_array($this->basedn)) ? $this->basedn : array();
+! 	foreach ($basedns as $basedn) {
+! 		if (empty($basedn)) continue;
+! 		$filter = '(' . $this->user_attr . '=' . ldap_server_massage_text($ldap_username, 'attr_value', LDAP_SERVER_MASSAGE_QUERY_LDAP) . ')';
+! 		$result = $this->search($basedn, $filter, $attributes);
+! 		if (!$result || !isset($result['count']) || !$result['count']) continue;
+! 
+! 		// Must find exactly one user for authentication to work.
+! 
+! 		if ($result['count'] != 1) {
+! 			$count = $result['count'];
+! 			watchdog('ldap_servers', "Error: !count users found with $filter under $basedn.", array('!count' => $count), WATCHDOG_ERROR);
+! 			continue;
+! 		}
+! 		$match = $result[0];
+! 		// These lines serve to fix the attribute name in case a
+! 		// naughty server (i.e.: MS Active Directory) is messing the
+! 		// characters' case.
+! 		// This was contributed by Dan "Gribnif" Wilga, and described
+! 		// here: http://drupal.org/node/87833
+! 		$name_attr = $this->user_attr;
+! 
+! 		if (isset($match[$name_attr][0])) {
+! 			// leave name
+! 		}
+! 		elseif (isset($match[drupal_strtolower($name_attr)][0])) {
+! 			$name_attr = drupal_strtolower($name_attr);
+! 
+! 		}
+! 		else {
+! 			if ($this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER) {
+! 				$result = array(
+! 						'dn' =>  $match['dn'],
+! 						'mail' => $this->userEmailFromLdapEntry($match),
+! 						'attr' => $match,
+! 						'sid' => $this->sid,
+! 				);
+! 				return $result;
+! 			}
+! 			else {
+! 				continue;
+! 			}
+! 		}
+! 
+! 		// Finally, we must filter out results with spaces added before
+! 		// or after, which are considered OK by LDAP but are no good for us
+! 		// We allow lettercase independence, as requested by Marc Galera
+! 		// on http://drupal.org/node/97728
+! 		//
+! 		// Some setups have multiple $name_attr per entry, as pointed out by
+! 		// Clarence "sparr" Risher on http://drupal.org/node/102008, so we
+! 		// loop through all possible options.
+! 		foreach ($match[$name_attr] as $value) {
+! 			if (drupal_strtolower(trim($value)) == drupal_strtolower($ldap_username)) {
+! 				$result = array(
+! 						'dn' =>  $match['dn'],
+! 						'mail' => $this->userEmailFromLdapEntry($match),
+! 						'attr' => $match,
+! 						'sid' => $this->sid,
+! 				);
+! 				return $result;
+! 			}
+! 		}
+! 	}
+! }
+  
+  /**
+!  * Is a user a member of group?
+   *
+!  * @param string $group_dn MIXED CASE
+!  * @param mixed $user
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array)
+!  *    - ldap dn of user (array)
+!  *    - drupal user name (string)
+!  * @param enum $nested = NULL (default to server configuration), TRUE, or FALSE indicating to test for nested groups
+   */
+! public function groupIsMember($group_dn, $user, $nested = NULL) {
+! 
+! 	$nested = ($nested === TRUE || $nested === FALSE) ? $nested : $this->groupNested;
+! 	$group_dns = $this->groupMembershipsFromUser($user, 'group_dns', $nested);
+! 	// while list of group dns is going to be in correct mixed case, $group_dn may not since it may be derived from user entered values
+! 	// so make sure in_array() is case insensitive
+! 	return (is_array($group_dns) && in_array(drupal_strtolower($group_dn), $this->dnArrayToLowerCase($group_dns)));
+! }
+  
+  
+  
+! /**
+!  * NOT TESTED
+!  * add a group entry
+!  *
+!  * @param string $group_dn as ldap dn
+!  * @param array $attributes in key value form
+!  *    $attributes = array(
+!  *      "attribute1" = "value",
+!  *      "attribute2" = array("value1", "value2"),
+!  *      )
+!  * @return boolean success
+!  */
+! public function groupAddGroup($group_dn, $attributes = array()) {
+  
++ 	//debug("this->dnExists(   $group_dn, boolean)"); debug($this->dnExists($group_dn, 'boolean'));
++ 	// debug("this->dnExists(   $group_dn, boolean)"); debug($this->dnExists($group_dn));
++ 	if ($this->dnExists($group_dn, 'boolean')) {
++ 		return FALSE;
++ 	}
++ 
++ 	$attributes = array_change_key_case($attributes, CASE_LOWER);
++ 	$objectclass = (empty($attributes['objectclass'])) ? $this->groupObjectClass : $attributes['objectclass'];
++ 	$attributes['objectclass'] = $objectclass;
++ 
++ 	/**
++ 	 * 2. give other modules a chance to add or alter attributes
++ 	 */
++ 	$context = array(
++ 			'action' => 'add',
++ 			'corresponding_drupal_data' => array($group_dn => $attributes),
++ 			'corresponding_drupal_data_type' => 'group',
++ 	);
++ 	$ldap_entries = array($group_dn => $attributes);
++ 	drupal_alter('ldap_entry_pre_provision', $ldap_entries, $this, $context);
++ 	$attributes = $ldap_entries[$group_dn];
++ 
++ 
++ 	/**
++ 	 * 4. provision ldap entry
++ 	 *   @todo how is error handling done here?
++ 	 */
++ 	$ldap_entry_created = $this->createLdapEntry($attributes, $group_dn);
++ 
++ 
++ 	/**
++ 	 * 5. allow other modules to react to provisioned ldap entry
++ 	 *   @todo how is error handling done here?
++ 	 */
++ 	if ($ldap_entry_created) {
++ 		module_invoke_all('ldap_entry_post_provision', $ldap_entries, $this, $context);
++ 		return TRUE;
++ 	}
++ 	else {
++ 		return FALSE;
++ 	}
+  
++ }
+  
+  /**
+!  * NOT TESTED
+!  * remove a group entry
+   *
+!  * @param string $group_dn as ldap dn
+!  * @param boolean $only_if_group_empty
+!  *   TRUE = group should not be removed if not empty
+!  *   FALSE = groups should be deleted regardless of members
+!  */
+! public function groupRemoveGroup($group_dn, $only_if_group_empty = TRUE) {
+! 
+! 	if ($only_if_group_empty) {
+! 		$members = $this->groupAllMembers($group_dn);
+! 		if (is_array($members) && count($members) > 0) {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 	return $this->delete($group_dn);
+! 
+! }
+! 
+! /**
+!  * NOT TESTED
+!  * add a member to a group
+   *
+!  * @param string $ldap_user_dn as ldap dn
+!  * @param mixed $user
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  *    - ldap dn of user (array)
+!  *    - drupal username of user (string)
+   */
++ public function groupAddMember($group_dn, $user) {
+  
+! 	$user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+! 	$result = FALSE;
+! 	if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) {
+! 		$add = array();
+! 		$add[$this->groupMembershipsAttr] = $user_ldap_entry['dn'];
+! 		$this->connectAndBindIfNotAlready();
+! 		$result = @ldap_mod_add($this->connection, $group_dn, $add);
+! 	}
+  
+! 	return $result;
+! }
+  
+! /**
+!  * NOT TESTED
+!  * remove a member from a group
+!  *
+!  * @param string $group_dn as ldap dn
+!  * @param mixed $user
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  *    - ldap dn of user (array)
+!  *    - drupal username of user (string)
+!  */
+! public function groupRemoveMember($group_dn, $user) {
+  
+! 	$user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+! 	$result = FALSE;
+! 	if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) {
+! 		$del = array();
+! 		$del[$this->groupMembershipsAttr] = $user_ldap_entry['dn'];
+! 		$this->connectAndBindIfNotAlready();
+! 		$result = @ldap_mod_del($this->connection, $group_dn, $del);
+! 	}
+! 	return $result;
+! }
+  
+  
+! /**
+!  *
+!  * @todo: NOT IMPLEMENTED: nested groups
+!  *
+!  * get all members of a group
+!  *
+!  * @param string $group_dn as ldap dn
+!  *
+!  * @return FALSE on error otherwise array of group members (could be users or groups)
+!  */
+! public function groupAllMembers($group_dn) {
+! 	// debug("groupAllMembers $group_dn, this->groupMembershipsAttr=". $this->groupMembershipsAttr . 'this->groupGroupEntryMembershipsConfigured=' . $this->groupGroupEntryMembershipsConfigured);
+! 	if (!$this->groupGroupEntryMembershipsConfigured) {
+! 		return FALSE;
+! 	}
+! 	$attributes = array($this->groupMembershipsAttr, 'cn');
+! 	$group_entry = $this->dnExists($group_dn, 'ldap_entry', $attributes);
+! 	if (!$group_entry) {
+! 		return FALSE;
+! 	}
+! 	else {
+! 		if (empty($group_entry['cn'])) { // if attributes weren't returned, don't give false  empty group
+! 			return FALSE;
+! 		}
+! 		if (empty($group_entry[$this->groupMembershipsAttr])) {
+! 			return array(); // if no attribute returned, no members
+! 		}
+! 		$members = $group_entry[$this->groupMembershipsAttr];
+! 		if (isset($members['count'])) {
+! 			unset($members['count']);
+! 		}
+! 		return $members;
+! 	}
+! 
+! 
+! 
+! 	$this->groupMembersResursive($current_group_entries, $all_group_dns, $tested_group_ids, 0, $max_levels, $object_classes);
+! 
+! 	//  dpm('all_group_dns'); dpm($all_group_dns);
+! 	return $all_group_dns;
+  
+! }
+  
++ /**
++  *   NOT IMPLEMENTED
++  * recurse through all child groups and add members.
++  *
++  * @param array $current_group_entries of ldap group entries that are starting point.  should include at least 1 entry.
++  * @param array $all_group_dns as array of all groups user is a member of.  MIXED CASE VALUES
++  * @param array $tested_group_ids as array of tested group dn, cn, uid, etc.  MIXED CASE VALUES
++  *   whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
++  *   whatever attribute is in $this->$tested_group_ids to avoid redundant recursing
++  * @param int $level of recursion
++  * @param int $max_levels as max recursion allowed
++  *
++  */
++ 
++ public function groupMembersResursive($current_member_entries, &$all_member_dns, &$tested_group_ids, $level, $max_levels, $object_classes = FALSE) {
++ 	// dpm("group membership recursive"); dpm($current_entries);
++ 	if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_member_entries) || count($current_member_entries) == 0) {
++ 		return FALSE;
++ 	}
++ 	if (isset($current_member_entries['count'])) {
++ 		unset($current_member_entries['count']);
++ 	};
++ 
++ 	foreach ($current_member_entries as $i => $member_entry) {
++ 		//dpm("groupMembersResursive:member_entry $i, level=$level < max_levels=$max_levels"); dpm($member_entry);
++ 		// 1.  Add entry itself if of the correct type to $all_member_dns
++ 		$objectClassMatch = (!$object_classes || (count(array_intersect(array_values($member_entry['objectclass']), $object_classes)) > 0));
++ 		$objectIsGroup = in_array($this->groupObjectClass, array_values($member_entry['objectclass']));
++ 		if ($objectClassMatch && !in_array($member_entry['dn'], $all_member_dns)) { // add member
++ 			$all_member_dns[] = $member_entry['dn'];
++ 		}
++ 
++ 		// 2. If its a group, keep recurse the group for descendants
++ 		if ($objectIsGroup && $level < $max_levels) {
++ 			if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
++ 				$group_id = $member_entry['dn'];
++ 			}
++ 			else {
++ 				$group_id = $member_entry[$this->groupMembershipsAttrMatchingUserAttr][0];
++ 			}
++ 			// 3. skip any groups that have already been tested
++ 			if (!in_array($group_id, $tested_group_ids)) {
++ 				$tested_group_ids[] = $group_id;
++ 				$member_ids = $member_entry[$this->groupMembershipsAttr];
++ 				if (isset($member_ids['count'])) {
++ 					unset($member_ids['count']);
++ 				};
++ 				$ors = array();
++ 				foreach ($member_ids as $i => $member_id) {
++ 					$ors[] =  $this->groupMembershipsAttr . '=' . $member_id; // @todo this would be replaced by query template
++ 				}
++ 
++ 				if (count($ors)) {
++ 					$query_for_child_members = '(|(' . join(")(", $ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
++ 					if (count($object_classes)) { // add or on object classe, otherwise get all object classes
++ 						$object_classes_ors = array('(objectClass=' . $this->groupObjectClass . ')');
++ 						foreach ($object_classes as $object_class) {
++ 							$object_classes_ors[] = '(objectClass=' . $object_class . ')';
++ 						}
++ 						$query_for_child_members = '&(|' . join($object_classes_ors) . ')(' . $query_for_child_members . ')';
++ 					}
++ 					foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
++ 						$child_member_entries = $this->search($base_dn, $query_for_child_members, array('objectclass', $this->groupMembershipsAttr, $this->groupMembershipsAttrMatchingUserAttr));
++ 						if ($child_member_entries !== FALSE) {
++ 							$this->groupMembersResursive($child_member_entries, $all_member_dns, $tested_group_ids, $level + 1, $max_levels, $object_classes);
++ 						}
++ 					}
++ 				}
++ 			}
++ 		}
++ 	}
++ }
++ 
++ 
++ /**
+   /**
+!  *  get list of all groups that a user is a member of.
+!  *
+!  *    If $nested = TRUE,
+!  *    list will include all parent group.  That is if user is a member of "programmer" group
+!  *    and "programmer" group is a member of "it" group, user is a member of
+!  *    both "programmer" and "it" groups.
+!  *
+!  *    If $nested = FALSE, list will only include groups user is in directly.
+!  *
+!  *  @param mixed
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  *    - ldap dn of user (array)
+!  *    - drupal username of user (string)
+!  *  @param enum $return = 'group_dns'
+!  *  @param boolean $nested if groups should be recursed or not.
+!  *
+!  *  @return array of groups dns in mixed case or FALSE on error
+!  */
+! 
+! public function groupMembershipsFromUser($user, $return = 'group_dns', $nested = NULL) {
+  
++ 	$group_dns = FALSE;
++ 	$user_ldap_entry = @$this->userUserToExistingLdapEntry($user);
++ 	if (!$user_ldap_entry || $this->groupFunctionalityUnused) {
++ 		return FALSE;
++ 	}
++ 	if ($nested === NULL) {
++ 		$nested = $this->groupNested;
++ 	}
++ 
++ 	if ($this->groupUserMembershipsConfigured) { // preferred method
++ 		$group_dns = $this->groupUserMembershipsFromUserAttr($user_ldap_entry, $nested);
++ 	}
++ 	elseif ($this->groupGroupEntryMembershipsConfigured) {
++ 		$group_dns = $this->groupUserMembershipsFromEntry($user_ldap_entry, $nested);
++ 	}
++ 
++ 	if ($return == 'group_dns') {
++ 		return $group_dns;
++ 	}
+  
++ }
++ 
++ 
++ /**
++  *  get list of all groups that a user is a member of by using memberOf attribute first,
++  *    then if nesting is true, using group entries to find parent groups
++  *
++  *    If $nested = TRUE,
++  *    list will include all parent group.  That is if user is a member of "programmer" group
++  *    and "programmer" group is a member of "it" group, user is a member of
++  *    both "programmer" and "it" groups.
++  *
++  *    If $nested = FALSE, list will only include groups user is in directly.
++  *
++  *  @param mixed
++  *    - drupal user object (stdClass Object)
++  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
++  *    - ldap dn of user (array)
++  *    - drupal username of user (string)
++  *  @param boolean $nested if groups should be recursed or not.
++  *
++  *  @return array of group dns
++  */
+  
+! public function groupUserMembershipsFromUserAttr($user, $nested = NULL) {
+  
+! 	if (!$this->groupUserMembershipsConfigured) {
+! 		return FALSE;
+! 	}
+! 	if ($nested === NULL) {
+! 		$nested = $this->groupNested;
+! 	}
+! 
+! 	if (!is_array($user['attr']) && !isset($user['attr'][$this->groupUserMembershipsAttr])) {
+! 		$user_ldap_entry = $this->userUserToExistingLdapEntry($user);
+! 		if (!isset($user_ldap_entry['attr'][$this->groupUserMembershipsAttr])) {
+! 			return FALSE; // user's membership attribute is not present.  either misconfigured or query failed
+! 		}
+! 	}
+! 	else {
+! 		$user_ldap_entry = $user;
+! 	}
+! 
+! 	$all_group_dns = array();
+! 	$tested_group_ids = array();
+! 	$level = 0;
+! 
+! 	$member_group_dns = $user_ldap_entry['attr'][$this->groupUserMembershipsAttr];
+! 	if (isset($member_group_dns['count'])) {
+! 		unset($member_group_dns['count']);
+! 	}
+! 	$ors = array();
+! 	foreach ($member_group_dns as $i => $member_group_dn) {
+! 		$all_group_dns[] = $member_group_dn;
+! 		if ($nested) {
+! 			if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+! 				$member_value = $member_group_dn;
+! 			}
+! 			else {
+! 				$member_value = ldap_servers_get_first_rdn_value_from_dn($member_group_dn, $this->groupMembershipsAttrMatchingUserAttr);
+! 			}
+! 			$ors[] =  $this->groupMembershipsAttr . '=' . $member_value;
+! 		}
+! 	}
+! 
+! 	if ($nested && count($ors)) {
+! 		$count = count($ors);
+! 		for ($i=0; $i < $count; $i=$i+LDAP_SERVER_LDAP_QUERY_CHUNK) { // only 50 or so per query
+! 			$current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK);
+! 			$or = '(|(' . join(")(", $current_ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
+! 			$query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')';
+! 
+! 			foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+! 				// debug("query for parent groups, base_dn=$base_dn, $query_for_parent_groups");
+! 				$group_entries = $this->search($base_dn, $query_for_parent_groups);  // no attributes, just dns needed
+! 				if ($group_entries !== FALSE  && $level < LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT) {
+! 					$this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT);
+! 				}
+! 			}
+! 		}
+! 	}
+  
+! 	return $all_group_dns;
+! }
+  
+  /**
+!  *  get list of all groups that a user is a member of by querying groups
+!  *
+!  *    If $nested = TRUE,
+!  *    list will include all parent group.  That is if user is a member of "programmer" group
+!  *    and "programmer" group is a member of "it" group, user is a member of
+!  *    both "programmer" and "it" groups.
+!  *
+!  *    If $nested = FALSE, list will only include groups user is in directly.
+!  *
+!  *  @param mixed
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  *    - ldap dn of user (array)
+!  *    - drupal username of user (string)
+!  *  @param boolean $nested if groups should be recursed or not.
+!  *
+!  *  @return array of group dns MIXED CASE VALUES
+!  *
+!  *  @see tests/DeriveFromEntry/ldap_servers.inc for fuller notes and test example
+!  */
+! public function groupUserMembershipsFromEntry($user, $nested = NULL) {
+  
++ 	if (!$this->groupGroupEntryMembershipsConfigured) {
++ 		return FALSE;
++ 	}
++ 	if ($nested === NULL) {
++ 		$nested = $this->groupNested;
++ 	}
++ 
++ 	$user_ldap_entry = $this->userUserToExistingLdapEntry($user);
++ 
++ 	$all_group_dns = array(); // MIXED CASE VALUES
++ 	$tested_group_ids = array(); // array of dns already tested to avoid excess queries MIXED CASE VALUES
++ 	$level = 0;
++ 
++ 	if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
++ 		$member_value = $user_ldap_entry['dn'];
++ 	}
++ 	else {
++ 		$member_value = $user_ldap_entry['attr'][$this->groupMembershipsAttrMatchingUserAttr][0];
++ 	}
++ 
++ 	$group_query = '(&(objectClass=' . $this->groupObjectClass . ')(' . $this->groupMembershipsAttr . "=$member_value))";
++ 
++ 	foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
++ 		$group_entries = $this->search($base_dn, $group_query, array()); // only need dn, so empty array forces return of no attributes
++ 		if ($group_entries !== FALSE) {
++ 			$max_levels = ($nested) ? LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT : 0;
++ 			$this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level, $max_levels);
++ 		}
++ 	}
+  
+! 	return $all_group_dns;
+! }
+  
+! /**
+!  * recurse through all groups, adding parent groups to $all_group_dns array.
+!  *
+!  * @param array $current_group_entries of ldap group entries that are starting point.  should include at least 1 entry.
+!  * @param array $all_group_dns as array of all groups user is a member of.  MIXED CASE VALUES
+!  * @param array $tested_group_ids as array of tested group dn, cn, uid, etc.  MIXED CASE VALUES
+!  *   whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
+!  *   whatever attribute is in $this->$tested_group_ids to avoid redundant recursing
+!  * @param int $level of recursion
+!  * @param int $max_levels as max recursion allowed
+!  *
+!  * given set of groups entries ($current_group_entries such as it, hr, accounting),
+!  * find parent groups (such as staff, people, users) and add them to list of group memberships ($all_group_dns)
+!  *
+!  * (&(objectClass=[$this->groupObjectClass])(|([$this->groupMembershipsAttr]=groupid1)([$this->groupMembershipsAttr]=groupid2))
+!  *
+!  * @return FALSE for error or misconfiguration, otherwise TRUE.  results are passed by reference.
+!  */
+  
++ public function groupMembershipsFromEntryResursive($current_group_entries, &$all_group_dns, &$tested_group_ids, $level, $max_levels) {
+  
+! 	if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_group_entries) || count($current_group_entries) == 0) {
+! 		return FALSE;
+! 	}
+! 	if (isset($current_group_entries['count'])) {
+! 		unset($current_group_entries['count']);
+! 	};
+! 
+! 	$ors = array();
+! 	foreach ($current_group_entries as $i => $group_entry) {
+! 		if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') {
+! 			$member_id = $group_entry['dn'];
+! 		}
+! 		else {// maybe cn, uid, etc is held
+! 			$member_id = ldap_servers_get_first_rdn_value_from_dn($group_entry['dn'], $this->groupMembershipsAttrMatchingUserAttr);
+! 		}
+! 
+! 		if ($member_id && !in_array($member_id, $tested_group_ids)) {
+! 			$tested_group_ids[] = $member_id;
+! 			$all_group_dns[] = $group_entry['dn'];
+! 			// add $group_id (dn, cn, uid) to query
+! 			$ors[] =  $this->groupMembershipsAttr . '=' . $member_id;
+! 		}
+! 	}
+! 
+! 	if (count($ors)) {
+! 		$count = count($ors);
+! 		for ($i=0; $i < $count; $i=$i+LDAP_SERVER_LDAP_QUERY_CHUNK) { // only 50 or so per query
+! 			$current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK);
+! 			//dpm("current_ors $i," . LDAP_SERVER_LDAP_QUERY_CHUNK); dpm($current_ors);
+! 			$or = '(|(' . join(")(", $current_ors) . '))';  // e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
+! 			$query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')';
+! 
+! 
+! 			// debug('query_for_parent_groups'); debug($query_for_parent_groups);
+! 			foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
+! 				$group_entries = $this->search($base_dn, $query_for_parent_groups);  // no attributes, just dns needed
+! 				if ($group_entries !== FALSE  && $level < $max_levels) {
+! 					$this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, $max_levels);
+! 				}
+! 			}
+! 		}
+! 	}
+! 
+! 	return TRUE;
+! }
+! 
+! 
+! /**
+!  *  get "groups" from derived from DN.  Has limited usefulness
+!  *
+!  *  @param mixed
+!  *    - drupal user object (stdClass Object)
+!  *    - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
+!  *    - ldap dn of user (array)
+!  *    - drupal username of user (string)
+!  *
+!  *  @return array of group strings
+!  */
+! public function groupUserMembershipsFromDn($user) {
+! 
+! 	if (!$this->groupDeriveFromDn || !$this->groupDeriveFromDnAttr) {
+! 		return FALSE;
+! 	}
+! 	elseif ($user_ldap_entry = $this->userUserToExistingLdapEntry($user)) {
+! 		return ldap_servers_get_all_rdn_values_from_dn($user_ldap_entry['dn'], $this->groupDeriveFromDnAttr);
+! 	}
+! 	else {
+! 		return FALSE;
+! 	}
+! 
+! }
+! /**
+!  * Error methods and properties.
+!  */
+! 
+! public $detailedWatchdogLog = FALSE;
+! protected $_errorMsg = NULL;
+! protected $_hasError = FALSE;
+! protected $_errorName = NULL;
+! 
+! public function setError($_errorName, $_errorMsgText = NULL) {
+! 	$this->_errorMsgText = $_errorMsgText;
+! 	$this->_errorName = $_errorName;
+! 	$this->_hasError = TRUE;
+! }
+! 
+! public function clearError() {
+! 	$this->_hasError = FALSE;
+! 	$this->_errorMsg = NULL;
+! 	$this->_errorName = NULL;
+! }
+! 
+! public function hasError() {
+! 	return ($this->_hasError || $this->ldapErrorNumber());
+! }
+! 
+! public function errorMsg($type = NULL) {
+! 	if ($type == 'ldap' && $this->connection) {
+! 		return ldap_err2str(ldap_errno($this->connection));
+! 	}
+! 	elseif ($type == NULL) {
+! 		return $this->_errorMsg;
+! 	}
+! 	else {
+! 		return NULL;
+! 	}
+! }
+! 
+! public function errorName($type = NULL) {
+! 	if ($type == 'ldap' && $this->connection) {
+! 		return "LDAP Error: " . ldap_error($this->connection);
+! 	}
+! 	elseif ($type == NULL) {
+! 		return $this->_errorName;
+! 	}
+! 	else {
+! 		return NULL;
+! 	}
+! }
+! 
+! public function ldapErrorNumber() {
+! 	if ($this->connection && ldap_errno($this->connection)) {
+! 		return ldap_errno($this->connection);
+! 	}
+! 	else {
+! 		return FALSE;
+! 	}
+! }
+  
+  }
+*** ldap_servers/LdapServerAdmin.class.php	Fri Dec 07 05:38:15 2012
+--- ldap_servers/LdapServerAdmin.class.php	Thu Feb 07 09:18:18 2013
+***************
+*** 66,73 ****
+--- 66,75 ----
+      $this->user_dn_expression = trim($values['user_dn_expression']);
+      $this->basedn = $this->linesToArray(trim($values['basedn']));
+      $this->user_attr = drupal_strtolower(trim($values['user_attr']));
++     $this->picture_attr = drupal_strtolower(trim($values['picture_attr']));
+      $this->account_name_attr = drupal_strtolower(trim($values['account_name_attr']));
+      $this->mail_attr = drupal_strtolower(trim($values['mail_attr']));
++     $this->picture_attr = drupal_strtolower(trim($values['picture_attr']));
+      $this->mail_template = trim($values['mail_template']);
+      $this->unique_persistent_attr = drupal_strtolower(trim($values['unique_persistent_attr']));
+      $this->unique_persistent_attr_binary = trim($values['unique_persistent_attr_binary']);
+***************
+*** 93,98 ****
+--- 95,101 ----
+  
+      $this->searchPagination = ($values['search_pagination']) ? 1 : 0;
+      $this->searchPageSize = trim($values['search_page_size']);
++     
+  
+    }
+  
+***************
+*** 531,536 ****
+--- 534,540 ----
+       * consumer_type is tag (unique alphanumeric id) of consuming authorization such as
+       *   drupal_roles, og_groups, civicrm_memberships
+       */
++   	
+      $fields = array(
+  
+        'sid' => array(
+***************
+*** 803,808 ****
+--- 807,827 ----
+            'length' => 255,
+            'not null' => FALSE,
+          ),
++       ),
++       
++       'picture_attr' => array(
++       		'form' => array(
++       				'fieldset' => 'users',
++       				'#type' => 'textfield',
++       				'#size' => 30,
++       				'#title' => t('Thumbnail attribute'),
++       				'#description' => t('The attribute that holds the users\' thumnail image. (eg. <code>thumbnailPhoto</code>). Leave empty if no such attribute exists'),
++       		),
++       		'schema' => array(
++       				'type' => 'varchar',
++       				'length' => 255,
++       				'not null' => FALSE,
++       		),
+        ),
+  
+        'mail_template' => array(
+*** ldap_user/ldap_user.module	Fri Dec 07 05:38:15 2012
+--- ldap_user/ldap_user.module	Mon Feb 11 14:44:49 2013
+***************
+*** 60,65 ****
+--- 60,66 ----
+  define('LDAP_USER_TEST_FORM_PATH', 'admin/config/people/ldap/user/test');
+  define('LDAP_USER_WS_USER_PATH', 'ldap/user/ws');
+  
++ 
+  /**
+   * Implements hook_menu().
+   */
+***************
+*** 223,228 ****
+--- 224,230 ----
+        case 'ldap_user_ldap_associate':
+          $attributes[$ldap_server->user_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->user_attr]); // array($ldap_server->user_attr, 0, NULL);
+          $attributes[$ldap_server->mail_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->mail_attr]);
++         $attributes[$ldap_server->picture_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->picture_attr]);
+          $attributes[$ldap_server->unique_persistent_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->unique_persistent_attr]);
+          if ($ldap_server->mail_template) {
+            ldap_servers_token_extract_attributes($attributes,  $ldap_server->mail_template);
+***************
+*** 270,275 ****
+--- 272,289 ----
+        'prov_module' => 'ldap_user',
+        'configurable_to_ldap' => TRUE,
+      );
++     
++     $available_user_attrs['[property.picture]'] =  array(
++     		'name' => 'Property: picture',
++     		'source' => '',
++     		'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
++     		'enabled' => TRUE,
++     		'prov_events' => array(LDAP_USER_EVENT_CREATE_LDAP_ENTRY, LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY),
++     		'config_module' => 'ldap_user',
++     		'prov_module' => 'ldap_user',
++     		'configurable_to_ldap' => TRUE,
++     );
++     
+  
+      $available_user_attrs['[property.uid]'] =  array(
+        'name' => 'Property: Drupal User Id (uid)',
+***************
+*** 342,347 ****
+--- 356,362 ----
+    if (!$ldap_user_conf->provisionsDrupalAccountsFromLdap) {
+      $available_user_attrs['[property.mail]']['config_module'] = 'ldap_user';
+      $available_user_attrs['[property.name]']['config_module'] = 'ldap_user';
++     $available_user_attrs['[property.picture]']['config_module'] = 'ldap_user';
+    }
+  
+    if ($direction == LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) {
+***************
+*** 804,815 ****
+    $prov_enabled = $ldap_user_conf->provisionEnabled(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE);
+  
+    if ($ldap_user_conf->provisionsDrupalAccountsFromLdap && in_array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, array_keys($ldap_user_conf->provisionsDrupalEvents))) {
+!     $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+      if ($ldap_user) {
+        $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
+        $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+      }
+      $account = user_save($account, $user_edit, 'ldap_user');
+    }
+  
+  }
+--- 819,832 ----
+    $prov_enabled = $ldap_user_conf->provisionEnabled(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE);
+  
+    if ($ldap_user_conf->provisionsDrupalAccountsFromLdap && in_array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, array_keys($ldap_user_conf->provisionsDrupalEvents))) {
+!     $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');    
+      if ($ldap_user) {
+        $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
+        $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+      }
+      $account = user_save($account, $user_edit, 'ldap_user');
++     
++ 	 
+    }
+  
+  }
+***************
+*** 823,829 ****
+  
+   // debug('ldap_user_user_insert'); debug($user_edit); debug($account); // ldap_user_user_insert, category='. $category . 'account->status = ' . $account->status);
+  
+- 
+    global $user;
+    $is_user_1 = ($category == 'ldap_user' || (is_object($account) && property_exists($account, 'uid') && $account->uid == 1));
+    $new_account_request = (boolean)($user->uid == 0 && $account->access == 0 && $account->login == 0); // check for first time user
+--- 840,845 ----
+***************
+*** 921,928 ****
+      if (ldap_user_is_ldap_associated($account, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER)) {
+        $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+        $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
+!       $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+        $account = user_save($account, $user_edit, 'ldap_user');
+      }
+    }
+  
+--- 937,945 ----
+      if (ldap_user_is_ldap_associated($account, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER)) {
+        $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+        $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
+!       $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));            
+        $account = user_save($account, $user_edit, 'ldap_user');
++           
+      }
+    }
+  
+*** ldap_user/LdapUserConf.class.php	Fri Dec 07 05:38:15 2012
+--- ldap_user/LdapUserConf.class.php	Mon Feb 11 13:20:29 2013
+***************
+*** 10,1302 ****
+  
+  class LdapUserConf {
+  
+!   /**
+!    * server providing Drupal account provisioning
+!    *
+!    * @var string
+!    *
+!    * @see LdapServer::sid
+!    */
+!   public $drupalAcctProvisionServer = LDAP_USER_NO_SERVER_SID;
+! 
+!   /**
+!    * server providing LDAP entry provisioning
+!    *
+!    * @var string
+!    *
+!    * @see LdapServer::sid
+!    */
+!   public $ldapEntryProvisionServer = LDAP_USER_NO_SERVER_SID;
+! 
+!   /**
+!    * Associative array mapping synch directions to ldap server instances.
+!    *
+!    * @var array
+!    */
+!   public $provisionSidFromDirection = array(
+!     LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER => LDAP_USER_NO_SERVER_SID,
+!     LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => LDAP_USER_NO_SERVER_SID,
+!   );
+! 
+!   /**
+!    * Array of events that trigger provisioning of Drupal Accounts
+!    * Valid constants are:
+!    *   LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE
+!    *   LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE
+!    *   LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE
+!    *
+!    * @var array
+!    */
+!   public $drupalAcctProvisionTriggers = array(LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE, LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE, LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE);
+! 
+!   /**
+!    * Array of events that trigger provisioning of LDAP Entries
+!    * Valid constants are:
+!    *   LDAP_USER_LDAP_ENTRY_PROV_ON_USER_UPDATE_CREATE
+!    *   LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE
+!    *   LDAP_USER_LDAP_ENTRY_DELETE_ON_USER_DELETE
+!    *
+!    * @var array
+!    */
+!   public $ldapEntryProvisionTriggers = array();
+! 
+!   /**
+!    * server providing LDAP entry provisioning
+!    *
+!    * @var string
+!    *
+!    * @see LdapServer::sid
+!    */
+!   public $userConflictResolve = LDAP_USER_CONFLICT_RESOLVE_DEFAULT;
+! 
+!   /**
+!    * drupal account creation model
+!    *
+!    * @var int
+!    *   LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR   /admin/config/people/accounts/settings do not affect "LDAP Associated" Drupal accounts.
+!    *   LDAP_USER_ACCT_CREATION_USER_SETTINGS_FOR_LDAP  use Account creation settings at /admin/config/people/accounts/settings
+!    */
+!   public $acctCreation = LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT;
+! 
+!   /**
+!    * has current object been saved to the database?
+!    *
+!    * @var boolean
+!    *
+!    */
+!   public $inDatabase = FALSE;
+! 
+!   /**
+!    * what to do when an ldap provisioned username conflicts with existing drupal user?
+!    *
+!    * @var int
+!    *   LDAP_USER_CONFLICT_LOG - log the conflict
+!    *   LDAP_USER_CONFLICT_RESOLVE - LDAP associate the existing drupal user
+!    *
+!    */
+!   public $manualAccountConflict = LDAP_USER_MANUAL_ACCT_CONFLICT_REJECT;
+! 
+!   public $setsLdapPassword = TRUE; // @todo default to FALSE and check for mapping to set to true
+! 
+!   public $loginConflictResolve = FALSE;
+!   /**
+!    * array of field synch mappings provided by all modules (via hook_ldap_user_attrs_list_alter())
+!    * array of the form: array(
+!    * LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
+!    *   <server_id> => array(
+!    *     'sid' => <server_id> (redundant)
+!    *     'ldap_attr' => e.g. [sn]
+!    *     'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
+!    *     'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
+!    *     'convert' => 1|0 boolean indicating need to covert from binary
+!    *     'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
+!    *     'config_module' => 'ldap_user'
+!    *     'prov_module' => 'ldap_user'
+!    *     'enabled' => 1|0 boolean
+!    *      prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
+!    *         - four permutations available
+!    *            to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
+!    *            to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
+!    *    )
+!    *  )
+!    */
+!   public $synchMapping = NULL; // array of field synching directions for each operation.  should include ldapUserSynchMappings
+!   // keyed on direction => property, ldap, or field token such as '[field.field_lname] with brackets in them.
+! 
+!   /**
+!   * synch mappings configured in ldap user module (not in other modules)
+!   *   array of the form: array(
+!     LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
+!       'sid' => <server_id> (redundant)
+!       'ldap_attr' => e.g. [sn]
+!       'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
+!       'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
+!       'convert' => 1|0 boolean indicating need to covert from binary
+!       'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
+!       'config_module' => 'ldap_user'
+!       'prov_module' => 'ldap_user'
+!       'enabled' => 1|0 boolean
+!        prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
+!           - four permutations available
+!              to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
+!              to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
+!       )
+!     )
+!   )
+!   */
+!   public $ldapUserSynchMappings = NULL;  //
+!   // keyed on property, ldap, or field token such as '[field.field_lname] with brackets in them.
+!   public $detailedWatchdog = FALSE;
+!   public $provisionsDrupalAccountsFromLdap = FALSE;
+!   public $provisionsLdapEntriesFromDrupalUsers = FALSE;
+! 
+!   // what should be done with ldap provisioned accounts that no longer have associated drupal accounts.
+!   public $orphanedDrupalAcctBehavior = 'ldap_user_orphan_email';
+!    /** options are partially derived from user module account cancel options:
+!     *
+!     'ldap_user_orphan_do_not_check' => Do not check for orphaned Drupal accounts.)
+!     'ldap_user_orphan_email' => Perform no action, but email list of orphaned accounts. (All the other options will send email summaries also.)
+!     'user_cancel_block' => Disable the account and keep its content.
+!     'user_cancel_block_unpublish' => Disable the account and unpublish its content.
+!     'user_cancel_reassign' => Delete the account and make its content belong to the Anonymous user.
+!     'user_cancel_delete' => Delete the account and its content.
+!     */
+! 
+!   public $orphanedCheckQty = 100;
+! 
+! // public $wsKey = NULL;
+! //  public $wsEnabled = 0;
+! //  public $wsUserIps = array();
+! 
+!   public $provisionsLdapEvents = array();
+!   public $provisionsDrupalEvents = array();
+! 
+!   public $saveable = array(
+!     'drupalAcctProvisionServer',
+!     'ldapEntryProvisionServer',
+!     'drupalAcctProvisionTriggers',
+!     'ldapEntryProvisionTriggers',
+!     'orphanedDrupalAcctBehavior',
+!     'orphanedCheckQty',
+!     'userConflictResolve',
+!     'manualAccountConflict',
+!     'acctCreation',
+!     'ldapUserSynchMappings',
+!   );
+! // 'wsKey','wsEnabled','wsUserIps',
+!   function __construct() {
+!     $this->load();
+! 
+!     $this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER] = $this->drupalAcctProvisionServer;
+!     $this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY] = $this->ldapEntryProvisionServer;
+! 
+!     $this->provisionsLdapEvents = array(
+!       LDAP_USER_EVENT_CREATE_LDAP_ENTRY => t('On LDAP Entry Creation'),
+!       LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY => t('On Synch to LDAP Entry'),
+!       );
+! 
+!     $this->provisionsDrupalEvents = array(
+!       LDAP_USER_EVENT_CREATE_DRUPAL_USER => t('On Drupal User Creation'),
+!       LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER => t('On Synch to Drupal User'),
+!       );
+! 
+!     $this->provisionsDrupalAccountsFromLdap = (
+!       $this->drupalAcctProvisionServer &&
+!       $this->drupalAcctProvisionServer &&
+!       (count(array_filter(array_values($this->drupalAcctProvisionTriggers))) > 0)
+!     );
+! 
+!     $this->provisionsLdapEntriesFromDrupalUsers = (
+!       $this->ldapEntryProvisionServer
+!       && $this->ldapEntryProvisionServer
+!       && (count(array_filter(array_values($this->ldapEntryProvisionTriggers))) > 0)
+!       );
+! 
+!     $this->setSynchMapping(TRUE);
+!     $this->detailedWatchdog = variable_get('ldap_help_watchdog_detail', 0);
+!   }
+! 
+!   function load() {
+! 
+!     if ($saved = variable_get("ldap_user_conf", FALSE)) {
+!       $this->inDatabase = TRUE;
+!       foreach ($this->saveable as $property) {
+!         if (isset($saved[$property])) {
+!           $this->{$property} = $saved[$property];
+!         }
+!       }
+!     }
+!     else {
+!       $this->inDatabase = FALSE;
+!     }
+! 
+!     // determine account creation configuration
+!     $user_register = variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
+!     if ($this->acctCreation == LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT || $user_register == USER_REGISTER_VISITORS) {
+!       $this->createLDAPAccounts = TRUE;
+!       $this->createLDAPAccountsAdminApproval = FALSE;
+!     }
+!     elseif ($user_register == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
+!       $this->createLDAPAccounts = FALSE;
+!       $this->createLDAPAccountsAdminApproval = TRUE;
+!     }
+!     else {
+!       $this->createLDAPAccounts = FALSE;
+!       $this->createLDAPAccountsAdminApproval = FALSE;
+!     }
+!   }
+! 
+!   /**
+!    * Destructor Method
+!    */
+!   function __destruct() { }
+! 
+! 
+!   /**
+!    * Util to fetch mappings for a given direction
+!    *
+!    * @param string $sid
+!    *   The server id
+!    * @param string $direction LDAP_USER_PROV_DIRECTION_* constant
+!    * @param array $prov_events
+!    *
+!    * @return array/bool
+!    *   Array of mappings (may be empty array)
+!   */
+!   public function getSynchMappings($direction = LDAP_USER_PROV_DIRECTION_ALL, $prov_events = NULL) {
+!     if (!$prov_events) {
+!       $prov_events = ldap_user_all_events();
+!     }
+! 
+!     $mappings = array();
+!     if ($direction == LDAP_USER_PROV_DIRECTION_ALL) {
+!       $directions = array(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
+!     }
+!     else {
+!       $directions = array($direction);
+!     }
+!     foreach ($directions as $direction) {
+!       if (!empty($this->ldapUserSynchMappings[$direction])) {
+!         foreach ($this->ldapUserSynchMappings[$direction] as $attribute => $mapping) {
+!           if (!empty($mapping['prov_events'])) {
+!             $result = count(array_intersect($prov_events, $mapping['prov_events']));
+!             if ($result) {
+!               $mappings[$attribute] = $mapping;
+!             }
+!           }
+!         }
+!       }
+!     }
+!     return $mappings;
+!   }
+! 
+!   public function isDrupalAcctProvisionServer($sid) {
+!     if (!$sid || !$this->drupalAcctProvisionServer) {
+!       return FALSE;
+!     }
+!     elseif ($this->ldapEntryProvisionServer == $sid) {
+!       return TRUE;
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+!   public function isLdapEntryProvisionServer($sid) {
+!     if (!$sid || !$this->ldapEntryProvisionServer) {
+!       return FALSE;
+!     }
+!     elseif ($this->ldapEntryProvisionServer == $sid) {
+!       return TRUE;
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+!   /**
+!    * Util to fetch attributes required for this user conf, not other modules.
+!    *
+!    * @param enum $direction LDAP_USER_PROV_DIRECTION_* constants
+!    * @param string $ldap_context
+!    *
+!   */
+!   public function getLdapUserRequiredAttributes($direction = LDAP_USER_PROV_DIRECTION_ALL, $ldap_context = NULL) {
+! 
+!     $attributes_map = array();
+!     $required_attributes = array();
+!     if ($this->drupalAcctProvisionServer) {
+!       $prov_events = $this->ldapContextToProvEvents($ldap_context);
+!       $attributes_map = $this->getSynchMappings($direction, $prov_events);
+!       $required_attributes = array();
+!       foreach ($attributes_map as $detail) {
+!         if (count(array_intersect($prov_events, $detail['prov_events']))) {
+!           // Add the attribute to our array.
+!           if ($detail['ldap_attr']) {
+!             ldap_servers_token_extract_attributes($required_attributes,  $detail['ldap_attr']);
+!           }
+!         }
+!       }
+!     }
+!     return $required_attributes;
+!   }
+! 
+! /**
+!  * converts the more general ldap_context string to its associated ldap user event
+!  */
+! 
+!   public function ldapContextToProvEvents($ldap_context = NULL) {
+! 
+!     switch ($ldap_context) {
+! 
+!       case 'ldap_user_prov_to_drupal':
+!         $result = array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT);
+!         break;
+! 
+!       case 'ldap_user_prov_to_ldap':
+!         $result = array(LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY, LDAP_USER_EVENT_CREATE_LDAP_ENTRY);
+!         break;
+! 
+!       default:
+!         $result = ldap_user_all_events();
+! 
+!     }
+! 
+!     return $result;
+! 
+!   }
+! 
+! 
+! /**
+!  * converts the more general ldap_context string to its associated ldap user prov direction
+!  */
+! 
+!   public function ldapContextToProvDirection($ldap_context = NULL) {
+! 
+!     switch ($ldap_context) {
+! 
+!       case 'ldap_user_prov_to_drupal':
+!         $result = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER;
+!         break;
+! 
+!       case 'ldap_user_prov_to_ldap':
+!       case 'ldap_user_delete_drupal_user':
+!         $result = LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY;
+!         break;
+! 
+!       // provisioning is can hapen in both directions in most contexts
+!       case 'ldap_user_insert_drupal_user':
+!       case 'ldap_user_update_drupal_user':
+!       case 'ldap_authentication_authenticate':
+!       case 'ldap_user_insert_drupal_user':
+!       case 'ldap_user_disable_drupal_user':
+!         $result = LDAP_USER_PROV_DIRECTION_ALL;
+!         break;
+! 
+!       default:
+!         $result = LDAP_USER_PROV_DIRECTION_ALL;
+! 
+!     }
+! 
+!     return $result;
+!   }
+! 
+!   /**
+!     derive mapping array from ldap user configuration and other configurations.
+!     if this becomes a resource hungry function should be moved to ldap_user functions
+!     and stored with static variable. should be cached also.
+! 
+!     this should be cached and modules implementing ldap_user_synch_mapping_alter
+!     should know when to invalidate cache.
+! 
+!    */
+! 
+!   function setSynchMapping($reset = TRUE) {  // @todo change default to false after development
+!     $synch_mapping_cache = cache_get('ldap_user_synch_mapping');
+!     if (!$reset && $synch_mapping_cache) {
+!       $this->synchMapping = $synch_mapping_cache->data;
+!     }
+!     else {
+!       $available_user_attrs = array();
+!       foreach (array(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) as $direction) {
+!         $sid = $this->provisionSidFromDirection[$direction];
+!         $available_user_attrs[$direction] = array();
+!         $ldap_server = ($sid) ? ldap_servers_get_servers($sid, NULL, TRUE) : FALSE;
+! 
+!         $params = array(
+!           'ldap_server' => $ldap_server,
+!           'ldap_user_conf' => $this,
+!           'direction' => $direction,
+!         );
+! 
+!         drupal_alter('ldap_user_attrs_list', $available_user_attrs[$direction], $params);
+!       }
+!     }
+!     $this->synchMapping = $available_user_attrs;
+! 
+!     cache_set('ldap_user_synch_mapping',  $this->synchMapping);
+!   }
+! 
+!   /**
+!    * given a $prov_event determine if ldap user configuration supports it.
+!    *   this is overall, not per field synching configuration
+!    *
+!    * @param enum $direction LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY
+!    *
+!    * @param enum $prov_event
+!    *   LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER,LDAP_USER_EVENT_CREATE_DRUPAL_USER
+!    *   LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY LDAP_USER_EVENT_CREATE_LDAP_ENTRY
+!    *   LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT
+!    *   LDAP_USER_EVENT_ALL
+!    *
+!    * @param enum $action 'synch', 'provision', 'delete_ldap_entry', 'delete_drupal_entry', 'cancel_drupal_entry'
+!    * @return boolean
+!    */
+! 
+!   public function provisionEnabled($direction, $provision_trigger) {
+!     $result = FALSE;
+! 
+!     if ($direction == LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) {
+!       if (!$this->ldapEntryProvisionServer) {
+!         //debug('provisionEnabled:a');
+!         $result = FALSE;
+!       }
+!       else {
+!         //debug('provisionEnabled:b');
+!         $result = in_array($provision_trigger, $this->ldapEntryProvisionTriggers);
+!       }
+!     }
+!     elseif ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER) {
+!       if (!$this->drupalAcctProvisionServer) {
+!         //debug('provisionEnabled:c');
+!         $result = FALSE;
+!       }
+!       else {
+!         //debug('provisionEnabled:d');
+!         $result = in_array($provision_trigger, $this->drupalAcctProvisionTriggers);
+!       }
+!     }
+! 
+!     return $result;
+!   }
+! 
+!  /**
+!    * given a drupal account, provision an ldap entry if none exists.  if one exists do nothing
+!    *
+!    * @param object $account drupal account object with minimum of name property
+!    * @param array $ldap_user as prepopulated ldap entry.  usually not provided
+!    *
+!    * @return array of form:
+!    *     array('status' => 'success', 'fail', or 'conflict'),
+!    *     array('ldap_server' => ldap server object),
+!    *     array('proposed' => proposed ldap entry),
+!    *     array('existing' => existing ldap entry),
+!    *     array('description' = > blah blah)
+!    *
+!    */
+! 
+!   public function provisionLdapEntry($account, $ldap_user = NULL, $test_query = FALSE) {
+!     //debug('provisionLdapEntry account'); //debug($account);
+!     $watchdog_tokens = array();
+!     $result = array(
+!       'status' => NULL,
+!       'ldap_server' => NULL,
+!       'proposed' => NULL,
+!       'existing' => NULL,
+!       'description' => NULL,
+!     );
+! 
+!     if (is_scalar($account)) {
+!       $username = $account;
+!       $account = new stdClass();
+!       $acount->name = $username;
+!     }
+! 
+!     list($account, $user_entity) = ldap_user_load_user_acct_and_entity($account->name);
+! 
+!     if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
+!       $result['status'] = 'fail';
+!       $result['error_description'] = 'can not provision drupal user 1';
+!       return $result; // do not provision or synch user 1
+!     }
+! 
+!     if ($account == FALSE || $account->uid == 0) {
+!       $result['status'] = 'fail';
+!       $result['error_description'] = 'can not provision ldap user unless corresponding drupal account exists first.';
+!       return $result;
+!     }
+! 
+!     if (!$this->ldapEntryProvisionServer || !$this->ldapEntryProvisionServer) {
+!       $result['status'] = 'fail';
+!       $result['error_description'] = 'no provisioning server enabled';
+!       return $result;
+!     }
+! 
+!     $ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
+!     $params = array(
+!       'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+!       'prov_events' => array(LDAP_USER_EVENT_CREATE_LDAP_ENTRY),
+!       'module' => 'ldap_user',
+!       'function' => 'provisionLdapEntry',
+!       'include_count' => FALSE,
+!     );
+! 
+!     list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
+!     $proposed_dn = (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn']) ? $proposed_ldap_entry['dn'] : NULL;
+!     $proposed_dn_lcase = drupal_strtolower($proposed_dn);
+!     $existing_ldap_entry = ($proposed_dn) ? $ldap_server->dnExists($proposed_dn, 'ldap_entry') : NULL;
+! 
+!     if ($error == LDAP_USER_PROV_RESULT_NO_PWD) {
+!       $result['status'] = 'fail';
+!       $result['description'] = 'Can not provision ldap account without user provided password.';
+!       $result['existing'] = $existing_ldap_entry;
+!       $result['proposed'] = $proposed_ldap_entry;
+!       $result['ldap_server'] = $ldap_server;
+!     }
+!     elseif (!$proposed_dn) {
+!       $result['status'] = 'fail';
+!       $result['description'] = t('failed to derive dn and or mappings');
+!       return $result;
+!     }
+!     elseif ($existing_ldap_entry) {
+!       $result['status'] = 'conflict';
+!       $result['description'] = 'can not provision ldap entry because exists already';
+!       $result['existing'] = $existing_ldap_entry;
+!       $result['proposed'] = $proposed_ldap_entry;
+!       $result['ldap_server'] = $ldap_server;
+!     }
+!     elseif ($test_query) {
+!       $result['status'] = 'fail';
+!       $result['description'] = 'not created because flagged as test query';
+!       $result['proposed'] = $proposed_ldap_entry;
+!       $result['ldap_server'] = $ldap_server;
+!     }
+!     else {
+!       // stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call
+!       $ldap_entries = array($proposed_dn_lcase => $proposed_ldap_entry);
+!       $context = array(
+!         'action' => 'add',
+!         'corresponding_drupal_data' => array($proposed_dn_lcase => $account),
+!         'corresponding_drupal_data_type' => 'user',
+!       );
+!       drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
+!       // remove altered $proposed_ldap_entry from $ldap_entries array
+!       $proposed_ldap_entry = $ldap_entries[$proposed_dn_lcase];
+! 
+!       $ldap_entry_created = $ldap_server->createLdapEntry($proposed_ldap_entry, $proposed_dn);
+!       if ($ldap_entry_created) {
+!         module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
+!         $result['status'] = 'success';
+!         $result['description'] = 'ldap account created';
+!         $result['proposed'] = $proposed_ldap_entry;
+!         $result['created'] = $ldap_entry_created;
+!         $result['ldap_server'] = $ldap_server;
+! 
+!         // need to store <sid>|<dn> in ldap_user_prov_entries field, which may contain more than one
+!         $ldap_user_prov_entry = $ldap_server->sid . '|' . $proposed_ldap_entry['dn'];
+!         if (!isset($user_entity->ldap_user_prov_entries['und'])) {
+!           $user_entity->ldap_user_prov_entries = array('und' => array());
+!         }
+!         $ldap_user_prov_entry_exists = FALSE;
+!         foreach ($user_entity->ldap_user_prov_entries['und'] as $i => $field_value_instance) {
+!           if ($field_value_instance == $ldap_user_prov_entry) {
+!             $ldap_user_prov_entry_exists = TRUE;
+!           }
+!         }
+!         if (!$ldap_user_prov_entry_exists) {
+!           $user_entity->ldap_user_prov_entries['und'][] = array(
+!             'value' =>  $ldap_user_prov_entry,
+!             'format' => NULL,
+!             'save_value' => $ldap_user_prov_entry,
+!           );
+!           $edit = array(
+!             'ldap_user_prov_entries' => $user_entity->ldap_user_prov_entries,
+!           );
+!           $account = user_load($account->uid);
+!           $account = user_save($account, $edit);
+!         }
+! 
+!       }
+!       else {
+!         $result['status'] = 'fail';
+!         $result['proposed'] = $proposed_ldap_entry;
+!         $result['created'] = $ldap_entry_created;
+!         $result['ldap_server'] = $ldap_server;
+!         $result['existing'] = NULL;
+!       }
+!     }
+! 
+!     $tokens = array(
+!       '%dn' => isset($result['proposed']['dn']) ? $result['proposed']['dn'] : NULL,
+!       '%sid' => (isset($result['ldap_server']) && $result['ldap_server']) ? $result['ldap_server']->sid : 0,
+!       '%username' => @$account->name,
+!       '%uid' => @$account->uid,
+!       '%description' => @$result['description'],
+!     );
+!     if (!$test_query && isset($result['status'])) {
+!       if ($result['status'] == 'success') {
+!         if ($this->detailedWatchdog) {
+!           watchdog('ldap_user', 'LDAP entry on server %sid created dn=%dn.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+!         }
+!       }
+!       elseif ($result['status'] == 'conflict') {
+!         if ($this->detailedWatchdog) {
+!           watchdog('ldap_user', 'LDAP entry on server %sid not created because of existing ldap entry. %description. username=%username, uid=%uid', $tokens, WATCHDOG_WARNING);
+!         }
+!       }
+!       elseif ($result['status'] == 'fail') {
+!         watchdog('ldap_user', 'LDAP entry on server %sid not created because error.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+!       }
+!     }
+!     return $result;
+!   }
+! 
+! 
+!   /**
+!    * given a drupal account, synch to related ldap entry
+!    *
+!    * @param drupal user object $account.  Drupal user object
+!    * @param array $user_edit.  Edit array for user_save.  generally null unless user account is being created or modified in same synching
+!    * @param array $ldap_user.  current ldap data of user. @see README.developers.txt for structure
+!    *
+!    * @return TRUE on success or FALSE on fail.
+!    */
+! 
+!   public function synchToLdapEntry($account, $user_edit = NULL, $ldap_user =  array(), $test_query = FALSE) {
+!     //dpm("synchToLdapEntry, test_query=$test_query, account, user_edit"); dpm($account); dpm($user_edit);
+!    // debug("synchToLdapEntry, test_query=$test_query, account, user_edit"); debug($account); debug($user_edit);
+! 
+!     if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
+!       return FALSE; // do not provision or synch user 1
+!     }
+! 
+!     $watchdog_tokens = array();
+!     $result = FALSE;
+!     $proposed_ldap_entry = FALSE;
+! 
+!     if ($this->ldapEntryProvisionServer) {
+!       $ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
+!      // dpm('synchToLdapEntry:ldap_server, prov='. $this->ldapEntryProvisionServer); dpm($ldap_server);
+!       $params = array(
+!         'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+!         'prov_events' => array(LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY),
+!         'module' => 'ldap_user',
+!         'function' => 'synchToLdapEntry',
+!         'include_count' => FALSE,
+!       );
+! 
+!       list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
+!      // //debug('synchToLdapEntry:proposed_ldap_entry'); //debug($proposed_ldap_entry); //debug("error=$error");
+!       if ($error != LDAP_USER_PROV_RESULT_NO_ERROR) {
+!        // //debug("synchToLdapEntry:proposed_ldap_entryerror=$error");
+!         $result = FALSE;
+!       }
+!       elseif (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn'])) {
+!         $existing_ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry');
+!         $attributes = array(); // this array represents attributes to be modified; not comprehensive list of attributes
+!        // //debug('synchToLdapEntry:proposed_ldap_entry'); //debug($proposed_ldap_entry);
+!         foreach ($proposed_ldap_entry as $attr_name => $attr_values) {
+!           if ($attr_name != 'dn') {
+!             if (isset($attr_values['count'])) {
+!               unset($attr_values['count']);
+!             }
+!             if (count($attr_values) == 1) {
+!               $attributes[$attr_name] = $attr_values[0];
+!             }
+!             else {
+!               $attributes[$attr_name] = $attr_values;
+!             }
+!           }
+!         }
+!   //     //dpm('synchToLdapEntry:attributes passed to modifyLdapEntry, dn='. $proposed_ldap_entry['dn']);//dpm($attributes);
+!         if ($test_query) {
+!           $proposed_ldap_entry = $attributes;
+!           $result = array(
+!             'proposed' => $proposed_ldap_entry,
+!             'server' => $ldap_server,
+!           );
+!         }
+!         else {
+!          //  //debug('modifyLdapEntry,dn=' . $proposed_ldap_entry['dn']);  //debug($attributes);
+!               // stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call
+!           $proposed_dn_lcase = drupal_strtolower($proposed_ldap_entry['dn']);
+!           $ldap_entries = array($proposed_dn_lcase => $attributes);
+!           $context = array(
+!             'action' => 'update',
+!             'corresponding_drupal_data' => array($proposed_dn_lcase => $attributes),
+!             'corresponding_drupal_data_type' => 'user',
+!           );
+!           drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
+!           // remove altered $proposed_ldap_entry from $ldap_entries array
+!           $attributes = $ldap_entries[$proposed_dn_lcase];
+!           $result = $ldap_server->modifyLdapEntry($proposed_ldap_entry['dn'], $attributes);
+!           if ($result) { // success
+!             module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
+!           }
+! 
+! 
+!         }
+!       }
+!       else { // failed to get acceptable proposed ldap entry
+!         $result = FALSE;
+!       }
+! 
+! 
+!       //  $attributes["attribute1"] = "value";
+!      //   $attributes["attribute2"][0] = "value1";
+!       //  $attributes["attribute2"][1] = "value2";
+!     }
+!    ////dpm('provisionLdapEntry:results');//dpm($results);
+! 
+! 
+!     $tokens = array(
+!       '%dn' => isset($result['proposed']['dn']) ? $result['proposed']['dn'] : NULL,
+!       '%sid' => $this->ldapEntryProvisionServer,
+!       '%username' => $account->name,
+!       '%uid' => ($test_query || !property_exists($account, 'uid')) ? '' : $account->uid,
+!     );
+! 
+!     if ($result) {
+!       watchdog('ldap_user', 'LDAP entry on server %sid synched dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+!     }
+!     else {
+!       watchdog('ldap_user', 'LDAP entry on server %sid not synched because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+!     }
+! 
+!     return $result;
+! 
+!   }
+! 
+!   /**
+!    * given a drupal account, query ldap and get all user fields and create user account
+!    *
+!    * @param array $account drupal account array with minimum of name
+!    * @param array $user_edit drupal edit array in form user_save($account, $user_edit) would take,
+!    *   generally empty unless overriding synchToDrupalAccount derived values
+!    * @param array $ldap_user as user's ldap entry.  passed to avoid requerying ldap in cases where already present
+!    * @param boolean $save indicating if drupal user should be saved.  generally depends on where function is called from.
+!    *
+!    * @return result of user_save() function is $save is true, otherwise return TRUE
+!    *   $user_edit data returned by reference
+!    *
+!    */
+!   public function synchToDrupalAccount($drupal_user, &$user_edit, $prov_event = LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user = NULL,  $save = FALSE) {
+! 
+!     $debug = array(
+!       'account' => $drupal_user,
+!       'user_edit' => $user_edit,
+!       'ldap_user' => $ldap_user,
+!     );
+! 
+!     if (
+!         (!$ldap_user  && !isset($drupal_user->name)) ||
+!         (!$drupal_user && $save) ||
+!         ($ldap_user && !isset($ldap_user['sid']))
+!     ) {
+!        // should throw watchdog error also
+!       return FALSE;
+!     }
+! 
+!     if (!$ldap_user && $this->drupalAcctProvisionServer) {
+!       $ldap_user = ldap_servers_get_user_ldap_data($drupal_user->name, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+!     }
+! 
+!     if (!$ldap_user) {
+!       return FALSE;
+!     }
+! 
+!     if ($this->drupalAcctProvisionServer) {
+!       $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, NULL, TRUE);
+!       $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array($prov_event));
+!     }
+! 
+!     if ($save) {
+!      // $account = new stdClass();
+!       $account = user_load($drupal_user->uid);
+!       $result = user_save($account, $user_edit, 'ldap_user');
+! 
+!       return $result;
+!     }
+!     else {
+!       return TRUE;
+!     }
+!   }
+! 
+! 
+!   /**
+!    * given a drupal account, delete user account
+!    *
+!    * @param string $username drupal account name
+!    * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
+!    */
+!   public function deleteDrupalAccount($username) {
+!     $user = user_load_by_name($username);
+!     if (is_object($user)) {
+!       user_delete($user->uid);
+!       return TRUE;
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+!   /**
+!    * given a drupal account, find the related ldap entry.
+!    *
+!    * @param drupal user object $account
+!    *
+!    * @return FALSE or ldap entry
+!    */
+!   public function getProvisionRelatedLdapEntry($account, $prov_events = NULL) {
+!     if (!$prov_events) {
+!       $prov_events = ldap_user_all_events();
+!     }
+!     $sid = $this->ldapEntryProvisionServer; //
+!     //debug("ldapEntryProvisionServer:$sid");
+!     if (!$sid) {
+!       return FALSE;
+!     }
+!     // $user_entity->ldap_user_prov_entries,
+!     $ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
+!     $params = array(
+!       'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+!       'prov_events' => $prov_events,
+!       'module' => 'ldap_user',
+!       'function' => 'getProvisionRelatedLdapEntry',
+!       'include_count' => FALSE,
+!       );
+!     list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params);
+!     if (!(is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn'])) {
+!       return FALSE;
+!     }
+!     $ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry', array());
+!     return $ldap_entry;
+! 
+!   }
+! 
+!   /**
+!    * given a drupal account, delete ldap entry that was provisioned based on it
+!    *   normally this will be 0 or 1 entry, but the ldap_user_provisioned_ldap_entries
+!    *   field attached to the user entity track each ldap entry provisioned
+!    *
+!    * @param object $account drupal account
+!    * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
+!    */
+!   public function deleteProvisionedLdapEntries($account) {
+!     // determine server that is associated with user
+! 
+!     $boolean_result = FALSE;
+!     $language = ($account->language) ? $account->language : 'und';
+!     if (isset($account->ldap_user_prov_entries[$language][0])) {
+!       foreach ($account->ldap_user_prov_entries[$language] as $i => $field_instance) {
+!         $parts = explode('|', $field_instance['value']);
+!         if (count($parts) == 2) {
+! 
+!           list($sid, $dn) = $parts;
+!           $ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
+!           if (is_object($ldap_server) && $dn) {
+!             $boolean_result = $ldap_server->delete($dn);
+!             $tokens = array('%sid' => $sid, '%dn' => $dn, '%username' => $account->name, '%uid' => $account->uid);
+!             if ($boolean_result) {
+!               watchdog('ldap_user', 'LDAP entry on server %sid deleted dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+!             }
+!             else {
+!               watchdog('ldap_user', 'LDAP entry on server %sid not deleted because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+!             }
+!           }
+!           else {
+!             $boolean_result = FALSE;
+!           }
+!         }
+!       }
+!     }
+!     return $boolean_result;
+! 
+!   }
+! 
+! /**
+!   *  populate ldap entry array for provisioning
+!   *
+!   * @param array $account drupal account
+!   * @param object $ldap_server
+!   * @param array $ldap_user ldap entry of user, returned by reference
+!   * @param array $params with the following key values:
+!   *    'ldap_context' =>
+!        'module' => module calling function, e.g. 'ldap_user'
+!        'function' => function calling function, e.g. 'provisionLdapEntry'
+!        'include_count' => should 'count' array key be included
+!        'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY || LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER
+!   *
+!   * @return array(ldap entry, $result) in ldap extension array format.!THIS IS NOT THE ACTUAL LDAP ENTRY
+!   */
+! 
+!   function drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user_entry = NULL) {
+!   //debug('call to drupalUserToLdapEntry, account:'); //debug($account); //debug('ldap_server'); //debug($ldap_server);
+!   //debug('params'); //debug($params); //debug('ldap_user_entry');//debug($ldap_user_entry);
+!     $provision = (isset($params['function']) && $params['function'] == 'provisionLdapEntry');
+!     $result = LDAP_USER_PROV_RESULT_NO_ERROR;
+!     if (!$ldap_user_entry) {
+!       $ldap_user_entry = array();
+!     }
+! 
+!     if (!is_object($account) || !is_object($ldap_server)) {
+!       return array(NULL, LDAP_USER_PROV_RESULT_BAD_PARAMS);
+!     }
+!     $watchdog_tokens = array(
+!       '%drupal_username' => $account->name,
+!     );
+!     $include_count = (isset($params['include_count']) && $params['include_count']);
+! 
+!     $direction = isset($params['direction']) ? $params['direction'] : LDAP_USER_PROV_DIRECTION_ALL;
+!     $prov_events = empty($params['prov_events']) ? ldap_user_all_events() : $params['prov_events'];
+! 
+!     $mappings = $this->getSynchMappings($direction, $prov_events);
+!      //debug('prov_events'); //debug(join(",",$prov_events));
+!   //  debug('mappings'); debug($mappings);
+!       // Loop over the mappings.
+!     foreach ($mappings as $field_key => $field_detail) {
+!       list($ldap_attr_name, $ordinal, $source_data_type, $target_data_type) = ldap_servers_token_extract_parts($field_key, TRUE);  //trim($field_key, '[]');
+!       $ordinal = (!$ordinal) ? 0 : $ordinal;
+!       if ($ldap_user_entry && isset($ldap_user_entry[$ldap_attr_name]) && is_array($ldap_user_entry[$ldap_attr_name]) && isset($ldap_user_entry[$ldap_attr_name][$ordinal]) ) {
+!         continue; // don't override values passed in;
+!       }
+! 
+!       $synched = $this->isSynched($field_key, $params['prov_events'], LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
+!     //  debug("isSynched $field_key: $synched");
+!       if ($synched) {
+!         $token = ($field_detail['user_attr'] == 'user_tokens') ? $field_detail['user_tokens'] : $field_detail['user_attr'];
+!         $value = ldap_servers_token_replace($account, $token, 'user_account');
+! 
+!         if (substr($token, 0, 10) == '[password.' && (!$value || $value == $token)) { // deal with empty/unresolved password
+!           if (!$provision) {
+!             continue; //don't overwrite password on synch if no value provided
+!           }
+!         }
+! 
+!         if ($ldap_attr_name == 'dn' && $value) {
+!           $ldap_user_entry['dn'] = $value;
+!         }
+!         elseif ($value) {
+!           if (!isset($ldap_user_entry[$ldap_attr_name]) || !is_array($ldap_user_entry[$ldap_attr_name])) {
+!             $ldap_user_entry[$ldap_attr_name] = array();
+!           }
+!           $ldap_user_entry[$ldap_attr_name][$ordinal] = $value;
+!           if ($include_count) {
+!             $ldap_user_entry[$ldap_attr_name]['count'] = count($ldap_user_entry[$ldap_attr_name]);
+!           }
+!          // dpm("ldap_user_entry: $ldap_attr_name=$ldap_attr_name, ordinal=$ordinal"); dpm($ldap_user_entry[$ldap_attr_name]);
+!         }
+! 
+!       }
+! 
+!     }
+! 
+!     /**
+!      * 4. call drupal_alter() to allow other modules to alter $ldap_user
+!      */
+! 
+!     drupal_alter('ldap_entry', $ldap_user_entry, $params);
+! 
+!     return array($ldap_user_entry, $result);
+! 
+!   }
+! 
+! 
+! 
+!    /**
+!    * given a drupal account, query ldap and get all user fields and save user account
+!    * (note: parameters are in odd order to match synchDrupalAccount handle)
+!    *
+!    * @param array $account drupal account object or null
+!    * @param array $user_edit drupal edit array in form user_save($account, $user_edit) would take.
+!    * @param array $ldap_user as user's ldap entry.  passed to avoid requerying ldap in cases where already present
+!    * @param boolean $save indicating if drupal user should be saved.  generally depends on where function is called from and if the
+!    *
+!    * @return result of user_save() function is $save is true, otherwise return TRUE on success or FALSE on any problem
+!    *   $user_edit data returned by reference
+!    *
+!    */
+! 
+!   public function provisionDrupalAccount($account = FALSE, &$user_edit, $ldap_user = NULL, $save = TRUE) {
+! 
+!     $watchdog_tokens = array();
+!     /**
+!      * @todo
+!      * -- add error catching for conflicts, conflicts should be checked before calling this function.
+!      *
+!      */
+! 
+!     if (!$account) {
+!       $account = new stdClass();
+!     }
+!     $account->is_new = TRUE;
+! 
+!     if (!$ldap_user && !isset($user_edit['name'])) {
+!       return FALSE;
+!     }
+! 
+!     if (!$ldap_user) {
+!       $watchdog_tokens['%username'] = $user_edit['name'];
+!       if ($this->drupalAcctProvisionServer) {
+!         $ldap_user = ldap_servers_get_user_ldap_data($user_edit['name'], $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+!       }
+!       if (!$ldap_user) {
+!         if ($this->detailedWatchdog) {
+!           watchdog('ldap_user', '%username : failed to find associated ldap entry for username in provision.', $watchdog_tokens, WATCHDOG_DEBUG);
+!         }
+!         return FALSE;
+!       }
+!     }
+!    // dpm('ldap_user 675');dpm($ldap_user);
+!     if (!isset($user_edit['name']) && isset($account->name)) {
+!       $user_edit['name'] = $account->name;
+!       $watchdog_tokens['%username'] = $user_edit['name'];
+!     }
+!     if ($this->drupalAcctProvisionServer) {
+!      // dpm("this->drupalAcctProvisionServer=" . $this->drupalAcctProvisionServer);
+!       $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);  // $ldap_user['sid']
+! 
+!       $params = array(
+!         'account' => $account,
+!         'user_edit' => $user_edit,
+!         'prov_event' => LDAP_USER_EVENT_CREATE_DRUPAL_USER,
+!         'module' => 'ldap_user',
+!         'function' => 'provisionDrupalAccount',
+!         'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER,
+!       );
+! 
+!       drupal_alter('ldap_entry', $ldap_user, $params);
+! 
+!       // look for existing drupal account with same puid.  if so update username and attempt to synch in current context
+!       $puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+!       $account2 = ($puid) ? $ldap_server->userUserEntityFromPuid($puid) : FALSE;
+! 
+!       if ($account2) { // synch drupal account, since drupal account exists
+!         // 1. correct username and authmap
+!         $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+!         $account = user_save($account2, $user_edit, 'ldap_user');
+!         user_set_authmaps($account, array("authname_ldap_user" => $user_edit['name']));
+!         // 2. attempt synch if appropriate for current context
+!         if ($account) {
+!           $account = $this->synchToDrupalAccount($account, $user_edit, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user, TRUE);
+!         }
+!         return $account;
+!       }
+!       else { // create drupal account
+!         $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_CREATE_DRUPAL_USER));
+!         if ($save) {
+!           $account = user_save(NULL, $user_edit, 'ldap_user');
+!           if (!$account) {
+!             drupal_set_message(t('User account creation failed because of system problems.'), 'error');
+!           }
+!           else {
+!             user_set_authmaps($account, array('authname_ldap_user' => $user_edit['name']));
+!           }
+!           return $account;
+!         }
+!         return TRUE;
+!       }
+!     }
+!   }
+! 
+!   /**
+!    * set ldap associations of a drupal account by altering user fields
+!    *
+!    * @param string $drupal_username
+!    *
+!    * @return boolean TRUE on success, FALSE on error or failure because of invalid user or ldap accounts
+!    *
+!    */
+!   function ldapAssociateDrupalAccount($drupal_username) {
+! 
+!     if ($this->drupalAcctProvisionServer) {
+!       $prov_events = array(LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT);
+!       $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);  // $ldap_user['sid']
+!       $account = user_load_by_name($drupal_username);
+!       $ldap_user = ldap_servers_get_user_ldap_data($drupal_username, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+!       if (!$account) {
+!         watchdog(
+!           'ldap_user',
+!           'Failed to LDAP associate drupal account %drupal_username because account not found',
+!           array('%drupal_username' => $drupal_username),
+!           WATCHDOG_ERROR
+!         );
+!         return FALSE;
+!       }
+!       elseif (!$ldap_user) {
+!         watchdog(
+!           'ldap_user',
+!           'Failed to LDAP associate drupal account %drupal_username because corresponding LDAP entry not found',
+!           array('%drupal_username' => $drupal_username),
+!           WATCHDOG_ERROR
+!         );
+!         return FALSE;
+!       }
+!       else {
+!         $user_edit = array();
+!         $user_edit['data']['ldap_user']['init'] = array(
+!           'sid'  => $ldap_user['sid'],
+!           'dn'   => $ldap_user['dn'],
+!           'mail'   => $account->mail,
+!         );
+!         $ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+!         if ($ldap_user_puid) {
+!           $user_edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid; //
+!         }
+!         $user_edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
+!         $user_edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
+!         $user_edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
+!         $account = user_save($account, $user_edit, 'ldap_user');
+!         return (boolean)$account;
+!       }
+!     }
+!     else {
+!       return FALSE;
+!     }
+!   }
+! 
+!   /** populate $user edit array (used in hook_user_save, hook_user_update, etc)
+!    * ... should not assume all attribues are present in ldap entry
+!    *
+!    * @param array ldap entry $ldap_user
+!    * @param object $ldap_server
+!    * @param array $edit see hook_user_save, hook_user_update, etc
+!    * @param drupal account object $account
+!    * @param string $op see hook_ldap_attributes_needed_alter
+!    */
+! 
+!   function entryToUserEdit($ldap_user, &$edit, $ldap_server, $direction = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, $prov_events = NULL) {
+! 
+!     // need array of user fields and which direction and when they should be synched.
+!     if (!$prov_events) {
+!       $prov_events = ldap_user_all_events();
+!     }
+!     $mail_synched = $this->isSynched('[property.mail]', $prov_events, $direction);
+!     if (!isset($edit['mail']) && $mail_synched) {
+!       $derived_mail = $ldap_server->userEmailFromLdapEntry($ldap_user['attr']);
+!       if ($derived_mail) {
+!         $edit['mail'] = $derived_mail;
+!       }
+!     }
+! 
+!     if ($this->isSynched('[property.name]', $prov_events, $direction) && !isset($edit['name'])) {
+!       $name = $ldap_server->userUsernameFromLdapEntry($ldap_user['attr']);
+!       if ($name) {
+!         $edit['name'] = $name;
+!       }
+!     }
+! 
+!     if ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER && in_array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, $prov_events)) {
+!       $edit['mail'] = isset($edit['mail']) ? $edit['mail'] : $ldap_user['mail'];
+!       $edit['pass'] = isset($edit['pass']) ? $edit['pass'] : user_password(20);
+!       $edit['init'] = isset($edit['init']) ? $edit['init'] : $edit['mail'];
+!       $edit['status'] = isset($edit['status']) ? $edit['status'] : 1;
+!       $edit['signature'] = isset($edit['signature']) ? $edit['signature'] : '';
+! 
+!       $edit['data']['ldap_user']['init'] = array(
+!         'sid'  => $ldap_user['sid'],
+!         'dn'   => $ldap_user['dn'],
+!         'mail' => $edit['mail'],
+!       );
+!     }
+! 
+!     /**
+!      * basic $user ldap fields
+!      */
+!     if ($this->isSynched('[field.ldap_user_puid]', $prov_events, $direction)) {
+!       $ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+!       if ($ldap_user_puid) {
+!         $edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid; //
+!       }
+!     }
+!     if ($this->isSynched('[field.ldap_user_puid_property]', $prov_events, $direction)) {
+!       $edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
+!     }
+!     if ($this->isSynched('[field.ldap_user_puid_sid]', $prov_events, $direction)) {
+!       $edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
+!     }
+!     if ($this->isSynched('[field.ldap_user_current_dn]', $prov_events, $direction)) {
+!       $edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
+!     }
+! 
+!     // Get any additional mappings.
+!     $mappings = $this->getSynchMappings($direction, $prov_events);
+! 
+!      // Loop over the mappings.
+!     foreach ($mappings as $user_attr_key => $field_detail) {
+!      // //dpm('field detail');//dpm($field_detail);
+!        // Make sure this mapping is relevant to the sync context.
+!       if (!$this->isSynched($user_attr_key, $prov_events, $direction)) {
+!         continue;
+!       }
+!        /**
+!         * if "convert from binary is selected" and no particular method is in token,
+!         * default to ldap_servers_binary() function
+!         */
+!       if ($field_detail['convert'] && strpos($field_detail['ldap_attr'], ';') === FALSE) {
+!         $field_detail['ldap_attr'] = str_replace(']', ';binary]', $field_detail['ldap_attr']);
+!       }
+!       $value = ldap_servers_token_replace($ldap_user['attr'], $field_detail['ldap_attr'], 'ldap_entry');
+!       list($value_type, $value_name, $value_instance) = ldap_servers_parse_user_attr_name($user_attr_key);
+! 
+!       // $value_instance not used, may have future use case
+! 
+!       // Are we dealing with a field?
+!       if ($value_type == 'field') {
+!         // Field api field - first we get the field.
+!         $field = field_info_field($value_name);
+!         // Then the columns for the field in the schema.
+!         $columns = array_keys($field['columns']);
+!         // Then we convert the value into an array if it's scalar.
+!         $values = $field['cardinality'] == 1 ? array($value) : (array) $value;
+! 
+!         $items = array();
+!         // Loop over the values and set them in our $items array.
+!         foreach ($values as $delta => $value) {
+!           if (isset($value)) {
+!             // We set the first column value only, this is consistent with
+!             // the Entity Api (@see entity_metadata_field_property_set).
+!             $items[$delta][$columns[0]] = $value;
+!           }
+!         }
+!         // Add them to our edited item.
+!         $edit[$value_name][LANGUAGE_NONE] = $items;
+!       }
+!       elseif ($value_type == 'property') {
+!         // Straight property.
+!         $edit[$value_name] = $value;
+!       }
+!     }
+! 
+!     // Allow other modules to have a say.
+! 
+!     drupal_alter('ldap_user_edit_user', $edit, $ldap_user, $ldap_server, $prov_events);
+! 
+!   }
+!   /**
+!    * given configuration of synching, determine is a given synch should occur
+!    *
+!    * @param string $attr_token e.g. [property.mail], [field.ldap_user_puid_property]
+!    * @param object $ldap_server
+!    * @param array $prov_events e.g. array(LDAP_USER_EVENT_CREATE_DRUPAL_USER).  typically array with 1 element
+!    * @param scalar $direction LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY
+!    */
+! 
+!   public function isSynched($attr_token, $prov_events, $direction) {
+!     $result = (boolean)(
+!       isset($this->synchMapping[$direction][$attr_token]['prov_events']) &&
+!       count(array_intersect($prov_events, $this->synchMapping[$direction][$attr_token]['prov_events']))
+!     );
+!     if (!$result) {
+!       if (isset($this->synchMapping[$direction][$attr_token])) {
+!         //debug($this->synchMapping[$direction][$attr_token]);
+!       }
+!       else {
+!       //  debug("$attr_token not in ldapUserConf::synchMapping");
+!       }
+!     }
+!     return $result;
+!   }
+  
+  
+  } // end LdapUserConf class
+--- 10,1320 ----
+  
+  class LdapUserConf {
+  
+! 	/**
+! 	 * server providing Drupal account provisioning
+! 	 *
+! 	 * @var string
+! 	 *
+! 	 * @see LdapServer::sid
+! 	 */
+! 	public $drupalAcctProvisionServer = LDAP_USER_NO_SERVER_SID;
+! 
+! 	/**
+! 	 * server providing LDAP entry provisioning
+! 	 *
+! 	 * @var string
+! 	 *
+! 	 * @see LdapServer::sid
+! 	 */
+! 	public $ldapEntryProvisionServer = LDAP_USER_NO_SERVER_SID;
+! 
+! 	/**
+! 	 * Associative array mapping synch directions to ldap server instances.
+! 	 *
+! 	 * @var array
+! 	 */
+! 	public $provisionSidFromDirection = array(
+! 			LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER => LDAP_USER_NO_SERVER_SID,
+! 			LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => LDAP_USER_NO_SERVER_SID,
+! 	);
+! 
+! 	/**
+! 	 * Array of events that trigger provisioning of Drupal Accounts
+! 	 * Valid constants are:
+! 	 *   LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE
+! 	 *   LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE
+! 	 *   LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE
+! 	 *
+! 	 * @var array
+! 	 */
+! 	public $drupalAcctProvisionTriggers = array(LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE, LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE, LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE);
+! 
+! 	/**
+! 	 * Array of events that trigger provisioning of LDAP Entries
+! 	 * Valid constants are:
+! 	 *   LDAP_USER_LDAP_ENTRY_PROV_ON_USER_UPDATE_CREATE
+! 	 *   LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE
+! 	 *   LDAP_USER_LDAP_ENTRY_DELETE_ON_USER_DELETE
+! 	 *
+! 	 * @var array
+! 	 */
+! 	public $ldapEntryProvisionTriggers = array();
+! 
+! 	/**
+! 	 * server providing LDAP entry provisioning
+! 	 *
+! 	 * @var string
+! 	 *
+! 	 * @see LdapServer::sid
+! 	 */
+! 	public $userConflictResolve = LDAP_USER_CONFLICT_RESOLVE_DEFAULT;
+! 
+! 	/**
+! 	 * drupal account creation model
+! 	 *
+! 	 * @var int
+! 	 *   LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR   /admin/config/people/accounts/settings do not affect "LDAP Associated" Drupal accounts.
+! 	 *   LDAP_USER_ACCT_CREATION_USER_SETTINGS_FOR_LDAP  use Account creation settings at /admin/config/people/accounts/settings
+! 	 */
+! 	public $acctCreation = LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT;
+! 
+! 	/**
+! 	 * has current object been saved to the database?
+! 	 *
+! 	 * @var boolean
+! 	 *
+! 	 */
+! 	public $inDatabase = FALSE;
+! 
+! 	/**
+! 	 * what to do when an ldap provisioned username conflicts with existing drupal user?
+! 	 *
+! 	 * @var int
+! 	 *   LDAP_USER_CONFLICT_LOG - log the conflict
+! 	 *   LDAP_USER_CONFLICT_RESOLVE - LDAP associate the existing drupal user
+! 	 *
+! 	 */
+! 	public $manualAccountConflict = LDAP_USER_MANUAL_ACCT_CONFLICT_REJECT;
+! 
+! 	public $setsLdapPassword = TRUE; // @todo default to FALSE and check for mapping to set to true
+! 
+! 	public $loginConflictResolve = FALSE;
+! 	/**
+! 	 * array of field synch mappings provided by all modules (via hook_ldap_user_attrs_list_alter())
+! 	 * array of the form: array(
+! 	 * LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
+! 	 *   <server_id> => array(
+! 	 *     'sid' => <server_id> (redundant)
+! 	 *     'ldap_attr' => e.g. [sn]
+! 	 *     'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
+! 	 *     'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
+! 	 *     'convert' => 1|0 boolean indicating need to covert from binary
+! 	 *     'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
+! 	 *     'config_module' => 'ldap_user'
+! 	 *     'prov_module' => 'ldap_user'
+! 	 *     'enabled' => 1|0 boolean
+! 	 *      prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
+! 	 *         - four permutations available
+! 	 *            to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
+! 	 *            to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
+! 	 *    )
+! 	 *  )
+! 	 */
+! 	public $synchMapping = NULL; // array of field synching directions for each operation.  should include ldapUserSynchMappings
+! 	// keyed on direction => property, ldap, or field token such as '[field.field_lname] with brackets in them.
+! 
+! 	/**
+! 	 * synch mappings configured in ldap user module (not in other modules)
+! 	 *   array of the form: array(
+! 	 LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
+! 	 'sid' => <server_id> (redundant)
+! 	 'ldap_attr' => e.g. [sn]
+! 	 'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
+! 	 'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
+! 	 'convert' => 1|0 boolean indicating need to covert from binary
+! 	 'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
+! 	 'config_module' => 'ldap_user'
+! 	 'prov_module' => 'ldap_user'
+! 	 'enabled' => 1|0 boolean
+! 	 prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
+! 	 		- four permutations available
+! 	 		to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
+! 	 		to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
+! 	 )
+! 	 )
+! 	)
+! 	*/
+! 	public $ldapUserSynchMappings = NULL;  //
+! 	// keyed on property, ldap, or field token such as '[field.field_lname] with brackets in them.
+! 	public $detailedWatchdog = FALSE;
+! 	public $provisionsDrupalAccountsFromLdap = FALSE;
+! 	public $provisionsLdapEntriesFromDrupalUsers = FALSE;
+! 
+! 	// what should be done with ldap provisioned accounts that no longer have associated drupal accounts.
+! 	public $orphanedDrupalAcctBehavior = 'ldap_user_orphan_email';
+! 	/** options are partially derived from user module account cancel options:
+! 	 *
+! 	 'ldap_user_orphan_do_not_check' => Do not check for orphaned Drupal accounts.)
+! 	 'ldap_user_orphan_email' => Perform no action, but email list of orphaned accounts. (All the other options will send email summaries also.)
+! 	 'user_cancel_block' => Disable the account and keep its content.
+! 	 'user_cancel_block_unpublish' => Disable the account and unpublish its content.
+! 	 'user_cancel_reassign' => Delete the account and make its content belong to the Anonymous user.
+! 	 'user_cancel_delete' => Delete the account and its content.
+! 	 */
+! 
+! 	public $orphanedCheckQty = 100;
+! 
+! 	// public $wsKey = NULL;
+! 	//  public $wsEnabled = 0;
+! 	//  public $wsUserIps = array();
+! 
+! 	public $provisionsLdapEvents = array();
+! 	public $provisionsDrupalEvents = array();
+! 
+! 	public $saveable = array(
+! 			'drupalAcctProvisionServer',
+! 			'ldapEntryProvisionServer',
+! 			'drupalAcctProvisionTriggers',
+! 			'ldapEntryProvisionTriggers',
+! 			'orphanedDrupalAcctBehavior',
+! 			'orphanedCheckQty',
+! 			'userConflictResolve',
+! 			'manualAccountConflict',
+! 			'acctCreation',
+! 			'ldapUserSynchMappings',
+! 	);
+! 	// 'wsKey','wsEnabled','wsUserIps',
+! 	function __construct() {
+! 		$this->load();
+! 
+! 		$this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER] = $this->drupalAcctProvisionServer;
+! 		$this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY] = $this->ldapEntryProvisionServer;
+! 
+! 		$this->provisionsLdapEvents = array(
+! 				LDAP_USER_EVENT_CREATE_LDAP_ENTRY => t('On LDAP Entry Creation'),
+! 				LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY => t('On Synch to LDAP Entry'),
+! 		);
+! 
+! 		$this->provisionsDrupalEvents = array(
+! 				LDAP_USER_EVENT_CREATE_DRUPAL_USER => t('On Drupal User Creation'),
+! 				LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER => t('On Synch to Drupal User'),
+! 		);
+! 
+! 		$this->provisionsDrupalAccountsFromLdap = (
+! 				$this->drupalAcctProvisionServer &&
+! 				$this->drupalAcctProvisionServer &&
+! 				(count(array_filter(array_values($this->drupalAcctProvisionTriggers))) > 0)
+! 		);
+! 
+! 		$this->provisionsLdapEntriesFromDrupalUsers = (
+! 				$this->ldapEntryProvisionServer
+! 				&& $this->ldapEntryProvisionServer
+! 				&& (count(array_filter(array_values($this->ldapEntryProvisionTriggers))) > 0)
+! 		);
+! 
+! 		$this->setSynchMapping(TRUE);
+! 		$this->detailedWatchdog = variable_get('ldap_help_watchdog_detail', 0);
+! 	}
+! 
+! 	function load() {
+! 
+! 		if ($saved = variable_get("ldap_user_conf", FALSE)) {
+! 			$this->inDatabase = TRUE;
+! 			foreach ($this->saveable as $property) {
+! 				if (isset($saved[$property])) {
+! 					$this->{$property} = $saved[$property];
+! 				}
+! 			}
+! 		}
+! 		else {
+! 			$this->inDatabase = FALSE;
+! 		}
+! 
+! 		// determine account creation configuration
+! 		$user_register = variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
+! 		if ($this->acctCreation == LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT || $user_register == USER_REGISTER_VISITORS) {
+! 			$this->createLDAPAccounts = TRUE;
+! 			$this->createLDAPAccountsAdminApproval = FALSE;
+! 		}
+! 		elseif ($user_register == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
+! 			$this->createLDAPAccounts = FALSE;
+! 			$this->createLDAPAccountsAdminApproval = TRUE;
+! 		}
+! 		else {
+! 			$this->createLDAPAccounts = FALSE;
+! 			$this->createLDAPAccountsAdminApproval = FALSE;
+! 		}
+! 	}
+! 
+! 	/**
+! 	 * Destructor Method
+! 	 */
+! 	function __destruct() {
+! 	}
+! 
+! 
+! 	/**
+! 	 * Util to fetch mappings for a given direction
+! 	 *
+! 	 * @param string $sid
+! 	 *   The server id
+! 	 * @param string $direction LDAP_USER_PROV_DIRECTION_* constant
+! 	 * @param array $prov_events
+! 	 *
+! 	 * @return array/bool
+! 	 *   Array of mappings (may be empty array)
+! 	 */
+! 	public function getSynchMappings($direction = LDAP_USER_PROV_DIRECTION_ALL, $prov_events = NULL) {
+! 		if (!$prov_events) {
+! 			$prov_events = ldap_user_all_events();
+! 		}
+! 
+! 		$mappings = array();
+! 		if ($direction == LDAP_USER_PROV_DIRECTION_ALL) {
+! 			$directions = array(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
+! 		}
+! 		else {
+! 			$directions = array($direction);
+! 		}
+! 		foreach ($directions as $direction) {
+! 			if (!empty($this->ldapUserSynchMappings[$direction])) {
+! 				foreach ($this->ldapUserSynchMappings[$direction] as $attribute => $mapping) {
+! 					if (!empty($mapping['prov_events'])) {
+! 						$result = count(array_intersect($prov_events, $mapping['prov_events']));
+! 						if ($result) {
+! 							$mappings[$attribute] = $mapping;
+! 						}
+! 					}
+! 				}
+! 			}
+! 		}
+! 		return $mappings;
+! 	}
+! 
+! 	public function isDrupalAcctProvisionServer($sid) {
+! 		if (!$sid || !$this->drupalAcctProvisionServer) {
+! 			return FALSE;
+! 		}
+! 		elseif ($this->ldapEntryProvisionServer == $sid) {
+! 			return TRUE;
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 	public function isLdapEntryProvisionServer($sid) {
+! 		if (!$sid || !$this->ldapEntryProvisionServer) {
+! 			return FALSE;
+! 		}
+! 		elseif ($this->ldapEntryProvisionServer == $sid) {
+! 			return TRUE;
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 	/**
+! 	 * Util to fetch attributes required for this user conf, not other modules.
+! 	 *
+! 	 * @param enum $direction LDAP_USER_PROV_DIRECTION_* constants
+! 	 * @param string $ldap_context
+! 	 *
+! 	 */
+! 	public function getLdapUserRequiredAttributes($direction = LDAP_USER_PROV_DIRECTION_ALL, $ldap_context = NULL) {
+! 
+! 		$attributes_map = array();
+! 		$required_attributes = array();
+! 		if ($this->drupalAcctProvisionServer) {
+! 			$prov_events = $this->ldapContextToProvEvents($ldap_context);
+! 			$attributes_map = $this->getSynchMappings($direction, $prov_events);
+! 			$required_attributes = array();
+! 			foreach ($attributes_map as $detail) {
+! 				if (count(array_intersect($prov_events, $detail['prov_events']))) {
+! 					// Add the attribute to our array.
+! 					if ($detail['ldap_attr']) {
+! 						ldap_servers_token_extract_attributes($required_attributes,  $detail['ldap_attr']);
+! 					}
+! 				}
+! 			}
+! 		}
+! 		return $required_attributes;
+! 	}
+! 
+! 	/**
+! 	 * converts the more general ldap_context string to its associated ldap user event
+! 	 */
+! 
+! 	public function ldapContextToProvEvents($ldap_context = NULL) {
+! 
+! 		switch ($ldap_context) {
+! 
+! 			case 'ldap_user_prov_to_drupal':
+! 				$result = array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT);
+! 				break;
+! 
+! 			case 'ldap_user_prov_to_ldap':
+! 				$result = array(LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY, LDAP_USER_EVENT_CREATE_LDAP_ENTRY);
+! 				break;
+! 
+! 			default:
+! 				$result = ldap_user_all_events();
+! 
+! 		}
+! 
+! 		return $result;
+! 
+! 	}
+! 
+! 
+! 	/**
+! 	 * converts the more general ldap_context string to its associated ldap user prov direction
+! 	 */
+! 
+! 	public function ldapContextToProvDirection($ldap_context = NULL) {
+! 
+! 		switch ($ldap_context) {
+! 
+! 			case 'ldap_user_prov_to_drupal':
+! 				$result = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER;
+! 				break;
+! 
+! 			case 'ldap_user_prov_to_ldap':
+! 			case 'ldap_user_delete_drupal_user':
+! 				$result = LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY;
+! 				break;
+! 
+! 				// provisioning is can hapen in both directions in most contexts
+! 			case 'ldap_user_insert_drupal_user':
+! 			case 'ldap_user_update_drupal_user':
+! 			case 'ldap_authentication_authenticate':
+! 			case 'ldap_user_insert_drupal_user':
+! 			case 'ldap_user_disable_drupal_user':
+! 				$result = LDAP_USER_PROV_DIRECTION_ALL;
+! 				break;
+! 
+! 			default:
+! 				$result = LDAP_USER_PROV_DIRECTION_ALL;
+! 
+! 		}
+! 
+! 		return $result;
+! 	}
+! 
+! 	/**
+! 	 derive mapping array from ldap user configuration and other configurations.
+! 	 if this becomes a resource hungry function should be moved to ldap_user functions
+! 	 and stored with static variable. should be cached also.
+! 
+! 	 this should be cached and modules implementing ldap_user_synch_mapping_alter
+! 	 should know when to invalidate cache.
+! 
+! 	 */
+! 
+! 	function setSynchMapping($reset = TRUE) {  // @todo change default to false after development
+! 		$synch_mapping_cache = cache_get('ldap_user_synch_mapping');
+! 		if (!$reset && $synch_mapping_cache) {
+! 			$this->synchMapping = $synch_mapping_cache->data;
+! 		}
+! 		else {
+! 			$available_user_attrs = array();
+! 			foreach (array(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) as $direction) {
+! 				$sid = $this->provisionSidFromDirection[$direction];
+! 				$available_user_attrs[$direction] = array();
+! 				$ldap_server = ($sid) ? ldap_servers_get_servers($sid, NULL, TRUE) : FALSE;
+! 
+! 				$params = array(
+! 						'ldap_server' => $ldap_server,
+! 						'ldap_user_conf' => $this,
+! 						'direction' => $direction,
+! 				);
+! 
+! 				drupal_alter('ldap_user_attrs_list', $available_user_attrs[$direction], $params);
+! 			}
+! 		}
+! 		$this->synchMapping = $available_user_attrs;
+! 
+! 		cache_set('ldap_user_synch_mapping',  $this->synchMapping);
+! 	}
+! 
+! 	/**
+! 	 * given a $prov_event determine if ldap user configuration supports it.
+! 	 *   this is overall, not per field synching configuration
+! 	 *
+! 	 * @param enum $direction LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY
+! 	 *
+! 	 * @param enum $prov_event
+! 	 *   LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER,LDAP_USER_EVENT_CREATE_DRUPAL_USER
+! 	 *   LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY LDAP_USER_EVENT_CREATE_LDAP_ENTRY
+! 	 *   LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT
+! 	 *   LDAP_USER_EVENT_ALL
+! 	 *
+! 	 * @param enum $action 'synch', 'provision', 'delete_ldap_entry', 'delete_drupal_entry', 'cancel_drupal_entry'
+! 	 * @return boolean
+! 	 */
+! 
+! 	public function provisionEnabled($direction, $provision_trigger) {
+! 		$result = FALSE;
+! 
+! 		if ($direction == LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) {
+! 			if (!$this->ldapEntryProvisionServer) {
+! 				//debug('provisionEnabled:a');
+! 				$result = FALSE;
+! 			}
+! 			else {
+! 				//debug('provisionEnabled:b');
+! 				$result = in_array($provision_trigger, $this->ldapEntryProvisionTriggers);
+! 			}
+! 		}
+! 		elseif ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER) {
+! 			if (!$this->drupalAcctProvisionServer) {
+! 				//debug('provisionEnabled:c');
+! 				$result = FALSE;
+! 			}
+! 			else {
+! 				//debug('provisionEnabled:d');
+! 				$result = in_array($provision_trigger, $this->drupalAcctProvisionTriggers);
+! 			}
+! 		}
+! 
+! 		return $result;
+! 	}
+! 
+! 	/**
+! 	 * given a drupal account, provision an ldap entry if none exists.  if one exists do nothing
+! 	 *
+! 	 * @param object $account drupal account object with minimum of name property
+! 	 * @param array $ldap_user as prepopulated ldap entry.  usually not provided
+! 	 *
+! 	 * @return array of form:
+! 	 *     array('status' => 'success', 'fail', or 'conflict'),
+! 	 *     array('ldap_server' => ldap server object),
+! 	 *     array('proposed' => proposed ldap entry),
+! 	 *     array('existing' => existing ldap entry),
+! 	 *     array('description' = > blah blah)
+! 	 *
+! 	 */
+! 
+! 	public function provisionLdapEntry($account, $ldap_user = NULL, $test_query = FALSE) {
+! 		//debug('provisionLdapEntry account'); //debug($account);
+! 		$watchdog_tokens = array();
+! 		$result = array(
+! 				'status' => NULL,
+! 				'ldap_server' => NULL,
+! 				'proposed' => NULL,
+! 				'existing' => NULL,
+! 				'description' => NULL,
+! 		);
+! 
+! 		if (is_scalar($account)) {
+! 			$username = $account;
+! 			$account = new stdClass();
+! 			$acount->name = $username;
+! 		}
+! 
+! 		list($account, $user_entity) = ldap_user_load_user_acct_and_entity($account->name);
+! 
+! 		if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
+! 			$result['status'] = 'fail';
+! 			$result['error_description'] = 'can not provision drupal user 1';
+! 			return $result; // do not provision or synch user 1
+! 		}
+! 
+! 		if ($account == FALSE || $account->uid == 0) {
+! 			$result['status'] = 'fail';
+! 			$result['error_description'] = 'can not provision ldap user unless corresponding drupal account exists first.';
+! 			return $result;
+! 		}
+! 
+! 		if (!$this->ldapEntryProvisionServer || !$this->ldapEntryProvisionServer) {
+! 			$result['status'] = 'fail';
+! 			$result['error_description'] = 'no provisioning server enabled';
+! 			return $result;
+! 		}
+! 
+! 		$ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
+! 		$params = array(
+! 				'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+! 				'prov_events' => array(LDAP_USER_EVENT_CREATE_LDAP_ENTRY),
+! 				'module' => 'ldap_user',
+! 				'function' => 'provisionLdapEntry',
+! 				'include_count' => FALSE,
+! 		);
+! 
+! 		list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
+! 		$proposed_dn = (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn']) ? $proposed_ldap_entry['dn'] : NULL;
+! 		$proposed_dn_lcase = drupal_strtolower($proposed_dn);
+! 		$existing_ldap_entry = ($proposed_dn) ? $ldap_server->dnExists($proposed_dn, 'ldap_entry') : NULL;
+! 
+! 		if ($error == LDAP_USER_PROV_RESULT_NO_PWD) {
+! 			$result['status'] = 'fail';
+! 			$result['description'] = 'Can not provision ldap account without user provided password.';
+! 			$result['existing'] = $existing_ldap_entry;
+! 			$result['proposed'] = $proposed_ldap_entry;
+! 			$result['ldap_server'] = $ldap_server;
+! 		}
+! 		elseif (!$proposed_dn) {
+! 			$result['status'] = 'fail';
+! 			$result['description'] = t('failed to derive dn and or mappings');
+! 			return $result;
+! 		}
+! 		elseif ($existing_ldap_entry) {
+! 			$result['status'] = 'conflict';
+! 			$result['description'] = 'can not provision ldap entry because exists already';
+! 			$result['existing'] = $existing_ldap_entry;
+! 			$result['proposed'] = $proposed_ldap_entry;
+! 			$result['ldap_server'] = $ldap_server;
+! 		}
+! 		elseif ($test_query) {
+! 			$result['status'] = 'fail';
+! 			$result['description'] = 'not created because flagged as test query';
+! 			$result['proposed'] = $proposed_ldap_entry;
+! 			$result['ldap_server'] = $ldap_server;
+! 		}
+! 		else {
+! 			// stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call
+! 			$ldap_entries = array($proposed_dn_lcase => $proposed_ldap_entry);
+! 			$context = array(
+! 					'action' => 'add',
+! 					'corresponding_drupal_data' => array($proposed_dn_lcase => $account),
+! 					'corresponding_drupal_data_type' => 'user',
+! 			);
+! 			drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
+! 			// remove altered $proposed_ldap_entry from $ldap_entries array
+! 			$proposed_ldap_entry = $ldap_entries[$proposed_dn_lcase];
+! 
+! 			$ldap_entry_created = $ldap_server->createLdapEntry($proposed_ldap_entry, $proposed_dn);
+! 			if ($ldap_entry_created) {
+! 				module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
+! 				$result['status'] = 'success';
+! 				$result['description'] = 'ldap account created';
+! 				$result['proposed'] = $proposed_ldap_entry;
+! 				$result['created'] = $ldap_entry_created;
+! 				$result['ldap_server'] = $ldap_server;
+! 
+! 				// need to store <sid>|<dn> in ldap_user_prov_entries field, which may contain more than one
+! 				$ldap_user_prov_entry = $ldap_server->sid . '|' . $proposed_ldap_entry['dn'];
+! 				if (!isset($user_entity->ldap_user_prov_entries['und'])) {
+! 					$user_entity->ldap_user_prov_entries = array('und' => array());
+! 				}
+! 				$ldap_user_prov_entry_exists = FALSE;
+! 				foreach ($user_entity->ldap_user_prov_entries['und'] as $i => $field_value_instance) {
+! 					if ($field_value_instance == $ldap_user_prov_entry) {
+! 						$ldap_user_prov_entry_exists = TRUE;
+! 					}
+! 				}
+! 				if (!$ldap_user_prov_entry_exists) {
+! 					$user_entity->ldap_user_prov_entries['und'][] = array(
+! 							'value' =>  $ldap_user_prov_entry,
+! 							'format' => NULL,
+! 							'save_value' => $ldap_user_prov_entry,
+! 					);
+! 					$edit = array(
+! 							'ldap_user_prov_entries' => $user_entity->ldap_user_prov_entries,
+! 					);
+! 					$account = user_load($account->uid);
+! 					
+! 					$account = user_save($account, $edit);
+! 				}
+! 
+! 			}
+! 			else {
+! 				$result['status'] = 'fail';
+! 				$result['proposed'] = $proposed_ldap_entry;
+! 				$result['created'] = $ldap_entry_created;
+! 				$result['ldap_server'] = $ldap_server;
+! 				$result['existing'] = NULL;
+! 			}
+! 		}
+! 
+! 		$tokens = array(
+! 				'%dn' => isset($result['proposed']['dn']) ? $result['proposed']['dn'] : NULL,
+! 				'%sid' => (isset($result['ldap_server']) && $result['ldap_server']) ? $result['ldap_server']->sid : 0,
+! 				'%username' => @$account->name,
+! 				'%uid' => @$account->uid,
+! 				'%description' => @$result['description'],
+! 		);
+! 		if (!$test_query && isset($result['status'])) {
+! 			if ($result['status'] == 'success') {
+! 				if ($this->detailedWatchdog) {
+! 					watchdog('ldap_user', 'LDAP entry on server %sid created dn=%dn.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+! 				}
+! 			}
+! 			elseif ($result['status'] == 'conflict') {
+! 				if ($this->detailedWatchdog) {
+! 					watchdog('ldap_user', 'LDAP entry on server %sid not created because of existing ldap entry. %description. username=%username, uid=%uid', $tokens, WATCHDOG_WARNING);
+! 				}
+! 			}
+! 			elseif ($result['status'] == 'fail') {
+! 				watchdog('ldap_user', 'LDAP entry on server %sid not created because error.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+! 			}
+! 		}
+! 		return $result;
+! 	}
+! 
+! 
+! 	/**
+! 	 * given a drupal account, synch to related ldap entry
+! 	 *
+! 	 * @param drupal user object $account.  Drupal user object
+! 	 * @param array $user_edit.  Edit array for user_save.  generally null unless user account is being created or modified in same synching
+! 	 * @param array $ldap_user.  current ldap data of user. @see README.developers.txt for structure
+! 	 *
+! 	 * @return TRUE on success or FALSE on fail.
+! 	 */
+! 
+! 	public function synchToLdapEntry($account, $user_edit = NULL, $ldap_user =  array(), $test_query = FALSE) {
+! 		//dpm("synchToLdapEntry, test_query=$test_query, account, user_edit"); dpm($account); dpm($user_edit);
+! 		// debug("synchToLdapEntry, test_query=$test_query, account, user_edit"); debug($account); debug($user_edit);
+! 
+! 		if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
+! 			return FALSE; // do not provision or synch user 1
+! 		}
+! 
+! 		$watchdog_tokens = array();
+! 		$result = FALSE;
+! 		$proposed_ldap_entry = FALSE;
+! 
+! 		if ($this->ldapEntryProvisionServer) {
+! 			$ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
+! 			// dpm('synchToLdapEntry:ldap_server, prov='. $this->ldapEntryProvisionServer); dpm($ldap_server);
+! 			$params = array(
+! 					'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+! 					'prov_events' => array(LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY),
+! 					'module' => 'ldap_user',
+! 					'function' => 'synchToLdapEntry',
+! 					'include_count' => FALSE,
+! 			);
+! 
+! 			list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
+! 			// //debug('synchToLdapEntry:proposed_ldap_entry'); //debug($proposed_ldap_entry); //debug("error=$error");
+! 			if ($error != LDAP_USER_PROV_RESULT_NO_ERROR) {
+! 				// //debug("synchToLdapEntry:proposed_ldap_entryerror=$error");
+! 				$result = FALSE;
+! 			}
+! 			elseif (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn'])) {
+! 				$existing_ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry');
+! 				$attributes = array(); // this array represents attributes to be modified; not comprehensive list of attributes
+! 				// //debug('synchToLdapEntry:proposed_ldap_entry'); //debug($proposed_ldap_entry);
+! 				foreach ($proposed_ldap_entry as $attr_name => $attr_values) {
+! 					if ($attr_name != 'dn') {
+! 						if (isset($attr_values['count'])) {
+! 							unset($attr_values['count']);
+! 						}
+! 						if (count($attr_values) == 1) {
+! 							$attributes[$attr_name] = $attr_values[0];
+! 						}
+! 						else {
+! 							$attributes[$attr_name] = $attr_values;
+! 						}
+! 					}
+! 				}
+! 				//     //dpm('synchToLdapEntry:attributes passed to modifyLdapEntry, dn='. $proposed_ldap_entry['dn']);//dpm($attributes);
+! 				if ($test_query) {
+! 					$proposed_ldap_entry = $attributes;
+! 					$result = array(
+! 							'proposed' => $proposed_ldap_entry,
+! 							'server' => $ldap_server,
+! 					);
+! 				}
+! 				else {
+! 					//  //debug('modifyLdapEntry,dn=' . $proposed_ldap_entry['dn']);  //debug($attributes);
+! 					// stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call
+! 					$proposed_dn_lcase = drupal_strtolower($proposed_ldap_entry['dn']);
+! 					$ldap_entries = array($proposed_dn_lcase => $attributes);
+! 					$context = array(
+! 							'action' => 'update',
+! 							'corresponding_drupal_data' => array($proposed_dn_lcase => $attributes),
+! 							'corresponding_drupal_data_type' => 'user',
+! 					);
+! 					drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
+! 					// remove altered $proposed_ldap_entry from $ldap_entries array
+! 					$attributes = $ldap_entries[$proposed_dn_lcase];
+! 					$result = $ldap_server->modifyLdapEntry($proposed_ldap_entry['dn'], $attributes);
+! 					if ($result) { // success
+! 						module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
+! 					}
+! 
+! 
+! 				}
+! 			}
+! 			else { // failed to get acceptable proposed ldap entry
+! 				$result = FALSE;
+! 			}
+! 
+! 
+! 			//  $attributes["attribute1"] = "value";
+! 			//   $attributes["attribute2"][0] = "value1";
+! 			//  $attributes["attribute2"][1] = "value2";
+! 		}
+! 		////dpm('provisionLdapEntry:results');//dpm($results);
+! 
+! 
+! 		$tokens = array(
+! 				'%dn' => isset($result['proposed']['dn']) ? $result['proposed']['dn'] : NULL,
+! 				'%sid' => $this->ldapEntryProvisionServer,
+! 				'%username' => $account->name,
+! 				'%uid' => ($test_query || !property_exists($account, 'uid')) ? '' : $account->uid,
+! 		);
+! 
+! 		if ($result) {
+! 			watchdog('ldap_user', 'LDAP entry on server %sid synched dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+! 		}
+! 		else {
+! 			watchdog('ldap_user', 'LDAP entry on server %sid not synched because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+! 		}
+! 
+! 		return $result;
+! 
+! 	}
+! 
+! 	/**
+! 	 * given a drupal account, query ldap and get all user fields and create user account
+! 	 *
+! 	 * @param array $account drupal account array with minimum of name
+! 	 * @param array $user_edit drupal edit array in form user_save($account, $user_edit) would take,
+! 	 *   generally empty unless overriding synchToDrupalAccount derived values
+! 	 * @param array $ldap_user as user's ldap entry.  passed to avoid requerying ldap in cases where already present
+! 	 * @param boolean $save indicating if drupal user should be saved.  generally depends on where function is called from.
+! 	 *
+! 	 * @return result of user_save() function is $save is true, otherwise return TRUE
+! 	 *   $user_edit data returned by reference
+! 	 *
+! 	 */
+! 	public function synchToDrupalAccount($drupal_user, &$user_edit, $prov_event = LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user = NULL,  $save = FALSE) {
+! 
+! 		$debug = array(
+! 				'account' => $drupal_user,
+! 				'user_edit' => $user_edit,
+! 				'ldap_user' => $ldap_user,
+! 		);
+! 
+! 		if (
+! 				(!$ldap_user  && !isset($drupal_user->name)) ||
+! 				(!$drupal_user && $save) ||
+! 				($ldap_user && !isset($ldap_user['sid']))
+! 		) {
+! 			// should throw watchdog error also
+! 			return FALSE;
+! 		}
+! 
+! 		if (!$ldap_user && $this->drupalAcctProvisionServer) {
+! 			$ldap_user = ldap_servers_get_user_ldap_data($drupal_user->name, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');			
+! 		}
+! 
+! 		if (!$ldap_user) {
+! 			return FALSE;
+! 		}
+! 
+! 		if ($this->drupalAcctProvisionServer) {
+! 			$ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, NULL, TRUE);
+! 			$this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array($prov_event));
+! 		}
+! 
+! 		if ($save) {
+! 			// $account = new stdClass();
+! 			$account = user_load($drupal_user->uid);			
+! 			$result = user_save($account, $user_edit, 'ldap_user');
+! 
+! 			return $result;
+! 		}
+! 		else {
+! 			return TRUE;
+! 		}
+! 	}
+! 
+! 
+! 	/**
+! 	 * given a drupal account, delete user account
+! 	 *
+! 	 * @param string $username drupal account name
+! 	 * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
+! 	 */
+! 	public function deleteDrupalAccount($username) {
+! 		$user = user_load_by_name($username);
+! 		if (is_object($user)) {
+! 			user_delete($user->uid);
+! 			return TRUE;
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 	/**
+! 	 * given a drupal account, find the related ldap entry.
+! 	 *
+! 	 * @param drupal user object $account
+! 	 *
+! 	 * @return FALSE or ldap entry
+! 	 */
+! 	public function getProvisionRelatedLdapEntry($account, $prov_events = NULL) {
+! 		if (!$prov_events) {
+! 			$prov_events = ldap_user_all_events();
+! 		}
+! 		$sid = $this->ldapEntryProvisionServer; //
+! 		//debug("ldapEntryProvisionServer:$sid");
+! 		if (!$sid) {
+! 			return FALSE;
+! 		}
+! 		// $user_entity->ldap_user_prov_entries,
+! 		$ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
+! 		$params = array(
+! 				'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+! 				'prov_events' => $prov_events,
+! 				'module' => 'ldap_user',
+! 				'function' => 'getProvisionRelatedLdapEntry',
+! 				'include_count' => FALSE,
+! 		);
+! 		list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params);
+! 		if (!(is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn'])) {
+! 			return FALSE;
+! 		}
+! 		$ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry', array());
+! 		return $ldap_entry;
+! 
+! 	}
+! 
+! 	/**
+! 	 * given a drupal account, delete ldap entry that was provisioned based on it
+! 	 *   normally this will be 0 or 1 entry, but the ldap_user_provisioned_ldap_entries
+! 	 *   field attached to the user entity track each ldap entry provisioned
+! 	 *
+! 	 * @param object $account drupal account
+! 	 * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
+! 	 */
+! 	public function deleteProvisionedLdapEntries($account) {
+! 		// determine server that is associated with user
+! 
+! 		$boolean_result = FALSE;
+! 		$language = ($account->language) ? $account->language : 'und';
+! 		if (isset($account->ldap_user_prov_entries[$language][0])) {
+! 			foreach ($account->ldap_user_prov_entries[$language] as $i => $field_instance) {
+! 				$parts = explode('|', $field_instance['value']);
+! 				if (count($parts) == 2) {
+! 
+! 					list($sid, $dn) = $parts;
+! 					$ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
+! 					if (is_object($ldap_server) && $dn) {
+! 						$boolean_result = $ldap_server->delete($dn);
+! 						$tokens = array('%sid' => $sid, '%dn' => $dn, '%username' => $account->name, '%uid' => $account->uid);
+! 						if ($boolean_result) {
+! 							watchdog('ldap_user', 'LDAP entry on server %sid deleted dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
+! 						}
+! 						else {
+! 							watchdog('ldap_user', 'LDAP entry on server %sid not deleted because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
+! 						}
+! 					}
+! 					else {
+! 						$boolean_result = FALSE;
+! 					}
+! 				}
+! 			}
+! 		}
+! 		return $boolean_result;
+! 
+! 	}
+! 
+! 	/**
+! 	 *  populate ldap entry array for provisioning
+! 	 *
+! 	 * @param array $account drupal account
+! 	 * @param object $ldap_server
+! 	 * @param array $ldap_user ldap entry of user, returned by reference
+! 	 * @param array $params with the following key values:
+! 	 *    'ldap_context' =>
+! 	 'module' => module calling function, e.g. 'ldap_user'
+! 	 'function' => function calling function, e.g. 'provisionLdapEntry'
+! 	 'include_count' => should 'count' array key be included
+! 	 'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY || LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER
+! 	 *
+! 	 * @return array(ldap entry, $result) in ldap extension array format.!THIS IS NOT THE ACTUAL LDAP ENTRY
+! 	 */
+! 
+! 	function drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user_entry = NULL) {
+! 		//debug('call to drupalUserToLdapEntry, account:'); //debug($account); //debug('ldap_server'); //debug($ldap_server);
+! 		//debug('params'); //debug($params); //debug('ldap_user_entry');//debug($ldap_user_entry);
+! 		$provision = (isset($params['function']) && $params['function'] == 'provisionLdapEntry');
+! 		$result = LDAP_USER_PROV_RESULT_NO_ERROR;
+! 		if (!$ldap_user_entry) {
+! 			$ldap_user_entry = array();
+! 		}
+! 
+! 		if (!is_object($account) || !is_object($ldap_server)) {
+! 			return array(NULL, LDAP_USER_PROV_RESULT_BAD_PARAMS);
+! 		}
+! 		$watchdog_tokens = array(
+! 				'%drupal_username' => $account->name,
+! 		);
+! 		$include_count = (isset($params['include_count']) && $params['include_count']);
+! 
+! 		$direction = isset($params['direction']) ? $params['direction'] : LDAP_USER_PROV_DIRECTION_ALL;
+! 		$prov_events = empty($params['prov_events']) ? ldap_user_all_events() : $params['prov_events'];
+! 
+! 		$mappings = $this->getSynchMappings($direction, $prov_events);
+! 		//debug('prov_events'); //debug(join(",",$prov_events));
+! 		//  debug('mappings'); debug($mappings);
+! 		// Loop over the mappings.
+! 		foreach ($mappings as $field_key => $field_detail) {
+! 			list($ldap_attr_name, $ordinal, $source_data_type, $target_data_type) = ldap_servers_token_extract_parts($field_key, TRUE);  //trim($field_key, '[]');
+! 			$ordinal = (!$ordinal) ? 0 : $ordinal;
+! 			if ($ldap_user_entry && isset($ldap_user_entry[$ldap_attr_name]) && is_array($ldap_user_entry[$ldap_attr_name]) && isset($ldap_user_entry[$ldap_attr_name][$ordinal]) ) {
+! 				continue; // don't override values passed in;
+! 			}
+! 
+! 			$synched = $this->isSynched($field_key, $params['prov_events'], LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
+! 			//  debug("isSynched $field_key: $synched");
+! 			if ($synched) {
+! 				$token = ($field_detail['user_attr'] == 'user_tokens') ? $field_detail['user_tokens'] : $field_detail['user_attr'];
+! 				$value = ldap_servers_token_replace($account, $token, 'user_account');
+! 
+! 				if (substr($token, 0, 10) == '[password.' && (!$value || $value == $token)) { // deal with empty/unresolved password
+! 					if (!$provision) {
+! 						continue; //don't overwrite password on synch if no value provided
+! 					}
+! 				}
+! 
+! 				if ($ldap_attr_name == 'dn' && $value) {
+! 					$ldap_user_entry['dn'] = $value;
+! 				}
+! 				elseif ($value) {
+! 					if (!isset($ldap_user_entry[$ldap_attr_name]) || !is_array($ldap_user_entry[$ldap_attr_name])) {
+! 						$ldap_user_entry[$ldap_attr_name] = array();
+! 					}
+! 					$ldap_user_entry[$ldap_attr_name][$ordinal] = $value;
+! 					if ($include_count) {
+! 						$ldap_user_entry[$ldap_attr_name]['count'] = count($ldap_user_entry[$ldap_attr_name]);
+! 					}
+! 					// dpm("ldap_user_entry: $ldap_attr_name=$ldap_attr_name, ordinal=$ordinal"); dpm($ldap_user_entry[$ldap_attr_name]);
+! 				}
+! 
+! 			}
+! 
+! 		}
+! 
+! 		/**
+! 		 * 4. call drupal_alter() to allow other modules to alter $ldap_user
+! 		 */
+! 
+! 		drupal_alter('ldap_entry', $ldap_user_entry, $params);
+! 
+! 		return array($ldap_user_entry, $result);
+! 
+! 	}
+! 
+! 
+! 
+! 	/**
+! 	 * given a drupal account, query ldap and get all user fields and save user account
+! 	 * (note: parameters are in odd order to match synchDrupalAccount handle)
+! 	 *
+! 	 * @param array $account drupal account object or null
+! 	 * @param array $user_edit drupal edit array in form user_save($account, $user_edit) would take.
+! 	 * @param array $ldap_user as user's ldap entry.  passed to avoid requerying ldap in cases where already present
+! 	 * @param boolean $save indicating if drupal user should be saved.  generally depends on where function is called from and if the
+! 	 *
+! 	 * @return result of user_save() function is $save is true, otherwise return TRUE on success or FALSE on any problem
+! 	 *   $user_edit data returned by reference
+! 	 *
+! 	 */
+! 
+! 	public function provisionDrupalAccount($account = FALSE, &$user_edit, $ldap_user = NULL, $save = TRUE) {
+! 
+! 		$watchdog_tokens = array();
+! 		/**
+! 		 * @todo
+! 		 * -- add error catching for conflicts, conflicts should be checked before calling this function.
+! 		 *
+! 		 */		
+! 		if (!$account) {
+! 			$account = new stdClass();
+! 		}
+! 		$account->is_new = TRUE;
+! 
+! 		if (!$ldap_user && !isset($user_edit['name'])) {
+! 			return FALSE;
+! 		}
+! 
+! 		if (!$ldap_user) {
+! 			$watchdog_tokens['%username'] = $user_edit['name'];
+! 			if ($this->drupalAcctProvisionServer) {
+! 				$ldap_user = ldap_servers_get_user_ldap_data($user_edit['name'], $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+! 			}
+! 			if (!$ldap_user) {
+! 				if ($this->detailedWatchdog) {
+! 					watchdog('ldap_user', '%username : failed to find associated ldap entry for username in provision.', $watchdog_tokens, WATCHDOG_DEBUG);
+! 				}
+! 				return FALSE;
+! 			}
+! 		}
+! 		// dpm('ldap_user 675');dpm($ldap_user);
+! 		if (!isset($user_edit['name']) && isset($account->name)) {
+! 			$user_edit['name'] = $account->name;
+! 			$watchdog_tokens['%username'] = $user_edit['name'];
+! 		}
+! 		if ($this->drupalAcctProvisionServer) {
+! 			// dpm("this->drupalAcctProvisionServer=" . $this->drupalAcctProvisionServer);
+! 			$ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);  // $ldap_user['sid']
+! 
+! 			$params = array(
+! 					'account' => $account,
+! 					'user_edit' => $user_edit,
+! 					'prov_event' => LDAP_USER_EVENT_CREATE_DRUPAL_USER,
+! 					'module' => 'ldap_user',
+! 					'function' => 'provisionDrupalAccount',
+! 					'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER,
+! 			);
+! 
+! 			drupal_alter('ldap_entry', $ldap_user, $params);
+! 
+! 			// look for existing drupal account with same puid.  if so update username and attempt to synch in current context
+! 			$puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+! 			$account2 = ($puid) ? $ldap_server->userUserEntityFromPuid($puid) : FALSE;
+! 
+! 			if ($account2) { // synch drupal account, since drupal account exists
+! 				// 1. correct username and authmap
+! 				$this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+! 				
+! 				$account = user_save($account2, $user_edit, 'ldap_user');
+! 				user_set_authmaps($account, array("authname_ldap_user" => $user_edit['name']));
+! 				// 2. attempt synch if appropriate for current context
+! 				if ($account) {
+! 					$account = $this->synchToDrupalAccount($account, $user_edit, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user, TRUE);
+! 				}
+! 				return $account;
+! 			}
+! 			else { // create drupal account
+! 				$this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_CREATE_DRUPAL_USER));
+! 				if ($save) {
+! 					
+! 					$account = user_save(NULL, $user_edit, 'ldap_user');
+! 					if (!$account) {
+! 						drupal_set_message(t('User account creation failed because of system problems.'), 'error');
+! 					}
+! 					else {
+! 						user_set_authmaps($account, array('authname_ldap_user' => $user_edit['name']));
+! 					}
+! 					return $account;
+! 				}
+! 				return TRUE;
+! 			}
+! 		}
+! 	}
+! 
+! 	/**
+! 	 * set ldap associations of a drupal account by altering user fields
+! 	 *
+! 	 * @param string $drupal_username
+! 	 *
+! 	 * @return boolean TRUE on success, FALSE on error or failure because of invalid user or ldap accounts
+! 	 *
+! 	 */
+! 	function ldapAssociateDrupalAccount($drupal_username) {
+! 
+! 		if ($this->drupalAcctProvisionServer) {
+! 			$prov_events = array(LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT);
+! 			$ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);  // $ldap_user['sid']
+! 			$account = user_load_by_name($drupal_username);
+! 			$ldap_user = ldap_servers_get_user_ldap_data($drupal_username, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+! 			if (!$account) {
+! 				watchdog(
+! 						'ldap_user',
+! 						'Failed to LDAP associate drupal account %drupal_username because account not found',
+! 						array('%drupal_username' => $drupal_username),
+! 						WATCHDOG_ERROR
+! 				);
+! 				return FALSE;
+! 			}
+! 			elseif (!$ldap_user) {
+! 				watchdog(
+! 						'ldap_user',
+! 						'Failed to LDAP associate drupal account %drupal_username because corresponding LDAP entry not found',
+! 						array('%drupal_username' => $drupal_username),
+! 						WATCHDOG_ERROR
+! 				);
+! 				return FALSE;
+! 			}
+! 			else {
+! 				$user_edit = array();
+! 				$user_edit['data']['ldap_user']['init'] = array(
+! 						'sid'  => $ldap_user['sid'],
+! 						'dn'   => $ldap_user['dn'],
+! 						'mail'   => $account->mail,
+! 				);
+! 				$ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+! 				if ($ldap_user_puid) {
+! 					$user_edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid; //
+! 				}
+! 				$user_edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
+! 				$user_edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
+! 				$user_edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
+! 							
+! 				$account = user_save($account, $user_edit, 'ldap_user');
+! 				return (boolean)$account;
+! 			}
+! 		}
+! 		else {
+! 			return FALSE;
+! 		}
+! 	}
+! 
+! 	/** populate $user edit array (used in hook_user_save, hook_user_update, etc)
+! 	 * ... should not assume all attribues are present in ldap entry
+! 	 *
+! 	 * @param array ldap entry $ldap_user
+! 	 * @param object $ldap_server
+! 	 * @param array $edit see hook_user_save, hook_user_update, etc
+! 	 * @param drupal account object $account
+! 	 * @param string $op see hook_ldap_attributes_needed_alter
+! 	 */
+! 
+! 	function entryToUserEdit($ldap_user, &$edit, $ldap_server, $direction = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, $prov_events = NULL) {
+! 		
+! 		// need array of user fields and which direction and when they should be synched.
+! 		if (!$prov_events) {
+! 			$prov_events = ldap_user_all_events();
+! 		}
+! 		$mail_synched = $this->isSynched('[property.mail]', $prov_events, $direction);
+! 		if (!isset($edit['mail']) && $mail_synched) {
+! 			$derived_mail = $ldap_server->userEmailFromLdapEntry($ldap_user['attr']);
+! 			if ($derived_mail) {
+! 				$edit['mail'] = $derived_mail;
+! 			}
+! 		}
+! 		//Load picture
+! 		if ($this->isSynched('[property.picture]', $prov_events, $direction) && !isset($edit['name'])){
+! 			$picture = $ldap_server->userPictureFromLdapEntry($ldap_user['attr']);
+! 			if($picture){
+! 				$edit['picture'] = $picture;
+! 				if(isset($picture->md5Sum)){
+! 					$edit['data']['ldap_user']['init']['thumb5md'] = $picture->md5Sum;
+! 				}				
+! 			}
+! 		}
+! 
+! 		if ($this->isSynched('[property.name]', $prov_events, $direction) && !isset($edit['name'])) {
+! 			$name = $ldap_server->userUsernameFromLdapEntry($ldap_user['attr']);
+! 			if ($name) {
+! 				$edit['name'] = $name;
+! 			}
+! 		}			
+! 		if ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER && in_array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, $prov_events)) {
+! 			$edit['mail'] = isset($edit['mail']) ? $edit['mail'] : $ldap_user['mail'];
+! 			$edit['picture'] = isset($edit['picture']) ? $edit['picture'] : $ldap_user['picture'];
+! 			$edit['pass'] = isset($edit['pass']) ? $edit['pass'] : user_password(20);
+! 			$edit['init'] = isset($edit['init']) ? $edit['init'] : $edit['mail'];
+! 			$edit['status'] = isset($edit['status']) ? $edit['status'] : 1;
+! 			$edit['signature'] = isset($edit['signature']) ? $edit['signature'] : '';
+! 
+! 			$edit['data']['ldap_user']['init'] = array(
+! 					'sid'  => $ldap_user['sid'],
+! 					'dn'   => $ldap_user['dn'],
+! 					'mail' => $edit['mail'],
+! 			);
+! 		}
+! 		
+! 		
+! 
+! 		/**
+! 		 * basic $user ldap fields
+! 		 */
+! 		if ($this->isSynched('[field.ldap_user_puid]', $prov_events, $direction)) {
+! 			$ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
+! 			if ($ldap_user_puid) {
+! 				$edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid; //
+! 			}
+! 		}
+! 		if ($this->isSynched('[field.ldap_user_puid_property]', $prov_events, $direction)) {
+! 			$edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
+! 		}
+! 		if ($this->isSynched('[field.ldap_user_puid_sid]', $prov_events, $direction)) {
+! 			$edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
+! 		}
+! 		if ($this->isSynched('[field.ldap_user_current_dn]', $prov_events, $direction)) {
+! 			$edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
+! 		}
+! 
+! 
+! 		// Get any additional mappings.
+! 		$mappings = $this->getSynchMappings($direction, $prov_events);				
+! 		// Loop over the mappings.
+! 		foreach ($mappings as $user_attr_key => $field_detail) {
+! 			// //dpm('field detail');//dpm($field_detail);
+! 			// Make sure this mapping is relevant to the sync context.
+! 			if (!$this->isSynched($user_attr_key, $prov_events, $direction)) {
+! 				continue;
+! 			}
+! 
+! 
+! 			/**
+! 			* if "convert from binary is selected" and no particular method is in token,
+! 			* default to ldap_servers_binary() function
+! 			*/
+! 			if ($field_detail['convert'] && strpos($field_detail['ldap_attr'], ';') === FALSE) {
+! 				$field_detail['ldap_attr'] = str_replace(']', ';binary]', $field_detail['ldap_attr']);
+! 			}
+! 			$value = ldap_servers_token_replace($ldap_user['attr'], $field_detail['ldap_attr'], 'ldap_entry');
+! 			list($value_type, $value_name, $value_instance) = ldap_servers_parse_user_attr_name($user_attr_key);
+! 			
+! 			// $value_instance not used, may have future use case
+! 
+! 			// Are we dealing with a field?
+! 			if ($value_type == 'field') {
+! 				// Field api field - first we get the field.
+! 				$field = field_info_field($value_name);
+! 				// Then the columns for the field in the schema.
+! 				$columns = array_keys($field['columns']);
+! 				// Then we convert the value into an array if it's scalar.
+! 				$values = $field['cardinality'] == 1 ? array($value) : (array) $value;				
+! 				$items = array();
+! 				// Loop over the values and set them in our $items array.
+! 				foreach ($values as $delta => $value) {
+! 					if (isset($value)) {
+! 						// We set the first column value only, this is consistent with
+! 						// the Entity Api (@see entity_metadata_field_property_set).
+! 						$items[$delta][$columns[0]] = $value;
+! 					}
+! 				}
+! 				// Add them to our edited item.
+! 				$edit[$value_name][LANGUAGE_NONE] = $items;
+! 			}
+! 			elseif ($value_type == 'property') {
+! 				// Straight property.
+! 				$edit[$value_name] = $value;
+! 			}
+! 		}
+! 
+! 		// Allow other modules to have a say.
+! 
+! 		drupal_alter('ldap_user_edit_user', $edit, $ldap_user, $ldap_server, $prov_events);
+! 		
+! 
+! 	}
+! 	/**
+! 	 * given configuration of synching, determine is a given synch should occur
+! 	 *
+! 	 * @param string $attr_token e.g. [property.mail], [field.ldap_user_puid_property]
+! 	 * @param object $ldap_server
+! 	 * @param array $prov_events e.g. array(LDAP_USER_EVENT_CREATE_DRUPAL_USER).  typically array with 1 element
+! 	 * @param scalar $direction LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY
+! 	 */
+! 
+! 	public function isSynched($attr_token, $prov_events, $direction) {
+! 		$result = (boolean)(
+! 				isset($this->synchMapping[$direction][$attr_token]['prov_events']) &&
+! 				count(array_intersect($prov_events, $this->synchMapping[$direction][$attr_token]['prov_events']))
+! 		);
+! 		if (!$result) {
+! 			if (isset($this->synchMapping[$direction][$attr_token])) {
+! 				//debug($this->synchMapping[$direction][$attr_token]);
+! 			}
+! 			else {
+! 				//  debug("$attr_token not in ldapUserConf::synchMapping");
+! 			}
+! 		}
+! 		return $result;
+! 	}
+  
+  
+  } // end LdapUserConf class
diff -Naur ldapdev/ldap_servers/LdapServerAdmin.class.php ldapdevpatch/ldap_servers/LdapServerAdmin.class.php
--- ldapdev/ldap_servers/LdapServerAdmin.class.php	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_servers/LdapServerAdmin.class.php	2013-02-12 10:42:37.000000000 +0100
@@ -66,8 +66,10 @@
     $this->user_dn_expression = trim($values['user_dn_expression']);
     $this->basedn = $this->linesToArray(trim($values['basedn']));
     $this->user_attr = drupal_strtolower(trim($values['user_attr']));
+    $this->picture_attr = drupal_strtolower(trim($values['picture_attr']));
     $this->account_name_attr = drupal_strtolower(trim($values['account_name_attr']));
     $this->mail_attr = drupal_strtolower(trim($values['mail_attr']));
+    $this->picture_attr = drupal_strtolower(trim($values['picture_attr']));
     $this->mail_template = trim($values['mail_template']);
     $this->unique_persistent_attr = drupal_strtolower(trim($values['unique_persistent_attr']));
     $this->unique_persistent_attr_binary = trim($values['unique_persistent_attr_binary']);
@@ -93,6 +95,7 @@
 
     $this->searchPagination = ($values['search_pagination']) ? 1 : 0;
     $this->searchPageSize = trim($values['search_page_size']);
+    
 
   }
 
@@ -514,6 +517,7 @@
      * consumer_type is tag (unique alphanumeric id) of consuming authorization such as
      *   drupal_roles, og_groups, civicrm_memberships
      */
+  	
     $fields = array(
 
       'sid' => array(
@@ -787,6 +791,21 @@
           'not null' => FALSE,
         ),
       ),
+      
+      'picture_attr' => array(
+      		'form' => array(
+      				'fieldset' => 'users',
+      				'#type' => 'textfield',
+      				'#size' => 30,
+      				'#title' => t('Thumbnail attribute'),
+      				'#description' => t('The attribute that holds the users\' thumnail image. (eg. <code>thumbnailPhoto</code>). Leave empty if no such attribute exists'),
+      		),
+      		'schema' => array(
+      				'type' => 'varchar',
+      				'length' => 255,
+      				'not null' => FALSE,
+      		),
+      ),
 
       'mail_template' => array(
         'form' => array(
diff -Naur ldapdev/ldap_servers/LdapServer.class.php ldapdevpatch/ldap_servers/LdapServer.class.php
--- ldapdev/ldap_servers/LdapServer.class.php	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_servers/LdapServer.class.php	2013-02-12 10:40:34.000000000 +0100
@@ -36,6 +36,7 @@
   public $bindpw = FALSE; // Default to an anonymous bind.
   public $user_dn_expression;
   public $user_attr;
+	public $picture_attr;
   public $account_name_attr; //lowercase
   public $mail_attr; //lowercase
   public $mail_template;
@@ -100,6 +101,7 @@
     'user_attr'  => 'user_attr',
     'account_name_attr'  => 'account_name_attr',
     'mail_attr'  => 'mail_attr',
+				'picture_attr'  => 'picture_attr',
     'mail_template'  => 'mail_template',
     'unique_persistent_attr' => 'unique_persistent_attr',
     'unique_persistent_attr_binary' => 'unique_persistent_attr_binary',
@@ -648,7 +650,7 @@
       * -- http://sgehrig.wordpress.com/2009/11/06/reading-paged-ldap-results-with-php-is-a-show-stopper/
       */
 
-
+		$filter = strtolower($filter);
     if ($base_dn == NULL) {
       if (count($this->basedn) == 1) {
         $base_dn = $this->basedn[0];
@@ -996,6 +998,71 @@
     }
   }
 
+	/**
+	 * @param ldap entry array $ldap_entry
+	 *
+	 * @return drupal file object image user's thumbnail or FALSE if none present or ERROR happens.
+	 */
+	public function userPictureFromLdapEntry($ldap_entry) {
+		if ($ldap_entry && $this->picture_attr) {
+			//Check if ldap entry has been provisioned.
+			$account = user_load_by_name($ldap_entry[$this->user_attr][0]);						
+			$thumb = isset($ldap_entry[$this->picture_attr][0]) ? $ldap_entry[$this->picture_attr][0] : FALSE;
+			if(!$thumb || $account->uid == '0'){
+				return false;
+			}
+			//Create md5 check.						
+			$md5thumb = md5($thumb);
+			/**
+			 * If account already has picture check if it has changed if so remove old file and create the new one 
+			 * If picture is not set but account has md5 something is wrong exit. 
+			 */
+			
+			if(isset($account->picture)){
+				// Check if image has changed
+				if(isset($account->data['ldap_user']['init']['thumb5md']) && $md5thumb === $account->data['ldap_user']['init']['thumb5md']){
+					//No change return same image					
+					return $account->picture;					
+				}else{
+					//Image is different check wether is obj/str and remove fileobject
+					if(is_object($account->picture)){
+						file_delete($account->picture, TRUE);
+					}else if(is_string($account->picture)){
+						$file = file_load(intval($account->picture));
+						file_delete($file, TRUE);
+					}
+				}			
+			}else if(isset($account->data['ldap_user']['init']['thumb5md'])) {
+				watchdog('ldap_server', "Some error happened during thumbnailPhoto sync", WATCHDOG_ERROR );
+				return false;
+			}
+			//Create tmp file to get image format.
+			$filename = uniqid();
+			$fileuri = file_directory_temp() .'/'. $filename;
+			$size = file_put_contents($fileuri, $thumb);				
+			$info = image_get_info($fileuri);
+			unlink($fileuri);
+			// create file object
+			$file = file_save_data($thumb, 'public://' . variable_get('user_picture_path') .'/'. $filename .'.'. $info['extension']);
+			$file->md5Sum = $md5thumb;
+			// standard Drupal validators for user pictures
+			$validators = array(
+					'file_validate_is_image' => array(),
+					'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
+					'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
+			);
+			$errors = file_validate($file ,$validators);
+			if (empty($errors)) {
+				return $file;
+			}else{
+				foreach($errors as $err => $err_val){
+					watchdog('ldap_server', "Error storing picture: %$err", "%$err_val", WATCHDOG_ERROR );
+				}
+				return false;
+			}
+		}
+	}
+	
 
   /**
    * @param ldap entry array $ldap_entry
diff -Naur ldapdev/ldap_servers/ldap_servers.install ldapdevpatch/ldap_servers/ldap_servers.install
--- ldapdev/ldap_servers/ldap_servers.install	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_servers/ldap_servers.install	2013-02-12 10:35:03.000000000 +0100
@@ -763,6 +763,22 @@
 
 }
 
+/**
+ * Add picture_attr field in schema
+ * 
+ * 
+ */
+function ldap_servers_update_7204() {
+	db_add_field(
+			'ldap_servers',			
+			'picture_attr',
+			array(
+					'type' => 'varchar',
+					'length' => 255,
+					'not null' => FALSE,
+			)
+	);		
+}
 
 function ldap_servers_install_update_schema($schema, &$change_log) {
   foreach ($schema as $table_name => $table_schema) {
diff -Naur ldapdev/ldap_servers/ldap_servers.module ldapdevpatch/ldap_servers/ldap_servers.module
--- ldapdev/ldap_servers/ldap_servers.module	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_servers/ldap_servers.module	2013-02-12 10:37:08.000000000 +0100
@@ -326,6 +326,9 @@
       if ($ldap_server->mail_template) {
         ldap_servers_token_extract_attributes($attributes, $ldap_server->mail_template);
       }
+      if(!isset($attributes[$ldap_server->picture_attr])){
+      	$attributes[$ldap_server->picture_attr] = ldap_servers_set_attribute_map();
+      }
       if (!isset($attributes[$ldap_server->user_attr])) {
         $attributes[$ldap_server->user_attr] = ldap_servers_set_attribute_map();
       }
@@ -456,6 +459,20 @@
         'config_module' => 'ldap_server',
         'prov_module' => 'ldap_user',
       ) + $available_user_attrs['[property.mail]'];
+      
+      if (!isset($available_user_attrs['[property.picture]']) || !is_array($available_user_attrs['[property.picture]'])) {
+      	$available_user_attrs['[property.picture]'] = array();
+      }
+      $available_user_attrs['[property.picture]'] = array(
+      		'name' => 'Property: Picture',
+      		'source' => '[' . $ldap_server->picture_attr . ']',
+      		'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER,
+      		'enabled' => TRUE,
+      		'prov_events' => array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER),
+      		'config_module' => 'ldap_server',
+      		'prov_module' => 'ldap_user',
+      ) + $available_user_attrs['[property.picture]'];
+      
     }
   }
  // debug('available_user_attrs2'); debug($available_user_attrs);
diff -Naur ldapdev/ldap_user/LdapUserConf.class.php ldapdevpatch/ldap_user/LdapUserConf.class.php
--- ldapdev/ldap_user/LdapUserConf.class.php	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_user/LdapUserConf.class.php	2013-02-12 10:50:13.000000000 +0100
@@ -1194,8 +1194,18 @@
       $derived_mail = $ldap_server->userEmailFromLdapEntry($ldap_user['attr']);
       if ($derived_mail) {
         $edit['mail'] = $derived_mail;
-      }
-    }
+			}
+		}
+		//Load picture
+		if ($this->isSynched('[property.picture]', $prov_events, $direction) && !isset($edit['name'])){
+			$picture = $ldap_server->userPictureFromLdapEntry($ldap_user['attr']);
+			if($picture){
+				$edit['picture'] = $picture;
+				if(isset($picture->md5Sum)){
+					$edit['data']['ldap_user']['init']['thumb5md'] = $picture->md5Sum;
+				}				
+			}
+		}
 
     if ($this->isSynched('[property.name]', $prov_events, $direction) && !isset($edit['name'])) {
       $name = $ldap_server->userUsernameFromLdapEntry($ldap_user['attr']);
@@ -1206,6 +1216,7 @@
 
     if ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER && in_array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, $prov_events)) {
       $edit['mail'] = isset($edit['mail']) ? $edit['mail'] : $ldap_user['mail'];
+			$edit['picture'] = isset($edit['picture']) ? $edit['picture'] : $ldap_user['picture'];
       $edit['pass'] = isset($edit['pass']) ? $edit['pass'] : user_password(20);
       $edit['init'] = isset($edit['init']) ? $edit['init'] : $edit['mail'];
       $edit['status'] = isset($edit['status']) ? $edit['status'] : 1;
diff -Naur ldapdev/ldap_user/ldap_user.module ldapdevpatch/ldap_user/ldap_user.module
--- ldapdev/ldap_user/ldap_user.module	2013-02-11 02:00:09.000000000 +0100
+++ ldapdevpatch/ldap_user/ldap_user.module	2013-02-12 10:45:41.000000000 +0100
@@ -60,6 +60,7 @@
 define('LDAP_USER_TEST_FORM_PATH', 'admin/config/people/ldap/user/test');
 define('LDAP_USER_WS_USER_PATH', 'ldap/user/ws');
 
+
 /**
  * Implements hook_menu().
  */
@@ -223,6 +224,7 @@
       case 'ldap_user_ldap_associate':
         $attributes[$ldap_server->user_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->user_attr]); // array($ldap_server->user_attr, 0, NULL);
         $attributes[$ldap_server->mail_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->mail_attr]);
+        $attributes[$ldap_server->picture_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->picture_attr]);
         $attributes[$ldap_server->unique_persistent_attr] = ldap_servers_set_attribute_map(@$attributes[$ldap_server->unique_persistent_attr]);
         if ($ldap_server->mail_template) {
           ldap_servers_token_extract_attributes($attributes,  $ldap_server->mail_template);
@@ -270,6 +272,18 @@
       'prov_module' => 'ldap_user',
       'configurable_to_ldap' => TRUE,
     );
+    
+    $available_user_attrs['[property.picture]'] =  array(
+    		'name' => 'Property: picture',
+    		'source' => '',
+    		'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
+    		'enabled' => TRUE,
+    		'prov_events' => array(LDAP_USER_EVENT_CREATE_LDAP_ENTRY, LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY),
+    		'config_module' => 'ldap_user',
+    		'prov_module' => 'ldap_user',
+    		'configurable_to_ldap' => TRUE,
+    );
+    
 
     $available_user_attrs['[property.uid]'] =  array(
       'name' => 'Property: Drupal User Id (uid)',
@@ -342,6 +356,7 @@
   if (!$ldap_user_conf->provisionsDrupalAccountsFromLdap) {
     $available_user_attrs['[property.mail]']['config_module'] = 'ldap_user';
     $available_user_attrs['[property.name]']['config_module'] = 'ldap_user';
+    $available_user_attrs['[property.picture]']['config_module'] = 'ldap_user';
   }
 
   if ($direction == LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) {
@@ -473,8 +488,6 @@
 }
 
 
-
-
 /**
  * Implements hook_form_FORM_ID_alter(). for user_register_form
  */
@@ -812,12 +825,14 @@
   $prov_enabled = $ldap_user_conf->provisionEnabled(LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE);
 
   if ($ldap_user_conf->provisionsDrupalAccountsFromLdap && in_array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, array_keys($ldap_user_conf->provisionsDrupalEvents))) {
-    $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
+    $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');    
     if ($ldap_user) {
       $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
       $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
     }
     $account = user_save($account, $user_edit, 'ldap_user');
+    
+	 
   }
 
 }
@@ -926,8 +941,9 @@
     if (ldap_user_is_ldap_associated($account, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER)) {
       $ldap_user = ldap_servers_get_user_ldap_data($account->name, $ldap_user_conf->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
       $ldap_server = ldap_servers_get_servers($ldap_user_conf->drupalAcctProvisionServer, NULL, TRUE);
-      $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));
+      $ldap_user_conf->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER));            
       $account = user_save($account, $user_edit, 'ldap_user');
+          
     }
   }
 
