forked from extern/egroupware
153a642b56
- Selectbox with groupmembers (only) - No user-selection at all These two options limit the visibility of other users. There for they should be forced and apply NOT to administrators.
952 lines
30 KiB
PHP
Executable File
952 lines
30 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Addressbook - General storage object
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @author Cornelius Weiss <egw-AT-von-und-zu-weiss.de>
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @package addressbook
|
|
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de> and Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @version $Id$
|
|
*/
|
|
|
|
/**
|
|
* General storage object of the adressbook
|
|
*
|
|
* The contact storage has 3 operation modi (contact_repository):
|
|
* - sql: contacts are stored in the SQL table egw_addressbook & egw_addressbook_extra (custom fields)
|
|
* - ldap: contacts are stored in LDAP (accounts have to be stored in LDAP too!!!).
|
|
* Custom fields are not availible in that case!
|
|
* - sql-ldap: contacts are read and searched in SQL, but saved to both SQL and LDAP.
|
|
* Other clients (Thunderbird, ...) can use LDAP readonly. The get maintained via eGroupWare only.
|
|
*
|
|
* The accounts can be stored in SQL or LDAP too (account_repository):
|
|
* If the account-repository is different from the contacts-repository, the filter all (no owner set)
|
|
* will only search the accounts and NOT the contacts! Only the filter accounts (owner=0) shows accounts.
|
|
*
|
|
* If sql-ldap is used as contact-storage (LDAP is managed from eGroupWare) the filter all, searches
|
|
* the accounts in the SQL contacts-table too. Change in made in LDAP, are not detected in that case!
|
|
*
|
|
* @package addressbook
|
|
* @author Cornelius Weiss <egw-AT-von-und-zu-weiss.de>
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de> and Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
*/
|
|
|
|
class socontacts
|
|
{
|
|
/**
|
|
* name of customefields table
|
|
*
|
|
* @var string
|
|
*/
|
|
var $extra_table = 'egw_addressbook_extra';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
var $extra_id = 'contact_id';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
var $extra_owner = 'contact_owner';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
var $extra_key = 'contact_name';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
var $extra_value = 'contact_value';
|
|
|
|
/**
|
|
* Contact repository in 'sql' or 'ldap'
|
|
*
|
|
* @var string
|
|
*/
|
|
var $contact_repository = 'sql';
|
|
|
|
/**
|
|
* Grants as account_id => rights pairs
|
|
*
|
|
* @var array
|
|
*/
|
|
var $grants;
|
|
|
|
/**
|
|
* userid of current user
|
|
*
|
|
* @var int $user
|
|
*/
|
|
var $user;
|
|
|
|
/**
|
|
* memberships of the current user
|
|
*
|
|
* @var array
|
|
*/
|
|
var $memberships;
|
|
|
|
/**
|
|
* LDAP searches only a limited set of attributes for performance reasons,
|
|
* you NEED an index for that columns, ToDo: make it configurable
|
|
* minimum: $this->columns_to_search = array('n_family','n_given','org_name','email');
|
|
*/
|
|
var $ldap_search_attributes = array(
|
|
'n_family','n_middle','n_given','org_name','org_unit',
|
|
'adr_one_location','adr_two_location','note',
|
|
'email','mozillasecondemail',
|
|
);
|
|
/**
|
|
* In SQL we can search all columns, though a view make on real sense
|
|
*/
|
|
var $sql_cols_not_to_search = array(
|
|
'jpegphoto','owner','tid','private','id','cat_id',
|
|
'modified','modifier','creator','created','tz','account_id',
|
|
);
|
|
/**
|
|
* columns to search, if we search for a single pattern
|
|
*
|
|
* @var array
|
|
*/
|
|
var $columns_to_search = array();
|
|
/**
|
|
* extra columns to search if accounts are included, eg. account_lid
|
|
*
|
|
* @var array
|
|
*/
|
|
var $account_extra_search = array();
|
|
/**
|
|
* columns to search for accounts, if stored in different repository
|
|
*
|
|
* @var array
|
|
*/
|
|
var $account_cols_to_search = array();
|
|
|
|
/**
|
|
* customfields name => array(...) pairs
|
|
*
|
|
* @var array
|
|
*/
|
|
var $customfields = array();
|
|
/**
|
|
* content-types as name => array(...) pairs
|
|
*
|
|
* @var array
|
|
*/
|
|
var $content_types = array();
|
|
|
|
/**
|
|
* total number of matches of last search
|
|
*
|
|
* @var int
|
|
*/
|
|
var $total;
|
|
|
|
/**
|
|
* storage object: sql (socontacts_sql) or ldap (so_ldap) backend class
|
|
*
|
|
* @var object
|
|
*/
|
|
var $somain;
|
|
/**
|
|
* storage object for accounts, if not identical to somain (eg. accounts in ldap, contacts in sql)
|
|
*
|
|
* @var object
|
|
*/
|
|
var $so_accounts;
|
|
/**
|
|
* account repository sql or ldap
|
|
*
|
|
* @var string
|
|
*/
|
|
var $account_repository = 'sql';
|
|
/**
|
|
* custom fields backend
|
|
*
|
|
* @var so_sql-object
|
|
*/
|
|
var $soextra;
|
|
|
|
function socontacts($contact_app='addressbook')
|
|
{
|
|
$this->user = $GLOBALS['egw_info']['user']['account_id'];
|
|
$this->memberships = $GLOBALS['egw']->accounts->memberships($this->user,true);
|
|
|
|
// account backend used
|
|
if ($GLOBALS['egw_info']['server']['account_repository'])
|
|
{
|
|
$this->account_repository = $GLOBALS['egw_info']['server']['account_repository'];
|
|
}
|
|
elseif ($GLOBALS['egw_info']['server']['auth_type'])
|
|
{
|
|
$this->account_repository = $GLOBALS['egw_info']['server']['auth_type'];
|
|
}
|
|
// contacts backend (contacts in LDAP require accounts in LDAP!)
|
|
if($GLOBALS['egw_info']['server']['contact_repository'] == 'ldap' && $this->account_repository == 'ldap')
|
|
{
|
|
$this->contact_repository = 'ldap';
|
|
$this->somain =& CreateObject('addressbook.so_ldap');
|
|
|
|
if ($this->user) // not set eg. in setup
|
|
{
|
|
// static grants from ldap: all rights for the own personal addressbook and the group ones of the meberships
|
|
$this->grants = array($this->user => ~0);
|
|
foreach($this->memberships as $gid)
|
|
{
|
|
$this->grants[$gid] = ~0;
|
|
}
|
|
}
|
|
$this->columns_to_search = $this->ldap_search_attributes;
|
|
}
|
|
else // sql or sql->ldap
|
|
{
|
|
if ($GLOBALS['egw_info']['server']['contact_repository'] == 'sql-ldap')
|
|
{
|
|
$this->contact_repository = 'sql-ldap';
|
|
}
|
|
$this->somain =& CreateObject('addressbook.socontacts_sql');
|
|
|
|
if ($this->user) // not set eg. in setup
|
|
{
|
|
// group grants are now grants for the group addressbook and NOT grants for all its members,
|
|
// therefor the param false!
|
|
$this->grants = $GLOBALS['egw']->acl->get_grants($contact_app,false);
|
|
}
|
|
// remove some columns, absolutly not necessary to search in sql
|
|
$this->columns_to_search = array_diff(array_values($this->somain->db_cols),$this->sql_cols_not_to_search);
|
|
}
|
|
if ($this->account_repository == 'ldap' && $this->contact_repository == 'sql')
|
|
{
|
|
if ($this->account_repository != $this->contact_repository)
|
|
{
|
|
$this->so_accounts =& CreateObject('addressbook.so_ldap');
|
|
$this->account_cols_to_search = $this->ldap_search_attributes;
|
|
}
|
|
else
|
|
{
|
|
$this->account_extra_search = array('uid');
|
|
}
|
|
}
|
|
// add grants for accounts: if account_selection not in ('none','groupmembers'): everyone has read access
|
|
// ToDo: be more specific for 'groupmembers', they should be able to see the groupmembers
|
|
if (!in_array($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'],array('none','groupmembers')))
|
|
{
|
|
$this->grants[0] = EGW_ACL_READ;
|
|
}
|
|
// add account grants for admins
|
|
if (isset($GLOBALS['egw_info']['user']['apps']['admin'])) // admin rights can be limited by ACL!
|
|
{
|
|
$this->grants[0] = EGW_ACL_READ; // admins always have read-access
|
|
if (!$GLOBALS['egw']->acl->check('account_access',16,'admin')) $this->grants[0] |= EGW_ACL_EDIT;
|
|
// no add at the moment if (!$GLOBALS['egw']->acl->check('account_access',4,'admin')) $this->grants[0] |= EGW_ACL_ADD;
|
|
if (!$GLOBALS['egw']->acl->check('account_access',32,'admin')) $this->grants[0] |= EGW_ACL_DELETE;
|
|
}
|
|
// ToDo: it should be the other way arround, the backend should set the grants it uses
|
|
$this->somain->grants =& $this->grants;
|
|
|
|
$this->soextra =& CreateObject('etemplate.so_sql');
|
|
$this->soextra->so_sql('phpgwapi',$this->extra_table);
|
|
|
|
$custom =& CreateObject('admin.customfields',$contact_app);
|
|
$this->customfields = $custom->get_customfields();
|
|
$this->content_types = $custom->get_content_types();
|
|
if (!$this->content_types)
|
|
{
|
|
$this->content_types = $custom->content_types = array('n' => array(
|
|
'name' => 'contact',
|
|
'options' => array(
|
|
'template' => 'addressbook.edit',
|
|
'icon' => 'navbar.png'
|
|
)));
|
|
$custom->save_repository();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read all customfields of the given id's
|
|
*
|
|
* @param int/array $ids
|
|
* @return array id => name => value
|
|
*/
|
|
function read_customfields($ids)
|
|
{
|
|
if ($this->contact_repository == 'ldap')
|
|
{
|
|
return array(); // ldap does not support custom-fields (non-nummeric uid)
|
|
}
|
|
foreach($ids as $key => $id)
|
|
{
|
|
if (!(int)$id) unset($ids[$key]);
|
|
}
|
|
if (!$ids) return array(); // nothing to do, eg. all these contacts are in ldap
|
|
|
|
$fields = array();
|
|
foreach((array)$this->soextra->search(array($this->extra_id => $ids),false) as $data)
|
|
{
|
|
if ($data) $fields[$data[$this->extra_id]][$data[$this->extra_key]] = $data[$this->extra_value];
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* changes the data from the db-format to your work-format
|
|
*
|
|
* it gets called everytime when data is read from the db
|
|
* This function needs to be reimplemented in the derived class
|
|
*
|
|
* @param array $data
|
|
*/
|
|
function db2data($data)
|
|
{
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* changes the data from your work-format to the db-format
|
|
*
|
|
* It gets called everytime when data gets writen into db or on keys for db-searches
|
|
* this needs to be reimplemented in the derived class
|
|
*
|
|
* @param array $data
|
|
*/
|
|
function data2db($data)
|
|
{
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* deletes contact entry including custom fields
|
|
*
|
|
* @param mixed $contact array with id or just the id
|
|
* @return boolean true on success or false on failiure
|
|
*/
|
|
function delete($contact)
|
|
{
|
|
if (is_array($contact)) $contact = $contact['id'];
|
|
|
|
// delete mainfields
|
|
if ($this->somain->delete($contact))
|
|
{
|
|
// delete customfields, can return 0 if there are no customfields
|
|
$this->soextra->delete(array($this->extra_id => $contact));
|
|
|
|
if ($this->contact_repository == 'sql-ldap')
|
|
{
|
|
if ($contact['account_id'])
|
|
{
|
|
// LDAP uses the uid attributes for the contact-id (dn),
|
|
// which need to be the account_lid for accounts!
|
|
$contact['id'] = $GLOBALS['egw']->accounts->id2name($contact['account_id']);
|
|
}
|
|
ExecMethod('addressbook.so_ldap.delete',$contact);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* saves contact data including custiom felds
|
|
*
|
|
* @param array &$contact contact data from etemplate::exec
|
|
* @return bool false if all went wrong, errornumber on failure
|
|
*/
|
|
function save(&$contact)
|
|
{
|
|
// save mainfields
|
|
if ($contact['id'] && $this->contact_repository != $this->account_repository && is_object($this->so_accounts) &&
|
|
($this->contact_repository == 'sql' && !is_numeric($contact['id']) ||
|
|
$this->contact_repository == 'ldap' && is_numeric($contact['id'])))
|
|
{
|
|
$this->so_accounts->data = $this->data2db($contact);
|
|
$error_nr = $this->so_accounts->save();
|
|
$contact['id'] = $this->so_accounts->data['id'];
|
|
}
|
|
else
|
|
{
|
|
// contact_repository sql-ldap (accounts in ldap) the person_id is the uid (account_lid)
|
|
// for the sql write here we need to find out the existing contact_id
|
|
if ($this->contact_repository == 'sql-ldap' && $contact['id'] && !is_numeric($contact['id']) &&
|
|
$contact['account_id'] && ($old = $this->somain->read(array('account_id' => $contact['account_id']))))
|
|
{
|
|
$contact['id'] = $old['id'];
|
|
}
|
|
$this->somain->data = $this->data2db($contact);
|
|
|
|
if (!($error_nr = $this->somain->save()))
|
|
{
|
|
$contact['id'] = $this->somain->data['id'];
|
|
|
|
if ($this->contact_repository == 'sql-ldap')
|
|
{
|
|
$data = $this->somain->data;
|
|
if ($contact['account_id'])
|
|
{
|
|
// LDAP uses the uid attributes for the contact-id (dn),
|
|
// which need to be the account_lid for accounts!
|
|
$data['id'] = $GLOBALS['egw']->accounts->id2name($contact['account_id']);
|
|
}
|
|
ExecMethod('addressbook.so_ldap.save',$data);
|
|
}
|
|
}
|
|
}
|
|
if($error_nr) return $error_nr;
|
|
|
|
// save customfields
|
|
foreach ((array)$this->customfields as $field => $options)
|
|
{
|
|
if (!isset($contact['#'.$field])) continue;
|
|
|
|
$data = array(
|
|
$this->extra_id => $contact['id'],
|
|
$this->extra_owner => $contact['owner'],
|
|
$this->extra_key => $field,
|
|
);
|
|
if((string) $contact['#'.$field] === '') // dont write empty values
|
|
{
|
|
$this->soextra->delete($data); // just delete them, in case they were previously set
|
|
continue;
|
|
}
|
|
$data[$this->extra_value] = $contact['#'.$field];
|
|
if (($error_nr = $this->soextra->save($data)))
|
|
{
|
|
return $error_nr;
|
|
}
|
|
}
|
|
return false; // no error
|
|
}
|
|
|
|
/**
|
|
* reads contact data including custom fields
|
|
*
|
|
* @param int/string $contact_id contact_id or 'a'.account_id
|
|
* @return array/boolean data if row could be retrived else False
|
|
*/
|
|
function read($contact_id)
|
|
{
|
|
if (!is_array($contact_id) && substr($contact_id,0,8) == 'account:')
|
|
{
|
|
$contact_id = array('account_id' => (int) substr($contact_id,8));
|
|
}
|
|
// read main data
|
|
$backend =& $this->get_backend($contact_id);
|
|
if (!($contact = $backend->read($contact_id)))
|
|
{
|
|
return $contact;
|
|
}
|
|
// try reading customfields only if we have some (none for LDAP!)
|
|
if ($this->customfields && $this->contact_repository != 'ldap')
|
|
{
|
|
$customfields = $this->soextra->search(array(
|
|
$this->extra_id => $contact['id'],
|
|
$this->extra_owner => $contact['owner'],
|
|
),false);
|
|
foreach ((array)$customfields as $field)
|
|
{
|
|
$contact['#'.$field[$this->extra_key]] = $field[$this->extra_value];
|
|
}
|
|
}
|
|
return $this->db2data($contact);
|
|
}
|
|
|
|
/**
|
|
* searches db for rows matching searchcriteria
|
|
*
|
|
* '*' and '?' are replaced with sql-wildcards '%' and '_'
|
|
*
|
|
* @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
|
|
* @param boolean/string $only_keys=true True returns only keys, False returns all cols. comma seperated list of keys to return
|
|
* @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
|
|
* @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
|
|
* @param string $wildcard='' appended befor and after each criteria
|
|
* @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
|
|
* @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
|
|
* @param mixed $start=false if != false, return only maxmatch rows begining with start, or array($start,$num)
|
|
* @param array $filter=null if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
|
|
* @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
|
|
* "LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
|
|
* @param boolean $need_full_no_count=false If true an unlimited query is run to determine the total number of rows, default false
|
|
* @return array of matching rows (the row is an array of the cols) or False
|
|
*/
|
|
function ex_search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='',$need_full_no_count=false)
|
|
{
|
|
//echo 'socontacts::search->criteria:'; _debug_array($criteria);
|
|
// we can only deal with one category atm.
|
|
$criteria['cat_id'] = $criteria['cat_id'][0];
|
|
if (empty($criteria['cat_id'])) unset($criteria['cat_id']);
|
|
|
|
// We just want to deal with generalized vars, to simpyfie porting of this code to so_sql later...
|
|
$this->main_id = $this->somain->contacts_id;
|
|
|
|
// seperate custom fields from main fields
|
|
foreach ($criteria as $crit_key => $crit_val)
|
|
{
|
|
if(!(isset($this->somain->db_data_cols [$crit_key]) || isset($this->somain->db_key_cols [$crit_key])))
|
|
{
|
|
if(strpos($crit_key,'#') !== false && $crit_key{0} != '!' )
|
|
{
|
|
$extra_crit_key = substr($crit_key,1);
|
|
$criteria_extra[$extra_crit_key][$this->extra_key] = $extra_crit_key;
|
|
$criteria_extra[$extra_crit_key][$this->extra_value] = $crit_val;
|
|
}
|
|
unset($criteria[$crit_key]);
|
|
}
|
|
}
|
|
//_debug_array($criteria);
|
|
//_debug_array($criteria_extra);
|
|
|
|
// search in custom fields
|
|
$resultextra = array();
|
|
if (count($criteria_extra) >= 1)
|
|
{
|
|
$firstrun = true;
|
|
foreach ((array)$criteria_extra as $extra_crit)
|
|
{
|
|
if($extra_crit[$this->extra_value]{0} == '!')
|
|
{
|
|
if(!isset($all_main_ids)) $all_main_ids = $this->somain->search(array($this->main_id => '*'));
|
|
$extra_crit[$this->extra_value] = substr($extra_crit[$this->extra_value],1);
|
|
$not_result = $this->soextra->search($extra_crit,true,'','',$wildcard);
|
|
if(is_array($not_result))
|
|
{
|
|
$expr = '$not_result[0]';
|
|
for($i=1; $i<count($not_result); $i++)
|
|
{
|
|
$expr .= ',$not_result['.$i.']';
|
|
}
|
|
@eval('$not_result = array_merge_recursive('.$expr.');');
|
|
}
|
|
foreach($all_main_ids as $entry)
|
|
{
|
|
if(array_search($entry[$this->main_id],(array)$not_result[$this->extra_id]) === false)
|
|
{
|
|
$result[] = array(
|
|
$this->extra_id => $entry[$this->main_id],
|
|
$this->extra_key => $extra_crit[$this->extra_key],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$result = $this->soextra->search($extra_crit,true,'','',$wildcard);
|
|
}
|
|
|
|
if ($op == 'OR' && $result)
|
|
{
|
|
$resultextra = array_merge_recursive((array)$result,(array)$resultextra);
|
|
}
|
|
elseif ($op == 'AND')
|
|
{
|
|
if (!$result)
|
|
{
|
|
return false;
|
|
//$resultextra = array();
|
|
//break;
|
|
}
|
|
$expr = '$result[0]';
|
|
for($i=1; $i<count($result); $i++)
|
|
{
|
|
$expr .= ',$result['.$i.']';
|
|
}
|
|
@eval('$merge = array_merge_recursive('.$expr.');');
|
|
if(!is_array($merge[$this->extra_id]))
|
|
{
|
|
$merge[$this->extra_id] = (array)$merge[$this->extra_id];
|
|
}
|
|
if($firstrun)
|
|
{
|
|
$resultextra = $merge[$this->extra_id];
|
|
$firstrun = false;
|
|
}
|
|
else
|
|
{
|
|
$resultextra = array_intersect((array)$resultextra,$merge[$this->extra_id]);
|
|
}
|
|
}
|
|
}
|
|
if($op == 'OR' && $resultextra)
|
|
{
|
|
$expr = '$resultextra[0]';
|
|
for($i=1; $i<count($resultextra); $i++)
|
|
{
|
|
$expr .= ',$resultextra['.$i.']';
|
|
}
|
|
@eval('$merge = array_merge_recursive('.$expr.');');
|
|
$resultextra = array_unique((array)$merge[$this->extra_id]);
|
|
}
|
|
}
|
|
//echo 'socontacts::search->resultextra:'; _debug_array($resultextra);
|
|
|
|
// search in main fields
|
|
$result = array();
|
|
// include results from extrafieldsearch
|
|
if(!empty($resultextra))
|
|
{
|
|
$criteria[$this->main_id] = $resultextra;
|
|
}
|
|
if (count($criteria) >= 0) // RB-CHANGED was 1
|
|
{
|
|
// We do have to apply wildcard by hand, as the result-ids of extrasearch are included in this search
|
|
if($wildcard)
|
|
{
|
|
foreach ($criteria as $field => $value)
|
|
{
|
|
if ($field == $this->main_id) continue;
|
|
$criteria[$field] = '*'.$value.'*';
|
|
}
|
|
}
|
|
$result = $this->somain->search($criteria,true,$order_by,$extra_cols,false,$empty,$op,false,$filter);
|
|
if(!is_array($result)) return false;
|
|
$expr = '$result[0]';
|
|
for($i=1; $i<count($result); $i++)
|
|
{
|
|
$expr .= ',$result['.$i.']';
|
|
}
|
|
@eval('$merge = array_merge_recursive('.$expr.');');
|
|
$result = ($merge[$this->main_id]);
|
|
}
|
|
//echo 'socontacts::search->result:'; _debug_array($result);
|
|
|
|
if(count($result) == 0) return false;
|
|
if(!is_bool($only_keys_main = $only_keys))
|
|
{
|
|
$keys_wanted = explode(',',$only_keys);
|
|
foreach ($keys_wanted as $num => $key_wanted)
|
|
{
|
|
if(!(isset($this->somain->db_data_cols [$key_wanted]) || isset($this->somain->db_key_cols [$key_wanted])))
|
|
{
|
|
unset($keys_wanted[$num]);
|
|
$keys_wanted_custom[] = $key_wanted;
|
|
}
|
|
}
|
|
$only_keys_main = implode(',',$keys_wanted);
|
|
}
|
|
$result = $this->somain->search(array($this->main_id => $result),$only_keys_main,$order_by,$extra_cols,'','','OR',$start,$filter,$join,$need_full_no_count);
|
|
|
|
// append custom fields for each row
|
|
if($only_keys === false || is_array($keys_wanted_custom))
|
|
{
|
|
foreach ($result as $num => $contact)
|
|
{
|
|
$extras = $this->soextra->search(array($this->extra_id => $contact[$this->main_id]),false);
|
|
foreach ((array)$extras as $extra)
|
|
{
|
|
if ($only_keys === false || in_array($extra[$this->extra_key],$keys_wanted_custom))
|
|
{
|
|
$result[$num][$extra[$this->extra_key]] = $extra[$this->extra_value];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach($result as $num => $contact)
|
|
{
|
|
$result[$num] = $this->db2data($contact);
|
|
}
|
|
return $need_full_no_count ? count($result) : $result;
|
|
}
|
|
|
|
/**
|
|
* searches db for rows matching searchcriteria
|
|
*
|
|
* '*' and '?' are replaced with sql-wildcards '%' and '_'
|
|
*
|
|
* @param array/string $criteria array of key and data cols, OR string to search over all standard search fields
|
|
* @param boolean/string $only_keys=true True returns only keys, False returns all cols. comma seperated list of keys to return
|
|
* @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
|
|
* @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
|
|
* @param string $wildcard='' appended befor and after each criteria
|
|
* @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
|
|
* @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
|
|
* @param mixed $start=false if != false, return only maxmatch rows begining with start, or array($start,$num)
|
|
* @param array $filter=null if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
|
|
* @param string $join='' sql to do a join (only used by sql backend!), eg. " RIGHT JOIN egw_accounts USING(account_id)"
|
|
* @return array of matching rows (the row is an array of the cols) or False
|
|
*/
|
|
function &search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='')
|
|
{
|
|
//echo "<p>socontacts::search(".print_r($criteria,true).",'$only_keys','$order_by','$extra_cols','$wildcard','$empty','$op','$start',".print_r($filter,true).",'$join')</p>\n";
|
|
error_log("socontacts::search(".print_r($criteria,true).",'$only_keys','$order_by','$extra_cols','$wildcard','$empty','$op','$start',".print_r($filter,true).",'$join')");
|
|
|
|
$backend =& $this->get_backend(null,$filter['owner']);
|
|
// single string to search for --> create so_sql conformant search criterial for the standard search columns
|
|
if ($criteria && !is_array($criteria))
|
|
{
|
|
$op = 'OR';
|
|
$wildcard = '%';
|
|
$search = $criteria;
|
|
$criteria = array();
|
|
|
|
if ($backend === $this->somain)
|
|
{
|
|
$cols = $this->columns_to_search;
|
|
}
|
|
else
|
|
{
|
|
$cols = $this->account_cols_to_search;
|
|
}
|
|
// search the customfields only if some exist, but only for sql!
|
|
if (get_class($backend) == 'socontacts_sql' && $this->customfields)
|
|
{
|
|
$cols[] = $this->extra_value;
|
|
}
|
|
foreach($cols as $col)
|
|
{
|
|
$criteria[$col] = $search;
|
|
}
|
|
}
|
|
if (is_array($criteria) && count($criteria))
|
|
{
|
|
$criteria = $this->data2db($criteria);
|
|
}
|
|
if (is_array($filter) && count($filter))
|
|
{
|
|
$filter = $this->data2db($filter);
|
|
}
|
|
else
|
|
{
|
|
$filter = $filter ? array($filter) : array();
|
|
}
|
|
// get the used backend for the search and call it's search method
|
|
$rows = $backend->search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join,$need_full_no_count);
|
|
$this->total = $backend->total;
|
|
|
|
if ($rows)
|
|
{
|
|
foreach($rows as $n => $row)
|
|
{
|
|
$rows[$n] = $this->db2data($row);
|
|
}
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Query organisations by given parameters
|
|
*
|
|
* @var array $param
|
|
* @var string $param[org_view] 'org_name', 'org_name,adr_one_location', 'org_name,org_unit' how to group
|
|
* @var int $param[owner] addressbook to search
|
|
* @var string $param[search] search pattern for org_name
|
|
* @var string $param[searchletter] letter the org_name need to start with
|
|
* @var int $param[start]
|
|
* @var int $param[num_rows]
|
|
* @var string $param[sort] ASC or DESC
|
|
* @return array or arrays with keys org_name,count and evtl. adr_one_location or org_unit
|
|
*/
|
|
function organisations($param)
|
|
{
|
|
if (!method_exists($this->somain,'organisations'))
|
|
{
|
|
$this->total = 0;
|
|
return false;
|
|
}
|
|
if ($param['search'] && !is_array($param['search']))
|
|
{
|
|
$search = $param['search'];
|
|
$param['search'] = array();
|
|
foreach($this->columns_to_search as $col)
|
|
{
|
|
if ($col != 'contact_value') $param['search'][$col] = $search; // we dont search the customfields
|
|
}
|
|
}
|
|
if (is_array($param['search']) && count($param['search']))
|
|
{
|
|
$param['search'] = $this->data2db($param['search']);
|
|
}
|
|
$rows = $this->somain->organisations($param);
|
|
$this->total = $this->somain->total;
|
|
//echo "<p>socontacts::organisations(".print_r($param,true).")<br />".$this->somain->db->Query_ID->sql."</p>\n";
|
|
|
|
if (!$rows) return array();
|
|
|
|
list(,$by) = explode(',',$param['org_view']);
|
|
if (!$by) $by = 'adr_one_locality';
|
|
|
|
foreach($rows as $n => $row)
|
|
{
|
|
$rows[$n]['id'] = 'org_name:'.$row['org_name'];
|
|
foreach(array(
|
|
'org_unit' => lang('departments'),
|
|
'adr_one_locality' => lang('locations'),
|
|
) as $by => $by_label)
|
|
{
|
|
if ($row[$by.'_count'] > 1)
|
|
{
|
|
$rows[$n][$by] = $row[$by.'_count'].' '.$by_label;
|
|
}
|
|
else
|
|
{
|
|
$rows[$n]['id'] .= '|||'.$by.':'.$row[$by];
|
|
}
|
|
}
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* gets all contact fields from database
|
|
*/
|
|
function get_contact_columns()
|
|
{
|
|
$fields = $this->somain->db_data_cols;
|
|
foreach ((array)$this->customfields as $cfield => $coptions)
|
|
{
|
|
$fields['#'.$cfield] = '#'.$cfield;
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* delete / move all contacts of an addressbook
|
|
*
|
|
* @param array $data
|
|
* @param int $data['account_id'] owner to change
|
|
* @param int $data['new_owner'] new owner or 0 for delete
|
|
*/
|
|
function deleteaccount($data)
|
|
{
|
|
$account_id = $data['account_id'];
|
|
$new_owner = $data['new_owner'];
|
|
|
|
if (!$new_owner)
|
|
{
|
|
$this->somain->delete(array('owner' => $account_id));
|
|
$this->soextra->delete(array($this->extra_owner => $account_id));
|
|
}
|
|
else
|
|
{
|
|
$this->somain->change_owner($account_id,$new_owner);
|
|
$this->soextra->db->update($this->soextra->table_name,array(
|
|
$this->extra_owner => $new_owner
|
|
),array(
|
|
$this->extra_owner => $account_id
|
|
),__LINE__,__FILE__);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* return the backend, to be used for the given $contact_id
|
|
*
|
|
* @param mixed $contact_id=null
|
|
* @param int $owner=null account_id of owner or 0 for accounts
|
|
* @return object
|
|
*/
|
|
function &get_backend($contact_id=null,$owner=null)
|
|
{
|
|
if ($this->contact_repository != $this->account_repository && is_object($this->so_accounts) &&
|
|
(!is_null($owner) && !$owner || !is_null($contact_id) &&
|
|
($this->contact_repository == 'sql' && !is_numeric($contact_id) ||
|
|
$this->contact_repository == 'ldap' && is_numeric($contact_id))))
|
|
{
|
|
return $this->so_accounts;
|
|
}
|
|
return $this->somain;
|
|
}
|
|
|
|
/**
|
|
* Returns the supported, all or unsupported fields of the backend (depends on owner or contact_id)
|
|
*
|
|
* @param sting $type='all' 'supported', 'unsupported' or 'all'
|
|
* @param mixed $contact_id=null
|
|
* @param int $owner=null account_id of owner or 0 for accounts
|
|
* @return array with eGW contact field names
|
|
*/
|
|
function get_fields($type='all',$contact_id=null,$owner=null)
|
|
{
|
|
$def = $this->soextra->db->get_table_definitions('phpgwapi','egw_addressbook');
|
|
|
|
$all_fields = array();
|
|
foreach($def['fd'] as $field => $data)
|
|
{
|
|
$all_fields[] = substr($field,0,8) == 'contact_' ? substr($field,8) : $field;
|
|
}
|
|
if ($type == 'all')
|
|
{
|
|
return $all_fields;
|
|
}
|
|
$backend =& $this->get_backend($contact_id,$owner);
|
|
|
|
$supported_fields = method_exists($backend,supported_fields) ? $backend->supported_fields() : $all_fields;
|
|
//echo "supported fields=";_debug_array($supported_fields);
|
|
|
|
if ($type == 'supported')
|
|
{
|
|
return $supported_fields;
|
|
}
|
|
//echo "unsupported fields=";_debug_array(array_diff($all_fields,$supported_fields));
|
|
return array_diff($all_fields,$supported_fields);
|
|
}
|
|
|
|
/**
|
|
* Migrates an SQL contact storage to LDAP or SQL-LDAP
|
|
*
|
|
* @param string $type "contacts" (default), "contacts+accounts" or "contacts+accounts-back" (sql-ldap!)
|
|
*/
|
|
function migrate2ldap($type)
|
|
{
|
|
$sql_contacts =& CreateObject('addressbook.socontacts_sql');
|
|
$ldap_contacts =& CreateObject('addressbook.so_ldap');
|
|
|
|
$start = $n = 0;
|
|
$num = 100;
|
|
while (($contacts = $sql_contacts->search(false,false,'n_family,n_given','','',false,'AND',
|
|
array($start,$num),$type != 'contacts,accounts' ? array('contact_owner != 0') : false)))
|
|
{
|
|
foreach($contacts as $contact)
|
|
{
|
|
if ($contact['account_id']) $contact['id'] = $GLOBALS['egw']->accounts->id2name($contact['account_id']);
|
|
|
|
$ldap_contacts->data = $contact;
|
|
$n++;
|
|
if (!($err = $ldap_contacts->save()))
|
|
{
|
|
echo '<p style="margin: 0px;">'.$n.': '.$contact['n_fn'].
|
|
($contact['org_name'] ? ' ('.$contact['org_name'].')' : '')." --> LDAP</p>\n";
|
|
}
|
|
else
|
|
{
|
|
echo '<p style="margin: 0px; color: red;">'.$n.': '.$contact['n_fn'].
|
|
($contact['org_name'] ? ' ('.$contact['org_name'].')' : '').': '.$err."</p>\n";
|
|
}
|
|
}
|
|
$start += $num;
|
|
}
|
|
if ($type == 'contacts,accounts-back') // migrate the accounts to sql
|
|
{
|
|
foreach($ldap_contacts->search(false,false,'n_family,n_given','','',false,'AND',
|
|
false,array('owner' => 0)) as $contact)
|
|
{
|
|
if ($contact['jpegphoto']) // photo is NOT read by LDAP backend on search, need to do an extra read
|
|
{
|
|
$contact = $ldap_contacts->read($contact['id']);
|
|
}
|
|
unset($contact['id']); // ldap uid/account_lid
|
|
if ($contact['account_id'] && ($old = $sql_contacts->read(array('account_id' => $contact['account_id']))))
|
|
{
|
|
$contact['id'] = $old['id'];
|
|
}
|
|
$sql_contacts->data = $contact;
|
|
|
|
$n++;
|
|
if (!($err = $sql_contacts->save()))
|
|
{
|
|
echo '<p style="margin: 0px;">'.$n.': '.$contact['n_fn'].
|
|
($contact['org_name'] ? ' ('.$contact['org_name'].')' : '')." --> SQL (".lang('User').")</p>\n";
|
|
}
|
|
else
|
|
{
|
|
echo '<p style="margin: 0px; color: red;">'.$n.': '.$contact['n_fn'].
|
|
($contact['org_name'] ? ' ('.$contact['org_name'].')' : '').': '.$err."</p>\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|