forked from extern/egroupware
967 lines
30 KiB
PHP
Executable File
967 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
|
|
*/
|
|
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 socontacts_sql
|
|
*/
|
|
var $somain;
|
|
/**
|
|
* storage object for accounts, if not identical to somain (eg. accounts in ldap, contacts in sql)
|
|
*
|
|
* @var so_ldap
|
|
*/
|
|
var $so_accounts;
|
|
/**
|
|
* account repository sql or ldap
|
|
*
|
|
* @var string
|
|
*/
|
|
var $account_repository = 'sql';
|
|
/**
|
|
* custom fields backend
|
|
*
|
|
* @var so_sql
|
|
*/
|
|
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 ($this->is_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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the user is an admin (can unconditionally edit accounts)
|
|
*
|
|
* We check now the admin ACL for edit users, as the admin app does it for editing accounts.
|
|
*
|
|
* @param array $contact=null for future use, where admins might not be admins for all accounts
|
|
* @return boolean
|
|
*/
|
|
function is_admin($contact=null)
|
|
{
|
|
return isset($GLOBALS['egw_info']['user']['apps']['admin']) && !$GLOBALS['egw']->acl->check('account_access',16,'admin');
|
|
}
|
|
|
|
/**
|
|
* 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 on success, 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
|
|
*
|
|
* @return array of (internal) field-names
|
|
*/
|
|
function get_contact_columns()
|
|
{
|
|
$fields = $this->get_fields('all');
|
|
foreach ((array)$this->customfields as $cfield => $coptions)
|
|
{
|
|
$fields[] = '#'.$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";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|