2006-04-23 16:40:31 +02:00
< ? php
2006-06-13 06:30:16 +02:00
/**
2016-03-06 14:45:15 +01:00
* EGroupware API : Contacts - SQL storage
2006-06-13 06:30:16 +02:00
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2016-03-06 14:45:15 +01:00
* @ package api
* @ subpackage contacts
* @ copyright ( c ) 2006 - 16 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2006-06-13 06:30:16 +02:00
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
2008-04-25 21:06:15 +02:00
* @ version $Id $
2006-06-13 06:30:16 +02:00
*/
2006-04-23 16:40:31 +02:00
2016-03-06 14:45:15 +01:00
namespace EGroupware\Api\Contacts ;
use EGroupware\Api ;
2006-04-23 16:40:31 +02:00
/**
2016-03-06 14:45:15 +01:00
* Contacts - SQL storage
2006-04-23 16:40:31 +02:00
*/
2016-03-06 14:45:15 +01:00
class Sql extends Api\Storage
2006-04-23 16:40:31 +02:00
{
2006-10-11 15:00:23 +02:00
/**
2010-04-09 00:42:25 +02:00
* name of custom fields table
2008-04-25 21:06:15 +02:00
*
2006-10-11 15:00:23 +02:00
* @ var string
*/
2006-06-13 06:30:16 +02:00
var $account_repository = 'sql' ;
2006-06-17 20:50:07 +02:00
var $contact_repository = 'sql' ;
2007-07-11 17:34:13 +02:00
var $grants ;
2008-04-25 21:06:15 +02:00
2009-02-26 15:58:55 +01:00
/**
* join to show only active account ( and not already expired ones )
*/
2017-11-15 19:29:10 +01:00
const ACCOUNT_ACTIVE_JOIN = ' LEFT JOIN egw_accounts ON egw_addressbook.account_id=egw_accounts.account_id ' ;
2009-02-26 15:58:55 +01:00
/**
2013-01-25 14:21:31 +01:00
* filter to show only active account ( and not already expired or deactived ones )
2009-02-26 15:58:55 +01:00
* UNIX_TIMESTAMP ( NOW ()) gets replaced with value of time () in the code !
*/
2013-01-29 12:26:54 +01:00
const ACOUNT_ACTIVE_FILTER = " (account_expires IS NULL OR account_expires = -1 OR account_expires > UNIX_TIMESTAMP(NOW())) AND (account_type IS NULL OR account_type!='u' OR account_status='A') " ;
2009-02-26 15:58:55 +01:00
2006-07-08 02:36:23 +02:00
/**
* internal name of the id , gets mapped to uid
*
* @ var string
*/
var $contacts_id = 'id' ;
2007-03-13 14:38:15 +01:00
/**
* Name of the table for distribution lists
*
* @ var string
*/
var $lists_table = 'egw_addressbook_lists' ;
/**
* Name of the table with the members ( contacts ) of the distribution lists
*
* @ var string
*/
var $ab2list_table = 'egw_addressbook2list' ;
2008-04-25 21:06:15 +02:00
2013-09-02 13:19:42 +02:00
const EXTRA_TABLE = 'egw_addressbook_extra' ;
2014-03-03 12:49:41 +01:00
const EXTRA_VALUE = 'contact_value' ;
2013-09-02 13:19:42 +02:00
2010-07-30 17:30:46 +02:00
/**
* Constructor
*
2016-03-06 14:45:15 +01:00
* @ param Api\Db $db = null
2010-07-30 17:30:46 +02:00
*/
2016-03-06 14:45:15 +01:00
function __construct ( Api\Db $db = null )
2006-06-13 06:30:16 +02:00
{
2016-04-02 21:55:08 +02:00
parent :: __construct ( 'api' , 'egw_addressbook' , self :: EXTRA_TABLE ,
2016-03-06 14:45:15 +01:00
'contact_' , '_name' , '_value' , '_id' , $db );
2010-04-09 00:42:25 +02:00
2016-04-02 21:55:08 +02:00
// Get custom fields from addressbook instead of api
2016-03-06 14:45:15 +01:00
$this -> customfields = Api\Storage\Customfields :: get ( 'addressbook' );
2006-06-13 06:30:16 +02:00
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' ];
}
2006-06-17 20:50:07 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'contact_repository' ])
{
$this -> contact_repository = $GLOBALS [ 'egw_info' ][ 'server' ][ 'contact_repository' ];
}
2006-06-13 06:30:16 +02:00
}
2008-04-25 21:06:15 +02:00
2006-04-30 11:34:24 +02:00
/**
* 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 array $param [ col_filter ] filter
* @ var string $param [ search ] or ' ed search pattern
2011-03-30 15:31:09 +02:00
* @ var array $param [ advanced_search ] indicator that advanced search is active
* @ var string $param [ op ] ( operator like AND or OR ; will be passed when advanced search is active )
* @ var string $param [ wildcard ] ( wildcard like % or empty or not set ( for no wildcard ); will be passed when advanced search is active )
2006-04-30 11:34:24 +02:00
* @ 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
2008-04-25 21:06:15 +02:00
*/
2006-04-30 11:34:24 +02:00
function organisations ( $param )
{
$filter = is_array ( $param [ 'col_filter' ]) ? $param [ 'col_filter' ] : array ();
2017-11-15 19:29:10 +01:00
$join = '' ;
2011-03-30 15:31:09 +02:00
$op = 'OR' ;
if ( isset ( $param [ 'op' ]) && ! empty ( $param [ 'op' ])) $op = $param [ 'op' ];
$advanced_search = false ;
if ( isset ( $param [ 'advanced_search' ]) && ! empty ( $param [ 'advanced_search' ])) $advanced_search = true ;
$wildcard = '%' ;
if ( $advanced_search || ( isset ( $param [ 'wildcard' ]) && ! empty ( $param [ 'wildcard' ]))) $wildcard = ( $param [ 'wildcard' ] ? $param [ 'wildcard' ] : '' );
2011-04-05 22:39:13 +02:00
2006-05-24 04:28:57 +02:00
// fix cat_id filter to search in comma-separated multiple cats and return subcats
2016-09-26 14:35:52 +02:00
if ( $filter [ 'cat_id' ])
2006-05-24 04:28:57 +02:00
{
$filter [] = $this -> _cat_filter ( $filter [ 'cat_id' ]);
unset ( $filter [ 'cat_id' ]);
}
2006-04-30 11:34:24 +02:00
// add filter for read ACL in sql, if user is NOT the owner of the addressbook
if ( $param [ 'owner' ] && $param [ 'owner' ] == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
{
$filter [ 'owner' ] = $param [ 'owner' ];
}
else
{
// we have no private grants in addressbook at the moment, they have then to be added here too
if ( $param [ 'owner' ])
{
if ( ! $this -> grants [( int ) $filter [ 'owner' ]]) return false ; // we have no access to that addressbook
2008-04-25 21:06:15 +02:00
2006-04-30 11:34:24 +02:00
$filter [ 'owner' ] = $param [ 'owner' ];
$filter [ 'private' ] = 0 ;
}
else // search all addressbooks, incl. accounts
{
2006-06-17 20:50:07 +02:00
if ( $this -> account_repository != 'sql' && $this -> contact_repository != 'sql-ldap' )
{
2006-06-24 07:54:28 +02:00
$filter [] = $this -> table_name . '.contact_owner != 0' ; // in case there have been accounts in sql previously
2006-06-17 20:50:07 +02:00
}
2010-04-20 10:11:34 +02:00
$filter [] = " ( " . $this -> table_name . " .contact_owner= " . ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] .
2019-03-09 11:44:55 +01:00
( ! $this -> grants ? ')' :
2010-04-20 10:11:34 +02:00
" OR contact_private=0 AND " . $this -> table_name . " .contact_owner IN ( " .
2019-03-09 11:44:55 +01:00
implode ( ',' , array_keys ( $this -> grants )) . " )) " );
2006-04-30 11:34:24 +02:00
}
2017-11-21 19:07:50 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ] !== 'none' )
2017-11-15 19:29:10 +01:00
{
$join .= self :: ACCOUNT_ACTIVE_JOIN ;
2017-11-21 19:07:50 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ] === '0' )
2017-11-15 19:29:10 +01:00
{
$filter [] = str_replace ( 'UNIX_TIMESTAMP(NOW())' , time (), self :: ACOUNT_ACTIVE_FILTER );
}
else
{
$filter [] = 'egw_accounts.account_id IS NULL' ;
}
}
2006-04-30 11:34:24 +02:00
}
if ( $param [ 'searchletter' ])
{
2016-03-06 14:45:15 +01:00
$filter [] = 'org_name ' . $this -> db -> capabilities [ Api\Db :: CAPABILITY_CASE_INSENSITIV_LIKE ] . ' ' . $this -> db -> quote ( $param [ 'searchletter' ] . '%' );
2006-04-30 11:34:24 +02:00
}
else
{
$filter [] = " org_name != '' " ; // AND org_name IS NOT NULL";
}
2018-07-19 22:54:05 +02:00
if ( isset ( $filter [ 'list' ]))
{
if ( $filter [ 'list' ] < 0 )
{
$join .= " JOIN egw_acl ON $this->table_name .account_id=acl_account AND acl_appname='phpgw_group' AND " .
$this -> db -> expression ( 'egw_acl' , array ( 'acl_location' => $filter [ 'list' ]));
}
else
{
$join .= " JOIN $this->ab2list_table ON $this->table_name .contact_id= $this->ab2list_table .contact_id AND " .
$this -> db -> expression ( $this -> ab2list_table , array ( 'list_id' => $filter [ 'list' ]));
}
unset ( $filter [ 'list' ]);
}
2006-04-30 11:34:24 +02:00
$sort = $param [ 'sort' ] == 'DESC' ? 'DESC' : 'ASC' ;
list (, $by ) = explode ( ',' , $param [ 'org_view' ]);
if ( ! $by )
{
$extra = array (
2010-08-19 18:15:10 +02:00
'COUNT(DISTINCT egw_addressbook.contact_id) AS org_count' ,
2006-04-30 11:34:24 +02:00
" COUNT(DISTINCT CASE WHEN org_unit IS NULL THEN '' ELSE org_unit END) AS org_unit_count " ,
" COUNT(DISTINCT CASE WHEN adr_one_locality IS NULL THEN '' ELSE adr_one_locality END) AS adr_one_locality_count " ,
);
$append = " GROUP BY org_name ORDER BY org_name $sort " ;
}
else // by adr_one_location or org_unit
{
// org total for more then one $by
2008-04-25 21:06:15 +02:00
$by_expr = $by == 'org_unit_count' ? " COUNT(DISTINCT CASE WHEN org_unit IS NULL THEN '' ELSE org_unit END) " :
2007-05-25 19:33:26 +02:00
" COUNT(DISTINCT CASE WHEN adr_one_locality IS NULL THEN '' ELSE adr_one_locality END) " ;
2016-03-06 14:45:15 +01:00
parent :: search ( $param [ 'search' ], array ( 'org_name' ),
" GROUP BY org_name HAVING $by_expr > 1 ORDER BY org_name $sort " , array (
2006-04-30 11:34:24 +02:00
" NULL AS $by " ,
2007-06-03 11:27:41 +02:00
'1 AS is_main' ,
2010-08-19 18:15:10 +02:00
'COUNT(DISTINCT egw_addressbook.contact_id) AS org_count' ,
2006-04-30 11:34:24 +02:00
" COUNT(DISTINCT CASE WHEN org_unit IS NULL THEN '' ELSE org_unit END) AS org_unit_count " ,
" COUNT(DISTINCT CASE WHEN adr_one_locality IS NULL THEN '' ELSE adr_one_locality END) AS adr_one_locality_count " ,
2017-11-29 17:11:46 +01:00
), $wildcard , false , $op /*'OR'*/ , 'UNION' , $filter , $join );
2006-04-30 11:34:24 +02:00
// org by location
2016-03-06 14:45:15 +01:00
parent :: search ( $param [ 'search' ], array ( 'org_name' ),
" GROUP BY org_name, $by ORDER BY org_name $sort , $by $sort " , array (
2006-04-30 11:34:24 +02:00
" CASE WHEN $by IS NULL THEN '' ELSE $by END AS $by " ,
2007-06-03 11:27:41 +02:00
'0 AS is_main' ,
2010-08-19 18:15:10 +02:00
'COUNT(DISTINCT egw_addressbook.contact_id) AS org_count' ,
2006-04-30 11:34:24 +02:00
" COUNT(DISTINCT CASE WHEN org_unit IS NULL THEN '' ELSE org_unit END) AS org_unit_count " ,
" COUNT(DISTINCT CASE WHEN adr_one_locality IS NULL THEN '' ELSE adr_one_locality END) AS adr_one_locality_count " ,
2017-11-29 17:11:46 +01:00
), $wildcard , false , $op /*'OR'*/ , 'UNION' , $filter , $join );
2007-06-03 11:27:41 +02:00
$append = " ORDER BY org_name $sort ,is_main DESC, $by $sort " ;
2006-04-30 11:34:24 +02:00
}
2011-03-30 15:31:09 +02:00
$rows = parent :: search ( $param [ 'search' ], array ( 'org_name' ), $append , $extra , $wildcard , false , $op /*'OR'*/ ,
2017-11-15 19:29:10 +01:00
array ( $param [ 'start' ], $param [ 'num_rows' ]), $filter , $join );
2008-04-25 21:06:15 +02:00
2006-04-30 11:34:24 +02:00
if ( ! $rows ) return false ;
// query the values for *_count == 1, to display them instead
$filter [ 'org_name' ] = $orgs = array ();
2016-03-06 14:45:15 +01:00
foreach ( $rows as $row )
2006-04-30 11:34:24 +02:00
{
if ( $row [ 'org_unit_count' ] == 1 || $row [ 'adr_one_locality_count' ] == 1 )
{
$filter [ 'org_name' ][ $row [ 'org_name' ]] = $row [ 'org_name' ]; // use as key too to have every org only once
}
$org_key = $row [ 'org_name' ] . ( $by ? '|||' . ( $row [ $by ] || $row [ $by . '_count' ] == 1 ? $row [ $by ] : '|||' ) : '' );
2017-03-13 19:11:37 +01:00
$row [ 'group_count' ] = $row [ 'org_count' ];
2008-04-25 21:06:15 +02:00
$orgs [ $org_key ] = $row ;
2006-04-30 11:34:24 +02:00
}
unset ( $rows );
2008-04-25 21:06:15 +02:00
2006-04-30 11:34:24 +02:00
if ( count ( $filter [ 'org_name' ]))
{
2016-03-06 14:45:15 +01:00
foreach (( array ) parent :: search ( null , array ( 'org_name' , 'org_unit' , 'adr_one_locality' ),
'GROUP BY org_name,org_unit,adr_one_locality' ,
2017-11-15 19:29:10 +01:00
'' , $wildcard , false , $op /*'AND'*/ , false , $filter , $join ) as $row )
2006-04-30 11:34:24 +02:00
{
$org_key = $row [ 'org_name' ] . ( $by ? '|||' . $row [ $by ] : '' );
if ( $orgs [ $org_key ][ 'org_unit_count' ] == 1 )
{
$orgs [ $org_key ][ 'org_unit' ] = $row [ 'org_unit' ];
}
if ( $orgs [ $org_key ][ 'adr_one_locality_count' ] == 1 )
{
$orgs [ $org_key ][ 'adr_one_locality' ] = $row [ 'adr_one_locality' ];
}
if ( $by && isset ( $orgs [ $org_key = $row [ 'org_name' ] . '||||||' ]))
{
if ( $orgs [ $org_key ][ 'org_unit_count' ] == 1 )
{
$orgs [ $org_key ][ 'org_unit' ] = $row [ 'org_unit' ];
}
if ( $orgs [ $org_key ][ 'adr_one_locality_count' ] == 1 )
{
$orgs [ $org_key ][ 'adr_one_locality' ] = $row [ 'adr_one_locality' ];
}
}
}
}
return array_values ( $orgs );
}
2017-03-13 19:11:37 +01:00
/**
* Query for duplicate contacts according to given parameters
*
* We join egw_addressbook to itself , and count how many fields match . If
* enough of the fields we care about match , we count those two records as
* duplicates .
2017-09-19 11:38:02 +02:00
*
2017-03-13 19:11:37 +01:00
* @ var array $param
* @ var string $param [ grouped_view ] 'duplicate' , 'duplicate,adr_one_location' , 'duplicate,org_name' 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 name need to start with
* @ var array $param [ col_filter ] filter
* @ var string $param [ search ] or ' ed search pattern
* @ var array $param [ advanced_search ] indicator that advanced search is active
* @ var string $param [ op ] ( operator like AND or OR ; will be passed when advanced search is active )
* @ var string $param [ wildcard ] ( wildcard like % or empty or not set ( for no wildcard ); will be passed when advanced search is active )
* @ 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 duplicates ( $param )
{
$join = 'JOIN ' . $this -> table_name . ' AS a2 ON ' ;
2017-03-16 17:53:53 +01:00
$filter = $param [ 'col_filter' ];
2017-03-13 19:11:37 +01:00
$op = 'OR' ;
if ( isset ( $param [ 'op' ]) && ! empty ( $param [ 'op' ])) $op = $param [ 'op' ];
$advanced_search = false ;
if ( isset ( $param [ 'advanced_search' ]) && ! empty ( $param [ 'advanced_search' ])) $advanced_search = true ;
$wildcard = '%' ;
if ( $advanced_search || ( isset ( $param [ 'wildcard' ]) && ! empty ( $param [ 'wildcard' ]))) $wildcard = ( $param [ 'wildcard' ] ? $param [ 'wildcard' ] : '' );
// fix cat_id filter to search in comma-separated multiple cats and return subcats
if ( $param [ 'cat_id' ])
{
$cat_filter = $this -> _cat_filter ( $filter [ 'cat_id' ]);
$filter [] = str_replace ( 'cat_id' , $this -> table_name . '.cat_id' , $cat_filter );
$join .= str_replace ( 'cat_id' , 'a2.cat_id' , $cat_filter ) . ' AND ' ;
unset ( $filter [ 'cat_id' ]);
}
2017-03-17 17:01:23 +01:00
if ( $filter [ 'tid' ])
2017-03-16 17:53:53 +01:00
{
2017-03-17 17:01:23 +01:00
$filter [ $this -> table_name . '.contact_tid' ] = $param [ 'col_filter' ][ 'tid' ];
$join .= 'a2.contact_tid = ' . $this -> db -> quote ( $filter [ 'tid' ]) . ' AND ' ;
unset ( $filter [ 'tid' ]);
2017-03-16 17:53:53 +01:00
}
else
{
$join .= 'a2.contact_tid != \'D\' AND ' ;
}
2017-03-13 19:11:37 +01:00
// add filter for read ACL in sql, if user is NOT the owner of the addressbook
2017-03-22 21:23:05 +01:00
if ( array_key_exists ( 'owner' , $param ) && $param [ 'owner' ] == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
2017-03-13 19:11:37 +01:00
{
2017-03-22 21:23:05 +01:00
$filter [ $this -> table_name . '.contact_owner' ] = $param [ 'owner' ];
$join .= 'a2.contact_owner = ' . $this -> db -> quote ( $param [ 'owner' ]) . ' AND ' ;
2017-03-13 19:11:37 +01:00
}
else
{
// we have no private grants in addressbook at the moment, they have then to be added here too
2017-03-22 21:23:05 +01:00
if ( array_key_exists ( 'owner' , $param ))
2017-03-13 19:11:37 +01:00
{
2017-03-22 21:23:05 +01:00
if ( ! $this -> grants [( int ) $param [ 'owner' ]]) return false ; // we have no access to that addressbook
2017-03-13 19:11:37 +01:00
2017-03-22 21:23:05 +01:00
$filter [ $this -> table_name . '.contact_owner' ] = $param [ 'owner' ];
$filter [ $this -> table_name . '.private' ] = 0 ;
$join .= 'a2.contact_owner = ' . $this -> db -> quote ( $param [ 'owner' ]) . ' AND ' ;
2017-03-16 17:53:53 +01:00
$join .= 'a2.contact_private = ' . $this -> db -> quote ( $filter [ 'private' ]) . ' AND ' ;
2017-03-13 19:11:37 +01:00
}
else // search all addressbooks, incl. accounts
{
if ( $this -> account_repository != 'sql' && $this -> contact_repository != 'sql-ldap' )
{
$filter [] = $this -> table_name . '.contact_owner != 0' ; // in case there have been accounts in sql previously
}
$filter [] = $access = " ( " . $this -> table_name . " .contact_owner= " . ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] .
" OR { $this -> table_name } .contact_private=0 AND " . $this -> table_name . " .contact_owner IN ( " .
implode ( ',' , array_keys ( $this -> grants )) . " )) " ;
$join .= str_replace ( $this -> table_name , 'a2' , $access ) . ' AND ' ;
}
}
if ( $param [ 'searchletter' ])
{
$filter [] = $this -> table_name . '.n_fn ' . $this -> db -> capabilities [ Api\Db :: CAPABILITY_CASE_INSENSITIV_LIKE ] . ' ' . $this -> db -> quote ( $param [ 'searchletter' ] . '%' );
}
$sort = $param [ 'sort' ] == 'DESC' ? 'DESC' : 'ASC' ;
$group = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'duplicate_fields' ] ?
explode ( ',' , $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'duplicate_fields' ]) :
2017-03-17 17:01:23 +01:00
array ( 'n_family' , 'org_name' , 'contact_email' );
2017-03-13 19:11:37 +01:00
$match_count = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'duplicate_threshold' ] ?
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'duplicate_threshold' ] : 3 ;
2017-03-16 17:53:53 +01:00
$columns = Array ();
2017-03-13 19:11:37 +01:00
$extra = Array ();
$order = in_array ( $param [ 'order' ], $group ) ? $param [ 'order' ] : $group [ 0 ];
2017-03-16 17:53:53 +01:00
$join .= $this -> table_name . '.contact_id != a2.contact_id AND (' ;
2017-03-13 19:11:37 +01:00
$join_fields = Array ();
2017-03-16 17:53:53 +01:00
foreach ( $group as $field )
2017-03-13 19:11:37 +01:00
{
$extra [] = " IF( { $this -> table_name } . $field = a2. $field , 1, 0) " ;
$join_fields [] = $this -> table_name . " . $field = a2. $field " ;
2017-03-16 17:53:53 +01:00
$columns [] = " IF( { $this -> table_name } . $field = a2. $field , { $this -> table_name } . $field , '') AS $field " ;
2017-03-13 19:11:37 +01:00
}
$extra = Array (
implode ( '+' , $extra ) . ' AS match_count'
);
$join .= $this -> db -> column_data_implode ( ' OR ' , $join_fields ) . ')' ;
2017-11-21 19:07:50 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ] !== 'none' )
2017-11-15 19:29:10 +01:00
{
2017-11-21 19:07:50 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ] === '0' )
2017-11-15 19:29:10 +01:00
{
$join .= ' LEFT JOIN egw_accounts AS account_1 ON egw_addressbook.account_id=account_1.account_id ' ;
$join .= ' LEFT JOIN egw_accounts AS account_2 ON egw_addressbook.account_id=account_2.account_id ' ;
$filter [] = str_replace ( array ( 'UNIX_TIMESTAMP(NOW())' , 'account_' ), array ( time (), 'account_1.account_' ), self :: ACOUNT_ACTIVE_FILTER );
$filter [] = str_replace ( array ( 'UNIX_TIMESTAMP(NOW())' , 'account_' ), array ( time (), 'account_2.account_' ), self :: ACOUNT_ACTIVE_FILTER );
}
else
{
$filter [] = 'egw_addressbook.account_id IS NULL and a2.account_id IS NULL' ;
}
}
2017-03-16 17:53:53 +01:00
$append = " HAVING match_count >= $match_count ORDER BY { $order } $sort , $this->table_name .contact_id " ;
$columns [] = $this -> table_name . '.contact_id AS contact_id' ;
2017-03-13 19:11:37 +01:00
2017-03-16 17:53:53 +01:00
$criteria = array ();
if ( $param [ 'search' ] && ! is_array ( $param [ 'search' ]))
{
2017-03-16 18:06:58 +01:00
$search_cols = array ();
foreach ( $group as $col )
{
$search_cols [] = $this -> table_name . '.' . $col ;
}
$search = $this -> search2criteria ( $param [ 'search' ], $wildcard , $op , null , $search_cols );
2017-03-16 17:53:53 +01:00
$criteria = array ( $search );
}
$query = $this -> parse_search ( array_merge ( $criteria , $filter ), $wildcard , false , ' AND ' );
2017-09-19 11:38:02 +02:00
$sub_query = $this -> db -> select ( $this -> table_name ,
2017-03-16 17:53:53 +01:00
'DISTINCT ' . implode ( ', ' , array_merge ( $columns , $extra )),
$query ,
False , False , 0 , $append , False , - 1 ,
$join
);
$columns = implode ( ', ' , $group );
if ( $this -> db -> Type == 'mysql' && $this -> db -> ServerInfo [ 'version' ] >= 4.0 )
{
$mysql_calc_rows = 'SQL_CALC_FOUND_ROWS ' ;
}
2017-09-19 11:38:02 +02:00
2017-03-16 17:53:53 +01:00
$rows = $this -> db -> query (
2017-03-23 18:34:35 +01:00
" SELECT $mysql_calc_rows " . $columns . ', COUNT(contact_id) AS group_count' .
2017-03-16 17:53:53 +01:00
' FROM (' . $sub_query . ') AS matches GROUP BY ' . implode ( ',' , $group ) .
2017-03-16 18:06:58 +01:00
' HAVING group_count > 1 ORDER BY ' . $order ,
2017-03-16 17:53:53 +01:00
__LINE__ , __FILE__ , ( int ) $param [ 'start' ], $mysql_calc_rows ? ( int ) $param [ 'num_rows' ] : - 1
);
2017-03-13 19:11:37 +01:00
// Go through rows and only return one for each pair/triplet/etc. of matches
$dupes = array ();
2017-03-16 17:53:53 +01:00
foreach ( $rows as $key => $row )
2017-03-13 19:11:37 +01:00
{
2017-03-16 17:53:53 +01:00
$row [ 'email' ] = $row [ 'contact_email' ];
$row [ 'email_home' ] = $row [ 'contact_email_home' ];
$dupes [] = $this -> db2data ( $row );
2017-03-13 19:11:37 +01:00
}
2017-09-19 11:38:02 +02:00
2017-03-23 18:34:35 +01:00
if ( $mysql_calc_rows )
{
$this -> total = $this -> db -> query ( 'SELECT FOUND_ROWS()' ) -> fetchColumn ();
}
else
{
$this -> total = $rows -> NumRows ();
}
2017-03-16 17:53:53 +01:00
return $dupes ;
2017-03-13 19:11:37 +01:00
}
2006-04-23 16:40:31 +02:00
/**
* searches db for rows matching searchcriteria
*
* '*' and '?' are replaced with sql - wildcards '%' and '_'
*
* For a union - query you call search for each query with $start == 'UNION' and one more with only $order_by and $start set to run the union - query .
*
2016-03-06 14:45:15 +01:00
* @ param array | string $criteria array of key and data cols , OR a SQL query ( content for WHERE ), fully quoted ( ! )
* @ param boolean | string | array $only_keys = true True returns only keys , False returns all cols . or
2006-04-24 22:52:14 +02:00
* comma seperated list or array of columns to return
2016-03-06 14:45:15 +01:00
* @ 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 ), or 'UNION' for a part of a union query
* @ 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
2006-04-23 16:40:31 +02:00
* " LEFT JOIN table2 ON (x=y) " , Note : there ' s no quoting done on $join !
2016-03-06 14:45:15 +01:00
* @ param boolean $need_full_no_count = false If true an unlimited query is run to determine the total number of rows , default false
2016-11-07 09:26:14 +01:00
* @ param boolean $ignore_acl = false true : no acl check
2006-04-23 16:40:31 +02:00
* @ return boolean / array of matching rows ( the row is an array of the cols ) or False
*/
2016-11-07 09:26:14 +01:00
function & search ( $criteria , $only_keys = True , $order_by = '' , $extra_cols = '' , $wildcard = '' , $empty = False , $op = 'AND' , $start = false , $filter = null , $join = '' , $need_full_no_count = false , $ignore_acl = false )
2006-04-23 16:40:31 +02:00
{
2013-10-24 16:57:36 +02:00
if (( int ) $this -> debug >= 4 ) echo '<p>' . __METHOD__ . '(' . array2string ( $criteria ) . ',' . array2string ( $only_keys ) . " ,' $order_by ',' $extra_cols ',' $wildcard ',' $empty ',' $op ', $start , " . array2string ( $filter ) . " ,' $join ')</p> \n " ;
//error_log(__METHOD__.'('.array2string($criteria,true).','.array2string($only_keys).",'$order_by', ".array2string($extra_cols).",'$wildcard','$empty','$op',$start,".array2string($filter).",'$join')");
2008-01-19 06:41:04 +01:00
2006-04-23 16:40:31 +02:00
$owner = isset ( $filter [ 'owner' ]) ? $filter [ 'owner' ] : ( isset ( $criteria [ 'owner' ]) ? $criteria [ 'owner' ] : null );
2010-06-28 14:36:10 +02:00
// fix cat_id criteria to search in comma-separated multiple cats and return subcats
2010-06-28 16:11:20 +02:00
if ( is_array ( $criteria ) && ( $cats = $criteria [ 'cat_id' ]))
2010-06-28 14:36:10 +02:00
{
2010-06-29 18:45:01 +02:00
$criteria = array_merge ( $criteria , $this -> _cat_search ( $criteria [ 'cat_id' ]));
2010-06-28 14:36:10 +02:00
unset ( $criteria [ 'cat_id' ]);
}
2006-05-24 04:28:57 +02:00
// fix cat_id filter to search in comma-separated multiple cats and return subcats
2007-07-04 21:13:00 +02:00
if (( $cats = $filter [ 'cat_id' ]))
2006-05-24 04:28:57 +02:00
{
2008-11-12 19:05:19 +01:00
if ( $filter [ 'cat_id' ][ 0 ] == '!' )
2007-07-04 21:13:00 +02:00
{
$filter [ 'cat_id' ] = substr ( $filter [ 'cat_id' ], 1 );
$not = 'NOT' ;
}
2016-09-26 14:35:52 +02:00
$filter [] = $this -> _cat_filter ( $filter [ 'cat_id' ], $not );
2006-05-24 04:28:57 +02:00
unset ( $filter [ 'cat_id' ]);
}
2008-04-25 21:06:15 +02:00
2006-04-23 16:40:31 +02:00
// add filter for read ACL in sql, if user is NOT the owner of the addressbook
2016-11-07 09:26:14 +01:00
if ( isset ( $this -> grants ) && ! $ignore_acl &&
! ( isset ( $filter [ 'owner' ]) && $filter [ 'owner' ] == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ]))
2006-04-23 16:40:31 +02:00
{
2010-11-05 09:56:41 +01:00
// add read ACL for groupmembers (they have no
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'account_selection' ] == 'groupmembers' &&
( ! isset ( $filter [ 'owner' ]) || in_array ( '0' ,( array ) $filter [ 'owner' ])))
{
$groupmembers = array ();
foreach ( $GLOBALS [ 'egw' ] -> accounts -> memberships ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ], true ) as $group_id )
{
if (( $members = $GLOBALS [ 'egw' ] -> accounts -> members ( $group_id , true )))
{
$groupmembers = array_merge ( $groupmembers , $members );
}
}
$groupmember_sql = $this -> db -> expression ( $this -> table_name , ' OR ' . $this -> table_name . '.' , array (
'account_id' => array_unique ( $groupmembers ),
));
}
2006-04-23 16:40:31 +02:00
// we have no private grants in addressbook at the moment, they have then to be added here too
if ( isset ( $filter [ 'owner' ]))
{
2010-11-05 09:56:41 +01:00
// no grants for selected owner/addressbook
2019-02-21 18:59:11 +01:00
if ( ! array_intersect (( array ) $filter [ 'owner' ], array_keys ( $this -> grants )))
2010-11-05 09:56:41 +01:00
{
if ( ! isset ( $groupmember_sql )) return false ;
$filter [] = substr ( $groupmember_sql , 4 );
unset ( $filter [ 'owner' ]);
}
2010-09-08 11:47:57 +02:00
// for an owner filter, which does NOT include current user, filter out private entries
2019-03-09 11:44:55 +01:00
elseif ( ! in_array ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ], ( array ) $filter [ 'owner' ]))
2010-09-08 11:47:57 +02:00
{
$filter [ 'private' ] = 0 ;
}
// if multiple addressbooks (incl. current owner) are searched, we need full acl filter
2019-03-03 14:39:57 +01:00
elseif ( is_array ( $filter [ 'owner' ]) && count ( $filter [ 'owner' ]) > 1 )
2010-09-08 11:47:57 +02:00
{
$filter [] = " ( $this->table_name .contact_owner= " . ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] .
" OR contact_private=0 AND $this->table_name .contact_owner IN ( " .
2010-11-05 09:56:41 +01:00
implode ( ',' , array_keys ( $this -> grants )) . " ) $groupmember_sql OR $this->table_name .contact_owner IS NULL) " ;
2010-09-08 11:47:57 +02:00
}
2006-04-23 16:40:31 +02:00
}
else // search all addressbooks, incl. accounts
{
2006-06-17 20:50:07 +02:00
if ( $this -> account_repository != 'sql' && $this -> contact_repository != 'sql-ldap' )
{
2006-06-24 07:54:28 +02:00
$filter [] = $this -> table_name . '.contact_owner != 0' ; // in case there have been accounts in sql previously
2006-06-17 20:50:07 +02:00
}
2006-04-24 13:09:52 +02:00
$filter [] = " ( $this->table_name .contact_owner= " . ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] .
2019-03-09 11:44:55 +01:00
( $this -> grants ? " OR contact_private=0 AND $this->table_name .contact_owner IN ( " .
implode ( ',' , array_keys ( $this -> grants )) . " ) " : '' ) .
$groupmember_sql . " OR $this->table_name .contact_owner IS NULL) " ;
2006-04-23 16:40:31 +02:00
}
2006-10-11 15:00:23 +02:00
}
2007-06-03 11:49:10 +02:00
if ( isset ( $filter [ 'list' ]))
{
2017-01-23 11:16:24 +01:00
if ( $filter [ 'list' ] < 0 )
{
$join .= " JOIN egw_acl ON $this->table_name .account_id=acl_account AND acl_appname='phpgw_group' AND " .
$this -> db -> expression ( 'egw_acl' , array ( 'acl_location' => $filter [ 'list' ]));
}
else
{
$join .= " JOIN $this->ab2list_table ON $this->table_name .contact_id= $this->ab2list_table .contact_id AND " .
$this -> db -> expression ( $this -> ab2list_table , array ( 'list_id' => $filter [ 'list' ]));
}
2007-06-03 11:49:10 +02:00
unset ( $filter [ 'list' ]);
}
2011-03-20 13:37:22 +01:00
// add join to show only active accounts (only if accounts are shown and in sql and we not already join the accounts table, eg. used by admin)
2013-08-02 20:28:44 +02:00
if (( is_array ( $owner ) ? in_array ( 0 , $owner ) : ! $owner ) && substr ( $this -> account_repository , 0 , 3 ) == 'sql' &&
2018-04-26 22:34:09 +02:00
strpos ( $join , $GLOBALS [ 'egw' ] -> accounts -> backend -> table ) === false && ! array_key_exists ( 'account_id' , $filter ))
2011-03-20 13:37:22 +01:00
{
$join .= self :: ACCOUNT_ACTIVE_JOIN ;
2017-11-21 19:07:50 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ] === '0' )
2017-11-15 19:29:10 +01:00
{
$filter [] = str_replace ( 'UNIX_TIMESTAMP(NOW())' , time (), self :: ACOUNT_ACTIVE_FILTER );
}
2011-03-20 13:37:22 +01:00
}
2012-06-01 14:51:22 +02:00
if ( $join || ( $criteria && is_string ( $criteria )) || ( $criteria && is_array ( $criteria ) && $order_by )) // search also adds a join for custom fields!
2007-06-03 11:49:10 +02:00
{
2006-12-11 08:35:49 +01:00
switch ( gettype ( $only_keys ))
{
case 'boolean' :
2010-04-21 20:52:49 +02:00
// Correctly handled by parent class
2006-12-11 08:35:49 +01:00
break ;
case 'string' :
$only_keys = explode ( ',' , $only_keys );
// fall through
}
2007-06-24 11:19:48 +02:00
// postgres requires that expressions in order by appear in the columns of a distinct select
2016-03-06 14:45:15 +01:00
$all_matches = null ;
if ( $this -> db -> Type != 'mysql' && preg_match_all ( " /(#?[a-zA-Z_.]+) *(<> *''|IS NULL|IS NOT NULL)? *(ASC|DESC)?(,| $ )/ui " ,
$order_by , $all_matches , PREG_SET_ORDER ))
2007-06-24 11:19:48 +02:00
{
if ( ! is_array ( $extra_cols )) $extra_cols = $extra_cols ? explode ( ',' , $extra_cols ) : array ();
2011-04-11 14:07:30 +02:00
foreach ( $all_matches as $matches )
2011-01-14 13:58:46 +01:00
{
2011-04-11 14:07:30 +02:00
$table = '' ;
2011-04-21 16:31:10 +02:00
$column = $matches [ 1 ];
2013-10-24 16:57:36 +02:00
if ( $column [ 0 ] == '#' ) continue ; // order by custom field is handeled in so_sql_cf anyway
2011-04-21 16:31:10 +02:00
if (( $key = array_search ( $column , $this -> db_cols )) !== false ) $column = $key ;
if ( strpos ( $column , '.' ) === false )
2011-04-11 14:07:30 +02:00
{
2011-04-21 16:31:10 +02:00
$table = $column == $this -> extra_value ? $this -> extra_table : $this -> table_name ;
2011-04-21 17:05:10 +02:00
if ( isset ( $this -> db_cols [ $column ]))
2011-04-21 16:31:10 +02:00
{
$table .= '.' ;
}
else
{
$table = '' ;
}
2011-04-11 14:07:30 +02:00
}
2011-04-21 16:31:10 +02:00
$extra_cols [] = $table . $column . ' ' . $matches [ 2 ];
2011-04-11 14:07:30 +02:00
//_debug_array($matches);
if ( ! empty ( $order_by ) && $table ) // postgres requires explizit order by
{
2011-04-21 16:31:10 +02:00
$order_by = str_replace ( $matches [ 0 ], $table . $column . ' ' . $matches [ 2 ] . ' ' . $matches [ 3 ] . $matches [ 4 ], $order_by );
2011-04-11 14:07:30 +02:00
}
2011-01-14 13:58:46 +01:00
}
2011-04-11 14:07:30 +02:00
//_debug_array($order_by); _debug_array($extra_cols);
2007-06-24 11:19:48 +02:00
}
2013-01-14 23:04:41 +01:00
// Understand search by date with wildcard (????.10.??) according to user date preference
2013-01-24 17:35:39 +01:00
if ( is_string ( $criteria ) && strpos ( $criteria , '?' ) !== false )
2013-01-14 23:04:41 +01:00
{
// First, check for a 'date', with wildcards, in the user's format
2016-03-06 14:45:15 +01:00
$date_regex = str_replace ( 'Q' , 'd' ,
str_replace ( array ( 'Y' , 'm' , 'd' , '.' , '-' ),
array ( '(?P<Y>(?:\?|\Q){4})' , '(?P<m>(?:\?|\Q){2})' , '(?P<d>(?:\?|\Q){2})' , '\.' , '\-' ),
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'dateformat' ]));
2013-01-14 23:04:41 +01:00
if ( preg_match_all ( '$' . $date_regex . '$' , $criteria , $matches ))
{
foreach ( $matches [ 0 ] as $m_id => $match )
{
// Birthday is Y-m-d
2013-01-24 17:35:39 +01:00
$criteria = str_replace ( $match , " * { $matches [ 'Y' ][ $m_id ] } - { $matches [ 'm' ][ $m_id ] } - { $matches [ 'd' ][ $m_id ] } * " , $criteria );
2013-01-14 23:04:41 +01:00
}
}
}
2006-04-23 16:40:31 +02:00
}
2007-04-14 11:23:04 +02:00
$rows =& parent :: search ( $criteria , $only_keys , $order_by , $extra_cols , $wildcard , $empty , $op , $start , $filter , $join , $need_full_no_count );
2008-04-25 21:06:15 +02:00
2007-04-14 11:23:04 +02:00
if ( $start === false ) $this -> total = is_array ( $rows ) ? count ( $rows ) : 0 ; // so_sql sets total only for $start !== false!
2008-04-25 21:06:15 +02:00
2007-04-14 11:23:04 +02:00
return $rows ;
2006-04-23 16:40:31 +02:00
}
2008-04-25 21:06:15 +02:00
2006-05-24 04:28:57 +02:00
/**
* fix cat_id filter to search in comma - separated multiple cats and return subcats
2008-04-25 21:06:15 +02:00
*
* @ internal
2016-09-26 14:35:52 +02:00
* @ param int | array $cat_id
2006-05-24 04:28:57 +02:00
* @ return string sql to filter by given cat
*/
2007-07-04 21:13:00 +02:00
function _cat_filter ( $cat_id , $not = '' )
2006-05-24 04:28:57 +02:00
{
if ( ! is_object ( $GLOBALS [ 'egw' ] -> categories ))
{
2016-03-20 14:41:33 +01:00
$GLOBALS [ 'egw' ] -> categories = new Api\Categories ;
2006-05-24 04:28:57 +02:00
}
2016-09-26 14:35:52 +02:00
foreach ( $GLOBALS [ 'egw' ] -> categories -> return_all_children ( $cat_id ) as $cat )
2006-05-24 04:28:57 +02:00
{
2019-03-29 13:38:51 +01:00
$cat_filter [] = $this -> db -> concat ( " ',' " , 'cat_id' , " ',' " ) . " $not LIKE '%, $cat ,%' " ;
2007-07-04 21:13:00 +02:00
}
$cfilter = '(' . implode ( ' OR ' , $cat_filter ) . ')' ;
if ( ! empty ( $not ))
{
$cfilter = " ( $cfilter OR cat_id IS NULL ) " ;
2006-05-24 04:28:57 +02:00
}
2007-07-04 21:13:00 +02:00
return $cfilter ;
2006-05-24 04:28:57 +02:00
}
2008-04-25 21:06:15 +02:00
2007-05-15 16:23:28 +02:00
/**
* fix cat_id criteria to search in comma - separated multiple cats
2008-04-25 21:06:15 +02:00
*
* @ internal
2014-10-06 23:58:06 +02:00
* @ param int | array | string $cats
2007-05-15 16:23:28 +02:00
* @ return array of sql - strings to be OR 'ed or AND' ed together
*/
function _cat_search ( $cats )
{
$cat_filter = array ();
2014-10-06 23:58:06 +02:00
foreach ( is_array ( $cats ) ? $cats : ( is_numeric ( $cats ) ? array ( $cats ) : explode ( ',' , $cats )) as $cat )
2007-05-15 16:23:28 +02:00
{
if ( is_numeric ( $cat )) $cat_filter [] = $this -> db -> concat ( " ',' " , cat_id , " ',' " ) . " LIKE '%, $cat ,%' " ;
}
return $cat_filter ;
}
2008-04-25 21:06:15 +02:00
2006-12-11 08:35:49 +01:00
/**
2013-08-30 09:05:19 +02:00
* Change the ownership of contacts and distribution - lists owned by a given account
2006-12-11 08:35:49 +01:00
*
* @ param int $account_id account - id of the old owner
* @ param int $new_owner account - id of the new owner
*/
function change_owner ( $account_id , $new_owner )
{
if ( ! $new_owner ) // otherwise we would create an account (contact_owner==0)
{
2016-03-06 14:45:15 +01:00
throw Api\Exception\WrongParameter ( __METHOD__ . " ( $account_id , $new_owner ) new owner must not be 0! " );
2006-12-11 08:35:49 +01:00
}
2013-08-30 09:05:19 +02:00
// contacts
2006-12-11 08:35:49 +01:00
$this -> db -> update ( $this -> table_name , array (
'contact_owner' => $new_owner ,
), array (
'contact_owner' => $account_id ,
), __LINE__ , __FILE__ );
2013-08-30 09:05:19 +02:00
// cfs
2013-09-02 13:19:42 +02:00
$this -> db -> update ( self :: EXTRA_TABLE , array (
'contact_owner' => $new_owner
2013-08-30 09:05:19 +02:00
), array (
2013-09-02 13:19:42 +02:00
'contact_owner' => $account_id
), __LINE__ , __FILE__ );
2013-08-30 09:05:19 +02:00
// lists
$this -> db -> update ( $this -> lists_table , array (
'list_owner' => $new_owner ,
), array (
'list_owner' => $account_id ,
), __LINE__ , __FILE__ );
2006-12-11 08:35:49 +01:00
}
2007-07-11 17:34:13 +02:00
2007-03-13 14:38:15 +01:00
/**
* Get the availible distribution lists for givens users and groups
*
2012-01-31 10:57:59 +01:00
* @ param array $uids array of user or group id 's for $uid_column=' list_owners ' , or values for $uid_column ,
* or whole where array : column - name => value ( s ) pairs
2016-03-06 14:45:15 +01:00
* @ param string $uid_column = 'list_owner' column - name or null to use $uids as where array
* @ param string $member_attr = null null : no members , 'contact_uid' , 'contact_id' , 'caldav_name' return members as that attribute
* @ param boolean | int | array $limit_in_ab = false if true only return members from the same owners addressbook ,
2012-02-07 18:10:21 +01:00
* if int | array only return members from the given owners addressbook ( s )
2007-03-13 14:38:15 +01:00
* @ return array with list_id => array ( list_id , list_name , list_owner , ... ) pairs
2008-04-25 21:06:15 +02:00
*/
2012-02-01 01:31:24 +01:00
function get_lists ( $uids , $uid_column = 'list_owner' , $member_attr = null , $limit_in_ab = false )
2007-03-13 14:38:15 +01:00
{
2013-08-06 11:14:20 +02:00
if ( is_array ( $uids ) && array_key_exists ( 'list_id' , $uids ))
{
$uids [] = $this -> db -> expression ( $this -> lists_table , $this -> lists_table . '.' , array ( 'list_id' => $uids [ 'list_id' ]));
unset ( $uids [ 'list_id' ]);
}
2007-03-13 14:38:15 +01:00
$lists = array ();
2012-01-31 10:57:59 +01:00
foreach ( $this -> db -> select ( $this -> lists_table , '*' , $uid_column ? array ( $uid_column => $uids ) : $uids , __LINE__ , __FILE__ ,
2008-03-21 21:56:50 +01:00
false , 'ORDER BY list_owner<>' . ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] . ',list_name' ) as $row )
2007-03-13 14:38:15 +01:00
{
2012-02-01 01:31:24 +01:00
if ( $member_attr ) $row [ 'members' ] = array ();
2007-03-13 14:38:15 +01:00
$lists [ $row [ 'list_id' ]] = $row ;
}
2012-01-31 10:57:59 +01:00
if ( $lists && $member_attr && in_array ( $member_attr , array ( 'contact_id' , 'contact_uid' , 'caldav_name' )))
{
2012-02-07 18:10:21 +01:00
if ( $limit_in_ab )
{
$in_ab_join = " JOIN $this->lists_table ON $this->lists_table .list_id= $this->ab2list_table .list_id AND $this->lists_table . " ;
if ( ! is_bool ( $limit_in_ab ))
{
$in_ab_join .= $this -> db -> expression ( $this -> lists_table , array ( 'list_owner' => $limit_in_ab ));
}
else
{
$in_ab_join .= " list_owner= $this->table_name .contact_owner " ;
}
}
2012-02-01 01:31:24 +01:00
foreach ( $this -> db -> select ( $this -> ab2list_table , " $this->ab2list_table .list_id, $this->table_name . $member_attr " ,
$this -> db -> expression ( $this -> ab2list_table , $this -> ab2list_table . '.' , array ( 'list_id' => array_keys ( $lists ))),
2012-01-31 10:57:59 +01:00
__LINE__ , __FILE__ , false , $member_attr == 'contact_id' ? '' :
2012-02-07 18:10:21 +01:00
'' , false , 0 , " JOIN $this->table_name ON $this->ab2list_table .contact_id= $this->table_name .contact_id " . $in_ab_join ) as $row )
2012-01-31 10:57:59 +01:00
{
$lists [ $row [ 'list_id' ]][ 'members' ][] = $row [ $member_attr ];
}
}
2017-01-23 11:16:24 +01:00
/* groups as list are implemented currently in Contacts\Storage :: get_lists () for all backends
if ( $uid_column == 'list_owner' && in_array ( 0 , ( array ) $uids ) && ( ! $limit_in_ab || in_array ( 0 , ( array ) $limit_in_ab )))
{
foreach ( $GLOBALS [ 'egw' ] -> accounts -> search ( array (
'type' => 'groups'
)) as $account_id => $group )
{
$list = array (
'list_id' => $account_id ,
'list_name' => Api\Accounts :: format_username ( $group [ 'account_lid' ], '' , '' , $account_id ),
'list_owner' => 0 ,
'list_uid' => 'group' . $account_id ,
'list_carddav_name' => 'group' . $account_id . '.vcf' ,
'list_etag' => md5 ( json_encode ( $GLOBALS [ 'egw' ] -> accounts -> members ( $account_id , true )))
);
if ( $member_attr )
{
$list [ 'members' ] = array (); // ToDo
}
$lists [( string ) $account_id ] = $list ;
}
} */
2012-02-01 01:31:24 +01:00
//error_log(__METHOD__.'('.array2string($uids).", '$uid_column', '$member_attr') returning ".array2string($lists));
2007-03-13 14:38:15 +01:00
return $lists ;
}
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
/**
2012-01-31 10:57:59 +01:00
* Adds / updates a distribution list
2007-03-13 14:38:15 +01:00
*
2012-01-31 10:57:59 +01:00
* @ param string | array $keys list - name or array with column - name => value pairs to specify the list
2007-03-13 14:38:15 +01:00
* @ param int $owner user - or group - id
2016-03-06 14:45:15 +01:00
* @ param array $contacts = array () contacts to add ( only for not yet existing lists ! )
2012-01-31 10:57:59 +01:00
* @ param array & $data = array () values for keys 'list_uid' , 'list_carddav_name' , 'list_name'
* @ return int | boolean integer list_id or false on error
2007-03-13 14:38:15 +01:00
*/
2012-01-31 10:57:59 +01:00
function add_list ( $keys , $owner , $contacts = array (), array & $data = array ())
2007-03-13 14:38:15 +01:00
{
2012-02-01 01:59:25 +01:00
//error_log(__METHOD__.'('.array2string($keys).", $owner, ".array2string($contacts).', '.array2string($data).') '.function_backtrace());
2012-01-31 20:47:52 +01:00
if ( ! $keys && ! $data || ! ( int ) $owner ) return false ;
2008-04-25 21:06:15 +02:00
2012-01-31 20:47:52 +01:00
if ( $keys && ! is_array ( $keys )) $keys = array ( 'list_name' => $keys );
if ( $keys )
{
$keys [ 'list_owner' ] = $owner ;
}
else
{
$data [ 'list_owner' ] = $owner ;
}
2012-02-01 01:59:25 +01:00
if ( ! $keys || ! ( $list_id = $this -> db -> select ( $this -> lists_table , 'list_id' , $keys , __LINE__ , __FILE__ ) -> fetchColumn ()))
2012-01-31 10:57:59 +01:00
{
$data [ 'list_created' ] = time ();
$data [ 'list_creator' ] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
}
else
2007-04-15 15:25:06 +02:00
{
2012-01-31 10:57:59 +01:00
$data [] = 'list_etag=list_etag+1' ;
2007-04-15 15:25:06 +02:00
}
2012-01-31 10:57:59 +01:00
$data [ 'list_modified' ] = time ();
$data [ 'list_modifier' ] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
2012-01-31 20:47:52 +01:00
if ( ! $data [ 'list_id' ]) unset ( $data [ 'list_id' ]);
2008-04-25 21:06:15 +02:00
2012-01-31 10:57:59 +01:00
if ( ! $this -> db -> insert ( $this -> lists_table , $data , $keys , __LINE__ , __FILE__ )) return false ;
if ( ! $list_id && ( $list_id = $this -> db -> get_last_insert_id ( $this -> lists_table , 'list_id' )) &&
( ! isset ( $data [ 'list_uid' ]) || ! isset ( $data [ 'list_carddav_name' ])))
2007-03-13 14:38:15 +01:00
{
2012-01-31 10:57:59 +01:00
$update = array ();
if ( ! isset ( $data [ 'list_uid' ]))
{
2016-04-02 21:55:08 +02:00
$update [ 'list_uid' ] = $data [ 'list_uid' ] = Api\CalDAV :: generate_uid ( 'addresbook-lists' , $list_id );
2012-01-31 10:57:59 +01:00
}
if ( ! isset ( $data [ 'list_carddav_name' ]))
2007-03-13 14:38:15 +01:00
{
2012-11-26 12:45:06 +01:00
$update [ 'list_carddav_name' ] = $data [ 'list_carddav_name' ] = $data [ 'list_uid' ] . '.vcf' ;
2007-03-13 14:38:15 +01:00
}
2012-01-31 20:47:52 +01:00
$this -> db -> update ( $this -> lists_table , $update , array ( 'list_id' => $list_id ), __LINE__ , __FILE__ );
2012-01-31 10:57:59 +01:00
$this -> add2list ( $list_id , $contacts , array ());
2007-03-13 14:38:15 +01:00
}
2012-01-31 20:47:52 +01:00
if ( $keys ) $data += $keys ;
//error_log(__METHOD__.'('.array2string($keys).", $owner, ...) data=".array2string($data).' returning '.array2string($list_id));
2007-03-13 14:38:15 +01:00
return $list_id ;
}
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
/**
2012-01-31 10:57:59 +01:00
* Adds contact ( s ) to a distribution list
2007-03-13 14:38:15 +01:00
*
2012-01-31 10:57:59 +01:00
* @ param int | array $contact contact_id ( s )
2007-03-13 14:38:15 +01:00
* @ param int $list list - id
2016-03-06 14:45:15 +01:00
* @ param array $existing = null array of existing contact - id ( s ) of list , to not reread it , eg . array ()
2007-03-13 14:38:15 +01:00
* @ return false on error
*/
2012-01-31 10:57:59 +01:00
function add2list ( $contact , $list , array $existing = null )
2007-03-13 14:38:15 +01:00
{
2012-01-31 10:57:59 +01:00
if ( ! ( int ) $list || ! is_array ( $contact ) && ! ( int ) $contact ) return false ;
2007-03-13 14:38:15 +01:00
2012-01-31 20:47:52 +01:00
if ( ! is_array ( $existing ))
{
$existing = array ();
foreach ( $this -> db -> select ( $this -> ab2list_table , 'contact_id' , array ( 'list_id' => $list ), __LINE__ , __FILE__ ) as $row )
{
$existing [] = $row [ 'contact_id' ];
}
}
2012-01-31 10:57:59 +01:00
if ( ! ( $to_add = array_diff (( array ) $contact , $existing )))
2007-05-01 10:18:33 +02:00
{
return true ; // no need to insert it, would give sql error
}
2012-01-31 10:57:59 +01:00
foreach ( $to_add as $contact )
{
$this -> db -> insert ( $this -> ab2list_table , array (
'contact_id' => $contact ,
'list_id' => $list ,
'list_added' => time (),
'list_added_by' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ],
), array (), __LINE__ , __FILE__ );
}
// update etag
2012-01-31 20:47:52 +01:00
return $this -> db -> update ( $this -> lists_table , array (
2012-01-31 10:57:59 +01:00
'list_etag=list_etag+1' ,
'list_modified' => time (),
'list_modifier' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ],
), array (
2007-03-13 14:38:15 +01:00
'list_id' => $list ,
2012-01-31 10:57:59 +01:00
), __LINE__ , __FILE__ );
2007-03-13 14:38:15 +01:00
}
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
/**
* Removes one contact from distribution list ( s )
*
2012-01-31 10:57:59 +01:00
* @ param int | array $contact contact_id ( s )
2016-03-06 14:45:15 +01:00
* @ param int $list = null list - id or null to remove from all lists
2007-03-13 14:38:15 +01:00
* @ return false on error
*/
function remove_from_list ( $contact , $list = null )
{
2012-01-31 10:57:59 +01:00
if ( ! ( int ) $list && ! is_null ( $list ) || ! is_array ( $contact ) && ! ( int ) $contact ) return false ;
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
$where = array (
'contact_id' => $contact ,
);
2012-01-31 10:57:59 +01:00
if ( ! is_null ( $list ))
{
$where [ 'list_id' ] = $list ;
}
else
{
$list = array ();
foreach ( $this -> db -> select ( $this -> ab2list_table , 'list_id' , $where , __LINE__ , __FILE__ ) as $row )
{
$list [] = $row [ 'list_id' ];
}
}
if ( ! $this -> db -> delete ( $this -> ab2list_table , $where , __LINE__ , __FILE__ ))
{
return false ;
}
foreach (( array ) $list as $list_id )
{
2012-01-31 20:47:52 +01:00
$this -> db -> update ( $this -> lists_table , array (
2012-01-31 10:57:59 +01:00
'list_etag=list_etag+1' ,
'list_modified' => time (),
'list_modifier' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ],
), array (
'list_id' => $list_id ,
), __LINE__ , __FILE__ );
}
return true ;
2007-03-13 14:38:15 +01:00
}
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
/**
* Deletes a distribution list ( incl . it ' s members )
*
2016-03-06 14:45:15 +01:00
* @ param int | array $list list_id ( s )
2007-03-13 14:38:15 +01:00
* @ return number of members deleted or false if list does not exist
*/
function delete_list ( $list )
{
if ( ! $this -> db -> delete ( $this -> lists_table , array ( 'list_id' => $list ), __LINE__ , __FILE__ )) return false ;
2008-04-25 21:06:15 +02:00
2007-03-13 14:38:15 +01:00
$this -> db -> delete ( $this -> ab2list_table , array ( 'list_id' => $list ), __LINE__ , __FILE__ );
2008-04-25 21:06:15 +02:00
return $this -> db -> affected_rows ();
2007-03-13 14:38:15 +01:00
}
2008-04-25 21:06:15 +02:00
2012-01-31 21:06:27 +01:00
/**
* Get ctag ( max list_modified as timestamp ) for lists
*
2016-03-06 14:45:15 +01:00
* @ param int | array $owner = null null for all lists user has access too
2012-01-31 21:06:27 +01:00
* @ return int
*/
function lists_ctag ( $owner = null )
{
if ( is_null ( $owner )) $owner = array_keys ( $this -> grants );
if ( ! ( $modified = $this -> db -> select ( $this -> lists_table , 'MAX(list_modified)' , array ( 'list_owner' => $owner ),
__LINE__ , __FILE__ ) -> fetchColumn ()))
{
return 0 ;
}
return $this -> db -> from_timestamp ( $modified );
}
2008-04-25 21:06:15 +02:00
/**
* Reads a contact , reimplemented to use the uid , if a non - numeric key is given
*
* @ param int | string | array $keys
* @ param string | array $extra_cols
* @ param string $join
* @ return array | boolean
*/
function read ( $keys , $extra_cols = '' , $join = '' )
{
2009-07-15 21:44:09 +02:00
if ( isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ])) {
$minimum_uid_length = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ];
} else {
$minimum_uid_length = 8 ;
}
2008-04-25 21:06:15 +02:00
if ( ! is_array ( $keys ) && ! is_numeric ( $keys ))
{
2013-03-01 09:25:29 +01:00
$keys = array ( 'uid' => $keys );
2008-04-25 21:06:15 +02:00
}
2015-07-21 14:53:23 +02:00
try {
$contact = parent :: read ( $keys , $extra_cols , $join );
}
// catch Illegal mix of collations (ascii_general_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '=' (1267)
// caused by non-ascii chars compared with ascii field uid
2016-03-06 14:45:15 +01:00
catch ( Api\Db\Exception $e ) {
2015-07-21 14:53:23 +02:00
_egw_log_exception ( $e );
return false ;
}
2010-04-09 00:42:25 +02:00
2009-07-15 21:44:09 +02:00
// enforce a minium uid strength
if ( is_array ( $contact ) && ( ! isset ( $contact [ 'uid' ])
|| strlen ( $contact [ 'uid' ]) < $minimum_uid_length )) {
2016-04-02 21:55:08 +02:00
parent :: update ( array ( 'uid' => Api\CalDAV :: generate_uid ( 'addressbook' , $contact [ 'id' ])));
2009-07-15 21:44:09 +02:00
}
return $contact ;
2008-04-25 21:06:15 +02:00
}
/**
* Saves a contact , reimplemented to check a given etag and set a uid
*
* @ param array $keys if given $keys are copied to data before saveing => allows a save as
2016-03-06 14:45:15 +01:00
* @ param string | array $extra_where = null extra where clause , eg . to check the etag , returns 'nothing_affected' if not affected rows
2008-04-25 21:06:15 +02:00
* @ return int 0 on success and errno != 0 else
*/
2012-03-04 14:33:10 +01:00
function save ( $keys = NULL , $extra_where = NULL )
2008-04-25 21:06:15 +02:00
{
2016-03-06 14:45:15 +01:00
unset ( $extra_where ); // not used, but required by function signature
2009-07-15 21:44:09 +02:00
if ( isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ])) {
$minimum_uid_length = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ];
} else {
$minimum_uid_length = 8 ;
}
2008-04-25 21:06:15 +02:00
if ( is_array ( $keys ) && count ( $keys )) $this -> data_merge ( $keys );
2008-07-02 17:28:52 +02:00
$new_entry = ! $this -> data [ 'id' ];
2008-04-26 10:46:44 +02:00
if ( isset ( $this -> data [ 'etag' ])) // do we have an etag in the data to write
2008-04-25 21:06:15 +02:00
{
$etag = $this -> data [ 'etag' ];
unset ( $this -> data [ 'etag' ]);
if ( ! ( $err = parent :: save ( array ( 'contact_etag=contact_etag+1' ), array ( 'contact_etag' => $etag ))))
{
$this -> data [ 'etag' ] = $etag + 1 ;
}
else
{
$this -> data [ 'etag' ] = $etag ;
}
}
else
{
2008-05-17 08:44:17 +02:00
unset ( $this -> data [ 'etag' ]);
2008-04-26 10:46:44 +02:00
if ( ! ( $err = parent :: save ( array ( 'contact_etag=contact_etag+1' ))) && $new_entry )
{
$this -> data [ 'etag' ] = 0 ;
}
2008-04-25 21:06:15 +02:00
}
2010-04-09 00:42:25 +02:00
2011-04-05 22:39:13 +02:00
$update = array ();
2008-05-20 06:59:26 +02:00
// enforce a minium uid strength
2011-04-05 22:39:13 +02:00
if ( ! isset ( $this -> data [ 'uid' ]) || strlen ( $this -> data [ 'uid' ]) < $minimum_uid_length )
{
2016-04-02 21:55:08 +02:00
$update [ 'uid' ] = Api\CalDAV :: generate_uid ( 'addressbook' , $this -> data [ 'id' ]);
2008-04-26 10:46:44 +02:00
//echo "<p>set uid={$this->data['uid']}, etag={$this->data['etag']}</p>";
2008-04-25 21:06:15 +02:00
}
2011-04-05 22:39:13 +02:00
// set carddav_name, if not given by caller
2011-09-28 11:48:47 +02:00
if ( empty ( $this -> data [ 'carddav_name' ]))
2011-04-05 22:39:13 +02:00
{
$update [ 'carddav_name' ] = $this -> data [ 'id' ] . '.vcf' ;
}
2017-09-19 11:38:02 +02:00
// update photo in entry-directory, unless hinted it is unchanged
if ( ! $err && $this -> data [ 'photo_unchanged' ] !== true )
{
// in case files bit-field is not available read it from DB
if ( ! isset ( $this -> data [ 'files' ]))
{
$this -> data [ 'files' ] = ( int ) $this -> db -> select ( $this -> table_name , 'contact_files' , array (
'contact_id' => $this -> data [ 'id' ],
), __LINE__ , __FILE__ ) -> fetchColumn ();
}
$path = Api\Link :: vfs_path ( 'addressbook' , $this -> data [ 'id' ], Api\Contacts :: FILES_PHOTO );
$backup = Api\Vfs :: $is_root ; Api\Vfs :: $is_root = true ;
if ( empty ( $this -> data [ 'jpegphoto' ]))
{
unlink ( $path );
$update [ 'files' ] = $this -> data [ 'files' ] & ~ Api\Contacts :: FILES_BIT_PHOTO ;
}
else
{
file_put_contents ( $path , $this -> data [ 'jpegphoto' ]);
$update [ 'files' ] = $this -> data [ 'files' ] | Api\Contacts :: FILES_BIT_PHOTO ;
}
Api\Vfs :: $is_root = $backup ;
}
2011-04-05 22:39:13 +02:00
if ( ! $err && $update )
{
parent :: update ( $update );
}
2008-04-25 21:06:15 +02:00
return $err ;
}
2007-03-13 14:38:15 +01:00
/**
* Read data of a distribution list
*
* @ param int $list list_id
* @ return array of data or false if list does not exist
*/
function read_list ( $list )
{
if ( ! $list ) return false ;
2008-04-25 21:06:15 +02:00
2008-03-21 21:56:50 +01:00
return $this -> db -> select ( $this -> lists_table , '*' , array ( 'list_id' => $list ), __LINE__ , __FILE__ ) -> fetch ();
2008-04-25 21:06:15 +02:00
}
2010-04-09 00:42:25 +02:00
/**
2010-04-20 10:11:34 +02:00
* saves custom field data
* Re - implemented to deal with extra contact_owner column
*
* @ param array $data data to save ( cf ' s have to be prefixed with self :: CF_PREFIX = #)
2016-02-22 18:13:48 +01:00
* @ param array $extra_cols = array () extra - data to be saved
2010-04-20 10:11:34 +02:00
* @ return bool false on success , errornumber on failure
*/
2017-04-10 18:39:04 +02:00
function save_customfields ( & $data , array $extra_cols = array ())
2010-04-20 10:11:34 +02:00
{
2016-02-22 18:13:48 +01:00
return parent :: save_customfields ( $data , array ( 'contact_owner' => $data [ 'owner' ]) + $extra_cols );
2010-04-20 10:11:34 +02:00
}
2010-04-20 19:49:32 +02:00
/**
* Deletes custom field data
* Implemented to deal with LDAP backend , which saves CFs in SQL , but the account record is in LDAP
*/
function delete_customfields ( $data )
{
$this -> db -> delete ( $this -> extra_table , $data , __LINE__ , __FILE__ );
}
2007-07-04 21:13:00 +02:00
}