<?php
	/**
	* View and manipulate account records using LDAP
	* @author Joseph Engo <jengo@phpgroupware.org>
	* @author Lars Kneschke <lkneschke@phpgw.de>
	* @author Bettina Gille <ceb@phpgroupware.org>
	* @author Philipp Kamps <pkamps@probusiness.de>
	* @copyright Copyright (C) 2000-2002 Joseph Engo, Lars Kneschke
	* @copyright Copyright (C) 2003 Lars Kneschke, Bettina Gille
	* @copyright Portions Copyright (C) 2000-2004 Free Software Foundation, Inc. http://www.fsf.org/
	* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
	* @package phpgwapi
	* @subpackage accounts
	* @version $Id: class.accounts_ldap.inc.php,v 1.101.2.13.2.33 2004/07/29 07:35:24 fipsfuchs Exp $
	*/

	/**
	* View and manipulate account records using LDAP
	*
	* @package phpgwapi
	* @subpackage accounts
	* @ignore
	*/
	class accounts extends accounts_
	{
		var $ds;
		var $user_context  = '';
		var $group_context = '';

		/**
		* @internal Be Carefull to correctly set the following! 
		* @internal Need to be set in the setup system! (Caeies)
		*/
		var $rdn_account = 'uid';
		var $rdn_group   = 'cn';
		/**
		* @internal Be secure by default (Caeies)
		*/
		var $fallback_homedirectory = '/dev/null';
		/**
		* @internal Nothing allowed (Caeies)
		*/
		var $fallback_loginshell    = '/bin/false';
		var $fallback_apps          = array('addressbook',
		                                    'calendar',
		                                    'email',
		                                    'notes',
		                                    'todo'
					                              );

		function accounts($account_id = '', $account_type = '')
		{
			if($GLOBALS['phpgw_info']['server']['account_repository'] == 'ldap')
			{
				$this->ds = $GLOBALS['phpgw']->common->ldapConnect();
				$this->user_context  = $GLOBALS['phpgw_info']['server']['ldap_context'];
				$this->group_context = $GLOBALS['phpgw_info']['server']['ldap_group_context'];
			}
			$this->accounts_($account_id, $account_type);
		}

		function read_repository()
		{

			$acct_type = $this->get_type($this->account_id);

			/* search the dn for the given uid */
			if ( ($acct_type == 'g') && $this->group_context )
			{
				$sri = ldap_search($this->ds, $this->group_context, 'gidnumber='.$this->account_id);
			}
			else
			{
				$sri = ldap_search($this->ds, $this->user_context, 'uidnumber='.$this->account_id);
			}
			$allValues = ldap_get_entries($this->ds, $sri);

			/* Now dump it into the array; take first entry found */
			if($acct_type == 'g')
			{
				$this->data['account_id']           = $allValues[0]['gidnumber'][0];
				$this->data['account_lid']          = $allValues[0]['cn'][0];
				$this->data['account_firstname']    = utf8_decode($allValues[0]['cn'][0]);
				$this->data['account_lastname']     = 'Group';
				$this->data['type']                 = 'g';
			}
			else
			{
				$this->data['account_id']           = $allValues[0]['uidnumber'][0];
				$this->data['account_lid']          = $allValues[0]['uid'][0];
				$this->data['account_firstname']    = utf8_decode($allValues[0]['givenname'][0]);
				$this->data['account_lastname']     = utf8_decode($allValues[0]['sn'][0]);
				$this->data['type']                 = 'u';
			}
			$this->data['account_dn']             = $allValues[0]['dn'];
			$this->data['fullname']               = utf8_decode($allValues[0]['cn'][0]);

			$this->data['homedirectory']          = $allValues[0]['homedirectory'][0];
			$this->data['loginshell']             = $allValues[0]['loginshell'][0];

			$this->data['account_expires']        = $allValues[0]['phpgwaccountexpires'][0];
			$this->data['expires']                = $allValues[0]['phpgwaccountexpires'][0];
			//$allValues[0]['description'][0] is not needed
			if ($allValues[0]['phpgwaccountstatus'][0] == 'A')
			{
				$this->data['status'] = 'A';
			}
			
			if ( $allValues[0]['phpgwcontactid'][0] )
			{
				$this->data['person_id']  = $allValues[0]['phpgwcontactid'][0];
				$this->account_contact_id	= $this->data['person_id'];
			}
			return $this->data;
		}

		function save_repository()
		{
			$acct_type = $this->get_type($this->account_id);

			if ($acct_type == 'g')
			{
				return $this->create_group($this->data, '');
			}
			else
			{
				return $this->create_account($this->data, '');			
			}
		}
		
		/**
		* Delete an account or group
		*
		* @param integer $id Id of group/account to delete
		* @return boolean True on success otherwise false
		*/
		function delete($id = '')
		{
			$id = get_account_id($id);
			$type = $this->get_type($id);

			if ($type == 'g')
			{
				$sri = ldap_search($this->ds, $this->group_context, '(&(objectclass=phpgwgroup)(gidnumber='.$id.'))');
				$allValues = ldap_get_entries($this->ds, $sri);
			}
			else
			{
				$sri = ldap_search($this->ds, $this->user_context, '(&(objectclass=phpgwaccount)(uidnumber='.$id.'))');
				$allValues = ldap_get_entries($this->ds, $sri);
			}
				
			if ($allValues[0]['dn'])
			{
				$del = ldap_delete($this->ds, $allValues[0]['dn']);
				return $del; 
			}
			else
			{
				return false;
			}
		}

		/**
		* Read all accounts or groups
		*
		* @param string $_type Type of list 'accounts', 'groups' or 'both'
		* @param integer $start Start position
		* @param string $sort 'ASC'ending or 'DESC'ending sort order
		* @param string $order Order by 'account_' Field. Defaults to 'account_lid'
		* @param string $query LDAP query
		* @param integer $offset Offset from start position (-1 == no limit)
		* @return array|boolean List with all accounts|groups or false
		*/
		function get_list($_type='both', $start = -1,$sort = '', $order = '', $query = '', $offset = -1)
		{
			$query = strtolower($query);
			if ($offset != -1)
			{
				$limitclause = '';
			}
			elseif ($start != -1 && $offset == -1)
			{
				$limitclause = '';
			}

			if (! $sort)
			{
				$sort = '';//"desc";
			}

			if ($_type == 'accounts')
			{
				$listentries = $this->get_accountList($query);
			}
			elseif ($_type == 'groups')
			{
				$listentries = $this->get_groupList($query);
			}
			else
			{
				$listentries = array_merge($this->get_accountList($query), $this->get_groupList($query));
			}

			// sort the array
			$arrayFunctions = CreateObject('phpgwapi.arrayfunctions');
			if(empty($order))
			{
				$order = 'account_lid';
			}
			$sortedlist = $arrayFunctions->arfsort($listentries,array($order),$sort);
			$this->total = count($listentries); // this shouldn't be an obejct var for one account/group whatever

			if (is_array($sortedlist))
			{
				reset($sortedlist);
				if($start != -1 && $offset != -1)
				{
					return array_slice($sortedlist, $start, $offset);
				}
				elseif($start != -1)
				{
					return array_slice($sortedlist, $start, $GLOBALS['phpgw_info']['user']['preferences']['common']['maxmatchs']);
				}
				else
				{
					return $sortedlist;
				}
			}
			return false;
		}
		
		/**
		* Read accounts into an array
		*
		* @param string $query LDAP query
		* @return array Array with account fields 'acount_id', 'account_lid', 'account_type', 'account_firstname', 'account_lastname', 'account_status'
		*/
		function get_accountList($query)
		{
			if(empty($query) || $query == "*")
			{
				$filter = '(&(uidnumber=*)(objectclass=posixaccount))';
			}
			else
			{
				$filter = "(&(uidnumber=*)(objectclass=posixaccount)(|(uid=*$query*)(sn=*$query*)(cn=*$query*)(givenname=*$query*)))";
			}
			$sri = ldap_search($this->ds, $this->user_context, $filter);
			$allValues = ldap_get_entries($this->ds, $sri);
			while (list($null,$allVals) = @each($allValues))
			{
				settype($allVals,'array');
				$test = @$allVals['uid'][0];
				if (!$GLOBALS['phpgw_info']['server']['global_denied_users'][$test] && $allVals['uid'][0])
				{
					$accounts[] = Array(
						'account_id'        => $allVals['uidnumber'][0],
						'account_lid'       => $allVals['uid'][0],
						'account_type'      => 'u',
						'account_firstname' => utf8_decode($allVals['givenname'][0]),
						'account_lastname'  => utf8_decode($allVals['sn'][0]),
						'account_status'    => $allVals['phpgwaccountstatus'][0]
					);
				}
			}
			return $accounts;
		}

		/**
		* Reads groups into an array
		*
		* @param string $query LDAP query
		* @return array Array with group fields 'account_id', 'account_lid', 'account_type'
		*/
		function get_groupList($query)
		{
			if(empty($query) || $query == "*")
			{
				$filter = '(&(gidnumber=*)(objectclass=posixgroup))';
			}
			else
			{
				$filter = "(&(gidnumber=*)(objectclass=posixgroup)(|(uid=*$query*)(sn=*$query*)(cn=*$query*)(givenname=*$query*)))";
			}
			$sri = ldap_search($this->ds, $this->group_context, $filter);
			$allValues = ldap_get_entries($this->ds, $sri);
			while (list($null,$allVals) = @each($allValues))
			{
				settype($allVals,'array');
				$test = $allVals['cn'][0];
				if (!$GLOBALS['phpgw_info']['server']['global_denied_groups'][$test] && $allVals['cn'][0])
				{
					$groups[] = Array(
						'account_id'        => $allVals['gidnumber'][0],
						'account_lid'       => $allVals['cn'][0],
						'account_type'      => 'g'
					);
				}
			}
			return $groups;
		}

		/**
		* Convert id to the corresponding account (or group) name
		*
		* @param string $account_lid Account name or group name for which you want the id
		* @return integer|boolean Id of the account/group when found otherwise false
		*/
		function name2id($account_lid)
		{
			static $name_list;

			if(@isset($name_list[$account_lid]) && $name_list[$account_lid])
			{
				return $name_list[$account_lid];
			}

			$id  = $this->groupName2id($account_lid);
			$uid = $this->accountName2id($account_lid);
			
			if ($uid)
			{
				$id = $uid;
			}
			return $id;
		}

		/**
		* Convert group name to the corresponding id
		*
		* @param string $name Group name for which you want the id
		* @return integer|boolean Id of the group when found otherwise false
		*/
		function groupName2id($name)
		{
			$sri = ldap_search($this->ds, $this->group_context, '(&(cn='. $name .')(objectclass=phpgwgroup))');
			$allValues = ldap_get_entries($this->ds, $sri);
			
			if (@$allValues[0]['gidnumber'][0])
			{
				return intval($allValues[0]['gidnumber'][0]);
			}
			else
			{
				return false;
			}
		}
		
		/**
		* Convert account name into corresponding id
		*
		* @param string $name Account name
		* @return integer|boolean Id of the account when found otherwise false
		*/
		function accountName2id($name)
		{
			$sri = ldap_search($this->ds, $this->user_context, '(&(uid='. $name .')(objectclass=phpgwaccount))');
			$allValues = ldap_get_entries($this->ds, $sri);

			if (@$allValues[0]['uidnumber'][0])
			{
				return intval($allValues[0]['uidnumber'][0]);
			}
			else
			{
				return false;
			}
		}

		/**
		* Convert an id into its corresponding account or group name
		*
		* @param integer $id Account or group id
		* @return string|boolean Name of the account or the group when found othwerwise false
		*/
		function id2name($id)
		{
			static $id_list;
			if(isset($id_list[$id]))
			{
				return $id_list[$id];
			}

			$type = $this->get_type($id);
			
			if ($type == 'g')
			{
				$group = $this->group_exists($id);
				$name = $group['cn'][0];
			}
			elseif ($type == 'u')
			{
				$account = $this->account_exists($id);
				$name = $account['uid'][0];
			}
			else
			{
				return false;
			}
			return $name;
		}

 		function search_person($person_id)
		{
			static $person_list;
			if(isset($person_list[$person_id]))
			{
				return intval($person_list[$person_id]);
			}

			$allValues = array();
			// Groups are person? are you sure?
			$sri = ldap_search($this->ds, $this->group_context, "(&(person_id=$person_id)(phpgwaccounttype=g))");
			$allValues = ldap_get_entries($this->ds, $sri);

			if (@$allValues[0]['gidnumber'][0])
			{
				$person_list[$person_id] = intval($allValues[0]['gidnumber'][0]);
				return $person_list[$person_id];
			}

			$allValues = array();
			$sri = ldap_search($this->ds, $this->user_context, "(&(person_id=$person_id)(phpgwaccounttype=u))");
			$allValues = ldap_get_entries($this->ds, $sri);

			if (@$allValues[0]['uidnumber'][0])
			{
				$id_list[$person_id] = intval($allValues[0]['uidnumber'][0]);
				return $person_list[$person_id];
			}

			return $person_list[$person_id];
		}

		/**
		* Get type (account or group) for an id
		*
		* @param integer $id Account or group id
		* @return string|boolean 'u' : account (user); 'g' : group; false for none existing id
		*/
		function get_type($id = '') // get_type() without an id - what do you expect me to return!?
		{
			if ($this->account_exists($id))
			{
				$type = 'u';
			}
			if ($this->group_exists($id))
			{
				if ($type == 'u')
				{
					die('account/group id ('.$id.')conflict - bad luck');
				}
				else
				{
					$type = 'g';
				}
			}
			if ($type)
			{
				return $type;
			}
			else
			{
				return false;
			}
		}

		

		/**
		* Get new id for an account/group
		*
		* @param string $type 'u' : account (user); 'g' : group
		* @return integer|boolean New id for an account/group or false
		*/
		function get_nextid($type = 'u')
		{
			if ($type == 'u')
			{
				return $this->get_nextAccountID();
			}
			elseif ($type == 'g')
			{
				return $this->get_nextGroupID();
			}
			else
			{
				return false;
			}
		}
		
		/**
		* Get new id for an account
		*
		* @return integer|boolean New id for an account or false
		*/
		function get_nextAccountID()
		{
			$filter = '(|(objectclass=posixaccount)(objectclass=phpgwaccount))';
			$result = ldap_search($this->ds, $this->user_context, $filter, array('uidnumber'));
			$allValues = ldap_get_entries($this->ds, $result);

			// parse all LDAP uidnumbers in a single array '$IDs'
			while (list($key,$val) = each($allValues))
			{
				if (is_int($key))
				{
					$IDs[] = $allValues[$key]['uidnumber'][0];
				}
			}
			return $this->idTester($IDs, $GLOBALS['phpgw_info']['server']['account_min_id'], $GLOBALS['phpgw_info']['server']['account_max_id'] );
		}
		
		/**
		* Get new id for a group
		*
		* @return integer|boolean New id for a group or false
		*/
		function get_nextGroupID()
		{
			$filter = '(|(objectclass=posixgroup)(objectclass=phpgwgroup))';
			$result = ldap_search($this->ds, $this->group_context, $filter, array('gidnumber'));
			$allValues = ldap_get_entries($this->ds, $result);

			while (list($key,$val) = each($allValues))
			{
				if (is_int($key))
				{
					$IDs[] = $allValues[$key]['gidnumber'][0];
				}
			}
			return $this->idTester($IDs, $GLOBALS['phpgw_info']['server']['group_min_id'], $GLOBALS['phpgw_info']['server']['group_max_id'] );
		}

		/**
		* Test if group exists
		*
		* @param integer $id Group id
		* @param string $dn LDAP distinguised name
		* @return array|boolean Array with 'dn' infos or false
		*/
		function group_exists($id, $dn = '')
		{
			if ($id)
			{
				$result = ldap_search($this->ds, $this->group_context, '(&(gidnumber='.$id.')(objectclass=posixgroup))');
				$allValues = ldap_get_entries($this->ds, $result);
				if ($allValues[0]['dn'])
				{
					return $allValues[0];
				}
				else
				{
					return $this->dn_exists($dn);
				}
			}
			return false;
		}
		
		/**
		* Test if account exists
		*
		* @param integer $id Account id
		* @param string $dn LDAP distinguised name
		* @return array|boolean Array with 'dn' infos or false
		*/
		function account_exists($id, $dn = null)
		{
			if ($id)
			{
				$result = ldap_search($this->ds, $this->user_context, '(&(uidnumber='.$id.')(objectclass=posixaccount))');
				$allValues = ldap_get_entries($this->ds, $result);
				if ($allValues[0]['dn'])
				{
					return $allValues[0];
				}
			}
			if($dn)
			{
				return $this->dn_exists($dn);
			}
			return false;
		}

		/**
		* Test if contact exists
		*
		* @param integer $id Contact id
		* @param string $dn LDAP distinguised name
		* @return array|boolean Array with 'dn' infos or false
		*/
		function person_exists($id, $dn = '')
		{
			if ($id)
			{
				$result = ldap_search($this->ds, $this->user_context, 'phpgwcontact=' . $id);
				$allValues = ldap_get_entries($this->ds, $result);
				if ($allValues[0]['dn'])
				{
					return $allValues[0];
				}
				else
				{
					return $this->dn_exists($dn);
				}
			}
			return false;
		}

		
		/**
		* Test if the given dn exists
		*
		* @param string $dn LDAP distinguised name
		* @return array|boolean Array with 'dn', 'count' and attributes or false
		*/
		function dn_exists($dn)
		{
			if ($dn != '')
			{
				$result = @ldap_search($this->ds, $dn, 'objectclass=*');
				if ($result)
				{
					$allValues = ldap_get_entries($this->ds, $result);
					if ($allValues[0]['dn'])
					{
						return $allValues[0];
					}
				}
			}
			return false;
		}

		/**
		* Test if a group or an account exists
		*
		* @param integer $id Account or group id
		* @return integer|boolean 1 : account or group exist; 2 : account and group exist; 0/false nothing exist
		*/
		function exists($id)
		{
			if (!is_int($id) && $id != '')
			{
				$id = $this->name2id($id);
			}

			if ($id)
			{
				$return = 0;
				if ($this->account_exists($id))
				{
					$return++;
				}
				if ($this->group_exists($id));
				{
					$return++;
				}
				return $return;
			}
			else
			{
				return false;
			}
		}

		/**
		* Create account or group
		*
		* @param array $account_info Account/group information
		* @param string $default_prefs Unused
		* @return array|boolean Id of the newly created account or false 
		*/
		function create($account_info,$default_prefs = true)
		{
			if (empty($account_info['account_id']) || !$account_info['account_id'])
			{
				$account_info['account_id'] = $this->get_nextid($account_info['account_type']);
			}
			if ($account_info['account_type'] == 'u')
			{
				/* this line is not working
				$account_info['person_id'] = $this->save_contact_for_account($this->data);
				*/
				$this->create_account($account_info, $default_prefs);
			}
			elseif($account_info['account_type'] == 'g')
			{
				$this->create_group($account_info, $default_prefs);
			}
			else
			{
				return false;
			}
			
			if($account_info['account_id'] && is_object($GLOBALS['phpgw']->preferences) && $default_prefs)
			{
				$GLOBALS['phpgw']->preferences->create_defaults($account_info['account_id']);
			}
			return $account_info['account_id'];
		}

		/**
		* Create new account
		*
		* @param array $account_info Account information: account_id, account_expires, account_status, lastlogin, lastloginfrom, lastpasswd_change, account_firstname, account_lastname, account_passwd, homedirectory, ...
		* @param string $default_prefs Unused
		*/
		function create_account($account_info, $default_prefs)
		{
			$dn = $this->rdn_account .
			      '=' .
			      $this->get_leafName($account_info['account_firstname'], $account_info['account_lastname'], $account_info['account_lid']) .
			      ',' .
			      $this->user_context;
				
			// phpgw attributes
			$entry['objectclass'][]       = 'phpgwAccount';
			$entry['phpgwaccountid']      = $account_info['account_id'];
			$entry['phpgwaccountexpires'] = isset($account_info['account_expires'])  ? $account_info['account_expires'] : $account_info['expires'];
			if ($account_info['account_status'] || $account_info['status'])
			{
				$entry['phpgwaccountstatus'] = isset($account_info['account_status']) ? $account_info['account_status'] : $account_info['status'];
			}
			else
			{
				$entry['phpgwaccountstatus'] = 'I'; // 'I' for inactiv
			}
			if ($account_info['lastlogin'])
			{
				$entry['phpgwlastlogin'] = $account_info['lastlogin'];
			}
			if ($account_info['lastloginfrom'])
			{
				$entry['phpgwlastloginfrom'] = $account_info['lastloginfrom'];
			}
			if ($account_info['lastpasswd_change'])
			{
				$entry['phpgwlastpasswordchange'] = $account_info['lastpasswd_change'];
			}
				
			$structural_modification = false;
			if((int) $account_info['person_id'])
			{
				$entry['objectclass'][] = 'phpgwContact'; // shouldn't be structural
				$entry['phpgwcontactid'] = (int)$account_info['person_id'];
			}
			else
			{
				$entry['objectclass'][]       = 'account';
			}

			// additional attributes from the phpgw for groups
			$entry['objectclass'][]       = 'posixAccount';
			$entry['cn']                  = utf8_encode($this->get_fullname($account_info['account_firstname'], $account_info['account_lastname']));
			$entry['uidnumber']           = $account_info['account_id'];
			$entry['uid']                 = $account_info['account_lid'];
			$entry['description']         = utf8_encode(str_replace('*','',lang('phpgw-created account')));
			if ( $account_info['account_firstname'] )
			{
				$entry['givenname'] = utf8_encode($account_info['account_firstname']);
			}
			if ( $account_info['account_lastname'] )
			{
				$entry['sn'] = utf8_encode($account_info['account_lastname']);
			}
			else
			{
				$entry['sn'] = ' ';
			}
			if ($account_info['account_passwd'])
			{
				$entry['userpassword'] = $GLOBALS['phpgw']->common->encrypt_password($account_info['account_passwd']);
			}

			// Fields are must for LDAP - so we write them in any case
			$entry['homedirectory']       = $this->get_homedirectory($account_info['homedirectory'], $account_info['account_lid']);
			// In fact the loginshell is not Required (Must) it's only optional (May) in posixAccount (Caeies), so don't need it now
			//$entry['loginshell']          = $this->get_loginshell($account_info['loginshell']);

				
			// special gidnumber handling
			if ($GLOBALS['phpgw_info']['server']['ldap_group_id'])
			{
				$enty['gidnumber'] = $GLOBALS['phpgw_info']['server']['ldap_group_id'];
			}
			else
			{
				$entry['gidnumber']           = $account_info['account_id'];
			}

			$oldEntry = $this->account_exists($account_info['account_id'], $dn);
			
			if ($oldEntry) // found an existing entry in LDAP
			{
				if ($this->createMode == 'replace')
				{
					ldap_delete($this->ds, $oldEntry['dn']);
					$this->add_LDAP_Entry($dn, $entry);
				}
				elseif ($this->createMode == 'extend')
				{
					/* not yet implemented */
				}
				else  // createMode == 'modify'
				{
					while (list($key,$val) = each($oldEntry))
					{
						if (!is_int($key))
						{
							unset($oldEntry[$key]['count']);
							switch ($key)
							{
								case 'dn':
									if ($oldEntry['dn'] != $dn)  // new group name DN should renamed as well
									{
										$oldEntry['dn'] = $this->rename_LDAP_entry($oldEntry['dn'], $dn, $this->user_context);
										if (!$oldEntry)
										{
											die('ldap_rename FAILED: [' . ldap_errno($this->ds) . '] ' . ldap_error($this->ds));
										}											
									}
									break;
								
								case 'count':
								case 'cn':
								case 'description':
								case 'phpgwaccountid':
								case 'gidnumber':
								case 'phpgwaccountstatus':
								case 'phpgwaccountexpires':
								case 'uidnumber':
								case 'uid':
								case 'userpassword':
								case 'homedirectory':
								case 'loginshell':
								case 'givenname':
								case 'sn':
								case 'phpgwlastlogin':
								case 'phpgwlastloginfrom':
								case 'phpgwlastpasswordchange':
								case 'phpgwcontactid':
								case 'phpgwquota':
									break;
									
								case 'objectclass':
									if( !in_array('phpgwAccount', $oldEntry[$key]) && !in_array('phpgwContact', $oldEntry[$key]) )
									{
										$entry[$key] = $oldEntry[$key];
										array_push($entry[$key], 'phpgwAccount');
									}
									elseif((in_array('phpgwContact',$entry[$key]) && ! in_array('phpgwContact',$oldEntry[$key])))
									{
										$structural_modification = true;
									} 
									else
									{
										$entry[$key] = $oldEntry[$key];
									}
									break;
										
								default:
									$entry[$key] = $oldEntry[$key];
							}
						} 
					}

					//Caeies Bonification
					//When a structural object is modified you need to remove it then re add it ...
					//So You need to add to entry all the old stuff not modified in $entry .
					if ( $structural_modification )
					{
						for( $i = 0; $i < $oldEntry['count']; ++$i)
						{
							if ( !empty($oldEntry[$i]) && !(array_key_exists($oldEntry[$i],$entry)) ) 
							{
								if ( count($oldEntry[$oldEntry[$i]]) == 1 ) 
								{
									$entry[$oldEntry[$i]] = $oldEntry[$oldEntry[$i]][0];
								}
								else
								{
									$entry[$oldEntry[$i]] = $oldEntry[$oldEntry[$i]];
								}
							}
						}
						ldap_delete($this->ds, $oldEntry['dn']);
						$this->add_LDAP_Entry($oldEntry['dn'], $entry);
					}
					else
					{
						$this->modify_LDAP_Entry($oldEntry['dn'], $entry);
					}
					$dn = $oldEntry['dn'];
				}
			}
			else // entry not yet in LDAP
			{
				$this->add_LDAP_Entry($dn, $entry);
			}				
		}
		
		/**
		* Create new group
		*
		* @param array $account_info Group information: account_id, account_lid, ...
		* @param string $default_prefs Unused
		*/
		function create_group($account_info, $default_prefs)
		{
			$dn = $this->rdn_group . '=' . $account_info['account_lid'] . ',' . $this->group_context;

			// phpgw needed attributes
			
			$entry['objectclass'][]  = 'phpgwGroup';
			$entry['phpgwgroupID']   = $account_info['account_id'];
			$entry['gidnumber']      = $account_info['account_id'];				

			// additional attributes from the phpgw for groups
			$entry['objectclass'][]  = 'posixGroup';
			$entry['cn']             = $account_info['account_lid'];
			$entry['description']    = utf8_encode(str_replace('*', '', lang('phpgw-created group')));
			$entry['memberuid']      = $this->get_memberUIDs($account_info['account_id']);
			if (!$entry['memberuid'])
			{
				unset ($entry['memberuid']);
			}

			$oldEntry = $this->group_exists($account_info['account_id'], $dn);

			if ($oldEntry) // found an existing entry in LDAP
			{
				if ($this->createMode == 'replace')
				{
					ldap_delete($this->ds, $oldEntry['dn']);
					$this->add_LDAP_Entry($dn, $entry);
				}
				elseif ($this->createMode == 'extend')
				{
					/* not yet implemented */
				}
				else  // createMode == 'modify'
				{
					while (list($key,$val) = each($oldEntry))
					{
						if (!is_int($key))
						{
							unset($oldEntry[$key]['count']);
							switch ($key)
							{
								case 'dn':
									if ($oldEntry['dn'] != $dn)  // new group name DN should renamed as well
									{
										$oldEntry['dn'] = $this->rename_LDAP_entry($oldEntry['dn'], $dn, $this->group_context);
										if (!$oldEntry)
										{
                                                                                      die('ldap_rename FAILED: [' . ldap_errno($this->ds) . '] ' . ldap_error($this->ds));
										}											
									}
									break;
								case 'count':
								case 'cn':
								case 'description':
								case 'phpgwgroupid':
								case 'gidnumber':
								case 'memberuid':
									break;
									
								case 'objectclass':
									if( !in_array('phpgwGroup', $oldEntry[$key]) && !in_array('phpgwgroup', $oldEntry[$key]) )
									{
										$entry[$key] = $oldEntry[$key];
										array_push($entry[$key], 'phpgwGroup');
									}
									else
									{
											$entry[$key] = $oldEntry[$key];
									}
									break;
										
								default:
									$entry[$key] = $oldEntry[$key];
							}
						} 
					}
					$this->modify_LDAP_Entry($oldEntry['dn'], $entry);
				}
			}
			else // entry not yet in LDAP
			{
				$this->add_LDAP_Entry($dn, $entry);
			}				
		}
		
		/**
		* Add entry to LDAP
		*
		* @param string $dn The distinguised name which should be added
		* @param array $entry Array of all LDAP attributes to be added
		* @return boolean True when successful otherwise false (die for now)
		*/
		function add_LDAP_Entry($dn, $entry)
		{
			$success = ldap_add($this->ds, $dn, $entry);
			if (!$success)
			{
				echo 'ldap_add FAILED: [' . ldap_errno($this->ds) . '] ' . ldap_error($this->ds).'<br /><br />';
				echo "<strong>Adds: ".$dn."</strong><br />";
				echo "<pre>";
				print_r($entry);
				echo "</pre>";
				echo "<br />";
				die('');
			}
			else
			{
				return true;
			}
		}
		
		/**
		* Modify an entry in LDAP
		*
		* @param string $dn the distinguised name which should be modified
		* @param array $entry Array of all LDAP attributes which are going to be modified
		* @return boolean True on success otherwise false (die for now)
		*/
		function modify_LDAP_Entry($dn, $entry)
		{
			$success = ldap_modify($this->ds, $dn, $entry);
			if (!$success)
			{
				echo 'ldap_modified FAILED: [' . ldap_errno($this->ds) . '] ' . ldap_error($this->ds).'<br /><br />';
				echo "<strong>Modifies: ".$dn."</strong><br />";
				echo "<pre>";
				print_r($entry);
				echo "</pre>";
				echo "<br />";
				die('');
			}
			else
			{
				return true;
			}
		}

		/**
		* Rename LDAP entry
		*
		* @param string $oldDN Old distinguised name that should be renamed
		* @param string $newDN New distinguised name to which the old one should be renamed
		* @param string $baseDN Base distinguised name for the rename operation
		* @return string|boolean The new distinguised name on success otherwise false
		*/
		function rename_LDAP_Entry($oldDN, $newDN, $baseDN)
		{
			$newDN_array = (ldap_explode_dn($newDN, 0));
			$oldDN_array = (ldap_explode_dn($oldDN, 0));

			unset($newDN_array['count']);
			unset($oldDN_array['count']);
										
			$newDN_RDN = $newDN_array[0];
			$oldDN_RDN = array_shift($oldDN_array);
			$oldDN_base  = implode(',', $oldDN_array);
			if (($newDN_RDN != $oldDN_RDN) && ($oldDN_base == $baseDN))
			{
				$success = ldap_rename ( $this->ds, $oldDN, $newDN_RDN, $baseDN, false);
				if ($success)
				{
					return $newDN;
				}
				else
				{
					return false;
				}
			}
		}
			

		/**
		* Create a non existing but authorized user 
		*
		* @param string $accountname User name
		* @param string $passwd User password
		* @param boolean $default_prefs Default preferences for this new user
		* @param boolean $default_acls Acls (modules) for this new user
		* @param integer $expiredate Expire date of this account. '-1' for never. Defaults to 'in 30 days'
		* @param char $account_status Status for new user. 'A' for active user.
		* @return integer Account id 
		*/
		function auto_add($accountname, $passwd, $default_prefs = false, $default_acls = false, $expiredate = 0, $account_status = 'A')
		{
			if ($expiredate)
			{
				$expires = mktime(2,0,0,date('n',$expiredate), intval(date('d',$expiredate)), date('Y',$expiredate));
			}
			else
			{
				if($GLOBALS['phpgw_info']['server']['auto_create_expire'])
				{
					if($GLOBALS['phpgw_info']['server']['auto_create_expire'] == 'never')
					{
						$expires = -1;
					}
					else
					{
						$expiredate = time() + $GLOBALS['phpgw_info']['server']['auto_create_expire'];
						$expires   = mktime(2,0,0,date('n',$expiredate), intval(date('d',$expiredate)), date('Y',$expiredate));
					}
				}
				else
				{
					/* expire in 30 days by default */
					$expiredate = time() + ( ( 60 * 60 ) * (30 * 24) );
					$expires   = mktime(2,0,0,date('n',$expiredate), intval(date('d',$expiredate)), date('Y',$expiredate));
				}
			}

			$acct_info = array(
				'account_id'        => $this->get_nextid('u'), //'u' for account
				'account_lid'       => $accountname,
				'account_passwd'    => $passwd,
				'account_firstname' => 'New',
				'account_lastname'  => 'User',
				'account_status'    => $account_status,
				'account_expires'   => $expires
			);
			$this->create_account($acct_info, $default_prefs);
			$accountid = $this->accountName2id($accountname);

			$this->db->transaction_begin();
			if ($default_acls == false)
			{
				$apps = $this->fallback_apps;
				
				$default_group_lid = $GLOBALS['phpgw_info']['server']['default_group_lid'];
				$default_group_id  = $this->groupName2id($default_group_lid);
				$defaultgroupid    = $default_group_id ? $default_group_id : $this->groupName2id('Default');
				if($defaultgroupid)
				{
					$this->db->query("INSERT INTO phpgw_acl (acl_appname, acl_location, acl_account, acl_rights) values('phpgw_group', "
						. $defaultgroupid . ', ' . $accountid . ', 1)',__LINE__,__FILE__);
				}
				$this->db->query("INSERT INTO phpgw_acl (acl_appname, acl_location, acl_account, acl_rights)values('preferences', 'changepassword', ".$accountid.", 1)",__LINE__,__FILE__);
				@reset($apps);
				while(list($key,$app) = each($apps))
				{
					$this->db->query("INSERT INTO phpgw_acl (acl_appname, acl_location, acl_account, acl_rights) VALUES ('".$app."', 'run', ".$accountid.", 1)",__LINE__,__FILE__);
				}
			}
			$this->db->transaction_commit();
			return $accountid;
		}

		function get_account_name($accountid,&$lid,&$fname,&$lname)
		{
			static $account_name;
			
			$account_id = get_account_id($accountid);
			if(isset($account_name[$account_id]))
			{
				$lid = $account_name[$account_id]['lid'];
				$fname = $account_name[$account_id]['fname'];
				$lname = $account_name[$account_id]['lname'];
				return;
			}
			$acct_type = $this->get_type($account_id);

			/* search the dn for the given uid */
			if ( ($acct_type == 'g') && $this->group_context )
			{
				$sri = ldap_search($this->ds, $this->group_context, 'gidnumber='.$account_id);
			}
			else
			{
				$sri = ldap_search($this->ds, $this->user_context, 'uidnumber='.$account_id);
			}
			$allValues = ldap_get_entries($this->ds, $sri);

			if($acct_type =='g')
			{
				$account_name[$account_id]['lid']   = $allValues[0]['cn'][0];
				$account_name[$account_id]['fname'] = utf8_decode($allValues[0]['cn'][0]);
				$account_name[$account_id]['lname'] = 'Group';
			}
			else
			{
				$account_name[$account_id]['lid']   = $allValues[0]['uid'][0];
				$account_name[$account_id]['fname'] = utf8_decode($allValues[0]['givenname'][0]);
				$account_name[$account_id]['lname'] = utf8_decode($allValues[0]['sn'][0]);
			}
			$lid = $account_name[$account_id]['lid'];
			$fname = $account_name[$account_id]['fname'];
			$lname = $account_name[$account_id]['lname'];
			return;
		}

		function get_account_data($account_id)
		{
			$this->account_id = $account_id;
			$this->read_repository();

			$data[$this->data['account_id']]['lid']       = $this->data['account_lid'];
			$data[$this->data['account_id']]['firstname'] = $this->data['account_firstname'];
			$data[$this->data['account_id']]['lastname']  = $this->data['account_lastname'];
			$data[$this->data['account_id']]['fullname']  = $this->data['fullname'];
			$data[$this->data['account_id']]['type']      = $this->data['type'];
			$data[$this->data['account_id']]['person_id'] = $this->data['person_id'];
			return $data;
		}

		/**
		* Get the DN for the given account id 
		*
		* @param interger $id Account id
		* @return string|boolean Distinguised name or false
		*/
		function getDNforID($id = '')
		{
			return $this->getDNforAccountID($id); 
		}

		/**
		* Get the DN for the account id 
		*
		* @param integer $_accountid Account id
		* @return string|boolean Distinguised name or false
		*/
		function getDNforAccountID($id = '')
		{
			$_account_id = get_account_id($id);

			$sri = ldap_search($this->ds, $this->user_context, 'uidnumber='.$id, array('dn'));
			$allValues = ldap_get_entries($this->ds, $sri);
			if ($allValues[0]['dn'])
			{
				return $allValues[0]['dn'];
			}
			else
			{
				return false;
			}
		}

		function get_account_with_contact()
		{
			$sri = ldap_search($this->ds, $this->user_context, "(&(phpgwaccounttype=u)(phpgwpersonid=*))", array('uidnumber', 'phpgwpersonid'));
			$allValues = ldap_get_entries($this->ds, $sri);
			if(is_array($allValues))
			{
				$count = intval($allValues['count']);
				for($i=0;$i<$count; $i++)
				{
					$value = &$allValue[$i];
					$accounts[$value['uidnumber'][0]] = $value['phpgwpersonid'][0];
				}
			}
			
			return $accounts;
		}

		function get_account_without_contact()
		{
			$sri = ldap_search($this->ds, $this->user_context, "(&(phpgwaccounttype=u)(!(phpgwpersonid=*)))", array('uidnumber'));
			$allValues = ldap_get_entries($this->ds, $sri);
			if(is_array($allValues))
			{
				$count = intval($allValues['count']);
				for($i=0;$i<$count;$i++)//foreach(allValues as $value)
				{
					$value = &$allValue[$i];
					$accounts[] = $value['uidnumber'][0];
				}
			}
			return $accounts;
		}
		
		/**
		* Distinguised name leaf name generation
		*
		* @param string $first Firstname
		* @param string $last Lastname
		* @param string $login Login name
		* @return string Generated name of leaf of the distinguised name
		*/
		function get_leafName($first, $last, $login)
		{
			//if ($first != '' && $last == '')
			{
				$return = $login;
			}
			//else
			{
				//$return = $first.' '.$last;  
			}
			return $return;
		}
		
		/**
		* Full name generation
		*
		* @param string $first Firstname
		* @param string $last Lastname
		* @return string Fullname
		*/
		function get_fullname($first, $last)
		{
			return $first.' '.$last;
		}
		
		/**
		* Test an array with ids for a free id in respect to a min and max id
		*
		* @param array $IDs Array with existing id's
		* @param integer $min Minimum for id number
		* @param integer $max Maximum for id number 
		* @return integer New id that can be used
		*/
		function idTester($IDs, $min = 1, $max = 0)
		{
		
			$interval = intval($max) - intval($min);
			$base = intval($min) ? intval($min) : 1;
			if ($interval > 0)
			{
				$pointer = $start = $IDs[count($IDs)-1]%$interval;
			}
			else
			{
				$pointer = $IDs[count($IDs)-1];
			}
			if (is_array($IDs))
			{
				do
				{
					if ($interval > 0)
					{
						$pointer = ($pointer+1)%$interval;
						if ($pointer == $start)
						{
							die('no free id found');
						}
					}
					else
					{
						$pointer++;
					}
				}
				while (in_array($base + $pointer, $IDs) && $pointer != $start);
			}
			else
			{
				$pointer = 0;
			}
			return $base + $pointer;
		}
		
		function get_homedirectory($newValue, $login)
		{
			if ($newValue != '' && $newValue != $GLOBALS['phpgw_info']['server']['ldap_account_home'])
			{
				$return = $newValue;
			}
			else
			{
				if ($GLOBALS['phpgw_info']['server']['ldap_account_home'] != '')
				{
					$return = $GLOBALS['phpgw_info']['server']['ldap_account_home'].SEP.$login;
				}
				else
				{
					$return = $this->fallback_homedirectory;
				}
			}
			return $return;
		}
		
		function get_loginshell($newValue)
		{
			if ($newValue != '')
			{
				$return = $newValue;
			}
			else
			{
				if ($GLOBALS['phpgw_info']['server']['ldap_account_shell'] != '')
				{
					$return = $GLOBALS['phpgw_info']['server']['ldap_account_shell'];
				}
				else
				{
					$return = $this->fallback_loginshell;
				}
			}
			return $return;
		}
		
		function get_memberUIDs($account_id = '')
		{
			if ( !empty($account_id) )
			{
   			$members = $this->member($account_id);
			}
			else
			{
   			$members = $this->member($this->data['account_id']);
			}
			$return = array();
			for ($i=0; $i<count($members); $i++)
			{
				$member = $this->id2name($members[$i]['account_id']);
				// function $this->member returns duplicated entries and empty entries :-(
				if (!in_array($member, $return) && $member != '') 
				{
					$return[] = $member;
				}
			}
			if (count($return))
			{
				return $return;
			}
			else
			{
				return false;
			}
		}

		/**
		* Add an account to a group entry by adding the account name to the memberuid attribute
		*
		* @param integer $accountID Account id
		* @param integer $groupID Group id
		* @return boolean True on success otherwise false
		*/
		function add_account2Group($accountID, $groupID)
		{
			if ($accountID && $groupID)
			{
				$groupEntry = $this->group_exists($groupID);
				$memberUID = $this->id2name($accountID);
				if ($groupEntry && $memberUID)
				{
					if (!is_array($groupEntry['memberuid']) || !in_array($memberUID, $groupEntry['memberuid']))
					{
						$entry['memberuid'][] = $memberUID;
						return ldap_mod_add($this->ds, $groupEntry['dn'], $entry); 
					}
				}
			}
			return false;
		}
		
		/**
		* Delete an account for a group entry by removing the account name from the memberuid attribute
		*
		* @param integer $accountID Account id
		* @param integer $groupID Group id
		* @return boolean True on success otherwise false
		*/
		function delete_account4Group($accountID, $groupID)
		{
			if ($accountID && $groupID)
			{
				$groupEntry = $this->group_exists($groupID);
				$memberUID = $this->id2name($accountID);
				if ($groupEntry && $memberUID)
				{
					if (is_array($groupEntry['memberuid']))
					{
						for ($i=0; $i < count($groupEntry['memberuid']); $i++)
						{
							if ($groupEntry['memberuid'][$i] == $memberUID)
							{
								$entry['memberuid'][] = $memberUID;
								return ldap_mod_del($this->ds, $groupEntry['dn'], $entry);
							}
						}
					}
				}
			}
			return false;
		}
	}
?>
