2006-04-23 16:40:31 +02:00
< ? php
2006-06-13 06:30:16 +02:00
/**
* Addressbook - SQL backend
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ package addressbook
2012-01-31 10:57:59 +01:00
* @ copyright ( c ) 2006 - 12 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
/**
* SQL storage object of the adressbook
*/
2010-04-09 00:42:25 +02:00
class addressbook_sql extends so_sql_cf
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 )
*/
2011-03-20 13:37:22 +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
/**
* filter to show only active account ( and not already expired ones )
* UNIX_TIMESTAMP ( NOW ()) gets replaced with value of time () in the code !
*/
const ACOUNT_ACTIVE_FILTER = '(account_expires IS NULL OR account_expires = -1 OR account_expires > UNIX_TIMESTAMP(NOW()))' ;
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
2010-07-30 17:30:46 +02:00
/**
* Constructor
*
* @ param egw_db $db = null
*/
function __construct ( egw_db $db = null )
2006-06-13 06:30:16 +02:00
{
2010-07-30 17:30:46 +02:00
parent :: __construct ( 'phpgwapi' , 'egw_addressbook' , 'egw_addressbook_extra' , 'contact_' ,
$extra_key = '_name' , $extra_value = '_value' , $extra_id = '_id' , $db );
2010-04-09 00:42:25 +02:00
// Get custom fields from addressbook instead of phpgwapi
$this -> customfields = config :: get_customfields ( '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 ();
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
if (( int ) $filter [ 'cat_id' ])
{
$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' ] .
" OR contact_private=0 AND " . $this -> table_name . " .contact_owner IN ( " .
2006-04-30 11:34:24 +02:00
implode ( ',' , array_keys ( $this -> grants )) . " )) " ;
}
}
if ( $param [ 'searchletter' ])
{
2011-04-11 18:33:00 +02:00
$filter [] = 'org_name ' . $this -> db -> capabilities [ egw_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";
}
$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) " ;
$append = " GROUP BY org_name HAVING $by_expr > 1 ORDER BY org_name $sort " ;
2006-04-30 11:34:24 +02:00
parent :: search ( $param [ 'search' ], array ( 'org_name' ), $append , array (
" 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 " ,
2011-03-30 15:31:09 +02:00
), $wildcard , false , $op /*'OR'*/ , 'UNION' , $filter );
2006-04-30 11:34:24 +02:00
// org by location
$append = " GROUP BY org_name, $by ORDER BY org_name $sort , $by $sort " ;
parent :: search ( $param [ 'search' ], array ( 'org_name' ), $append , array (
" 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 " ,
2011-03-30 15:31:09 +02:00
), $wildcard , false , $op /*'OR'*/ , 'UNION' , $filter );
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'*/ ,
2006-04-30 11:34:24 +02:00
array ( $param [ 'start' ], $param [ 'num_rows' ]), $filter );
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 ();
foreach ( $rows as $n => $row )
{
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 ] : '|||' ) : '' );
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' ]))
{
foreach (( array ) parent :: search ( $criteria , array ( 'org_name' , 'org_unit' , 'adr_one_locality' ), 'GROUP BY org_name,org_unit,adr_one_locality' ,
2011-03-30 15:31:09 +02:00
'' , $wildcard , false , $op /*'AND'*/ , false , $filter ) 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 );
}
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 .
*
* @ param array / string $criteria array of key and data cols , OR a SQL query ( content for WHERE ), fully quoted ( ! )
2008-04-25 21:06:15 +02:00
* @ 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
2006-04-23 16:40:31 +02: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
2008-04-25 21:06:15 +02:00
* @ 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 !
* @ param boolean $need_full_no_count = false If true an unlimited query is run to determine the total number of rows , default false
* @ return boolean / 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 = '' , $need_full_no_count = false )
{
2011-03-23 10:37:19 +01:00
if (( int ) $this -> debug >= 4 ) echo '<p>' . __METHOD__ . '(' . array2string ( $criteria , true ) . ',' . array2string ( $only_keys ) . " ,' $order_by ',' $extra_cols ',' $wildcard ',' $empty ',' $op ', $start , " . array2string ( $filter , true ) . " ,' $join ')</p> \n " ;
//error_log(__METHOD__.'('.array2string($criteria,true).','.array2string($only_keys).",'$order_by','$extra_cols','$wildcard','$empty','$op',$start,".array2string($filter,true).",'$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' ;
}
$filter [] = $this -> _cat_filter (( int ) $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
2006-07-08 02:36:23 +02:00
if ( isset ( $this -> grants ) && ! ( 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
if ( ! ( $filter [ 'owner' ] = array_intersect (( array ) $filter [ 'owner' ], array_keys ( $this -> grants ))))
{
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
2010-11-05 09:56:41 +01:00
elseif ( ! in_array ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ], $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
elseif ( count ( $filter [ 'owner' ]) > 1 )
{
$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' ] .
" 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) " ;
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' ]))
{
$join .= " JOIN $this->ab2list_table ON $this->table_name .contact_id= $this->ab2list_table .contact_id AND list_id= " . ( int ) $filter [ 'list' ];
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)
if ( ! $owner && substr ( $this -> account_repository , 0 , 3 ) == 'sql' &&
strpos ( $join , $GLOBALS [ 'egw' ] -> accounts -> backend -> table ) === false && ! array_key_exists ( 'account_id' , $filter ))
{
$join .= self :: ACCOUNT_ACTIVE_JOIN ;
$filter [] = str_replace ( 'UNIX_TIMESTAMP(NOW())' , time (), self :: ACOUNT_ACTIVE_FILTER );
}
if ( $join || $criteria && is_string ( $criteria )) // 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
2011-04-15 08:46:51 +02:00
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 ];
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
}
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
2006-05-24 04:28:57 +02:00
* @ param int $cat_id
* @ 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 ))
{
$GLOBALS [ 'egw' ] -> categories = CreateObject ( 'phpgwapi.categories' );
}
foreach ( $GLOBALS [ 'egw' ] -> categories -> return_all_children (( int ) $cat_id ) as $cat )
{
2007-07-04 21:13:00 +02:00
$cat_filter [] = $this -> db -> concat ( " ',' " , cat_id , " ',' " ) . " $not LIKE '%, $cat ,%' " ;
}
$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
2007-05-15 16:23:28 +02:00
* @ param int / array $cats
* @ return array of sql - strings to be OR 'ed or AND' ed together
*/
function _cat_search ( $cats )
{
$cat_filter = array ();
foreach ( is_array ( $cats ) ? $cats : array ( $cats ) as $cat )
{
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
/**
* Change the ownership of contacts owned by a given account
*
* @ 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)
{
die ( " socontacts_sql::change_owner( $account_id , $new_owner ) new owner must not be 0 " );
}
$this -> db -> update ( $this -> table_name , array (
'contact_owner' => $new_owner ,
), array (
'contact_owner' => $account_id ,
), __LINE__ , __FILE__ );
}
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
* @ 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
2012-02-01 01:31:24 +01:00
* @ param boolean $limit_in_ab = false if true only return members from the same owners addressbook
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
{
$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-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-01 01:31:24 +01:00
'' , false , 0 , " JOIN $this->table_name ON $this->ab2list_table .contact_id= $this->table_name .contact_id " .
( $limit_in_ab ? " JOIN $this->lists_table ON $this->lists_table .list_id= $this->ab2list_table .list_id AND $this->lists_table .list_owner= $this->table_name .contact_owner " : '' )) as $row )
2012-01-31 10:57:59 +01:00
{
$lists [ $row [ 'list_id' ]][ 'members' ][] = $row [ $member_attr ];
}
}
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
2012-01-31 10:57:59 +01:00
* @ param array $contacts = array () contacts to add ( only for not yet existing lists ! )
* @ 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-01-31 20:47:52 +01:00
error_log ( __METHOD__ . '(' . array2string ( $keys ) . " , $owner , ..., " . array2string ( $data ) . ')' );
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 ;
}
if ( ! $keys || ! ( $list_id = $this -> db -> select ( $this -> lists_table , 'list_id' , $keys ) -> 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' ]))
{
2012-01-31 20:47:52 +01:00
$update [ 'list_uid' ] = $data [ 'list_uid' ] = common :: 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-01-31 10:57:59 +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
2012-01-31 10:57:59 +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 )
2007-03-13 14:38:15 +01:00
* @ param int $list = null list - id or null to remove from all lists
* @ 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 )
*
* @ param int / array $list list_id ( s )
* @ 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
*
* @ param int | array $owner = null null for all lists user has access too
* @ 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 ))
{
$keys = array ( 'contact_uid' => $keys );
}
2009-07-15 21:44:09 +02:00
$contact = parent :: read ( $keys , $extra_cols , $join );
2010-04-09 00:42:25 +02:00
// Change autoinc_id to match $this->db_cols
$this -> autoinc_id = $this -> db_cols [ $this -> autoinc_id ];
if (( $id = ( int ) $this -> data [ $this -> autoinc_id ]) && $cfs = $this -> read_customfields ( $keys )) {
2011-01-06 15:13:52 +01:00
if ( is_array ( $cfs [ $id ])) $contact = array_merge ( $contact , $cfs [ $id ]);
2010-04-09 00:42:25 +02:00
}
$this -> autoinc_id = array_search ( $this -> autoinc_id , $this -> db_cols );
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 )) {
parent :: update ( array ( 'uid' => common :: generate_uid ( 'addressbook' , $contact [ 'id' ])));
}
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
* @ param string | array $extra_where = null extra where clause , eg . to check the etag , returns 'nothing_affected' if not affected rows
* @ return int 0 on success and errno != 0 else
*/
function save ( $keys = null )
{
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 )
{
$update [ 'uid' ] = common :: 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' ;
}
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 = #)
* @ return bool false on success , errornumber on failure
*/
function save_customfields ( $data )
{
foreach (( array ) $this -> customfields as $name => $options )
{
if ( ! isset ( $data [ $field = $this -> get_cf_field ( $name )])) continue ;
$where = array (
$this -> extra_id => $data [ 'id' ],
$this -> extra_key => $name ,
);
$is_multiple = $this -> is_multiple ( $name );
// we explicitly need to delete fields, if value is empty or field allows multiple values or we have no unique index
if ( empty ( $data [ $field ]) || $is_multiple || ! $this -> extra_has_unique_index )
{
$this -> db -> delete ( $this -> extra_table , $where , __LINE__ , __FILE__ , $this -> app );
if ( empty ( $data [ $field ])) continue ; // nothing else to do for empty values
}
foreach ( $is_multiple && ! is_array ( $data [ $field ]) ? explode ( ',' , $data [ $field ]) : ( array ) $data [ $field ] as $value )
{
if ( ! $this -> db -> insert ( $this -> extra_table , array ( $this -> extra_value => $value , 'contact_owner' => $data [ 'owner' ]), $where , __LINE__ , __FILE__ , $this -> app ))
{
return $this -> db -> Errno ;
}
}
}
return false ; // no error
}
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
}