<?php
  /**************************************************************************\
  * eGroupWare API - Accounts manager for LDAP                               *
  * This file written by Miles Lott <milosch@groupwhere.org>                 *
  * View and manipulate contact records using LDAP                           *
  * ------------------------------------------------------------------------ *
  * This library is part of the eGroupWare API                               *
  * http://www.egroupware.org/api                                            *
  * ------------------------------------------------------------------------ *
  * This library is free software; you can redistribute it and/or modify it  *
  * under the terms of the GNU Lesser General Public License as published by *
  * the Free Software Foundation; either version 2.1 of the License,         *
  * or any later version.                                                    *
  * This library is distributed in the hope that it will be useful, but      *
  * WITHOUT ANY WARRANTY; without even the implied warranty of               *
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
  * See the GNU Lesser General Public License for more details.              *
  * You should have received a copy of the GNU Lesser General Public License *
  * along with this library; if not, write to the Free Software Foundation,  *
  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA            *
  \**************************************************************************/

  /* $Id: class.contacts_ldap.inc.php 21449 2006-04-25 15:02:50Z ralfbecker $ */

	/*!
	 @class contacts
	 @abstract Contact List System
	 @discussion Author: jengo/Milosch <br>
	 This class provides a contact database scheme. <br>
	 It attempts to be based on the vcard 2.1 standard, with mods as needed to make for more reasonable sql storage. <br>
	 The LDAP schema used here may require installation of schema files available in the phpgwapi/doc/ldap dir.
	 Please see the README file there.
	 Syntax: CreateObject('phpgwapi.contacts'); <br>
	 Example1: $contacts = CreateObject('phpgwapi.contacts');
	*/
	class contacts_
	{
		var $db = '';
		var $ldap = '';
		var $nextid = '';
		var $std_table = '';
		var $ext_table = 'egw_addressbook_extra';

		var $account_id;
		var $adr_types;
		var $total_records;
		var $grants;

		/* The left side are the array elements used throughout phpgw, right side are the ldap attributes */
		var $stock_contact_fields = array(
			'fn'                  => 'cn',
			'n_given'             => 'givenname',
			'n_family'            => 'sn',
			'n_middle'            => 'phpgwmiddlename',
			'n_prefix'            => 'phpgwprefix',
			'n_suffix'            => 'phpgwsuffix',
			'sound'               => 'phpgwaudio',
			'bday'                => 'phpgwbirthday',
			'note'                => 'description',
			'tz'                  => 'phpgwtz',
			'geo'                 => 'phpgwgeo',
			'url'                 => 'phpgwurl',
			'pubkey'              => 'phpgwpublickey',

			'org_name'            => 'o',
			'org_unit'            => 'ou',
			'title'               => 'title',

			'adr_one_street'      => 'street',
			'adr_one_locality'    => 'l',
			'adr_one_region'      => 'st',
			'adr_one_postalcode'  => 'postalcode',
			'adr_one_countryname' => 'co',
			'adr_one_type'        => 'phpgwadronetype',
			'label'               => 'phpgwaddresslabel',

			'adr_two_street'      => 'phpgwadrtwostreet',
			'adr_two_locality'    => 'phpgwadrtwolocality',
			'adr_two_region'      => 'phpgwadrtworegion',
			'adr_two_postalcode'  => 'phpgwadrtwopostalcode',
			'adr_two_countryname' => 'phpgwadrtwocountryname',
			'adr_two_type'        => 'phpgwadrtwotype',

			'tel_work'            => 'telephonenumber',
			'tel_home'            => 'homephone',
			'tel_voice'           => 'phpgwvoicetelephonenumber',
			'tel_fax'             => 'facsimiletelephonenumber',
			'tel_msg'             => 'phpgwmsgtelephonenumber',
			'tel_cell'            => 'phpgwcelltelephonenumber',
			'tel_pager'           => 'phpgwpagertelephonenumber',
			'tel_bbs'             => 'phpgwbbstelephonenumber',
			'tel_modem'           => 'phpgwmodemtelephonenumber',
			'tel_car'             => 'phpgwmobiletelephonenumber',
			'tel_isdn'            => 'phpgwisdnphonenumber',
			'tel_video'           => 'phpgwvideophonenumber',
			'tel_prefer'          => 'phpgwpreferphone',
			'email'               => 'mail',
			'email_type'          => 'phpgwmailtype',
			'email_home'          => 'phpgwmailhome',
			'email_home_type'     => 'phpgwmailhometype'
		);

		var $non_contact_fields = array(
			'id'     => 'uidnumber',
			'lid'    => 'uid',
			'tid'    => 'phpgwcontacttypeid',
			'cat_id' => 'phpgwcontactcatid',
			'access' => 'phpgwcontactaccess',
			'owner'  => 'phpgwcontactowner'
		);

		/* Used to set preferphone field */
		var $tel_types = array(
			'work'  => 'work',
			'home'  => 'home',
			'voice' => 'voice',
			'fax'   => 'fax',
			'msg'   => 'msg',
			'cell'  => 'cell',
			'pager' => 'pager',
			'bbs'   => 'bbs',
			'modem' => 'modem',
			'car'   => 'car',
			'isdn'  => 'isdn',
			'video' => 'video'
		);

		/* Used to set mail_type fields */
		var $email_types = array(
			'INTERNET'   => 'INTERNET',
			'CompuServe' => 'CompuServe',
			'AOL'        => 'AOL',
			'Prodigy'    => 'Prodigy',
			'eWorld'     => 'eWorld',
			'AppleLink'  => 'AppleLink',
			'AppleTalk'  => 'AppleTalk',
			'PowerShare' => 'PowerShare',
			'IBMMail'    => 'IBMMail',
			'ATTMail'    => 'ATTMail',
			'MCIMail'    => 'MCIMail',
			'X.400'      => 'X.400',
			'TLX'        => 'TLX'
		);

		function contacts_()
		{
			$this->db = clone($GLOBALS['egw']->db);
			$this->ldap = $GLOBALS['egw']->common->ldapConnect(
				$GLOBALS['egw_info']['server']['ldap_contact_host'],
				$GLOBALS['egw_info']['server']['ldap_contact_dn'],
				$GLOBALS['egw_info']['server']['ldap_contact_pw']
			);
			$this->account_id = $GLOBALS['egw_info']['user']['account_id'];
			$this->grants     = $GLOBALS['egw']->acl->get_grants('addressbook');

			/* Used to flag an address as being:
			   domestic OR  international(default)
			   parcel(default)
			   postal(default)
			   work(default) OR home
			*/
			$this->adr_types = array(
				'dom'    => lang('Domestic'),
				'intl'   => lang('International'),
				'parcel' => lang('Parcel'),
				'postal' => lang('Postal')
			);
		}

		/* send this the id and whatever fields you want to see */
		function read_single_entry($id,$fields='')
		{
			if(!$fields || empty($fields))
			{
				$fields = $this->stock_contact_fields;
			}
			list($stock_fields,$stock_fieldnames,$extra_fields) = $this->split_stock_and_extras($fields);

			if(count($stock_fieldnames))
			{
				$t_fields = ',' . implode(',',$stock_fieldnames);
				if($t_fields == ',')
				{
					unset($t_fields);
				}
			}

			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'uidnumber=' . (int)$id);
			$ldap_fields = ldap_get_entries($this->ldap, $sri);

			$return_fields[0]['id']     = $ldap_fields[0]['uidnumber'][0];
			$return_fields[0]['lid']    = $ldap_fields[0]['uid'][0];
			$return_fields[0]['tid']    = $ldap_fields[0]['phpgwcontacttypeid'][0];
			$return_fields[0]['owner']  = $ldap_fields[0]['phpgwcontactowner'][0];
			$return_fields[0]['access'] = $ldap_fields[0]['phpgwcontactaccess'][0];
			// create from the multiple cat_id's in ldap a comma-separated value as in sql
			unset($ldap_fields[0]['phpgwcontactcatid']['count']);
			$return_fields[0]['cat_id'] = implode(',',$ldap_fields[0]['phpgwcontactcatid']);
			$return_fields[0]['rights'] = (int)$this->grants[$return_fields[0]['owner']];
			if(@is_array($stock_fieldnames))
			{
				foreach($stock_fieldnames as $name => $value)
				{
					$return_fields[0][$name] = $GLOBALS['egw']->translation->convert(($ldap_fields[0][$value][0]),'utf-8');
				}
			}

			/* Setup address type fields */
			if($return_fields[0]['adr_one_type'])
			{
				$one_type = $return_fields[0]['adr_one_type'];
				foreach($this->adr_types as $name => $val)
				{
					if(strstr($one_type,$name))
					{
						$return_fields[0]['one_'.$name] = 'on';
					}
				}
			}
			if($return_fields[0]['adr_two_type'])
			{
				$two_type = $return_fields[0]['adr_two_type'];
				foreach($this->adr_types as $name => $val)
				{
					if (strstr($two_type,$name))
					{
						$return_fields[0]['two_'.$name] = 'on';
					}
				}
			}

			$this->db->query("SELECT contact_name,contact_value FROM $this->ext_table WHERE contact_id='"
				. (int)$id . "'",__LINE__,__FILE__);
			while($this->db->next_record())
			{
				if($extra_fields[$this->db->f('contact_name')])
				{
					$return_fields[0][$this->db->f('contact_name')] = $this->db->f('contact_value');
				}
			}
			return $return_fields;
		}

		function read_last_entry($fields = '')
		{
			if(!$fields || empty($fields))
			{
				$fields = $this->stock_contact_fields;
			}
			list($stock_fields,$stock_fieldnames,$extra_fields) = $this->split_stock_and_extras($fields);

			if(count($stock_fieldnames))
			{
				$t_fields = ',' . implode(',',$stock_fieldnames);
				if($t_fields == ',')
				{
					unset($t_fields);
				}
			}

			$id = $this->nextid;
			if($id == -1)
			{
				$id = 1;
			}

			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'uidnumber=' . (int)$id);
			$ldap_fields = ldap_get_entries($this->ldap, $sri);

			$return_fields[0]['id']     = $ldap_fields[0]['uidnumber'][0];
			$return_fields[0]['lid']    = $ldap_fields[0]['uid'][0];
			$return_fields[0]['tid']    = $ldap_fields[0]['phpgwcontacttypeid'][0];
			$return_fields[0]['owner']  = $ldap_fields[0]['phpgwcontactowner'][0];
			$return_fields[0]['access'] = $ldap_fields[0]['phpgwcontactaccess'][0];
			// create from the multiple cat_id's in ldap a comma-separated value as in sql
			unset($ldap_fields[0]['phpgwcontactcatid']['count']);
			$return_fields[0]['cat_id'] = implode(',',$ldap_fields[0]['phpgwcontactcatid']);
			$return_fields[0]['rights'] = (int)$this->grants[$return_fields[0]['owner']];

			if(@is_array($stock_fieldnames))
			{
				foreach($stock_fieldnames as $name => $value)
				{
					$return_fields[0][$name] = $GLOBALS['egw']->translation->convert(($ldap_fields[0][$value][0]),'utf-8');
				}
			}

			/* Setup address type fields */
			if($return_fields[0]['adr_one_type'])
			{
				$one_type = $return_fields[0]['adr_one_type'];
				foreach($this->adr_types as $name => $val)
				{
					if(strstr($one_type,$name))
					{
						$return_fields[0]['one_'.$name] = 'on';
					}
				}
			}
			if($return_fields[0]['adr_two_type'])
			{
				$two_type = $return_fields[0]['adr_two_type'];
				foreach($this->adr_types as $name => $val)
				{
					if (strstr($two_type,$name))
					{
						$return_fields[0]['two_'.$name] = 'on';
					}
				}
			}

			$this->db->query("SELECT contact_name,contact_value FROM $this->ext_table WHERE contact_id='" . (int)$id . "'",__LINE__,__FILE__);
			while($this->db->next_record())
			{
				if($extra_fields[$this->db->f('contact_name')])
				{
					$return_fields[0][$this->db->f('contact_name')] = $this->db->f('contact_value');
				}
			}
			return $return_fields;
		}

		/**
		 * Searches for contacts meating certain criteria and evtl. return only a range of them
		 *
		 * @param int $start=0 starting number of the range, if $limit != 0
		 * @param int $limit=0 max. number of entries to return, 0=all
		 * @param array $fields=null fields to return or null for all stock fields
		 * @param string $query='' search pattern or '' for none
		 * @param string $filter='' filters with syntax like <name>=<value>,<name2>=<value2> OR <name3>=<value3>,<name4>=!'' for not empty
		 * @param string $sort='' sorting: ASC or DESC
		 * @param string $order='' column to order, default ('') n_family,n_given,email ASC
		 * @param int $lastmod=-1 return only values modified after given timestamp, default (-1) return all
		 * @param string $cquery='' return only entries starting with given character, default ('') all
		 * @return array of contacts
		 */
		function read($start=0,$limit=0,$fields=null,$query='',$filter='',$sort='',$order='', $lastmod=-1,$cquery='')
		{
			if (!$limit)
			{
				$limit = -1;	// db::query uses -1 for all
			}
			elseif (!$start)
			{
				$start  = 0;
			}
			if(!$filter) { $filter = 'tid=n'; }

			if(!$fields || empty($fields))
			{
				$fields = $this->stock_contact_fields;
			}
			$DEBUG = 0;

			list($stock_fields,$stock_fieldnames,$extra_fields) = $this->split_stock_and_extras($fields);

			$filterfields = array();
			/* turn filter's a=b,c=d OR a=b into an array */
			if($filter)
			{
				if($DEBUG) { echo 'DEBUG - Inbound filter is: #'.$filter.'#'; }

				foreach(explode(',',$filter) as $f)
				{
					list($name,$value) = explode('=',$f,2);

					if ($this->stock_contact_fields[$name])
					{
						$filterfields[$this->stock_contact_fields[$name]] = $value;
					}
					elseif ($this->non_contact_fields[$name])
					{
						$filterfields[$this->non_contact_fields[$name]] = $value;
					}
					else

					{
						if ($DEBUG) echo "<p>contacts_ldap::read(filter=$filter): Cant filter for '$name', allowed only: ".implode(', ',array_keys($this->non_contact_fields))."</p>\n";
					}
				}
			}
			else
			{
				$filterfields += array('phpgwcontacttypeid' => 'n');
				if($DEBUG) { echo "<br>DEBUG - Filter strings: #phpgwcontacttypeid=n#"; }
			}

			/*
			need some way of using the lastmod arg in the filter like this:
			if($lastmod >= 0)
			{
				$filterfields += array('last_mod' => (int)$lastmod;
			}
			or maybe not like this - i am not sure what i am doing :)
			*/

			if(@is_array($this->grants) && strpos($filter,'owner')===false )
			{
				$filterfields['phpgwcontactowner'] = $filterfields['priv'] = array();

				// to not show private entries we add an additional filter access==public or owner==users with private grants
				// please note: private grants are not yet supported by the addressbook UI and the SQL backend
				$filterfields['priv'][]=array('phpgwcontactaccess' => 'public');

				if($DEBUG) { echo '<br>DEBUG - My user id is: ' . $this->account_id; }
				foreach($this->grants as $user => $right)
				{
					if($DEBUG) { echo '<br>DEBUG - Grant from owner: ' . $user; }
					$filterfields['phpgwcontactowner'][] = array('phpgwcontactowner' => $user);
					
					// add users we have a private grants from
					if ($right & EGW_ACL_PRIVATE)
					{
						$filterfields['priv'][]=array('phpgwcontactowner' => $user);
					}
				}
			}
			/*
			if($DEBUG)
			{
				while(list($name,$value) = each($filterfields))
				{
					echo '<br>DEBUG - Filter strings: #' . $name . ',' . $value . '#';
				}
			}
			*/

			$sort  = $sort  ? $sort  : 'ASC';
			$order = $order ? $order : 'n_family';

			if($DEBUG && $order)
			{
				echo "<br>DEBUG - ORDER by $order";
			}

			$ldap_fields = array();
			$myfilter = '';

			if($cquery)
			{
				$search_filter = array(
					'fn'       => 'cn',
					'n_family' => 'sn',
					'org_name' => 'o'
				);
				$myfilter = $this->makefilter($filterfields,$search_filter,"$cquery*",$DEBUG);
			}
			elseif($query)
			{
				// the old code was searching about all fields
				// this was very slow
				#reset($this->stock_contact_fields);
				#$myfilter = $this->makefilter($filterfields,$this->stock_contact_fields,$query,$DEBUG);

				if(is_array($query))
				{
					// must be fixed somehow Milosch????
					$myfilter = $this->makefilter($filterfields,$GLOBALS['egw']->common->ldap_addslashes($query),'',$DEBUG);
				}
				else
				{
					// don't search about any fields any more
					$search_filter = array(
						'fn'       => 'cn',
						'n_given'  => 'givenname',
						'n_family' => 'sn',
						'email'    => 'mail',
						'org_name' => 'o',
						'org_unit' => 'ou'
					);
					$myfilter = $this->makefilter($filterfields,$search_filter,$GLOBALS['egw']->common->ldap_addslashes($query),$DEBUG);
				}
			}
			else
			{
				$myfilter = $this->makefilter($filterfields,'','',$DEBUG);
			}
			$myfilter = $GLOBALS['egw']->translation->convert($myfilter,$GLOBALS['egw']->translation->system_charset,'utf-8');

			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], $myfilter);

			$ldap_fields = ldap_get_entries($this->ldap, $sri);
			/* _debug_array($ldap_fields);exit; */

			$this->total_records = ldap_count_entries($this->ldap, $sri);
			/* echo '<br>total="'.$this->total_records.'"'; */
			if($DEBUG) { echo '<br>Query returned "'.$this->total_records.'" records.'; }

			/* Use usort to sort the complete result, since ldap_search can not do that */
			@set_time_limit(0); /* Try not to die, this can take some time on slow machines... */
			$order_array = "array('";
			if(!empty($order)) $order_array .= $this->stock_contact_fields[$order] . "','";
			$order_array .= $this->stock_contact_fields['n_family'] . "', '"
				. $this->stock_contact_fields['n_given'] . "', '"
				. $this->stock_contact_fields['email'] . "')";
			# remove the "count" field from the array, because usort screws up associative arrays
			unset($ldap_fields['count']);
			# sort the array
			$function = 'return contacts::_cmp(' . $order_array . ",'" . $sort . "'," . '$a, $b);';
			usort($ldap_fields, create_function('$a, $b', $function));

			/*
			This logic allows you to limit rows, or not.
			The export feature, for example, does not limit rows.
			This way, it can retrieve all rows at once.
			*/
			//echo "<p align=right>search() start=$start, limit=$limit, total=$this->total_records</p>\n";
			if($limit == -1)	// return all
			{
				$start = 0;
				$limit = $this->total_records;
			}
			else	// return range from $start to (excluding) $start+$limit
			{
				if ($this->total_records < $start) $start = 0;
				$limit = $start + $limit;
			}

			@reset($ldap_fields);
			$j = 0;
			for($i=$start;$i<$limit;$i++)
			{
				if($i<$this->total_records && $ldap_fields[$i]['uid'][0])
				{
					$return_fields[$j]['id']     = $ldap_fields[$i]['uidnumber'][0];
					$return_fields[$j]['lid']    = $ldap_fields[$i]['uid'][0];
					$return_fields[$j]['tid']    = $ldap_fields[$i]['phpgwcontacttypeid'][0];
					$return_fields[$j]['owner']  = $ldap_fields[$i]['phpgwcontactowner'][0];
					$return_fields[$j]['access'] = $ldap_fields[$i]['phpgwcontactaccess'][0];
					// create from the multiple cat_id's in ldap a comma-separated value as in sql
					unset($ldap_fields[$i]['phpgwcontactcatid']['count']);
					$return_fields[$j]['cat_id'] = implode(',',$ldap_fields[$i]['phpgwcontactcatid']);
					$return_fields[$j]['rights'] = (int)$this->grants[$return_fields[$j]['owner']];

					if(@is_array($stock_fieldnames))
					{
						foreach($stock_fieldnames as $f_name => $f_value)
						{
							$return_fields[$j][$f_name] = $GLOBALS['egw']->translation->convert(($ldap_fields[$i][$f_value][0]),'utf-8');
						}
					}
					$this->db->query("SELECT contact_name,contact_value FROM $this->ext_table WHERE contact_id='"
						. (int)$ldap_fields[$i]['uidnumber'] . "'",__LINE__,__FILE__);
					while($this->db->next_record())
					{
						if($extra_fields[$this->db->f('contact_name')])
						{
							$return_fields[$j][$this->db->f('contact_name')] = $this->db->f('contact_value');
						}
					}
					$j++;
				}
			}
			return $return_fields;
		}

		/* Used by read() above to build the ldap filter string */
		function makefilter($qarray,$extra='',$query='', $DEBUG=False)
		{
			if($DEBUG) echo "<p>contacts_ldap::makefilter(".print_r($qarray,true).",'$extra','$query')</p>\n";

			if(!@is_array($qarray))
			{
				return $qarray;
			}

			$first = $last = "*";
			// this can only be the case, if $query is a character-query.
			// normal queries don't allow wildcards and escape them
			if(strstr($query,"*") && !strstr($query,"\*"))
			{
				if(substr($query,-1) == "*")
				{
					$last = '';
				}
				if(substr($query,1) == "*")
				{
					$first = '';
				}
			}

			if(@is_array($extra))
			{
				if($DEBUG) { echo '<br>Searching...'; }
				foreach($extra as $name => $value)
				{
					$qarray[] = array($value => $query);
				}
			}
			elseif($extra)
			{
				$tmp = split('=',$extra);
				$qarray[] = array($tmp[0] => $tmp[1]);
			}

			@ksort($qarray);

			$aquery = '(&';
			$oquery = '(|';
			$hasor = False;

			foreach($qarray as $name => $value)
			{
				if(@is_array($value))
				{
					foreach($value as $x => $y)
					{
						if($y == '*')
						{
							$oquery .= '(' . $x . '=*)';
							$hasor = True;
						}
						elseif(@is_array($y))
						{
							/* This was most likely created from acl grants in read() above */
							foreach($y as $a => $b)
							{
								$tmp .= '(' . $a . '=' . $b . ')';
							}
						}
						else
						{
							$oquery .= '(' . $x . '=' . $first . $y . $last . ')';
							$hasor = True;
						}
					}
				}
				elseif($value == $query)
				{
					/* searching */
					$oquery .= '(' . $name . '=' . $first . $value . $last . ')';
					$hasor = True;
				}
				else
				{
					/* exact value (filtering based on tid, etc...) */
					if($name == 'phpgwcontactcatid')
					{
						if (!is_object($GLOBALS['egw']->categories))
						{
							$GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories');
						}
						$cats = $GLOBALS['egw']->categories->return_all_children((int)$value);

						/* new code to find cats stored as multiple values only (!)
						if (count($cats) > 1)
						{
							$aquery .= '(|('.$name.'='.implode(')('.$name.'=',$cats).'))';
						}
						else
						{
							$aquery .= '('.$name.'='.(int)$cats[0].')';
						}
						*/
						// old code finds cats stored as multiple values or as comma-separated fields with leading and training comma (old format)
						// but generates a more complex query, so I think it should be removed in the next release by the new code
 						$aquery .= '(|';
 						foreach($cats as $cat)
 						{
 							$aquery .= '(' . $name . '=*,' . $cat . ',*)(' . $name . '=' . $cat . ')';
 						}
 						$aquery .= ')';
					}
					elseif ($value == "!''")	// query for not empty
					{
						$aquery .= '(' . $name . '=*)';
					}
					else
					{
						$aquery .= '(' . $name . '=' . $value . ')';
					}
				}

				if($tmp)
				{
					if(strstr($tmp,')('))
					{
						$aquery .= '(|' . $tmp . ')';
					}
					else
					{
						$aquery .= $tmp;
					}
					unset($tmp);
				}
			}
			$aquery .= ')';
			$oquery .= ')';
			if(!$hasor)
			{
				$oquery = '';
				$fquery = $aquery;
			}
			else
			{
				$fquery = '(&' . $aquery . $oquery . ')';
			}

			if($DEBUG)
			{
				echo '<br>AND query:  "' . $aquery . '"';
				echo '<br>OR query:   "' . $oquery . '"';
				echo '<br>Full query: "' . $fquery . '"';
				echo '<br>Will search in "' . $GLOBALS['egw_info']['server']['ldap_contact_context'] . '"';
			}

			return $fquery;
		}

		function add($owner,$fields,$access=NULL,$cat_id=NULL,$tid=NULL)
		{
			// access, cat_id and tid can be in $fields now or as extra params
			foreach(array('access','cat_id','tid') as $extra)
			{
				if(!is_null($$extra))
				{
					$fields[$extra] = $$extra;
				}
			}
			// store cat_id in ldap as multiple values (php array) and not as comma separated value
			if (isset($fields['cat_id']) && count(explode(',',$fields['cat_id'])) > 1)
			{
				$fields['cat_id'] = explode(',',$fields['cat_id']);
			}
			if(empty($fields['tid']))
			{
				$fields['tid'] = 'n';
			}

			if(!$GLOBALS['egw_info']['server']['ldap_contact_context'])
			{
				return False;
			}

			list($stock_fields,$stock_fieldnames,$extra_fields) = $this->split_stock_and_extras($fields);

			$free = 0;
			$this->nextid = $GLOBALS['egw']->common->last_id('contacts');
			/* Loop until we find a free id */
			while(!$free)
			{
				$ldap_fields = '';
				$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'uidnumber='.$this->nextid);
				$ldap_fields = ldap_get_entries($this->ldap, $sri);
				if($ldap_fields[0]['dn'][0])
				{
					$this->nextid = $GLOBALS['egw']->common->next_id('contacts');
				}
				else
				{
					$free = True;
				}
			}

			$ldap_fields = '';
			if(@is_array($stock_fieldnames))
			{
				foreach($stock_fieldnames as $name => $value)
				{
					if($stock_fields[$name] != '')
					{
						$ldap_fields[$value] = $GLOBALS['egw']->translation->convert($stock_fields[$name],$GLOBALS['egw']->translation->system_charset,'utf-8');
					}
				}
			}

			$time = gettimeofday();
			$ldap_fields['uid'] = time().$time['usec'].':'.$ldap_fields['givenname'];

			$dn = 'uid=' . $ldap_fields['uid'].',' . $GLOBALS['egw_info']['server']['ldap_contact_context'];
			$ldap_fields['phpgwcontacttypeid']    = $fields['tid'];
			$ldap_fields['phpgwcontactowner']     = $owner;
			if(!isset($fields['access']))
			{
				$fields['access'] = 'private';
			}
			$ldap_fields['phpgwcontactaccess'] = $fields['access'];
			$ldap_fields['phpgwcontactcatid']  = $fields['cat_id'] ? $fields['cat_id'] : '0';
			$ldap_fields['uidnumber']      = $this->nextid;
			/* $ldap_fields['objectclass'][0] = 'person'; */
			$ldap_fields['objectclass'][0] = 'organizationalPerson';
			$ldap_fields['objectclass'][1] = 'inetOrgPerson';
			$ldap_fields['objectclass'][2] = 'phpgwContact';
			//$ldap_fields['last_mod'] = $GLOBALS['egw']->datetime->gmtnow;

			$err = $this->validate($ldap_fields);
			if(@is_array($err) && @isset($err[0]))
			{
				return $err;
			}
			// _debug_array($ldap_fields); exit;
			$err = ldap_add($this->ldap, $dn, $ldap_fields);
			if(!$err)
			{
				return False;
			}

			if(count($extra_fields))
			{
				foreach($extra_fields as $name => $value)
				{
					$this->db->query("INSERT INTO $this->ext_table VALUES ('".$this->nextid."','" . $this->account_id . "','"
						. addslashes($name) . "','" . addslashes($value) . "')",__LINE__,__FILE__);
				}
			}
			return $this->nextid;
		}

		/* LDAP syntaxes require some testing prior to add */
		function validate(&$entry)
		{
			$errors = array();
			foreach($entry as $field => $value)
			{
				if(strstr($field,'phone'))
				{
					/* Regex for testing valid international phone number entries.
					 * LDAP may reject bad values here, such as an email address in a phone number.
					 * This format is somewhat loose, allowing for optional parenthesis, + sign,
					 * and 0-7 numbers between separators.
					 */
					$regex = "/^[-0-9\+\(\)\/]/";
					if(!preg_match($regex,$value))
					{
						$errors[] = array($field => $value);
					}
				}
				elseif(strstr($field,'mailtype') || strstr($field,'mailhometype'))
				{
					/* Check for valid mail type */
					if(!@isset($this->email_types[$value]))
					{
						$errors[] = array($field => $value);
					}
				}
				elseif(strstr($field,'mail'))
				{
					/* Check for valid email address - TODO - should depend on mail type */
					$regex = "/[ |\t|\r|\n]*\"?([^\"]+\"?@[^ <>\t]+\.[^ <>\t][^ <>\t]+)[ |\t|\r|\n]*/x";
					if(!preg_match($regex,$value))
					{
						$errors[] = array($field => $value);
					}
				}
			}
			/* Verify sn/cn attrs set */
			if(empty($entry['sn']) && !empty($entry['cn']))
			{
				$entry['sn'] = $entry['cn'];
			}
			if(empty($entry['cn']) && !empty($entry['sn']))
			{
				$entry['cn'] = $entry['sn'];
			}
			$entry['cn'] = $entry['cn'] ? $entry['cn'] : '-';
			$entry['sn'] = $entry['sn'] ? $entry['sn'] : '-';

			return $errors;
		}

		function field_exists($id,$field_name)
		{
			$this->db->query("SELECT COUNT(*) FROM $this->ext_table where contact_id='" . (int)$id . "' AND contact_name='"
			. addslashes($field_name) . "'",__LINE__,__FILE__);
			$this->db->next_record();
			return $this->db->f(0);
		}

		function add_single_extra_field($id,$owner,$field_name,$field_value)
		{
			$this->db->query("INSERT INTO $this->ext_table VALUES (" . (int)$id . ",'$owner','" . addslashes($field_name)
				. "','" . addslashes($field_value) . "')",__LINE__,__FILE__);
		}

		function delete_single_extra_field($id,$field_name)
		{
			$this->db->query("DELETE FROM $this->ext_table WHERE contact_id='" . (int)$id . "' AND contact_name='"
				. addslashes($field_name) . "'",__LINE__,__FILE__);
		}

		function update($id,$owner,$fields,$access=NULL,$cat_id=NULL,$tid=NULL)
		{
			// access, cat_id and tid can be in $fields now or as extra params
			foreach(array('access','cat_id','tid') as $extra)
			{
				if(!is_null($$extra))
				{
					$fields[$extra] = $$extra;
				}
				if(isset($fields[$extra]))
				{
					$stock_fields[$extra] = $fields[$extra];
				}
			}
			// store cat_id in ldap as multiple values (php array) and not as comma separated value
			if (isset($fields['cat_id']) && count(explode(',',$fields['cat_id'])) > 1)
			{
				$fields['cat_id'] = explode(',',$fields['cat_id']);
			}
			$nonfields = $this->non_contact_fields;

			if(!$GLOBALS['egw_info']['server']['ldap_contact_context'])
			{
				return False;
			}

			/* First make sure that id number exists */
			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'uidnumber=' . (int)$id);
			$ldap_fields = ldap_get_entries($this->ldap, $sri);

			if($ldap_fields[0]['dn'])
			{
				$dn = $ldap_fields[0]['dn'];
				list($stock_fields,$stock_fieldnames,$extra_fields) = $this->split_stock_and_extras($fields);
				if(@is_array($stock_fieldnames))
				{
					/*
					Check each value, add our extra attributes if they are missing, and
					otherwise fix the entry while we can.
					*/
					/* Verify uidnumber */
					$stock_fields['id']   = $id;
					if(empty($ldap_fields[0]['uidnumber']))
					{
						$err = ldap_modify($this->ldap,$dn,array('uidnumber'  => $stock_fields['uidnumber']));
					}
					elseif(!$ldap_fields[0]['uidnumber'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('uidnumber' => $stock_fields['uidnumber']));
					}

					/* Verify uid */
					$uids = split(',',$dn);
					$stock_fields['lid'] = $uids[0];
					if(empty($ldap_fields[0]['uid']))
					{
						$err = ldap_modify($this->ldap,$dn,array('uid'  => $stock_fields['lid']));
					}
					elseif(!$ldap_fields[0]['uid'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('uid' => $stock_fields['lid']));
					}

					/* Verify objectclasses are there */
					if(empty($ldap_fields[0]['objectclass']))
					{
						/* $stock_fields['objectclass'][0] = 'person'; */
						$stock_fields['objectclass'][0] = 'organizationalPerson';
						$stock_fields['objectclass'][1] = 'inetOrgPerson';
						$stock_fields['objectclass'][2] = 'phpgwContact';
						$err = ldap_modify($this->ldap,$dn,array('objectclass'  => $stock_fields['objectclass']));
					}
					elseif(!$ldap_fields[0]['objectclass'])
					{
						/* $stock_fields['objectclass'][0] = 'person'; */
						$stock_fields['objectclass'][0] = 'organizationalPerson';
						$stock_fields['objectclass'][1] = 'inetOrgPerson';
						$stock_fields['objectclass'][2] = 'phpgwContact';
						$err = ldap_mod_add($this->ldap,$dn,array('objectclass'  => $stock_fields['objectclass']));
					}

					/* Verify owner */
					$stock_fields['owner']  = $owner;
					if(empty($ldap_fields[0]['phpgwcontactowner']))
					{
						$err = ldap_modify($this->ldap,$dn,array('phpgwcontactowner'  => $stock_fields['owner']));
					}
					elseif(!$ldap_fields[0]['phpgwcontactowner'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('phpgwcontactowner' => $stock_fields['owner']));
					}

					/* Verify access */
					$stock_fields['access'] = $fields['access'];
					if(empty($ldap_fields[0]['phpgwcontactaccess']))
					{
						$err = ldap_modify($this->ldap,$dn,array('phpgwcontactaccess'  => $stock_fields['access']));
					}
					elseif(!$ldap_fields[0]['phpgwcontactaccess'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('phpgwcontactaccess' => $stock_fields['access']));
					}

					/* Verify cat_id */
					$stock_fields['cat_id']  = $fields['cat_id'] ? $fields['cat_id'] : ' ';
					if(empty($ldap_fields[0]['phpgwcontactcatid']))
					{
						$err = ldap_modify($this->ldap,$dn,array('phpgwcontactcatid'  => $stock_fields['cat_id']));
					}
					elseif(!$ldap_fields[0]['phpgwcontactcatid'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('phpgwcontactcatid' => $stock_fields['cat_id']));
					}

					/* Verify tid */
					$stock_fields['tid'] = $fields['tid'];
					if(empty($ldap_fields[0]['phpgwcontacttypeid']))
					{
						$err = ldap_modify($this->ldap,$dn,array('phpgwcontacttypeid'  => $stock_fields['tid']));
					}
					elseif(!$ldap_fields[0]['phpgwcontacttypeid'])
					{
						$err = ldap_mod_add($this->ldap,$dn,array('phpgwcontacttypeid' => $stock_fields['tid']));
					}

					/* OK, just mod the data already */
					$allfields = $stock_fieldnames + $nonfields;
					/* Don't try to modify the uid, since this affects the dn */
					unset($allfields['lid']);
					foreach($allfields as $fname => $fvalue)
					{
						if($ldap_fields[0][$fvalue] && $stock_fields[$fname] && $ldap_fields[0][$fvalue][0] != $stock_fields[$fname] )
						{
							//echo "<br>".$fname." => ".$fvalue." was there";
							$err = ldap_modify($this->ldap,$dn,array($fvalue => $GLOBALS['egw']->translation->convert($stock_fields[$fname],$GLOBALS['egw']->translation->system_charset,'utf-8')));
						}
						elseif(!$ldap_fields[0][$fvalue] && $stock_fields[$fname])
						{
							//echo "<br>".$fname." not there - '".$fvalue."'";
							$err = ldap_mod_add($this->ldap,$dn,array($fvalue => $GLOBALS['egw']->translation->convert($stock_fields[$fname],$GLOBALS['egw']->translation->system_charset,'utf-8')));
						}
						elseif($ldap_fields[0][$fvalue] && !$stock_fields[$fname])
						{
							//echo "<br>".$fname." gone...  deleting - '".$fvalue."'";
							/*
							NOTE: we use the ldap_fields because we need to send the
							_ORIGINAL_ contents as the value. see:
							http://www.php.net/manual/en/function.ldap-mod-del.php
							*/
							$err = ldap_mod_del($this->ldap,$dn,array($fvalue => $ldap_fields[0][$fvalue][0]));
						}
						/* Else we have nothing to do. */
					}
				}

				//something here to update the last_mod from $GLOBALS['egw']->datetime->gmtnow

				foreach($extra_fields as $x_name => $x_value)
				{
					if($this->field_exists($id,$x_name))
					{
						if(!$x_value)
						{
							$this->delete_single_extra_field($id,$x_name);
						}
						else
						{
							$this->db->query("UPDATE $this->ext_table SET contact_value='" . addslashes($x_value)
								. "',contact_owner='$owner' WHERE contact_name='" . addslashes($x_name)
								. "' AND contact_id='" . (int)$id . "'",__LINE__,__FILE__);
						}
					}
					else
					{
						$this->add_single_extra_field($id,$owner,$x_name,$x_value);
					}
				}
			}
			else
			{
				return False;
			}
		}

		/* Used by admin to change ownership on account delete */
		function change_owner($old_owner='',$new_owner='')
		{
			if(!($new_owner && $old_owner))
			{
				return False;
			}

			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'phpgwcontactowner='.$old_owner);
			$ldap_fields = ldap_get_entries($this->ldap, $sri);

			$entry = '';
			for($i=0; $i<$ldap_fields['count']; $i++)
			{
				$err = ldap_modify($this->ldap,$ldap_fields[$i]['dn'],array('phpgwcontactowner' => $new_owner));
			}

			$this->db->query("UPDATE $this->ext_table SET contact_owner='$new_owner' WHERE contact_owner=$old_owner",__LINE__,__FILE__);
			return;
		}

		/* This is where the real work of delete() is done, shared class file contains calling function */
		function delete_($id)
		{
			if(!$GLOBALS['egw_info']['server']['ldap_contact_context'])
			{
				return False;
			}

			$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'uidnumber='.$id);
			$ldap_fields = ldap_get_entries($this->ldap, $sri);

			if($ldap_fields[0]['dn'])
			{
				$err = ldap_delete($this->ldap,$ldap_fields[0]['dn']);

				$this->db->query("DELETE FROM $this->ext_table WHERE contact_id='" . (int)$id . "' AND contact_owner='"
					. $this->account_id . "'",__LINE__,__FILE__);
			}
			else
			{
				return False;
			}
		}

		// This is for the admin script deleteaccount.php
		function delete_all($owner=0)
		{
			if(!$GLOBALS['egw_info']['server']['ldap_contact_context'])
			{
				return False;
			}

			if($owner)
			{
				$sri = ldap_search($this->ldap, $GLOBALS['egw_info']['server']['ldap_contact_context'], 'phpgwcontactowner='.$owner);
				$ldap_fields = ldap_get_entries($this->ldap, $sri);

				$entry = '';
				foreach($ldap_fields as $nul => $entry)
				{
					$err = ldap_delete($this->ldap,$entry['dn']);
				}

				$this->db->query("DELETE FROM $this->ext_table WHERE contact_owner=$owner",__LINE__,__FILE__);
			}
			return;
		}
	}
?>
