2004-11-07 15:38:00 +01:00
< ? php
2007-03-09 12:39:47 +01:00
/**
2011-04-06 21:26:10 +02:00
* EGroupware - Calendar ' s storage - object
2007-03-09 12:39:47 +01:00
*
* @ link http :// www . egroupware . org
* @ package calendar
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2009-11-04 08:57:55 +01:00
* @ author Christian Binder < christian - AT - jaytraxx . de >
2009-07-15 22:35:56 +02:00
* @ author Joerg Lehrke < jlehrke @ noc . de >
2016-05-01 19:47:59 +02:00
* @ copyright ( c ) 2005 - 16 by RalfBecker - At - outdoor - training . de
2007-03-09 12:39:47 +01:00
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ version $Id $
*/
2004-11-07 15:38:00 +01:00
2016-05-01 19:47:59 +02:00
use EGroupware\Api ;
use EGroupware\Api\Link ;
use EGroupware\Api\Acl ;
2004-11-07 15:38:00 +01:00
/**
2005-11-09 00:15:14 +01:00
* some necessary defines used by the calendar
*/
2009-08-04 19:14:16 +02:00
if ( ! extension_loaded ( 'mcal' ))
2005-11-09 00:15:14 +01:00
{
define ( 'MCAL_RECUR_NONE' , 0 );
define ( 'MCAL_RECUR_DAILY' , 1 );
define ( 'MCAL_RECUR_WEEKLY' , 2 );
define ( 'MCAL_RECUR_MONTHLY_MDAY' , 3 );
define ( 'MCAL_RECUR_MONTHLY_WDAY' , 4 );
define ( 'MCAL_RECUR_YEARLY' , 5 );
define ( 'MCAL_RECUR_SECONDLY' , 6 );
define ( 'MCAL_RECUR_MINUTELY' , 7 );
define ( 'MCAL_RECUR_HOURLY' , 8 );
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
define ( 'MCAL_M_SUNDAY' , 1 );
define ( 'MCAL_M_MONDAY' , 2 );
define ( 'MCAL_M_TUESDAY' , 4 );
define ( 'MCAL_M_WEDNESDAY' , 8 );
define ( 'MCAL_M_THURSDAY' , 16 );
define ( 'MCAL_M_FRIDAY' , 32 );
define ( 'MCAL_M_SATURDAY' , 64 );
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
define ( 'MCAL_M_WEEKDAYS' , 62 );
define ( 'MCAL_M_WEEKEND' , 65 );
define ( 'MCAL_M_ALLDAYS' , 127 );
}
define ( 'REJECTED' , 0 );
define ( 'NO_RESPONSE' , 1 );
define ( 'TENTATIVE' , 2 );
define ( 'ACCEPTED' , 3 );
2010-02-17 14:29:28 +01:00
define ( 'DELEGATED' , 4 );
2005-11-09 00:15:14 +01:00
2009-08-10 11:24:39 +02:00
define ( 'HOUR_s' , 60 * 60 );
define ( 'DAY_s' , 24 * HOUR_s );
define ( 'WEEK_s' , 7 * DAY_s );
2005-11-09 00:15:14 +01:00
/**
* Class to store all calendar data ( storage object )
2004-11-07 15:38:00 +01:00
*
2012-09-18 10:02:56 +02:00
* Tables used by calendar_so :
* - egw_cal : general calendar data : cal_id , title , describtion , locations , range - start and - end dates
* - egw_cal_dates : start - and enddates ( multiple entry per cal_id for recuring events ! ), recur_exception flag
2007-10-09 10:50:06 +02:00
* - egw_cal_user : participant info including status ( multiple entries per cal_id AND startdate for recuring events )
2012-09-18 10:02:56 +02:00
* - egw_cal_repeats : recur - data : type , interval , days etc .
2005-11-09 00:15:14 +01:00
* - egw_cal_extra : custom fields ( multiple entries per cal_id possible )
2006-03-29 09:01:18 +02:00
*
2012-08-11 12:01:02 +02:00
* The new UI , BO and SO classes have a strict definition , in which timezone they operate :
2004-11-07 15:38:00 +01:00
* UI only operates in user - time , so there have to be no conversation at all !!!
* BO ' s functions take and return user - time only ( ! ), they convert internaly everything to servertime , because
2005-11-09 00:15:14 +01:00
* SO operates only on server - time
2012-01-23 08:41:29 +01:00
*
* DB - model uses egw_cal_user . cal_status = 'X' for participants who got deleted . They never get returned by
* read or search methods , but influence the ctag of the deleted users calendar !
2012-09-18 10:02:56 +02:00
*
2014-10-28 17:01:55 +01:00
* DB - model uses egw_cal_user . cal_status = 'E' for participants only participating in exceptions of recurring
* events , so whole recurring event get found for these participants too !
*
2015-08-17 16:07:25 +02:00
* All update methods now take care to update modification time of ( evtl . existing ) series master too ,
2013-02-26 09:48:50 +01:00
* to force an etag , ctag and sync - token change ! Methods not doing that are private to this class .
*
2012-09-18 10:02:56 +02:00
* range_start / _end in main - table contains start and end of whole event series ( range_end is NULL for unlimited recuring events ),
* saving the need to always join dates table , to query non - enumerating recuring events ( like CalDAV or ActiveSync does ) .
* This effectivly stores MIN ( cal_start ) and MAX ( cal_end ) permanently as column in main - table and improves speed tremendiously
* ( few milisecs instead of more then 2 minutes on huge installations ) !
* It ' s set in calendar_so :: save from start and end or recur_enddate , so nothing changes for higher level classes .
*
2015-08-17 16:07:25 +02:00
* egw_cal_user . cal_user_id contains since 14.3 . 001 only an md5 - hash of a lowercased raw email address ( not rfc822 address ! ) .
* Real email address and other possible attendee information for iCal or CalDAV are stored in cal_user_attendee .
* This allows a short 32 byte ascii cal_user_id and also storing attendee information for accounts and contacts .
* Outside of this class uid for email address is still " e $cn < $email > " or " e $email " .
* We use calendar_so :: split_user ( $uid , & $user_type , & $user_id , $md5_email = false ) with last param true to generate
* egw_cal_user . cal_user_id for DB and calendar_so :: combine_user ( $user_type , $user_id , $user_attendee ) to generate
* uid used outside of this class . Both methods are unchanged when using with their default parameters .
*
2012-09-18 10:02:56 +02:00
* @ ToDo drop egw_cal_repeats table in favor of a rrule colum in main table ( saves always used left join and allows to store all sorts of rrules )
2004-11-07 15:38:00 +01:00
*/
2008-06-07 19:45:33 +02:00
class calendar_so
2004-11-07 15:38:00 +01:00
{
/**
* name of the main calendar table and prefix for all other calendar tables
*/
2005-11-09 00:15:14 +01:00
var $cal_table = 'egw_cal' ;
var $extra_table , $repeats_table , $user_table , $dates_table , $all_tables ;
2006-03-29 09:01:18 +02:00
2004-11-07 15:38:00 +01:00
/**
2009-05-06 11:11:37 +02:00
* reference to global db - object
2008-05-08 00:12:25 +02:00
*
2016-05-01 19:47:59 +02:00
* @ var Api\Db
2004-11-07 15:38:00 +01:00
*/
var $db ;
2007-05-07 10:27:50 +02:00
/**
* instance of the async object
*
2016-05-09 11:15:48 +02:00
* @ var Api\Asyncservice
2007-05-07 10:27:50 +02:00
*/
var $async ;
2008-07-18 13:36:09 +02:00
/**
* SQL to sort by status U , T , A , R
*
*/
const STATUS_SORT = " CASE cal_status WHEN 'U' THEN 1 WHEN 'T' THEN 2 WHEN 'A' THEN 3 WHEN 'R' THEN 4 ELSE 0 END ASC " ;
2005-11-09 00:15:14 +01:00
2009-11-19 11:13:17 +01:00
/**
* Cached timezone data
*
* @ var array id => data
*/
protected static $tz_cache = array ();
2004-11-07 15:38:00 +01:00
/**
* Constructor of the socal class
*/
2008-06-07 19:45:33 +02:00
function __construct ()
2004-11-07 15:38:00 +01:00
{
2008-03-21 21:30:19 +01:00
$this -> async = $GLOBALS [ 'egw' ] -> asyncservice ;
2008-03-15 15:10:20 +01:00
$this -> db = $GLOBALS [ 'egw' ] -> db ;
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
$this -> all_tables = array ( $this -> cal_table );
foreach ( array ( 'extra' , 'repeats' , 'user' , 'dates' ) as $name )
2004-11-07 15:38:00 +01:00
{
$vname = $name . '_table' ;
2005-11-09 00:15:14 +01:00
$this -> all_tables [] = $this -> $vname = $this -> cal_table . '_' . $name ;
2004-11-07 15:38:00 +01:00
}
}
2006-03-29 09:01:18 +02:00
2015-01-14 20:41:01 +01:00
/**
* Return sql to fetch all events in a given timerange , to be used instead of full table in further sql queries
*
* @ param int $start
* @ param int $end
2015-01-19 20:32:26 +01:00
* @ param array $_where = null
2015-01-14 20:41:01 +01:00
* @ param boolean $deleted = false
* @ return string
*/
2015-01-19 20:32:26 +01:00
protected function cal_range_view ( $start , $end , array $_where = null , $deleted = false )
2015-01-14 20:41:01 +01:00
{
2015-03-31 16:28:35 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'no_timerange_views' ] || ! $start ) // using view without start-date is slower!
2015-01-19 20:32:26 +01:00
{
return $this -> cal_table ; // no need / use for a view
}
2015-01-14 20:41:01 +01:00
2015-01-19 20:32:26 +01:00
$where = array ();
if ( isset ( $deleted )) $where [] = " cal_deleted IS " . ( $deleted ? '' : 'NOT' ) . ' NULL' ;
if ( $end ) $where [] = " range_start< " . ( int ) $end ;
if ( $start ) $where [] = " (range_end IS NULL OR range_end> " . ( int ) $start . " ) " ;
if ( $_where ) $where = array_merge ( $where , $_where );
2015-01-14 20:41:01 +01:00
2015-01-19 20:32:26 +01:00
$sql = " (SELECT * FROM $this->cal_table WHERE " . $this -> db -> expression ( $this -> cal_table , $where ) . " ) $this->cal_table " ;
return $sql ;
}
/**
* Return sql to fetch all dates in a given timerange , to be used instead of full dates table in further sql queries
*
2015-02-18 09:15:54 +01:00
* Currently NOT used , as using two views joined together appears slower in my tests ( probably because no index ) then
* joining cal_range_view with real dates table ( with index ) .
*
2015-01-19 20:32:26 +01:00
* @ param int $start
* @ param int $end
* @ param array $_where = null
* @ param boolean $deleted = false
* @ return string
*/
protected function dates_range_view ( $start , $end , array $_where = null , $deleted = false )
{
2015-03-31 16:28:35 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'no_timerange_views' ] || ! $start || ! $end ) // using view without start- AND end-date is slower!
2015-01-19 20:32:26 +01:00
{
return $this -> dates_table ; // no need / use for a view
}
$where = array ();
if ( isset ( $deleted )) $where [ 'recur_exception' ] = $deleted ;
if ( $end ) $where [] = " cal_start< " . ( int ) $end ;
if ( $start ) $where [] = " cal_end> " . ( int ) $start ;
if ( $_where ) $where = array_merge ( $where , $_where );
2016-05-01 19:47:59 +02:00
// Api\Db::union uses Api\Db::select which check if join contains "WHERE"
2015-01-19 20:32:26 +01:00
// to support old join syntax like ", other_table WHERE ...",
// therefore we have to use eg. "WHERe" instead!
$sql = " (SELECT * FROM $this->dates_table WHERe " . $this -> db -> expression ( $this -> dates_table , $where ) . " ) $this->dates_table " ;
2015-01-14 20:41:01 +01:00
return $sql ;
}
/**
* Return events in a given timespan containing given participants ( similar to search but quicker )
*
* Not all search parameters are currently supported !!!
*
* @ param int $start startdate of the search / list ( servertime )
* @ param int $end enddate of the search / list ( servertime )
* @ param int | array $users user - id or array of user - id ' s , ! $users means all entries regardless of users
* @ param int | array $cat_id = 0 mixed category - id or array of cat - id ' s ( incl . all sub - categories ), default 0 = all
* @ param string $filter = 'default' string filter - name : all ( not rejected ), accepted , unknown , tentative , rejected or everything ( incl . rejected , deleted )
* @ param int | boolean $offset = False offset for a limited query or False ( default )
* @ param int $num_rows = 0 number of rows to return if offset set , default 0 = use default in user prefs
* @ param array $params = array ()
* @ param string | array $params [ 'query' ] string : pattern so search for , if unset or empty all matching entries are returned ( no search )
* Please Note : a search never returns repeating events more then once AND does not honor start + end date !!!
* array : everything is directly used as $where
* @ param string $params [ 'order' ] = 'cal_start' column - names plus optional DESC | ASC separted by comma
* @ param string $params [ 'sql_filter' ] sql to be and ' ed into query ( fully quoted )
* @ param string | array $params [ 'cols' ] what to select , default " $this->repeats_table .*, $this->cal_table .*,cal_start,cal_end,cal_recur_date " ,
* if specified and not false an iterator for the rows is returned
* @ param string $params [ 'append' ] SQL to append to the query before $order , eg . for a GROUP BY clause
* @ param array $params [ 'cfs' ] custom fields to query , null = none , array () = all , or array with cfs names
* @ param array $params [ 'users' ] raw parameter as passed to calendar_bo :: search () no memberships resolved !
* @ param boolean $params [ 'master_only' ] = false , true only take into account participants / status from master ( for AS )
* @ param boolean $params [ 'enum_recuring' ] = true enumerate recuring events
* @ param int $remove_rejected_by_user = null add join to remove entry , if given user has rejected it
* @ return array of events
*/
2015-01-19 20:32:26 +01:00
function & events ( $start , $end , $users , $cat_id = 0 , $filter = 'all' , $offset = False , $num_rows = 0 , array $params = array (), $remove_rejected_by_user = null )
2015-01-14 20:41:01 +01:00
{
2015-01-19 20:32:26 +01:00
error_log ( __METHOD__ . '(' . ( $start ? date ( 'Y-m-d H:i' , $start ) : '' ) . ',' . ( $end ? date ( 'Y-m-d H:i' , $end ) : '' ) . ',' . array2string ( $users ) . ',' . array2string ( $cat_id ) . " ,' $filter ', " . array2string ( $offset ) . " , $num_rows , " . array2string ( $params ) . ') ' . function_backtrace ());
$start_time = microtime ( true );
2015-01-14 20:41:01 +01:00
// not everything is supported by now
2015-01-19 20:32:26 +01:00
if ( ! $start || ! $end || is_string ( $params [ 'query' ]) ||
//in_array($filter,array('owner','deleted')) ||
2015-01-14 20:41:01 +01:00
$params [ 'enum_recuring' ] === false )
{
2016-05-01 19:47:59 +02:00
throw new Api\Exception\AssertionFailed ( " Unsupported value for parameters! " );
2015-01-14 20:41:01 +01:00
}
$where = is_array ( $params [ 'query' ]) ? $params [ 'query' ] : array ();
if ( $cat_id ) $where [] = $this -> cat_filter ( $cat_id );
2015-01-19 20:32:26 +01:00
$egw_cal = $this -> cal_range_view ( $start , $end , $where , $filter == 'everything' ? null : $filter != 'deleted' );
2015-01-14 20:41:01 +01:00
2015-01-19 20:32:26 +01:00
$status_filter = $this -> status_filter ( $filter , $params [ 'enum_recuring' ]);
2015-01-14 20:41:01 +01:00
$sql = " SELECT DISTINCT { $this -> cal_table } _repeats.*, $this->cal_table .*, \n " .
" CASE WHEN recur_type IS NULL THEN egw_cal.range_start ELSE cal_start END AS cal_start, \n " .
" CASE WHEN recur_type IS NULL THEN egw_cal.range_end ELSE cal_end END AS cal_end \n " .
// using time-limited range view, instead of complete table, give a big performance plus
2015-01-19 20:32:26 +01:00
" FROM $egw_cal\n " .
2015-01-14 20:41:01 +01:00
" JOIN egw_cal_user ON egw_cal_user.cal_id=egw_cal.cal_id \n " .
// need to left join dates, as egw_cal_user.recur_date is null for non-recuring event
" LEFT JOIN egw_cal_dates ON egw_cal_user.cal_id=egw_cal_dates.cal_id AND egw_cal_dates.cal_start=egw_cal_user.cal_recur_date \n " .
" LEFT JOIN egw_cal_repeats ON egw_cal_user.cal_id=egw_cal_repeats.cal_id \n " .
2015-01-19 20:32:26 +01:00
" WHERE " . ( $status_filter ? $this -> db -> expression ( $this -> table , $status_filter , " AND \n " ) : '' ) .
" CASE WHEN recur_type IS NULL THEN egw_cal.range_start ELSE cal_start END< " . ( int ) $end . " AND \n " .
" CASE WHEN recur_type IS NULL THEN egw_cal.range_end ELSE cal_end END> " . ( int ) $start ;
2015-01-14 20:41:01 +01:00
if ( $users )
{
2015-01-19 20:32:26 +01:00
// fix $users to also prefix system users and groups (with 'u')
if ( ! is_array ( $users )) $users = $users ? ( array ) $users : array ();
foreach ( $users as & $uid )
{
2015-08-17 16:07:25 +02:00
$user_type = $user_id = null ;
self :: split_user ( $uid , $user_type , $user_id , true );
$uid = $user_type . $user_id ;
2015-01-19 20:32:26 +01:00
}
$sql .= " AND \n CONCAT(cal_user_type,cal_user_id) IN ( " . implode ( ',' , array_map ( array ( $this -> db , 'quote' ), $users )) . " ) " ;
}
if ( $remove_rejected_by_user && ! in_array ( $filter , array ( 'everything' , 'deleted' )))
{
$sql .= " AND \n (cal_user_type!='u' OR cal_user_id!= " . ( int ) $remove_rejected_by_user . " OR cal_status!='R') " ;
}
if ( ! empty ( $params [ 'sql_filter' ]) && is_string ( $params [ 'sql_filter' ]))
{
$sql .= " AND \n " . $params [ 'sql_filter' ];
2015-01-14 20:41:01 +01:00
}
2015-01-19 20:32:26 +01:00
if ( $params [ 'order' ]) // only order if requested
{
if ( ! preg_match ( '/^[a-z_ ,c]+$/i' , $params [ 'order' ])) $params [ 'order' ] = 'cal_start' ; // gard against SQL injection
$sql .= " \n ORDER BY " . $params [ 'order' ];
}
2016-05-01 19:47:59 +02:00
if ( $offset === false ) // return all rows --> Api\Db::query wants offset=0, num_rows=-1
2015-01-19 20:32:26 +01:00
{
$offset = 0 ;
$num_rows = - 1 ;
}
$events =& $this -> get_events ( $this -> db -> query ( $sql , __LINE__ , __FILE__ , $offset , $num_rows ));
error_log ( __METHOD__ . " (...) $sql --> " . number_format ( microtime ( true ) - $start_time , 3 ));
return $events ;
2015-01-14 20:41:01 +01:00
}
2004-11-07 15:38:00 +01:00
/**
* reads one or more calendar entries
*
* All times ( start , end and modified ) are returned as timesstamps in servertime !
*
2009-07-15 22:35:56 +02:00
* @ param int | array | string $ids id or array of id ' s of the entries to read , or string with a single uid
2015-01-14 14:52:34 +01:00
* @ param int $recur_date = 0 if set read the next recurrence at or after the timestamp , default 0 = read the initital one
2011-04-06 21:26:10 +02:00
* @ return array | boolean array with cal_id => event array pairs or false if entry not found
2004-11-07 15:38:00 +01:00
*/
2005-11-09 00:15:14 +01:00
function read ( $ids , $recur_date = 0 )
2004-11-07 15:38:00 +01:00
{
2015-01-14 14:52:34 +01:00
//error_log(__METHOD__.'('.array2string($ids).",$recur_date) ".function_backtrace());
2012-09-18 10:02:56 +02:00
$cols = self :: get_columns ( 'calendar' , $this -> cal_table );
$cols [ 0 ] = $this -> db -> to_varchar ( $this -> cal_table . '.cal_id' );
$cols = " $this->repeats_table .recur_type, $this->repeats_table .recur_interval, $this->repeats_table .recur_data, " . implode ( ',' , $cols );
$join = " LEFT JOIN $this->repeats_table ON $this->cal_table .cal_id= $this->repeats_table .cal_id " ;
2005-11-09 00:15:14 +01:00
$where = array ();
2011-04-06 21:26:10 +02:00
if ( is_scalar ( $ids ) && ! is_numeric ( $ids )) // a single uid
2005-11-09 00:15:14 +01:00
{
2009-10-25 19:20:00 +01:00
// We want only the parents to match
2005-11-09 00:15:14 +01:00
$where [ 'cal_uid' ] = $ids ;
2009-07-15 22:35:56 +02:00
$where [ 'cal_reference' ] = 0 ;
2005-11-09 00:15:14 +01:00
}
2011-04-06 21:26:10 +02:00
elseif ( is_array ( $ids ) && isset ( $ids [ count ( $ids ) - 1 ]) || is_scalar ( $ids )) // one or more cal_id's
{
$where [ 'cal_id' ] = $ids ;
}
else // array with column => value pairs
{
$where = $ids ;
2011-10-17 16:06:21 +02:00
unset ( $ids ); // otherwise users get not read!
2011-04-06 21:26:10 +02:00
}
if ( isset ( $where [ 'cal_id' ])) // prevent non-unique column-name cal_id
{
$where [] = $this -> db -> expression ( $this -> cal_table , $this -> cal_table . '.' , array (
'cal_id' => $where [ 'cal_id' ],
));
unset ( $where [ 'cal_id' ]);
}
2005-11-09 00:15:14 +01:00
if (( int ) $recur_date )
{
$where [] = 'cal_start >= ' . ( int ) $recur_date ;
2012-09-18 10:02:56 +02:00
$group_by = 'GROUP BY ' . $cols ;
$cols .= ',MIN(cal_start) AS cal_start,MIN(cal_end) AS cal_end' ;
$join = " JOIN $this->dates_table ON $this->cal_table .cal_id= $this->dates_table .cal_id $join " ;
}
else
{
$cols .= ',range_start AS cal_start,(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id) AS cal_end' ;
2005-11-09 00:15:14 +01:00
}
2017-03-29 19:31:53 +02:00
$cols .= ',range_end-1 AS recur_enddate' ;
2012-09-18 10:02:56 +02:00
2015-01-14 20:41:01 +01:00
$events =& $this -> get_events ( $this -> db -> select ( $this -> cal_table , $cols , $where , __LINE__ , __FILE__ , false , $group_by , 'calendar' , 0 , $join ), $recur_date );
return $events ? $events : false ;
}
/**
* Get full event information from an iterator of a select on egw_cal
*
* @ param array | Iterator $rs
* @ param int $recur_date = 0
* @ return array
*/
protected function & get_events ( $rs , $recur_date = 0 )
{
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-03-15 15:10:20 +01:00
$events = array ();
2015-01-14 20:41:01 +01:00
foreach ( $rs as $row )
2004-11-07 15:38:00 +01:00
{
2012-09-18 10:02:56 +02:00
if ( ! $row [ 'recur_type' ])
{
$row [ 'recur_type' ] = MCAL_RECUR_NONE ;
unset ( $row [ 'recur_enddate' ]);
}
2012-08-11 12:01:02 +02:00
$row [ 'recur_exception' ] = $row [ 'alarm' ] = array ();
2016-05-01 19:47:59 +02:00
$events [ $row [ 'cal_id' ]] = Api\Db :: strip_array_keys ( $row , 'cal_' );
2004-11-07 15:38:00 +01:00
}
2015-01-14 20:41:01 +01:00
if ( ! $events ) return $events ;
$ids = array_keys ( $events );
if ( count ( $ids ) == 1 ) $ids = $ids [ 0 ];
2006-03-29 09:01:18 +02:00
2009-08-04 19:14:16 +02:00
foreach ( $events as & $event )
{
if ( ! isset ( $event [ 'uid' ]) || strlen ( $event [ 'uid' ]) < $minimum_uid_length )
{
2009-07-15 22:35:56 +02:00
// event (without uid), not strong enough uid => create new uid
2016-05-01 19:47:59 +02:00
$event [ 'uid' ] = Api\CalDAV :: generate_uid ( 'calendar' , $event [ 'id' ]);
2009-07-15 22:35:56 +02:00
$this -> db -> update ( $this -> cal_table , array ( 'cal_uid' => $event [ 'uid' ]),
array ( 'cal_id' => $event [ 'id' ]), __LINE__ , __FILE__ , 'calendar' );
}
2012-08-11 12:01:02 +02:00
if ( ! ( int ) $recur_date && $event [ 'recur_type' ] != MCAL_RECUR_NONE )
2010-02-17 14:29:28 +01:00
{
2012-08-11 12:01:02 +02:00
foreach ( $this -> db -> select ( $this -> dates_table , 'cal_id,cal_start' , array (
'cal_id' => $ids ,
'recur_exception' => true ,
), __LINE__ , __FILE__ , false , 'ORDER BY cal_id,cal_start' , 'calendar' ) as $row )
2010-02-17 14:29:28 +01:00
{
2012-08-11 12:01:02 +02:00
$events [ $row [ 'cal_id' ]][ 'recur_exception' ][] = $row [ 'cal_start' ];
2010-02-17 14:29:28 +01:00
}
2012-08-11 12:01:02 +02:00
break ; // as above select read all exceptions (and I dont think too short uid problem still exists)
}
2012-12-07 15:10:51 +01:00
// make sure we fetch only real exceptions (deleted occurrences of a series should not show up)
if (( $recur_date && $event [ 'recur_type' ] != MCAL_RECUR_NONE ))
{
//_debug_array(__METHOD__.__LINE__.' recur_date:'.$recur_date.' check cal_start:'.$event['start']);
2015-01-14 20:41:01 +01:00
foreach ( $this -> db -> select ( $this -> dates_table , 'cal_id,cal_start' , array (
2012-12-07 15:10:51 +01:00
'cal_id' => $event [ 'id' ],
'cal_start' => $event [ 'start' ],
'recur_exception' => true ,
2015-01-14 20:41:01 +01:00
), __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2012-12-07 15:10:51 +01:00
{
$isException [ $row [ 'cal_id' ]] = true ;
}
if ( $isException [ $event [ 'id' ]])
{
2015-01-14 20:41:01 +01:00
if ( ! $this -> db -> select ( $this -> cal_table , 'COUNT(*)' , array (
2012-12-07 15:10:51 +01:00
'cal_uid' => $event [ 'uid' ],
'cal_recurrence' => $event [ 'start' ],
'cal_deleted' => NULL
2015-01-14 20:41:01 +01:00
), __LINE__ , __FILE__ , false , '' , 'calendar' ) -> fetchColumn ())
2012-12-07 15:10:51 +01:00
{
$e = $this -> read ( $event [ 'id' ], $event [ 'start' ] + 1 );
$event = $e [ $event [ 'id' ]];
break ;
}
else
{
//real exception -> should we return it? probably not, so we live with the result of the next occurrence of the series
}
}
}
2009-07-15 22:35:56 +02:00
}
2005-11-09 00:15:14 +01:00
// check if we have a real recurance, if not set $recur_date=0
if ( is_array ( $ids ) || $events [( int ) $ids ][ 'recur_type' ] == MCAL_RECUR_NONE )
2004-11-07 15:38:00 +01:00
{
2005-11-09 00:15:14 +01:00
$recur_date = 0 ;
}
else // adjust the given recurance to the real time, it can be a date without time(!)
{
2007-03-02 14:14:17 +01:00
if ( $recur_date )
{
// also remember recur_date, maybe we need it later, duno now
2011-08-03 14:35:42 +02:00
$recur_date = array ( 0 , $events [ $ids ][ 'recur_date' ] = $events [ $ids ][ 'start' ]);
2007-03-02 14:14:17 +01:00
}
2004-11-07 15:38:00 +01:00
}
2007-03-02 14:14:17 +01:00
2011-08-03 14:35:42 +02:00
// participants, if a recur_date give, we read that recurance, plus the one users from the default entry with recur_date=0
// sorting by cal_recur_date ASC makes sure recurence status always overwrites series status
2008-03-15 15:10:20 +01:00
foreach ( $this -> db -> select ( $this -> user_table , '*' , array (
2005-11-09 00:15:14 +01:00
'cal_id' => $ids ,
'cal_recur_date' => $recur_date ,
2014-10-28 17:01:55 +01:00
" cal_status NOT IN ('X','E') " ,
2011-08-03 14:35:42 +02:00
), __LINE__ , __FILE__ , false , 'ORDER BY cal_user_type DESC,cal_recur_date ASC,' . self :: STATUS_SORT , 'calendar' ) as $row ) // DESC puts users before resources and contacts
2004-11-07 15:38:00 +01:00
{
2009-08-06 13:29:05 +02:00
// combine all participant data in uid and status values
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2009-08-06 13:29:05 +02:00
$status = self :: combine_status ( $row [ 'cal_status' ], $row [ 'cal_quantity' ], $row [ 'cal_role' ]);
$events [ $row [ 'cal_id' ]][ 'participants' ][ $uid ] = $status ;
2015-08-17 16:07:25 +02:00
$events [ $row [ 'cal_id' ]][ 'participant_types' ][ $row [ 'cal_user_type' ]][ is_numeric ( $uid ) ? $uid : substr ( $uid , 1 )] = $status ;
// make extra attendee information available eg. for iCal export (attendee used eg. in response to organizer for an account)
$events [ $row [ 'cal_id' ]][ 'attendee' ][ $uid ] = $row [ 'cal_user_attendee' ];
2011-11-09 18:53:42 +01:00
}
2004-11-07 15:38:00 +01:00
// custom fields
2008-03-15 15:10:20 +01:00
foreach ( $this -> db -> select ( $this -> extra_table , '*' , array ( 'cal_id' => $ids ), __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2004-11-07 15:38:00 +01:00
{
$events [ $row [ 'cal_id' ]][ '#' . $row [ 'cal_extra_name' ]] = $row [ 'cal_extra_value' ];
}
2006-03-29 09:01:18 +02:00
2013-05-16 17:59:25 +02:00
// alarms
if ( is_array ( $ids ))
2004-11-07 15:38:00 +01:00
{
2013-05-16 17:59:25 +02:00
foreach ( $this -> read_alarms (( array ) $ids ) as $cal_id => $alarms )
{
$events [ $cal_id ][ 'alarm' ] = $alarms ;
}
2004-11-07 15:38:00 +01:00
}
2013-05-16 17:59:25 +02:00
else
{
$events [ $ids ][ 'alarm' ] = $this -> read_alarms ( $ids );
}
2004-11-07 15:38:00 +01:00
//echo "<p>socal::read(".print_r($ids,true).")=<pre>".print_r($events,true)."</pre>\n";
return $events ;
}
2010-01-14 18:01:30 +01:00
2014-01-28 17:27:01 +01:00
/**
* Maximum time a ctag get cached , as ActiveSync ping requests can run for a long time
*/
2016-02-23 16:02:40 +01:00
const MAX_CTAG_CACHE_TIME = 29 ;
2014-01-28 17:27:01 +01:00
2011-04-06 14:46:21 +02:00
/**
* Get maximum modification time of events for given participants and optional owned by them
*
* This includes ALL recurences of an event series
*
2012-09-27 17:46:08 +02:00
* @ param int | string | array $users one or mulitple calendar users
2015-01-14 14:52:34 +01:00
* @ param booelan $owner_too = false if true return also events owned by given users
* @ param boolean $master_only = false only check recurance master ( egw_cal_user . recur_date = 0 )
2011-04-06 14:46:21 +02:00
* @ return int maximum modification timestamp
*/
2012-03-12 09:20:36 +01:00
function get_ctag ( $users , $owner_too = false , $master_only = false )
2011-04-06 14:46:21 +02:00
{
2012-09-25 13:54:41 +02:00
static $ctags = array (); // some per-request caching
2014-01-28 17:27:01 +01:00
static $last_request = null ;
if ( ! isset ( $last_request ) || time () - $last_request > self :: MAX_CTAG_CACHE_TIME )
{
$ctags = array ();
$last_request = time ();
}
2012-09-25 13:54:41 +02:00
$signature = serialize ( func_get_args ());
if ( isset ( $ctags [ $signature ])) return $ctags [ $signature ];
2012-09-27 17:46:08 +02:00
$types = array ();
foreach (( array ) $users as $uid )
{
2015-01-14 14:52:34 +01:00
$type = $id = null ;
2015-08-17 16:07:25 +02:00
self :: split_user ( $uid , $type , $id , true );
2012-09-27 17:46:08 +02:00
$types [ $type ][] = $id ;
}
foreach ( $types as $type => $ids )
{
$where = array (
'cal_user_type' => $type ,
'cal_user_id' => $ids ,
);
if ( count ( $types ) > 1 )
{
$types [ $type ] = $this -> db -> expression ( $this -> user_table , $where );
}
}
if ( count ( $types ) > 1 )
{
$where [] = '(' . explode ( ' OR ' , $types ) . ')' ;
}
2012-03-12 09:20:36 +01:00
if ( $master_only )
{
$where [ 'cal_recur_date' ] = 0 ;
}
2011-04-06 14:46:21 +02:00
if ( $owner_too )
{
2012-09-27 17:46:08 +02:00
// owner can only by users, no groups or resources
2011-04-06 14:46:21 +02:00
foreach ( $users as $key => $user )
{
2012-09-27 17:46:08 +02:00
if ( ! ( $user > 0 )) unset ( $users [ $key ]);
2011-04-06 14:46:21 +02:00
}
$where = $this -> db -> expression ( $this -> user_table , '(' , $where , ' OR ' ) .
$this -> db -> expression ( $this -> cal_table , array (
'cal_owner' => $users ,
), ')' );
}
2012-09-27 17:46:08 +02:00
return $ctags [ $signature ] = $this -> db -> select ( $this -> user_table , 'MAX(cal_modified)' ,
2012-09-25 13:54:41 +02:00
$where , __LINE__ , __FILE__ , false , '' , 'calendar' , 0 , 'JOIN egw_cal ON egw_cal.cal_id=egw_cal_user.cal_id' ) -> fetchColumn ();
2009-12-27 05:21:33 +01:00
}
2005-11-09 00:15:14 +01:00
2013-02-25 12:17:59 +01:00
/**
* Query calendar main table and return iterator of query
*
2016-05-01 19:47:59 +02:00
* Use as : foreach ( get_cal_data () as $data ) { $data = Api\Db :: strip_array_keys ( $data , 'cal_' ); // do something with $data
2013-02-25 12:17:59 +01:00
*
* @ param array $query filter , keys have to use 'cal_' prefix
2015-01-14 14:52:34 +01:00
* @ param string | array $cols = 'cal_id,cal_reference,cal_etag,cal_modified,cal_user_modified' cols to query
2016-05-01 19:47:59 +02:00
* @ return Iterator as Api\Db :: select
2013-02-25 12:17:59 +01:00
*/
2015-01-14 14:52:34 +01:00
function get_cal_data ( array $query , $cols = 'cal_id,cal_reference,cal_etag,cal_modified,cal_user_modified' )
2013-02-25 12:17:59 +01:00
{
if ( ! is_array ( $cols )) $cols = explode ( ',' , $cols );
// special handling of cal_user_modified "pseudo" column
if (( $key = array_search ( 'cal_user_modified' , $cols )) !== false )
{
$cols [ $key ] = $this -> db -> unix_timestamp ( '(SELECT MAX(cal_user_modified) FROM ' .
$this -> user_table . ' WHERE ' . $this -> cal_table . '.cal_id=' . $this -> user_table . '.cal_id)' ) .
' AS cal_user_modified' ;
}
return $this -> db -> select ( $this -> cal_table , $cols , $query , __LINE__ , __FILE__ );
}
2005-11-09 00:15:14 +01:00
/**
2010-10-14 18:16:02 +02:00
* generate SQL to filter after a given category ( incl . subcategories )
2006-03-29 09:01:18 +02:00
*
2009-05-05 00:39:27 +02:00
* @ param array | int $cat_id cat - id or array of cat - ids , or ! $cat_id for none
2005-11-09 00:15:14 +01:00
* @ return string SQL to include in the query
*/
function cat_filter ( $cat_id )
{
$sql = '' ;
if ( $cat_id )
{
2010-10-14 18:16:02 +02:00
$cats = $GLOBALS [ 'egw' ] -> categories -> return_all_children ( $cat_id );
2005-11-09 00:15:14 +01:00
array_walk ( $cats , create_function ( '&$val,$key' , '$val = (int) $val;' ));
2011-07-19 09:32:28 +02:00
if ( is_array ( $cat_id ) && count ( $cat_id ) == 1 ) $cat_id = $cat_id [ 0 ];
2007-12-13 17:04:34 +01:00
$sql = '(cal_category' . ( count ( $cats ) > 1 ? " IN (' " . implode ( " ',' " , $cats ) . " ') " : '=' . $this -> db -> quote (( int ) $cat_id ));
2005-11-09 00:15:14 +01:00
foreach ( $cats as $cat )
{
$sql .= ' OR ' . $this -> db -> concat ( " ',' " , 'cal_category' , " ',' " ) . ' LIKE ' . $this -> db -> quote ( '%,' . $cat . ',%' );
}
$sql .= ') ' ;
}
return $sql ;
}
2015-01-19 20:32:26 +01:00
/**
* Return filters to filter by given status
*
* @ param string $filter " default " , " all " , ...
* @ param boolean $enum_recuring are recuring events enumerated or not
* @ param array $where = array () array to add filters too
* @ return array
*/
protected function status_filter ( $filter , $enum_recuring = true , array $where = array ())
{
if ( $filter != 'deleted' && $filter != 'everything' )
{
$where [] = 'cal_deleted IS NULL' ;
}
switch ( $filter )
{
case 'everything' : // no filter at all
break ;
case 'showonlypublic' :
$where [ 'cal_public' ] = 1 ;
$where [] = " $this->user_table .cal_status NOT IN ('R','X','E') " ;
break ;
case 'deleted' :
$where [] = 'cal_deleted IS NOT NULL' ;
break ;
case 'unknown' :
$where [] = " $this->user_table .cal_status='U' " ;
break ;
case 'not-unknown' :
$where [] = " $this->user_table .cal_status NOT IN ('U','X','E') " ;
break ;
case 'accepted' :
$where [] = " $this->user_table .cal_status='A' " ;
break ;
case 'tentative' :
$where [] = " $this->user_table .cal_status='T' " ;
break ;
case 'rejected' :
$where [] = " $this->user_table .cal_status='R' " ;
break ;
case 'delegated' :
$where [] = " $this->user_table .cal_status='D' " ;
break ;
case 'all' :
case 'owner' :
$where [] = " $this->user_table .cal_status NOT IN ('X','E') " ;
break ;
default :
if ( $enum_recuring ) // regular UI
{
$where [] = " $this->user_table .cal_status NOT IN ('R','X','E') " ;
}
else // CalDAV / eSync / iCal need to include 'E' = exceptions
{
$where [] = " $this->user_table .cal_status NOT IN ('R','X') " ;
}
break ;
}
return $where ;
}
2005-11-09 00:15:14 +01:00
/**
* Searches / lists calendar entries , including repeating ones
*
* @ param int $start startdate of the search / list ( servertime )
* @ param int $end enddate of the search / list ( servertime )
2009-05-05 00:39:27 +02:00
* @ param int | array $users user - id or array of user - id ' s , ! $users means all entries regardless of users
2015-01-14 14:52:34 +01:00
* @ param int | array $cat_id = 0 mixed category - id or array of cat - id ' s ( incl . all sub - categories ), default 0 = all
* @ param string $filter = 'all' string filter - name : all ( not rejected ), accepted , unknown , tentative , rejected or everything ( incl . rejected , deleted )
* @ param int | boolean $offset = False offset for a limited query or False ( default )
* @ param int $num_rows = 0 number of rows to return if offset set , default 0 = use default in user prefs
* @ param array $params = array ()
2010-06-01 11:28:37 +02:00
* @ param string | array $params [ 'query' ] string : pattern so search for , if unset or empty all matching entries are returned ( no search )
2010-05-20 17:12:59 +02:00
* Please Note : a search never returns repeating events more then once AND does not honor start + end date !!!
2010-06-01 11:28:37 +02:00
* array : everything is directly used as $where
2015-01-14 14:52:34 +01:00
* @ param string $params [ 'order' ] = 'cal_start' column - names plus optional DESC | ASC separted by comma
2015-08-18 11:07:12 +02:00
* @ param string | array $params [ 'sql_filter' ] sql to be and ' ed into query ( fully quoted ), or usual filter array
2010-05-20 17:12:59 +02:00
* @ param string | array $params [ 'cols' ] what to select , default " $this->repeats_table .*, $this->cal_table .*,cal_start,cal_end,cal_recur_date " ,
2009-10-03 09:32:05 +02:00
* if specified and not false an iterator for the rows is returned
2010-05-20 17:12:59 +02:00
* @ param string $params [ 'append' ] SQL to append to the query before $order , eg . for a GROUP BY clause
* @ param array $params [ 'cfs' ] custom fields to query , null = none , array () = all , or array with cfs names
* @ param array $params [ 'users' ] raw parameter as passed to calendar_bo :: search () no memberships resolved !
2015-01-14 14:52:34 +01:00
* @ param boolean $params [ 'master_only' ] = false , true only take into account participants / status from master ( for AS )
2015-01-14 20:41:01 +01:00
* @ param boolean $params [ 'enum_recuring' ] = true enumerate recuring events
* @ param boolean $params [ 'use_so_events' ] = false , true return result of new $this -> events ()
2015-01-14 14:52:34 +01:00
* @ param int $remove_rejected_by_user = null add join to remove entry , if given user has rejected it
2015-01-19 20:32:26 +01:00
* @ return Iterator | array of events
2005-11-09 00:15:14 +01:00
*/
2011-04-05 17:32:20 +02:00
function & search ( $start , $end , $users , $cat_id = 0 , $filter = 'all' , $offset = False , $num_rows = 0 , array $params = array (), $remove_rejected_by_user = null )
2005-11-09 00:15:14 +01:00
{
2014-11-07 13:33:56 +01:00
//error_log(__METHOD__.'('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.array2string($users).','.array2string($cat_id).",'$filter',".array2string($offset).",$num_rows,".array2string($params).') '.function_backtrace());
2005-11-09 00:15:14 +01:00
2015-01-19 20:32:26 +01:00
/* not using new events method currently , as it not yet fully working and
using time - range views in old code gives simmilar improvments
2015-01-14 20:41:01 +01:00
// uncomment to use new events method for supported parameters
2015-01-19 20:32:26 +01:00
//if (!isset($params['use_so_events'])) $params['use_so_events'] = $params['use_so_events'] || $start && $end && !in_array($filter, array('owner', 'deleted')) && $params['enum_recuring']!==false;
2015-01-14 20:41:01 +01:00
// use new events method only if explicit requested
if ( $params [ 'use_so_events' ])
{
return call_user_func_array ( array ( $this , 'events' ), func_get_args ());
}
2015-01-19 20:32:26 +01:00
*/
2014-10-28 17:01:55 +01:00
if ( isset ( $params [ 'cols' ]))
{
$cols = $params [ 'cols' ];
}
else
{
$all_cols = self :: get_columns ( 'calendar' , $this -> cal_table );
$all_cols [ 0 ] = $this -> db -> to_varchar ( $this -> cal_table . '.cal_id' );
$cols = " $this->repeats_table .recur_type, $this->repeats_table .recur_interval, $this->repeats_table .recur_data,range_end AS recur_enddate, " . implode ( ',' , $all_cols ) . " ,cal_start,cal_end, $this->user_table .cal_recur_date " ;
}
2005-11-09 00:15:14 +01:00
$where = array ();
2010-05-20 17:12:59 +02:00
if ( is_array ( $params [ 'query' ]))
2005-11-09 00:15:14 +01:00
{
2010-05-20 17:12:59 +02:00
$where = $params [ 'query' ];
2005-11-09 00:15:14 +01:00
}
2010-05-20 17:12:59 +02:00
elseif ( $params [ 'query' ])
2005-11-09 00:15:14 +01:00
{
2016-10-20 17:49:39 +02:00
if ( is_numeric ( $params [ 'query' ]))
2005-11-09 00:15:14 +01:00
{
2016-10-20 17:49:39 +02:00
$where [] = $this -> cal_table . '.cal_id = ' . ( int ) $params [ 'query' ];
}
else
{
foreach ( array ( 'cal_title' , 'cal_description' , 'cal_location' ) as $col )
{
$to_or [] = $col . ' ' . $this -> db -> capabilities [ Api\Db :: CAPABILITY_CASE_INSENSITIV_LIKE ] . ' ' . $this -> db -> quote ( '%' . $params [ 'query' ] . '%' );
}
$where [] = '(' . implode ( ' OR ' , $to_or ) . ')' ;
2005-11-09 00:15:14 +01:00
}
2011-03-01 00:43:34 +01:00
// Searching - restrict private to own or private grant
2011-08-16 12:20:40 +02:00
if ( ! isset ( $params [ 'private_grants' ]))
{
2016-05-01 19:47:59 +02:00
$params [ 'private_grants' ] = $GLOBALS [ 'egw' ] -> acl -> get_ids_for_location ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ], Acl :: PRIVAT , 'calendar' );
2011-08-16 12:20:40 +02:00
$params [ 'private_grants' ][] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ]; // db query does NOT return current user
}
$private_filter = '(cal_public=1 OR cal_public=0 AND ' . $this -> db -> expression ( $this -> cal_table , array ( 'cal_owner' => $params [ 'private_grants' ])) . ')' ;
2011-03-01 00:43:34 +01:00
$where [] = $private_filter ;
2005-11-09 00:15:14 +01:00
}
2015-08-18 11:07:12 +02:00
if ( ! empty ( $params [ 'sql_filter' ]))
2009-12-27 05:21:33 +01:00
{
2015-08-18 11:07:12 +02:00
if ( is_string ( $params [ 'sql_filter' ]))
{
$where [] = $params [ 'sql_filter' ];
}
elseif ( is_array ( $params [ 'sql_filter' ]))
{
$where = array_merge ( $where , $params [ 'sql_filter' ]);
}
2009-12-27 05:21:33 +01:00
}
2017-01-18 18:29:44 +01:00
$useUnionQuery = $this -> db -> capabilities [ 'distinct_on_text' ] && $this -> db -> capabilities [ 'union' ];
2005-11-09 00:15:14 +01:00
if ( $users )
{
$users_by_type = array ();
2009-05-05 00:39:27 +02:00
foreach (( array ) $users as $user )
2005-11-09 00:15:14 +01:00
{
if ( is_numeric ( $user ))
{
$users_by_type [ 'u' ][] = ( int ) $user ;
}
2013-01-29 15:56:03 +01:00
else
2005-11-09 00:15:14 +01:00
{
2015-08-17 16:07:25 +02:00
$user_type = $user_id = null ;
self :: split_user ( $user , $user_type , $user_id , true );
$users_by_type [ $user_type ][] = $user_id ;
2005-11-09 00:15:14 +01:00
}
}
2011-08-03 18:13:56 +02:00
$to_or = $user_or = array ();
$owner_or = null ;
2008-03-15 15:10:20 +01:00
$table_def = $this -> db -> get_table_definitions ( 'calendar' , $this -> user_table );
2005-11-09 00:15:14 +01:00
foreach ( $users_by_type as $type => $ids )
{
2010-04-15 14:52:35 +02:00
// when we are able to use Union Querys, we do not OR our query, we save the needed parts for later construction of the union
if ( $useUnionQuery )
2009-09-29 21:58:51 +02:00
{
2011-04-05 17:32:20 +02:00
$user_or [] = $this -> db -> expression ( $table_def , $this -> user_table . '.' , array (
2010-04-15 14:52:35 +02:00
'cal_user_type' => $type ,
2011-04-05 17:32:20 +02:00
), ' AND ' . $this -> user_table . '.' , array (
2010-04-15 14:52:35 +02:00
'cal_user_id' => $ids ,
));
2011-08-03 18:13:56 +02:00
if ( $type == 'u' && $filter == 'owner' )
2010-04-15 14:52:35 +02:00
{
$cal_table_def = $this -> db -> get_table_definitions ( 'calendar' , $this -> cal_table );
2012-09-18 13:40:08 +02:00
// only users can be owners, no need to add groups
$user_ids = array ();
2015-01-14 14:52:34 +01:00
foreach ( $ids as $user_id )
{
if ( $GLOBALS [ 'egw' ] -> accounts -> get_type ( $user_id ) === 'u' ) $user_ids [] = $user_id ;
}
2012-09-18 13:40:08 +02:00
$owner_or = $this -> db -> expression ( $cal_table_def , array ( 'cal_owner' => $user_ids ));
2010-04-15 14:52:35 +02:00
}
}
else
{
2011-04-05 17:32:20 +02:00
$to_or [] = $this -> db -> expression ( $table_def , $this -> user_table . '.' , array (
2010-04-15 14:52:35 +02:00
'cal_user_type' => $type ,
2011-04-05 17:32:20 +02:00
), ' AND ' . $this -> user_table . '.' , array (
2010-04-15 14:52:35 +02:00
'cal_user_id' => $ids ,
));
2015-01-19 20:32:26 +01:00
if ( $type == 'u' && $filter == 'owner' )
2010-04-15 14:52:35 +02:00
{
$cal_table_def = $this -> db -> get_table_definitions ( 'calendar' , $this -> cal_table );
$to_or [] = $this -> db -> expression ( $cal_table_def , array ( 'cal_owner' => $ids ));
}
2009-09-29 21:58:51 +02:00
}
2005-11-09 00:15:14 +01:00
}
2010-04-15 14:52:35 +02:00
// this is only used, when we cannot use UNIONS
if ( ! $useUnionQuery ) $where [] = '(' . implode ( ' OR ' , $to_or ) . ')' ;
2006-03-29 09:01:18 +02:00
2015-01-19 20:32:26 +01:00
$where = $this -> status_filter ( $filter , $params [ 'enum_recuring' ], $where );
2005-11-09 00:15:14 +01:00
}
if ( $cat_id )
{
$where [] = $this -> cat_filter ( $cat_id );
}
2011-04-05 17:32:20 +02:00
if ( $start )
{
if ( $params [ 'enum_recuring' ])
{
$where [] = ( int ) $start . ' < cal_end' ;
}
else
{
2012-09-18 10:02:56 +02:00
$where [] = '(' . (( int ) $start ) . ' < range_end OR range_end IS NULL)' ;
2011-04-05 17:32:20 +02:00
}
}
2012-09-28 11:31:41 +02:00
if ( ! preg_match ( '/^[a-z_ ,c]+$/i' , $params [ 'order' ])) $params [ 'order' ] = 'cal_start' ; // gard against SQL injection
2011-08-03 18:13:56 +02:00
// if not enum recuring events, we have to use minimum start- AND end-dates, otherwise we get more then one event per cal_id!
2011-04-05 17:32:20 +02:00
if ( ! $params [ 'enum_recuring' ])
{
$where [] = " $this->user_table .cal_recur_date=0 " ;
2012-09-18 10:02:56 +02:00
$cols = str_replace ( array ( 'cal_start' , 'cal_end' ), array ( 'range_start AS cal_start' , '(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id) AS cal_end' ), $cols );
2012-09-28 11:31:41 +02:00
// in case cal_start is used in a query, eg. calendar_ical::find_event
$where = str_replace ( array ( 'cal_start' , 'cal_end' ), array ( 'range_start' , '(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id)' ), $where );
$params [ 'order' ] = str_replace ( 'cal_start' , 'range_start' , $params [ 'order' ]);
2012-09-18 10:02:56 +02:00
if ( $end ) $where [] = ( int ) $end . ' > range_start' ;
}
elseif ( $end ) $where [] = ( int ) $end . ' > cal_start' ;
2006-03-29 09:01:18 +02:00
2012-09-26 16:30:47 +02:00
if ( $remove_rejected_by_user && $filter != 'everything' )
2011-04-05 17:32:20 +02:00
{
2011-04-09 16:41:15 +02:00
$rejected_by_user_join = " LEFT JOIN $this->user_table rejected_by_user " .
2011-04-05 17:32:20 +02:00
" ON $this->cal_table .cal_id=rejected_by_user.cal_id " .
" AND rejected_by_user.cal_user_type='u' " .
" AND rejected_by_user.cal_user_id= " . $this -> db -> quote ( $remove_rejected_by_user ) .
2012-10-02 14:56:17 +02:00
" AND " . ( ! $params [ 'enum_recuring' ] ? 'rejected_by_user.cal_recur_date=0' :
'(recur_type IS NULL AND rejected_by_user.cal_recur_date=0 OR cal_start=rejected_by_user.cal_recur_date)' );
2011-04-09 16:41:15 +02:00
$or_required = array (
'rejected_by_user.cal_status IS NULL' ,
2012-01-23 08:41:29 +01:00
" rejected_by_user.cal_status NOT IN ('R','X') " ,
2011-04-09 16:41:15 +02:00
);
if ( $filter == 'owner' ) $or_required [] = 'cal_owner=' . ( int ) $remove_rejected_by_user ;
$where [] = '(' . implode ( ' OR ' , $or_required ) . ')' ;
2011-04-05 17:32:20 +02:00
}
2015-01-19 20:32:26 +01:00
// using a time-range and deleted attribute limited view instead of full table
$cal_table = $this -> cal_range_view ( $start , $end , null , $filter == 'everything' ? null : $filter != 'deleted' );
$cal_table_def = $this -> db -> get_table_definitions ( 'calendar' , $this -> cal_table );
$join = " JOIN $this->user_table ON $this->cal_table .cal_id= $this->user_table .cal_id " .
" LEFT JOIN $this->repeats_table ON $this->cal_table .cal_id= $this->repeats_table .cal_id " .
$rejected_by_user_join ;
// dates table join only needed to enum recuring events, we use a time-range limited view here too
if ( $params [ 'enum_recuring' ])
{
2015-02-18 09:15:54 +01:00
$join = " JOIN " . $this -> dates_table . // using dates_table direct seems quicker then an other view
//$this->dates_range_view($start, $end, null, $filter == 'everything' ? null : $filter == 'deleted').
2015-01-19 20:32:26 +01:00
" ON $this->cal_table .cal_id= $this->dates_table .cal_id " . $join ;
}
2017-02-24 18:32:40 +01:00
// Check for some special sorting, used by planner views
if ( $params [ 'order' ] == 'participants , cal_non_blocking DESC' )
{
$order = ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'account_display' ] == 'lastname' ? 'n_family' : 'n_fileas' );
$cols .= " ,egw_addressbook. { $order } " ;
2017-03-20 18:06:00 +01:00
$join .= " LEFT JOIN egw_addressbook ON " .
( $this -> db -> Type == 'pgsql' ? " egw_addressbook.account_id::varchar = " : " egw_addressbook.account_id = " ) .
" { $this -> user_table } .cal_user_id " ;
2017-02-24 18:32:40 +01:00
$params [ 'order' ] = " $order , cal_non_blocking DESC " ;
}
else if ( $params [ 'order' ] == 'categories , cal_non_blocking DESC' )
{
$params [ 'order' ] = 'cat_name, cal_non_blocking DESC' ;
$cols .= ',egw_categories.cat_name' ;
$join .= " LEFT JOIN egw_categories ON egw_categories.cat_id = { $this -> cal_table } .cal_category " ;
}
2011-04-09 16:41:15 +02:00
//$starttime = microtime(true);
2010-04-15 14:52:35 +02:00
if ( $useUnionQuery )
2006-03-03 13:03:33 +01:00
{
2010-01-21 04:00:53 +01:00
// allow apps to supply participants and/or icons
2010-05-21 17:05:31 +02:00
if ( ! isset ( $params [ 'cols' ])) $cols .= ',NULL AS participants,NULL AS icons' ;
2010-03-06 16:49:59 +01:00
2006-03-29 09:01:18 +02:00
// changed the original OR in the query into a union, to speed up the query execution under MySQL 5
2015-01-19 20:32:26 +01:00
// with time-range views benefit is now at best slim for huge tables or none at all!
2006-03-29 09:01:18 +02:00
$select = array (
2015-01-19 20:32:26 +01:00
'table' => $cal_table ,
'join' => $join ,
2009-05-05 00:39:27 +02:00
'cols' => $cols ,
2006-03-29 09:01:18 +02:00
'where' => $where ,
2008-03-15 15:10:20 +01:00
'app' => 'calendar' ,
2015-01-14 14:52:34 +01:00
'append' => $params [ 'append' ],
2015-01-19 20:32:26 +01:00
'table_def' => $cal_table_def ,
2006-03-29 09:01:18 +02:00
);
2011-04-05 17:32:20 +02:00
$selects = array ();
2010-04-23 08:52:48 +02:00
// we check if there are parts to use for the construction of our UNION query,
2010-04-15 14:52:35 +02:00
// as replace the OR by construction of a suitable UNION for performance reasons
2011-08-03 18:13:56 +02:00
if ( $owner_or || $user_or )
2010-04-15 14:52:35 +02:00
{
2011-04-05 17:32:20 +02:00
foreach ( $user_or as $user_sql )
2010-04-15 14:52:35 +02:00
{
2011-04-05 17:32:20 +02:00
$selects [] = $select ;
$selects [ count ( $selects ) - 1 ][ 'where' ][] = $user_sql ;
if ( $params [ 'enum_recuring' ])
2010-04-15 15:41:57 +02:00
{
2012-11-14 17:25:21 +01:00
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " recur_type IS NULL AND $this->user_table .cal_recur_date=0 " ;
2010-04-16 09:55:43 +02:00
$selects [] = $select ;
2011-04-05 17:32:20 +02:00
$selects [ count ( $selects ) - 1 ][ 'where' ][] = $user_sql ;
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " $this->user_table .cal_recur_date=cal_start " ;
2010-04-15 15:41:57 +02:00
}
2010-04-15 14:52:35 +02:00
}
2011-04-05 17:32:20 +02:00
// if the query is to be filtered by owner we need to add more selects for the union
if ( $owner_or )
2010-04-15 14:52:35 +02:00
{
2011-04-05 17:32:20 +02:00
$selects [] = $select ;
$selects [ count ( $selects ) - 1 ][ 'where' ][] = $owner_or ;
if ( $params [ 'enum_recuring' ])
2010-04-15 15:41:57 +02:00
{
2012-11-14 17:25:21 +01:00
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " recur_type IS NULL AND $this->user_table .cal_recur_date=0 " ;
2010-04-16 09:55:43 +02:00
$selects [] = $select ;
2011-04-05 17:32:20 +02:00
$selects [ count ( $selects ) - 1 ][ 'where' ][] = $owner_or ;
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " $this->user_table .cal_recur_date=cal_start " ;
2010-04-15 15:41:57 +02:00
}
2010-04-15 14:52:35 +02:00
}
}
else
{
// if the query is to be filtered by neither by user nor owner (should not happen?) we need 2 selects for the union
2011-04-05 17:32:20 +02:00
$selects [] = $select ;
if ( $params [ 'enum_recuring' ])
{
2012-11-14 17:25:21 +01:00
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " recur_type IS NULL AND $this->user_table .cal_recur_date=0 " ;
2011-04-05 17:32:20 +02:00
$selects [] = $select ;
$selects [ count ( $selects ) - 1 ][ 'where' ][] = " $this->user_table .cal_recur_date=cal_start " ;
}
2010-04-15 14:52:35 +02:00
}
2011-04-05 17:32:20 +02:00
if ( is_numeric ( $offset ) && ! $params [ 'no_total' ]) // get the total too
2006-03-29 09:01:18 +02:00
{
2011-04-05 17:32:20 +02:00
$save_selects = $selects ;
2006-03-29 09:01:18 +02:00
// we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns)
2010-04-23 08:52:48 +02:00
foreach ( array_keys ( $selects ) as $key )
2010-04-16 09:55:43 +02:00
{
2016-08-17 09:25:31 +02:00
$selects [ $key ][ 'cols' ] = " $this->repeats_table .recur_type,range_end AS recur_enddate, $this->repeats_table .recur_interval, $this->repeats_table .recur_data, " . $this -> db -> to_varchar ( $this -> cal_table . '.cal_id' ) . " ,cal_start,cal_end, $this->user_table .cal_recur_date " ;
2011-04-05 17:32:20 +02:00
if ( ! $params [ 'enum_recuring' ])
{
2014-02-20 18:46:15 +01:00
$selects [ $key ][ 'cols' ] = str_replace ( array ( 'cal_start' , 'cal_end' ),
array ( 'range_start AS cal_start' , 'range_end AS cal_end' ), $selects [ $key ][ 'cols' ]);
2011-04-05 17:32:20 +02:00
}
2010-04-16 09:55:43 +02:00
}
2015-01-19 20:32:26 +01:00
if ( ! isset ( $params [ 'cols' ]) && ! $params [ 'no_integration' ]) self :: get_union_selects ( $selects , $start , $end , $users , $cat_id , $filter , $params [ 'query' ], $params [ 'users' ]);
2006-03-03 13:03:33 +01:00
2008-03-15 15:10:20 +01:00
$this -> total = $this -> db -> union ( $selects , __LINE__ , __FILE__ ) -> NumRows ();
2005-11-09 00:15:14 +01:00
2011-04-05 17:32:20 +02:00
// restore original cols / selects
$selects = $save_selects ; unset ( $save_selects );
2006-03-29 09:01:18 +02:00
}
2015-01-19 20:32:26 +01:00
if ( ! isset ( $params [ 'cols' ]) && ! $params [ 'no_integration' ]) self :: get_union_selects ( $selects , $start , $end , $users , $cat_id , $filter , $params [ 'query' ], $params [ 'users' ]);
2010-10-28 11:22:01 +02:00
2010-05-20 17:12:59 +02:00
$rs = $this -> db -> union ( $selects , __LINE__ , __FILE__ , $params [ 'order' ], $offset , $num_rows );
2006-03-29 09:01:18 +02:00
}
else // MsSQL oder MySQL 3.23
2005-11-09 00:15:14 +01:00
{
2015-01-19 20:32:26 +01:00
$where [] = " (recur_type IS NULL AND $this->user_table .cal_recur_date=0 OR $this->user_table .cal_recur_date=cal_start) " ;
2006-03-29 09:01:18 +02:00
2015-01-19 20:32:26 +01:00
$selects = array ( array (
'table' => $cal_table ,
'join' => $join ,
'cols' => $cols ,
'where' => $where ,
'app' => 'calendar' ,
'append' => $params [ 'append' ],
'table_def' => $cal_table_def ,
));
if ( is_numeric ( $offset ) && ! $params [ 'no_total' ]) // get the total too
2006-03-29 09:01:18 +02:00
{
2015-01-19 20:32:26 +01:00
$save_selects = $selects ;
2006-03-29 09:01:18 +02:00
// we only select cal_table.cal_id (and not cal_table.*) to be able to use DISTINCT (eg. MsSQL does not allow it for text-columns)
2015-01-19 20:32:26 +01:00
$selects [ 0 ][ 'cols' ] = " $this->cal_table .cal_id,cal_start " ;
if ( ! isset ( $params [ 'cols' ]) && ! $params [ 'no_integration' ] && $this -> db -> capabilities [ 'union' ])
{
self :: get_union_selects ( $selects , $start , $end , $users , $cat_id , $filter , $params [ 'query' ], $params [ 'users' ]);
}
$this -> total = $this -> db -> union ( $selects , __LINE__ , __FILE__ ) -> NumRows ();
$selects = $save_selects ;
2006-03-29 09:01:18 +02:00
}
2015-01-19 20:32:26 +01:00
if ( ! isset ( $params [ 'cols' ]) && ! $params [ 'no_integration' ] && $this -> db -> capabilities [ 'union' ])
{
self :: get_union_selects ( $selects , $start , $end , $users , $cat_id , $filter , $params [ 'query' ], $params [ 'users' ]);
}
$rs = $this -> db -> union ( $selects , __LINE__ , __FILE__ , $params [ 'order' ], $offset , $num_rows );
2006-03-02 08:02:33 +01:00
}
2015-01-19 20:32:26 +01:00
//error_log(__METHOD__."() useUnionQuery=$useUnionQuery --> query took ".(microtime(true)-$starttime).'s '.$rs->sql);
2010-06-01 11:28:37 +02:00
if ( isset ( $params [ 'cols' ]))
2009-05-05 00:39:27 +02:00
{
return $rs ; // if colums are specified we return the recordset / iterator
}
2015-01-19 20:32:26 +01:00
// Todo: return $this->get_events($rs);
2006-03-29 09:01:18 +02:00
$events = $ids = $recur_dates = $recur_ids = array ();
2008-03-15 15:10:20 +01:00
foreach ( $rs as $row )
2005-11-09 00:15:14 +01:00
{
2010-01-19 23:20:44 +01:00
$id = $row [ 'cal_id' ];
if ( is_numeric ( $id )) $ids [] = $id ;
2008-03-20 13:49:34 +01:00
if ( $row [ 'cal_recur_date' ])
2005-11-09 00:15:14 +01:00
{
2008-03-20 13:49:34 +01:00
$id .= '-' . $row [ 'cal_recur_date' ];
$recur_dates [] = $row [ 'cal_recur_date' ];
2005-11-09 00:15:14 +01:00
}
2010-01-21 04:00:53 +01:00
if ( $row [ 'participants' ])
2010-02-01 11:35:05 +01:00
{
$row [ 'participants' ] = explode ( ',' , $row [ 'participants' ]);
$row [ 'participants' ] = array_combine ( $row [ 'participants' ],
2012-09-18 10:02:56 +02:00
array_fill ( 0 , count ( $row [ 'participants' ]), '' ));
2010-02-01 11:35:05 +01:00
}
else
{
$row [ 'participants' ] = array ();
}
2012-08-11 12:01:02 +02:00
$row [ 'recur_exception' ] = $row [ 'alarm' ] = array ();
2005-11-09 00:15:14 +01:00
2011-08-03 14:35:42 +02:00
// compile a list of recurrences per cal_id
if ( ! in_array ( $id ,( array ) $recur_ids [ $row [ 'cal_id' ]])) $recur_ids [ $row [ 'cal_id' ]][] = $id ;
2016-05-01 19:47:59 +02:00
$events [ $id ] = Api\Db :: strip_array_keys ( $row , 'cal_' );
2005-11-09 00:15:14 +01:00
}
2010-01-19 23:20:44 +01:00
//_debug_array($events);
2010-02-01 11:35:05 +01:00
if ( count ( $ids ))
2005-11-09 00:15:14 +01:00
{
2012-03-12 09:20:36 +01:00
$ids = array_unique ( $ids );
2005-11-09 00:15:14 +01:00
// now ready all users with the given cal_id AND (cal_recur_date=0 or the fitting recur-date)
// This will always read the first entry of each recuring event too, we eliminate it later
$recur_dates [] = 0 ;
2012-09-26 16:30:47 +02:00
$utcal_id_view = " (SELECT * FROM " . $this -> user_table . " WHERE cal_id IN ( " . implode ( ',' , $ids ) . " ) " .
2014-10-28 17:01:55 +01:00
( $filter != 'everything' ? " AND cal_status NOT IN ('X','E') " : '' ) . " ) utcalid " ;
2009-03-16 14:50:03 +01:00
//$utrecurdate_view = " (select * from ".$this->user_table." where cal_recur_date in (".implode(',',array_unique($recur_dates)).")) utrecurdates ";
foreach ( $this -> db -> select ( $utcal_id_view , '*' , array (
//'cal_id' => array_unique($ids),
'cal_recur_date' => $recur_dates ,
2017-01-18 18:29:44 +01:00
), __LINE__ , __FILE__ , false , 'ORDER BY cal_id,cal_user_type DESC,' . self :: STATUS_SORT , 'calendar' , - 1 , $join = '' ,
2009-03-16 14:50:03 +01:00
$this -> db -> get_table_definitions ( 'calendar' , $this -> user_table )) as $row ) // DESC puts users before resources and contacts
2005-11-09 00:15:14 +01:00
{
$id = $row [ 'cal_id' ];
if ( $row [ 'cal_recur_date' ]) $id .= '-' . $row [ 'cal_recur_date' ];
2006-03-29 09:01:18 +02:00
2009-08-06 13:29:05 +02:00
// combine all participant data in uid and status values
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2011-08-03 14:35:42 +02:00
$status = self :: combine_status ( $row [ 'cal_status' ], $row [ 'cal_quantity' ], $row [ 'cal_role' ]);
// set accept/reject/tentative of series for all recurrences
if ( ! $row [ 'cal_recur_date' ])
{
foreach (( array ) $recur_ids [ $row [ 'cal_id' ]] as $i )
{
if ( isset ( $events [ $i ]) && ! isset ( $events [ $i ][ 'participants' ][ $uid ]))
{
$events [ $i ][ 'participants' ][ $uid ] = $status ;
}
}
}
// set data, if recurrence is requested
if ( isset ( $events [ $id ])) $events [ $id ][ 'participants' ][ $uid ] = $status ;
2012-03-12 09:20:36 +01:00
}
2015-01-29 14:31:43 +01:00
// query recurrance exceptions, if needed: enum_recuring && !daywise is used in calendar_groupdav::get_series($uid,...)
if ( ! $params [ 'enum_recuring' ] || ! $params [ 'daywise' ])
2012-08-11 12:01:02 +02:00
{
foreach ( $this -> db -> select ( $this -> dates_table , 'cal_id,cal_start' , array (
'cal_id' => $ids ,
'recur_exception' => true ,
), __LINE__ , __FILE__ , false , 'ORDER BY cal_id,cal_start' , 'calendar' ) as $row )
{
2015-01-29 14:31:43 +01:00
// for enum_recurring events are not indexed by cal_id, but $cal_id.'-'.$cal_start
// find master, which is first recurrence
if ( ! isset ( $events [ $id = $row [ 'cal_id' ]]))
{
foreach ( $events as $id => $event )
{
if ( $event [ 'id' ] == $row [ 'cal_id' ]) break ;
}
}
$events [ $id ][ 'recur_exception' ][] = $row [ 'cal_start' ];
2012-08-11 12:01:02 +02:00
}
}
2009-10-03 09:32:05 +02:00
//custom fields are not shown in the regular views, so we only query them, if explicitly required
2010-05-20 17:12:59 +02:00
if ( ! is_null ( $params [ 'cfs' ]))
2005-11-09 00:15:14 +01:00
{
2009-11-03 17:13:45 +01:00
$where = array ( 'cal_id' => $ids );
2010-05-20 17:12:59 +02:00
if ( $params [ 'cfs' ]) $where [ 'cal_extra_name' ] = $params [ 'cfs' ];
2009-11-03 17:13:45 +01:00
foreach ( $this -> db -> select ( $this -> extra_table , '*' , $where ,
2009-10-03 09:32:05 +02:00
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2005-11-09 00:15:14 +01:00
{
2009-10-03 09:32:05 +02:00
foreach (( array ) $recur_ids [ $row [ 'cal_id' ]] as $id )
2005-11-09 00:15:14 +01:00
{
2009-10-03 09:32:05 +02:00
if ( isset ( $events [ $id ]))
{
$events [ $id ][ '#' . $row [ 'cal_extra_name' ]] = $row [ 'cal_extra_value' ];
}
2005-11-09 00:15:14 +01:00
}
}
}
2013-05-16 17:59:25 +02:00
// alarms
foreach ( $this -> read_alarms ( $ids ) as $cal_id => $alarms )
2005-11-09 00:15:14 +01:00
{
2013-05-16 17:59:25 +02:00
foreach ( $alarms as $id => $alarm )
2006-03-07 20:03:42 +01:00
{
2013-05-16 17:59:25 +02:00
$event_start = $alarm [ 'time' ] + $alarm [ 'offset' ];
if ( isset ( $events [ $cal_id ])) // none recuring event
{
$events [ $cal_id ][ 'alarm' ][ $id ] = $alarm ;
}
elseif ( isset ( $events [ $cal_id . '-' . $event_start ])) // recuring event
{
$events [ $cal_id . '-' . $event_start ][ 'alarm' ][ $id ] = $alarm ;
}
2006-03-29 09:01:18 +02:00
}
2005-11-09 00:15:14 +01:00
}
}
//echo "<p>socal::search\n"; _debug_array($events);
2011-04-05 17:32:20 +02:00
//error_log(__METHOD__."(,filter=".array2string($params['query']).",offset=$offset, num_rows=$num_rows) returning ".count($events)." entries".($offset!==false?" total=$this->total":'').' '.function_backtrace());
2005-11-09 00:15:14 +01:00
return $events ;
}
2010-01-29 22:42:54 +01:00
2010-01-22 00:36:05 +01:00
/**
* Data returned by calendar_search_union hook
*/
private static $integration_data ;
2006-03-29 09:01:18 +02:00
2010-01-19 23:20:44 +01:00
/**
* Ask other apps if they want to participate in calendar search / display
*
* @ param & $selects parts of union query
* @ param $start see search ()
* @ param $end
2010-05-20 17:12:59 +02:00
* @ param $users as used in calendar_so ( $users_raw plus all members and memberships added by calendar_bo )
2010-01-19 23:20:44 +01:00
* @ param $cat_id
* @ param $filter
* @ param $query
2010-05-20 17:12:59 +02:00
* @ param $users_raw as passed to calendar_bo :: search ( no members and memberships added )
2010-01-19 23:20:44 +01:00
*/
2010-05-20 17:12:59 +02:00
private static function get_union_selects ( array & $selects , $start , $end , $users , $cat_id , $filter , $query , $users_raw )
2010-01-19 23:20:44 +01:00
{
2011-04-05 17:32:20 +02:00
if ( in_array ( basename ( $_SERVER [ 'SCRIPT_FILENAME' ]), array ( 'groupdav.php' , 'rpc.php' , 'xmlrpc.php' , '/activesync/index.php' )) ||
2010-05-21 17:05:31 +02:00
! in_array ( $GLOBALS [ 'egw_info' ][ 'flags' ][ 'currentapp' ], array ( 'calendar' , 'home' )))
2010-01-24 04:50:40 +01:00
{
2010-01-29 22:42:54 +01:00
return ; // disable integration for GroupDAV, SyncML, ...
2010-01-24 04:50:40 +01:00
}
2016-05-01 19:47:59 +02:00
self :: $integration_data = Api\Hooks :: process ( array (
2010-01-19 23:20:44 +01:00
'location' => 'calendar_search_union' ,
2010-01-29 22:42:54 +01:00
'cols' => $selects [ 0 ][ 'cols' ], // cols to return
2010-01-19 23:20:44 +01:00
'start' => $start ,
'end' => $end ,
'users' => $users ,
2010-05-20 17:12:59 +02:00
'users_raw' => $users_raw ,
2010-01-19 23:20:44 +01:00
'cat_id' => $cat_id ,
'filter' => $filter ,
'query' => $query ,
));
2015-01-14 14:52:34 +01:00
foreach ( self :: $integration_data as $data )
2010-01-19 23:20:44 +01:00
{
if ( is_array ( $data [ 'selects' ]))
{
2010-01-22 00:36:05 +01:00
//echo $app; _debug_array($data);
2010-01-19 23:20:44 +01:00
$selects = array_merge ( $selects , $data [ 'selects' ]);
}
}
}
2010-01-29 22:42:54 +01:00
2010-01-22 00:36:05 +01:00
/**
* Get data from last 'calendar_search_union' hook call
2010-01-29 22:42:54 +01:00
*
2010-01-22 00:36:05 +01:00
* @ return array
*/
public static function get_integration_data ()
{
return self :: $integration_data ;
}
2010-01-29 22:42:54 +01:00
2010-04-23 08:52:48 +02:00
/**
* Return union cols constructed from application cols and required cols
*
* Every col not supplied in $app_cols get returned as NULL .
*
* @ param array $app_cols required name => own name pairs
* @ param string | array $required array or comma separated column names or table .*
2015-01-14 14:52:34 +01:00
* @ param string $required_app = 'calendar'
2010-04-23 08:52:48 +02:00
* @ return string cols for union query to match ones supplied in $required
*/
public static function union_cols ( array $app_cols , $required , $required_app = 'calendar' )
{
// remove evtl. used DISTINCT, we currently dont need it
if (( $distinct = substr ( $required , 0 , 9 ) == 'DISTINCT ' ))
{
$required = substr ( $required , 9 );
}
$return_cols = array ();
foreach ( is_array ( $required ) ? $required : explode ( ',' , $required ) as $cols )
{
2015-01-14 14:52:34 +01:00
$matches = null ;
2010-04-23 08:52:48 +02:00
if ( substr ( $cols , - 2 ) == '.*' )
{
$cols = self :: get_columns ( $required_app , substr ( $cols , 0 , - 2 ));
}
2013-10-15 13:30:01 +02:00
// remove CAST added for PostgreSQL from eg. "CAST(egw_cal.cal_id AS varchar)"
elseif ( preg_match ( '/CAST\(([a-z0-9_.]+) AS [a-z0-9_]+\)/i' , $cols , $matches ))
{
$cols = $matches [ 1 ];
}
2010-04-23 08:52:48 +02:00
elseif ( strpos ( $cols , ' AS ' ) !== false )
{
list (, $cols ) = explode ( ' AS ' , $cols );
}
foreach (( array ) $cols as $col )
{
if ( substr ( $col , 0 , 7 ) == 'egw_cal' ) // remove table name
{
2010-04-27 20:35:14 +02:00
$col = preg_replace ( '/^egw_cal[a-z_]*\./' , '' , $col );
2010-04-23 08:52:48 +02:00
}
if ( isset ( $app_cols [ $col ]))
{
$return_cols [] = $app_cols [ $col ];
}
else
{
$return_cols [] = 'NULL' ;
}
}
}
2013-10-15 13:30:01 +02:00
//error_log(__METHOD__."(".array2string($app_cols).", ".array2string($required).", '$required_app') returning ".array2string(implode(',',$return_cols)));
2010-04-23 08:52:48 +02:00
return implode ( ',' , $return_cols );
}
/**
* Get columns of given table , taking into account historically different column order of egw_cal table
*
* @ param string $app
* @ param string $table
* @ return array of column names
*/
static private function get_columns ( $app , $table )
{
if ( $table != 'egw_cal' )
{
$table_def = $GLOBALS [ 'egw' ] -> db -> get_table_definitions ( $app , $table );
$cols = array_keys ( $table_def [ 'fd' ]);
}
else
{
// special handling for egw_cal, as old databases have a different column order!!!
2016-05-01 19:47:59 +02:00
$cols =& Api\Cache :: getSession ( __CLASS__ , $table );
2010-04-23 08:52:48 +02:00
if ( is_null ( $cols ))
{
$meta = $GLOBALS [ 'egw' ] -> db -> metadata ( $table , true );
$cols = array_keys ( $meta [ 'meta' ]);
}
}
return $cols ;
}
2005-11-09 00:15:14 +01:00
/**
* Checks for conflicts
*/
/* folowing SQL checks for conflicts completly on DB level
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
SELECT cal_user_type , cal_user_id , SUM ( cal_quantity )
FROM egw_cal , egw_cal_dates , egw_cal_user
LEFT JOIN egw_cal_repeats ON egw_cal . cal_id = egw_cal_repeats . cal_id
WHERE egw_cal . cal_id = egw_cal_dates . cal_id
AND egw_cal . cal_id = egw_cal_user . cal_id
AND (
recur_type IS NULL
AND cal_recur_date = 0
OR cal_recur_date = cal_start
)
AND (
(
cal_user_type = 'u' # user of the checked event
AND cal_user_id
IN ( 7 , 5 )
)
AND 1118822400 < cal_end # start- and end-time of the checked event
AND cal_start < 1118833200
)
AND egw_cal . cal_id != 26 # id of the checked event
AND cal_non_blocking != 1
AND cal_status != 'R'
GROUP BY cal_user_type , cal_user_id
ORDER BY cal_user_type , cal_usre_id
2006-03-29 09:01:18 +02:00
*/
2005-11-09 00:15:14 +01:00
/**
* Saves or creates an event
*
* We always set cal_modified and cal_modifier and for new events cal_uid .
* All other column are only written if they are set in the $event parameter !
*
* @ param array $event
* @ param boolean & $set_recurrences on return : true if the recurrences need to be written , false otherwise
2009-08-10 11:24:39 +02:00
* @ param int & $set_recurrences_start = 0 on return : time from which on the recurrences should be rebuilt , default 0 = all
2015-01-14 14:52:34 +01:00
* @ param int $change_since = 0 time from which on the repetitions should be changed , default 0 = all
2008-05-08 00:12:25 +02:00
* @ param int & $etag etag = null etag to check or null , on return new etag
2009-05-05 00:39:27 +02:00
* @ return boolean | int false on error , 0 if etag does not match , cal_id otherwise
2005-11-09 00:15:14 +01:00
*/
2017-04-15 17:20:05 +02:00
function save ( $event , & $set_recurrences , & $set_recurrences_start = 0 , $change_since = 0 , & $etag = null )
2005-11-09 00:15:14 +01:00
{
2009-07-23 18:14:22 +02:00
if ( isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ]))
{
2009-07-15 22:35:56 +02:00
$minimum_uid_length = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'syncml' ][ 'minimum_uid_length' ];
2011-12-22 15:03:12 +01:00
if ( empty ( $minimum_uid_length ) || $minimum_uid_length <= 1 ) $minimum_uid_length = 8 ; // we just do not accept no uid, or uid way to short!
2009-07-23 18:14:22 +02:00
}
else
{
2009-07-15 22:35:56 +02:00
$minimum_uid_length = 8 ;
}
2010-02-17 14:29:28 +01:00
$old_min = $old_duration = 0 ;
2013-08-15 16:56:34 +02:00
//error_log(__METHOD__.'('.array2string($event).",$set_recurrences,$change_since,$etag) ".function_backtrace());
2005-11-09 00:15:14 +01:00
$cal_id = ( int ) $event [ 'id' ];
unset ( $event [ 'id' ]);
2015-11-24 19:15:11 +01:00
$set_recurrences = $set_recurrences || ! $cal_id && $event [ 'recur_type' ] != MCAL_RECUR_NONE ;
2006-03-29 09:01:18 +02:00
2009-11-27 07:46:32 +01:00
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE &&
! ( int ) $event [ 'recur_interval' ])
{
$event [ 'recur_interval' ] = 1 ;
}
2005-11-09 00:15:14 +01:00
// add colum prefix 'cal_' if there's not already a 'recur_' prefix
foreach ( $event as $col => $val )
{
2012-09-18 10:02:56 +02:00
if ( $col [ 0 ] != '#' && substr ( $col , 0 , 6 ) != 'recur_' && substr ( $col , 0 , 6 ) != 'range_' && $col != 'alarm' && $col != 'tz_id' && $col != 'caldav_name' )
2005-11-09 00:15:14 +01:00
{
$event [ 'cal_' . $col ] = $val ;
unset ( $event [ $col ]);
}
}
2012-10-02 18:43:49 +02:00
// set range_start/_end, but only if we have cal_start/_end, as otherwise we destroy present values!
if ( isset ( $event [ 'cal_start' ])) $event [ 'range_start' ] = $event [ 'cal_start' ];
if ( isset ( $event [ 'cal_end' ]))
{
$event [ 'range_end' ] = $event [ 'recur_type' ] == MCAL_RECUR_NONE ? $event [ 'cal_end' ] :
( $event [ 'recur_enddate' ] ? $event [ 'recur_enddate' ] : null );
}
2010-01-29 22:42:54 +01:00
// ensure that we find mathing entries later on
if ( ! is_array ( $event [ 'cal_category' ]))
{
$categories = array_unique ( explode ( ',' , $event [ 'cal_category' ]));
sort ( $categories );
}
else
{
$categories = array_unique ( $event [ 'cal_category' ]);
}
sort ( $categories , SORT_NUMERIC );
$event [ 'cal_category' ] = implode ( ',' , $categories );
2014-11-07 13:33:56 +01:00
2014-10-28 17:01:55 +01:00
// make sure recurring events never reference to an other recurrent event
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE ) $event [ 'cal_reference' ] = 0 ;
2005-11-09 00:15:14 +01:00
2008-05-08 00:12:25 +02:00
if ( $cal_id )
2008-01-15 09:21:25 +01:00
{
2012-10-23 16:55:41 +02:00
// query old recurrance information, before updating main table, where recur_endate is now stored
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
$old_repeats = $this -> db -> select ( $this -> repeats_table , " $this->repeats_table .*,range_end AS recur_enddate " ,
" $this->repeats_table .cal_id= " . ( int ) $cal_id , __LINE__ , __FILE__ ,
false , '' , 'calendar' , 0 , " JOIN $this->cal_table ON $this->repeats_table .cal_id= $this->cal_table .cal_id " ) -> fetch ();
}
2008-05-08 00:12:25 +02:00
$where = array ( 'cal_id' => $cal_id );
2009-11-12 20:11:27 +01:00
// read only timezone id, to check if it is changed
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
$old_tz_id = $this -> db -> select ( $this -> cal_table , 'tz_id' , $where , __LINE__ , __FILE__ , 'calendar' ) -> fetchColumn ();
}
2008-05-08 00:12:25 +02:00
if ( ! is_null ( $etag )) $where [ 'cal_etag' ] = $etag ;
unset ( $event [ 'cal_etag' ]);
$event [] = 'cal_etag=cal_etag+1' ; // always update the etag, even if none given to check
$this -> db -> update ( $this -> cal_table , $event , $where , __LINE__ , __FILE__ , 'calendar' );
if ( ! is_null ( $etag ) && $this -> db -> affected_rows () < 1 )
2008-01-15 09:21:25 +01:00
{
2008-05-08 00:12:25 +02:00
return 0 ; // wrong etag, someone else updated the entry
2008-01-15 09:21:25 +01:00
}
2008-05-08 00:12:25 +02:00
if ( ! is_null ( $etag )) ++ $etag ;
2005-11-09 00:15:14 +01:00
}
else
{
2009-07-15 22:35:56 +02:00
// new event
2005-11-09 00:15:14 +01:00
if ( ! $event [ 'cal_owner' ]) $event [ 'cal_owner' ] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
if ( ! $event [ 'cal_id' ] && ! isset ( $event [ 'cal_uid' ])) $event [ 'cal_uid' ] = '' ; // uid is NOT NULL!
2008-03-17 10:06:08 +01:00
$this -> db -> insert ( $this -> cal_table , $event , false , __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
if ( ! ( $cal_id = $this -> db -> get_last_insert_id ( $this -> cal_table , 'cal_id' )))
{
return false ;
}
2008-05-08 00:12:25 +02:00
$etag = 0 ;
2009-07-15 22:35:56 +02:00
}
2011-04-06 21:26:10 +02:00
$update = array ();
// event without uid or not strong enough uid
2009-07-23 18:14:22 +02:00
if ( ! isset ( $event [ 'cal_uid' ]) || strlen ( $event [ 'cal_uid' ]) < $minimum_uid_length )
{
2016-05-01 19:47:59 +02:00
$update [ 'cal_uid' ] = $event [ 'cal_uid' ] = Api\CalDAV :: generate_uid ( 'calendar' , $cal_id );
2011-04-06 21:26:10 +02:00
}
// set caldav_name, if not given by caller
if ( empty ( $event [ 'caldav_name' ]) && version_compare ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'calendar' ][ 'version' ], '1.9.003' , '>=' ))
{
$update [ 'caldav_name' ] = $event [ 'caldav_name' ] = $cal_id . '.ics' ;
}
if ( $update )
{
$this -> db -> update ( $this -> cal_table , $update , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
2010-10-28 11:22:01 +02:00
2010-06-26 17:58:33 +02:00
if ( $event [ 'recur_type' ] == MCAL_RECUR_NONE )
{
$this -> db -> delete ( $this -> dates_table , array (
'cal_id' => $cal_id ),
__LINE__ , __FILE__ , 'calendar' );
2010-10-28 11:22:01 +02:00
2010-06-26 17:58:33 +02:00
// delete all user-records, with recur-date != 0
$this -> db -> delete ( $this -> user_table , array (
'cal_id' => $cal_id , 'cal_recur_date != 0' ),
__LINE__ , __FILE__ , 'calendar' );
2010-10-28 11:22:01 +02:00
2010-06-26 17:58:33 +02:00
$this -> db -> delete ( $this -> repeats_table , array (
'cal_id' => $cal_id ),
__LINE__ , __FILE__ , 'calendar' );
2014-10-28 17:01:55 +01:00
// add exception marker to master, so participants added to exceptions *only* get found
if ( $event [ 'cal_reference' ])
{
$master_participants = array ();
2015-08-17 16:07:25 +02:00
foreach ( $this -> db -> select ( $this -> user_table , 'cal_user_type,cal_user_id,cal_user_attendee' , array (
2014-10-28 17:01:55 +01:00
'cal_id' => $event [ 'cal_reference' ],
'cal_recur_date' => 0 ,
" cal_status != 'X' " , // deleted need to be replaced with exception marker too
), __LINE__ , __FILE__ , 'calendar' ) as $row )
{
2015-08-17 16:07:25 +02:00
$master_participants [] = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2014-10-28 17:01:55 +01:00
}
foreach ( array_diff ( array_keys (( array ) $event [ 'cal_participants' ]), $master_participants ) as $uid )
{
$user_type = $user_id = null ;
2015-08-17 16:07:25 +02:00
self :: split_user ( $uid , $user_type , $user_id , true );
2014-10-28 17:01:55 +01:00
$this -> db -> insert ( $this -> user_table , array (
'cal_status' => 'E' ,
2015-08-17 16:07:25 +02:00
'cal_user_attendee' => $user_type == 'e' ? substr ( $uid , 1 ) : null ,
2014-10-28 17:01:55 +01:00
), array (
'cal_id' => $event [ 'cal_reference' ],
'cal_recur_date' => 0 ,
'cal_user_type' => $user_type ,
'cal_user_id' => $user_id ,
), __LINE__ , __FILE__ , 'calendar' );
}
}
2010-06-26 17:58:33 +02:00
}
else // write information about recuring event, if recur_type is present in the array
2005-11-09 00:15:14 +01:00
{
2009-07-17 19:16:34 +02:00
// fetch information about the currently saved (old) event
$old_min = ( int ) $this -> db -> select ( $this -> dates_table , 'MIN(cal_start)' , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , false , '' , 'calendar' ) -> fetchColumn ();
$old_duration = ( int ) $this -> db -> select ( $this -> dates_table , 'MIN(cal_end)' , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , false , '' , 'calendar' ) -> fetchColumn () - $old_min ;
2012-08-11 12:01:02 +02:00
$old_exceptions = array ();
foreach ( $this -> db -> select ( $this -> dates_table , 'cal_start' , array (
'cal_id' => $cal_id ,
'recur_exception' => true
), __LINE__ , __FILE__ , false , 'ORDER BY cal_start' , 'calendar' ) as $row )
2010-03-07 00:06:43 +01:00
{
2012-08-11 12:01:02 +02:00
$old_exceptions [] = $row [ 'cal_start' ];
2010-03-07 00:06:43 +01:00
}
2009-07-15 22:35:56 +02:00
2009-08-10 11:24:39 +02:00
$event [ 'recur_exception' ] = is_array ( $event [ 'recur_exception' ]) ? $event [ 'recur_exception' ] : array ();
2010-03-07 00:06:43 +01:00
if ( ! empty ( $event [ 'recur_exception' ]))
{
sort ( $event [ 'recur_exception' ]);
}
2012-08-11 12:01:02 +02:00
$where = array (
'cal_id' => $cal_id ,
'cal_recur_date' => 0 ,
);
2010-03-07 00:06:43 +01:00
$old_participants = array ();
2015-08-17 16:07:25 +02:00
foreach ( $this -> db -> select ( $this -> user_table , 'cal_user_type,cal_user_id,cal_user_attendee,cal_status,cal_quantity,cal_role' , $where ,
2010-03-07 00:06:43 +01:00
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2010-03-07 00:06:43 +01:00
$status = self :: combine_status ( $row [ 'cal_status' ], $row [ 'cal_quantity' ], $row [ 'cal_role' ]);
$old_participants [ $uid ] = $status ;
}
2009-08-10 11:24:39 +02:00
// re-check: did so much recurrence data change that we have to rebuild it from scratch?
if ( ! $set_recurrences )
2005-11-09 00:15:14 +01:00
{
2009-08-10 11:24:39 +02:00
$set_recurrences = ( isset ( $event [ 'cal_start' ]) && ( int ) $old_min != ( int ) $event [ 'cal_start' ]) ||
$event [ 'recur_type' ] != $old_repeats [ 'recur_type' ] || $event [ 'recur_data' ] != $old_repeats [ 'recur_data' ] ||
2009-11-12 20:11:27 +01:00
( int ) $event [ 'recur_interval' ] != ( int ) $old_repeats [ 'recur_interval' ] || $event [ 'tz_id' ] != $old_tz_id ;
2005-11-09 00:15:14 +01:00
}
2009-08-10 11:24:39 +02:00
if ( $set_recurrences )
2005-11-09 00:15:14 +01:00
{
2009-08-10 11:24:39 +02:00
// too much recurrence data has changed, we have to do a rebuild from scratch
// delete all, but the lowest dates record
$this -> db -> delete ( $this -> dates_table , array (
'cal_id' => $cal_id ,
'cal_start > ' . ( int ) $old_min ,
), __LINE__ , __FILE__ , 'calendar' );
2009-07-17 19:16:34 +02:00
2009-08-10 11:24:39 +02:00
// delete all user-records, with recur-date != 0
$this -> db -> delete ( $this -> user_table , array (
'cal_id' => $cal_id ,
'cal_recur_date != 0' ,
), __LINE__ , __FILE__ , 'calendar' );
}
else
2009-07-17 19:16:34 +02:00
{
2009-08-10 11:24:39 +02:00
// we adjust some possibly changed recurrences manually
// deleted exceptions: re-insert recurrences into the user and dates table
2010-03-07 00:06:43 +01:00
if ( count ( $deleted_exceptions = array_diff ( $old_exceptions , $event [ 'recur_exception' ])))
2009-07-17 19:16:34 +02:00
{
2010-03-07 00:06:43 +01:00
if ( isset ( $event [ 'cal_participants' ]))
{
$participants = $event [ 'cal_participants' ];
}
else
{
// use old default
$participants = $old_participants ;
}
2009-08-10 11:24:39 +02:00
foreach ( $deleted_exceptions as $id => $deleted_exception )
2009-07-17 19:16:34 +02:00
{
2009-08-10 11:24:39 +02:00
// rebuild participants for the re-inserted recurrence
$this -> recurrence ( $cal_id , $deleted_exception , $deleted_exception + $old_duration , $participants );
2009-07-17 19:16:34 +02:00
}
}
2009-08-10 11:24:39 +02:00
// check if recurrence enddate was adjusted
if ( isset ( $event [ 'recur_enddate' ]))
{
// recurrences need to be truncated
if (( int ) $event [ 'recur_enddate' ] > 0 &&
(( int ) $old_repeats [ 'recur_enddate' ] == 0 || ( int ) $old_repeats [ 'recur_enddate' ] > ( int ) $event [ 'recur_enddate' ])
)
{
2012-10-23 12:55:54 +02:00
$this -> db -> delete ( $this -> user_table , array ( 'cal_id' => $cal_id , 'cal_recur_date >= ' . ( $event [ 'recur_enddate' ] + 1 * DAY_s )), __LINE__ , __FILE__ , 'calendar' );
$this -> db -> delete ( $this -> dates_table , array ( 'cal_id' => $cal_id , 'cal_start >= ' . ( $event [ 'recur_enddate' ] + 1 * DAY_s )), __LINE__ , __FILE__ , 'calendar' );
2009-08-10 11:24:39 +02:00
}
// recurrences need to be expanded
if ((( int ) $event [ 'recur_enddate' ] == 0 && ( int ) $old_repeats [ 'recur_enddate' ] > 0 )
|| (( int ) $event [ 'recur_enddate' ] > 0 && ( int ) $old_repeats [ 'recur_enddate' ] > 0 && ( int ) $old_repeats [ 'recur_enddate' ] < ( int ) $event [ 'recur_enddate' ])
)
{
$set_recurrences = true ;
$set_recurrences_start = ( $old_repeats [ 'recur_enddate' ] + 1 * DAY_s );
}
2012-10-23 16:55:41 +02:00
//error_log(__METHOD__."() event[recur_enddate]=$event[recur_enddate], old_repeats[recur_enddate]=$old_repeats[recur_enddate] --> set_recurrences=".array2string($set_recurrences).", set_recurrences_start=$set_recurrences_start");
2009-08-10 11:24:39 +02:00
}
// truncate recurrences by given exceptions
if ( count ( $event [ 'recur_exception' ]))
{
2012-08-11 12:01:02 +02:00
// added and existing exceptions: delete the execeptions from the user table, it could be the first time
2009-08-10 11:24:39 +02:00
$this -> db -> delete ( $this -> user_table , array ( 'cal_id' => $cal_id , 'cal_recur_date' => $event [ 'recur_exception' ]), __LINE__ , __FILE__ , 'calendar' );
2012-08-11 12:01:02 +02:00
// update recur_exception flag based on current exceptions
$this -> db -> update ( $this -> dates_table , 'recur_exception=' . $this -> db -> expression ( $this -> dates_table , array (
'cal_start' => $event [ 'recur_exception' ],
)), array (
'cal_id' => $cal_id ,
), __LINE__ , __FILE__ , 'calendar' );
2009-08-10 11:24:39 +02:00
}
2005-11-09 00:15:14 +01:00
}
2009-07-23 18:14:22 +02:00
2009-08-10 11:24:39 +02:00
// write the repeats table
2008-05-16 13:02:46 +02:00
unset ( $event [ 0 ]); // unset the 'etag=etag+1', as it's not in the repeats table
2010-06-26 17:58:33 +02:00
$this -> db -> insert ( $this -> repeats_table , $event , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
// update start- and endtime if present in the event-array, evtl. we need to move all recurrences
if ( isset ( $event [ 'cal_start' ]) && isset ( $event [ 'cal_end' ]))
{
2010-02-17 14:29:28 +01:00
$this -> move ( $cal_id , $event [ 'cal_start' ], $event [ 'cal_end' ], ! $cal_id ? false : $change_since , $old_min , $old_min + $old_duration );
2005-11-09 00:15:14 +01:00
}
// update participants if present in the event-array
if ( isset ( $event [ 'cal_participants' ]))
{
$this -> participants ( $cal_id , $event [ 'cal_participants' ], ! $cal_id ? false : $change_since );
}
// Custom fields
2017-04-17 17:48:25 +02:00
Api\Storage\Customfields :: handle_files ( 'calendar' , $cal_id , $event );
2005-11-09 00:15:14 +01:00
foreach ( $event as $name => $value )
{
if ( $name [ 0 ] == '#' )
{
2011-08-03 14:35:42 +02:00
if ( is_array ( $value ) && array_key_exists ( 'id' , $value ))
2011-05-12 17:03:26 +02:00
{
//error_log(__METHOD__.__LINE__."$name => ".array2string($value).function_backtrace());
$value = $value [ 'id' ];
//error_log(__METHOD__.__LINE__."$name => ".array2string($value));
}
2009-09-27 09:59:01 +02:00
if ( $value )
2005-11-09 00:15:14 +01:00
{
$this -> db -> insert ( $this -> extra_table , array (
2009-09-27 09:59:01 +02:00
'cal_extra_value' => is_array ( $value ) ? implode ( ',' , $value ) : $value ,
2005-11-09 00:15:14 +01:00
), array (
'cal_id' => $cal_id ,
'cal_extra_name' => substr ( $name , 1 ),
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
else
{
$this -> db -> delete ( $this -> extra_table , array (
'cal_id' => $cal_id ,
'cal_extra_name' => substr ( $name , 1 ),
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2006-03-29 09:01:18 +02:00
}
2005-11-09 00:15:14 +01:00
}
}
2010-06-08 18:36:55 +02:00
// updating or saving the alarms; new alarms have a temporary numeric id!
2005-11-09 00:15:14 +01:00
if ( is_array ( $event [ 'alarm' ]))
{
foreach ( $event [ 'alarm' ] as $id => $alarm )
{
2015-06-25 22:39:53 +02:00
if ( $alarm [ 'id' ] && strpos ( $alarm [ 'id' ], 'cal:' . $cal_id . ':' ) !== 0 )
{
unset ( $alarm [ 'id' ]); // unset the temporary id to add the alarm
}
2008-11-17 20:06:27 +01:00
if ( ! isset ( $alarm [ 'offset' ]))
{
$alarm [ 'offset' ] = $event [ 'cal_start' ] - $alarm [ 'time' ];
}
elseif ( ! isset ( $alarm [ 'time' ]))
{
$alarm [ 'time' ] = $event [ 'cal_start' ] - $alarm [ 'offset' ];
}
2010-10-28 11:22:01 +02:00
2012-07-18 08:45:09 +02:00
if ( $alarm [ 'time' ] < time () && ! self :: shift_alarm ( $event , $alarm ))
2008-11-17 20:06:27 +01:00
{
2012-07-18 08:45:09 +02:00
continue ; // pgoerzen: don't add alarm in the past
2010-06-08 18:36:55 +02:00
}
2013-02-26 09:48:50 +01:00
$this -> save_alarm ( $cal_id , $alarm , false ); // false: not update modified, we do it anyway
2005-11-09 00:15:14 +01:00
}
}
2008-05-08 00:12:25 +02:00
if ( is_null ( $etag ))
{
2009-06-08 18:21:14 +02:00
$etag = $this -> db -> select ( $this -> cal_table , 'cal_etag' , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , false , '' , 'calendar' ) -> fetchColumn ();
2008-05-08 00:12:25 +02:00
}
2012-09-28 09:53:14 +02:00
// if event is an exception: update modified of master, to force etag, ctag and sync-token change
if ( $event [ 'cal_reference' ])
{
$this -> updateModified ( $event [ 'cal_reference' ]);
}
2005-11-09 00:15:14 +01:00
return $cal_id ;
}
2006-03-29 09:01:18 +02:00
2012-07-18 08:45:09 +02:00
/**
* Shift alarm on recurring events to next future recurrence
*
2015-01-14 14:52:34 +01:00
* @ param array $_event event with optional 'cal_' prefix in keys
2012-07-18 08:45:09 +02:00
* @ param array & $alarm
2016-01-18 18:45:25 +01:00
* @ param int $timestamp For recurring events , this is the date we
* are dealing with , default is now .
2012-07-18 08:45:09 +02:00
* @ return boolean true if alarm could be shifted , false if not
*/
2016-05-06 15:54:08 +02:00
public static function shift_alarm ( array $_event , array & $alarm , $timestamp = null )
2012-07-18 08:45:09 +02:00
{
2015-01-14 14:52:34 +01:00
if ( $_event [ 'recur_type' ] == MCAL_RECUR_NONE )
2012-07-18 08:45:09 +02:00
{
return false ;
}
2016-01-18 18:45:25 +01:00
$start = $timestamp ? $timestamp : ( int ) time () + $alarm [ 'offset' ];
2016-05-01 19:47:59 +02:00
$event = Api\Db :: strip_array_keys ( $_event , 'cal_' );
2012-07-18 08:45:09 +02:00
$rrule = calendar_rrule :: event2rrule ( $event , false );
foreach ( $rrule as $time )
{
2016-05-01 19:47:59 +02:00
if ( $start < ( $ts = Api\DateTime :: to ( $time , 'server' )))
2012-07-18 08:45:09 +02:00
{
$alarm [ 'time' ] = $ts - $alarm [ 'offset' ];
return true ;
}
}
return false ;
}
2005-11-09 00:15:14 +01:00
/**
* moves an event to an other start - and end - time taken into account the evtl . recurrences of the event ( ! )
*
* @ param int $cal_id
* @ param int $start new starttime
* @ param int $end new endtime
2015-01-14 14:52:34 +01:00
* @ param int | boolean $change_since = 0 false = new entry , > 0 time from which on the repetitions should be changed , default 0 = all
* @ param int $old_start = 0 old starttime or ( default ) 0 , to query it from the db
* @ param int $old_end = 0 old starttime or ( default ) 0
2009-11-04 08:57:55 +01:00
* @ todo Recalculate recurrences , if timezone changes
2009-05-05 00:39:27 +02:00
* @ return int | boolean number of moved recurrences or false on error
2005-11-09 00:15:14 +01:00
*/
2009-11-04 16:00:08 +01:00
function move ( $cal_id , $start , $end , $change_since = 0 , $old_start = 0 , $old_end = 0 )
2005-11-09 00:15:14 +01:00
{
//echo "<p>socal::move($cal_id,$start,$end,$change_since,$old_start,$old_end)</p>\n";
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
if ( ! ( int ) $cal_id ) return false ;
if ( ! $old_start )
{
2008-03-15 15:10:20 +01:00
if ( $change_since !== false ) $row = $this -> db -> select ( $this -> dates_table , 'MIN(cal_start) AS cal_start,MIN(cal_end) AS cal_end' ,
array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , false , '' , 'calendar' ) -> fetch ();
2005-11-09 00:15:14 +01:00
// if no recurrence found, create one with the new dates
2008-03-15 15:10:20 +01:00
if ( $change_since === false || ! $row || ! $row [ 'cal_start' ] || ! $row [ 'cal_end' ])
2005-11-09 00:15:14 +01:00
{
$this -> db -> insert ( $this -> dates_table , array (
'cal_id' => $cal_id ,
'cal_start' => $start ,
'cal_end' => $end ,
2008-03-15 15:10:20 +01:00
), false , __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
return 1 ;
}
$move_start = ( int ) ( $start - $row [ 'cal_start' ]);
$move_end = ( int ) ( $end - $row [ 'cal_end' ]);
}
else
{
$move_start = ( int ) ( $start - $old_start );
$move_end = ( int ) ( $end - $old_end );
}
2008-03-15 15:10:20 +01:00
$where = 'cal_id=' . ( int ) $cal_id ;
2005-11-09 00:15:14 +01:00
if ( $move_start )
{
// move the recur-date of the participants
$this -> db -> query ( " UPDATE $this->user_table SET cal_recur_date=cal_recur_date+ $move_start WHERE $where AND cal_recur_date " .
2009-08-09 09:51:27 +02:00
(( int ) $change_since ? '>= ' . ( int ) $change_since : '!= 0' ), __LINE__ , __FILE__ );
2005-11-09 00:15:14 +01:00
}
if ( $move_start || $move_end )
{
// move the event and it's recurrences
2009-11-04 16:00:08 +01:00
$this -> db -> query ( " UPDATE $this->dates_table SET cal_start=cal_start+ $move_start ,cal_end=cal_end+ $move_end WHERE $where " .
2008-03-15 15:10:20 +01:00
(( int ) $change_since ? ' AND cal_start >= ' . ( int ) $change_since : '' ), __LINE__ , __FILE__ );
2005-11-09 00:15:14 +01:00
}
return $this -> db -> affected_rows ();
}
2006-03-29 09:01:18 +02:00
2015-08-17 16:07:25 +02:00
/**
* Format attendee as email
*
* @ param string | array $attendee attendee information : email , json or array with attr cn and url
* @ return type
*/
static function attendee2email ( $attendee )
{
if ( is_string ( $attendee ) && $attendee [ 0 ] == '{' && substr ( $attendee , - 1 ) == '}' )
{
$user_attendee = json_decode ( $user_attendee , true );
}
if ( is_array ( $attendee ))
{
$email = ! empty ( $attendee [ 'email' ]) ? $user_attendee [ 'email' ] :
( strtolower ( substr ( $attendee [ 'url' ], 0 , 7 )) == 'mailto:' ? substr ( $user_attendee [ 'url' ], 7 ) : $attendee [ 'url' ]);
$attendee = ! empty ( $attendee [ 'cn' ]) ? $attendee [ 'cn' ] . ' <' . $email . '>' : $email ;
}
return $attendee ;
}
2005-11-09 00:15:14 +01:00
/**
* combines user_type and user_id into a single string or integer ( for users )
*
* @ param string $user_type 1 - char type : 'u' = user , ...
2008-05-08 17:02:35 +02:00
* @ param string | int $user_id id
2015-08-17 16:07:25 +02:00
* @ param string | array $attendee attendee information : email , json or array with attr cn and url
2008-05-08 17:02:35 +02:00
* @ return string | int combined id
2005-11-09 00:15:14 +01:00
*/
2015-08-17 16:07:25 +02:00
static function combine_user ( $user_type , $user_id , $attendee = null )
2005-11-09 00:15:14 +01:00
{
if ( ! $user_type || $user_type == 'u' )
{
return ( int ) $user_id ;
}
2015-08-17 16:07:25 +02:00
if ( $user_type == 'e' && $attendee )
{
$user_id = self :: attendee2email ( $attendee );
}
2005-11-09 00:15:14 +01:00
return $user_type . $user_id ;
}
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
/**
* splits the combined user_type and user_id into a single values
*
2015-08-17 16:07:25 +02:00
* This is the only method building ( normalized ) md5 hashes for user_type = " e " ,
* if called with $md5_email = true parameter !
*
2008-05-08 17:02:35 +02:00
* @ param string | int $uid
* @ param string & $user_type 1 - char type : 'u' = user , ...
* @ param string | int & $user_id id
2015-08-17 16:07:25 +02:00
* @ param boolean $md5_email = false md5 hash user_id for email / user_type == " e "
2005-11-09 00:15:14 +01:00
*/
2015-08-17 16:07:25 +02:00
static function split_user ( $uid , & $user_type , & $user_id , $md5_email = false )
2005-11-09 00:15:14 +01:00
{
if ( is_numeric ( $uid ))
{
$user_type = 'u' ;
$user_id = ( int ) $uid ;
}
2015-08-17 16:07:25 +02:00
// create md5 hash from lowercased and trimed raw email ("rb@stylite.de", not "Ralf Becker <rb@stylite.de>")
elseif ( $md5_email && $uid [ 0 ] == 'e' )
{
$user_type = $uid [ 0 ];
$email = substr ( $uid , 1 );
$matches = null ;
if ( preg_match ( '/<([^<>]+)>$/' , $email , $matches )) $email = $matches [ 1 ];
$user_id = md5 ( trim ( strtolower ( $email )));
}
2005-11-09 00:15:14 +01:00
else
{
$user_type = $uid [ 0 ];
2008-05-08 17:02:35 +02:00
$user_id = substr ( $uid , 1 );
2005-11-09 00:15:14 +01:00
}
}
2006-03-29 09:01:18 +02:00
2009-08-06 13:29:05 +02:00
/**
* Combine status , quantity and role into one value
*
2015-06-25 22:39:53 +02:00
* @ param string $status status letter : U , T , A , R
2015-01-14 14:52:34 +01:00
* @ param int $quantity = 1
* @ param string $role = 'REQ-PARTICIPANT'
2009-08-06 13:29:05 +02:00
* @ return string
*/
2009-10-13 10:58:54 +02:00
static function combine_status ( $status , $quantity = 1 , $role = 'REQ-PARTICIPANT' )
2009-08-06 13:29:05 +02:00
{
if (( int ) $quantity > 1 ) $status .= ( int ) $quantity ;
if ( $role != 'REQ-PARTICIPANT' ) $status .= $role ;
return $status ;
}
/**
* splits the combined status , quantity and role
*
* @ param string & $status I : combined value , O : status letter : U , T , A , R
2013-08-15 16:56:34 +02:00
* @ param int & $quantity = null only O : quantity
* @ param string & $role = null only O : role
* @ return string status U , T , A or R , same as $status parameter on return
2009-08-06 13:29:05 +02:00
*/
2013-08-15 16:56:34 +02:00
static function split_status ( & $status , & $quantity = null , & $role = null )
2009-08-06 13:29:05 +02:00
{
$quantity = 1 ;
$role = 'REQ-PARTICIPANT' ;
2014-03-20 17:10:52 +01:00
//error_log(__METHOD__.__LINE__.array2string($status));
2015-01-14 14:52:34 +01:00
$matches = null ;
2014-03-20 17:10:52 +01:00
if ( is_string ( $status ) && strlen ( $status ) > 1 && preg_match ( '/^.([0-9]*)(.*)$/' , $status , $matches ))
2009-08-06 13:29:05 +02:00
{
if (( int ) $matches [ 1 ] > 0 ) $quantity = ( int ) $matches [ 1 ];
2009-10-12 21:16:42 +02:00
if ( $matches [ 2 ]) $role = $matches [ 2 ];
2009-08-06 13:29:05 +02:00
$status = $status [ 0 ];
}
2009-11-26 19:36:19 +01:00
elseif ( $status === true )
{
$status = 'U' ;
}
2013-08-15 16:56:34 +02:00
return $status ;
2009-08-06 13:29:05 +02:00
}
2005-11-09 00:15:14 +01:00
/**
* updates the participants of an event , taken into account the evtl . recurrences of the event ( ! )
2009-08-17 16:45:42 +02:00
* this method just adds new participants or removes not longer set participants
2010-03-07 00:06:43 +01:00
* this method does never overwrite existing entries ( except the 0 - recurrence and for delete )
2006-03-29 09:01:18 +02:00
*
2005-11-09 00:15:14 +01:00
* @ param int $cal_id
2009-08-04 19:14:16 +02:00
* @ param array $participants uid => status pairs
2015-01-14 14:52:34 +01:00
* @ param int | boolean $change_since = 0 , false = new event ,
2010-03-07 00:06:43 +01:00
* 0 = all , > 0 time from which on the repetitions should be changed
2015-01-14 14:52:34 +01:00
* @ param boolean $add_only = false
2009-08-17 16:45:42 +02:00
* false = add AND delete participants if needed ( full list of participants required in $participants )
* true = only add participants if needed , no participant will be deleted ( participants to check / add required in $participants )
2009-05-05 00:39:27 +02:00
* @ return int | boolean number of updated recurrences or false on error
2005-11-09 00:15:14 +01:00
*/
2009-08-17 16:45:42 +02:00
function participants ( $cal_id , $participants , $change_since = 0 , $add_only = false )
2005-11-09 00:15:14 +01:00
{
2010-11-11 09:51:13 +01:00
//error_log(__METHOD__."($cal_id,".array2string($participants).",$change_since,$add_only");
2009-11-29 15:03:45 +01:00
$recurrences = array ();
2005-11-23 15:21:20 +01:00
// remove group-invitations, they are NOT stored in the db
foreach ( $participants as $uid => $status )
{
2009-08-06 13:29:05 +02:00
if ( $status [ 0 ] == 'G' )
2005-11-23 15:21:20 +01:00
{
unset ( $participants [ $uid ]);
}
}
2005-11-09 00:15:14 +01:00
$where = array ( 'cal_id' => $cal_id );
if (( int ) $change_since )
{
2011-01-06 06:19:10 +01:00
$where [] = '(cal_recur_date=0 OR cal_recur_date >= ' . ( int ) $change_since . ')' ;
2005-11-09 00:15:14 +01:00
}
2009-09-27 09:59:01 +02:00
2009-11-29 15:03:45 +01:00
if ( $change_since !== false )
2005-11-09 00:15:14 +01:00
{
2009-11-29 15:03:45 +01:00
// find all existing recurrences
foreach ( $this -> db -> select ( $this -> user_table , 'DISTINCT cal_recur_date' , $where , __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
$recurrences [] = $row [ 'cal_recur_date' ];
}
// update existing entries
2011-01-06 06:19:10 +01:00
$existing_entries = $this -> db -> select ( $this -> user_table , '*' , $where , __LINE__ , __FILE__ , false , 'ORDER BY cal_recur_date DESC' , 'calendar' );
2009-08-17 16:45:42 +02:00
// create a full list of participants which already exist in the db
2011-01-06 06:19:10 +01:00
// with status, quantity and role of the earliest recurence
2009-08-17 16:45:42 +02:00
$old_participants = array ();
foreach ( $existing_entries as $row )
2005-11-09 00:15:14 +01:00
{
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2011-01-06 06:19:10 +01:00
if ( $row [ 'cal_recur_date' ] || ! isset ( $old_participants [ $uid ]))
{
$old_participants [ $uid ] = self :: combine_status ( $row [ 'cal_status' ], $row [ 'cal_quantity' ], $row [ 'cal_role' ]);
}
2009-08-17 16:45:42 +02:00
}
// tag participants which should be deleted
if ( $add_only === false )
{
$deleted = array ();
foreach ( $existing_entries as $row )
2005-11-09 00:15:14 +01:00
{
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2009-08-17 16:45:42 +02:00
// delete not longer set participants
if ( ! isset ( $participants [ $uid ]))
{
$deleted [ $row [ 'cal_user_type' ]][] = $row [ 'cal_user_id' ];
}
2005-11-09 00:15:14 +01:00
}
}
2010-11-11 09:51:13 +01:00
// only keep added OR status (incl. quantity!) changed participants for further steps
// we do not touch unchanged (!) existing ones
foreach ( $participants as $uid => $status )
{
if ( $old_participants [ $uid ] === $status )
{
unset ( $participants [ $uid ]);
}
}
2009-08-17 16:45:42 +02:00
// delete participants tagged for delete
if ( $add_only === false && count ( $deleted ))
2005-11-09 00:15:14 +01:00
{
$to_or = array ();
2008-03-15 15:10:20 +01:00
$table_def = $this -> db -> get_table_definitions ( 'calendar' , $this -> user_table );
2005-11-09 00:15:14 +01:00
foreach ( $deleted as $type => $ids )
{
2008-03-15 15:10:20 +01:00
$to_or [] = $this -> db -> expression ( $table_def , array (
2005-11-09 00:15:14 +01:00
'cal_user_type' => $type ,
'cal_user_id' => $ids ,
));
}
2014-10-28 17:01:55 +01:00
$where [] = '(' . implode ( ' OR ' , $to_or ) . ')' ;
$where [] = " cal_status!='E' " ; // do NOT delete exception marker
2012-01-23 08:41:29 +01:00
$this -> db -> update ( $this -> user_table , array ( 'cal_status' => 'X' ), $where , __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
}
2009-09-27 09:59:01 +02:00
2009-08-17 16:45:42 +02:00
if ( count ( $participants )) // participants which need to be added
2005-11-09 00:15:14 +01:00
{
2009-11-29 15:03:45 +01:00
if ( ! count ( $recurrences )) $recurrences [] = 0 ; // insert the default recurrence
2005-11-09 00:15:14 +01:00
2013-10-17 14:02:24 +02:00
$delete_deleted = array ();
2009-08-17 16:45:42 +02:00
// update participants
2005-11-09 00:15:14 +01:00
foreach ( $participants as $uid => $status )
{
2010-03-07 00:06:43 +01:00
$type = $id = $quantity = $role = null ;
2015-08-17 16:07:25 +02:00
self :: split_user ( $uid , $type , $id , true );
2009-08-06 13:29:05 +02:00
self :: split_status ( $status , $quantity , $role );
2009-08-04 19:14:16 +02:00
$set = array (
2009-08-06 13:29:05 +02:00
'cal_status' => $status ,
'cal_quantity' => $quantity ,
'cal_role' => $role ,
2015-08-17 16:07:25 +02:00
'cal_user_attendee' => $type == 'e' ? substr ( $uid , 1 ) : null ,
2009-08-04 19:14:16 +02:00
);
2005-11-09 00:15:14 +01:00
foreach ( $recurrences as $recur_date )
{
2009-08-04 19:14:16 +02:00
$this -> db -> insert ( $this -> user_table , $set , array (
2007-06-11 19:13:43 +02:00
'cal_id' => $cal_id ,
'cal_recur_date' => $recur_date ,
2005-11-09 00:15:14 +01:00
'cal_user_type' => $type ,
'cal_user_id' => $id ,
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
2013-10-17 14:02:24 +02:00
// for new or changed group-invitations, remove previously deleted members, so they show up again
if ( $uid < 0 )
{
$delete_deleted = array_merge ( $delete_deleted , $GLOBALS [ 'egw' ] -> accounts -> members ( $uid , true ));
}
}
if ( $delete_deleted )
{
$this -> db -> delete ( $this -> user_table , $where = array (
'cal_id' => $cal_id ,
'cal_recur_date' => $recurrences ,
'cal_user_type' => 'u' ,
'cal_user_id' => array_unique ( $delete_deleted ),
'cal_status' => 'X' ,
), __LINE__ , __FILE__ , 'calendar' );
//error_log(__METHOD__."($cal_id, ".array2string($participants).", since=$change_since, add_only=$add_only) db->delete('$this->user_table', ".array2string($where).") affected ".$this->db->affected_rows().' rows');
2005-11-09 00:15:14 +01:00
}
}
2006-03-29 09:01:18 +02:00
return true ;
2005-11-09 00:15:14 +01:00
}
/**
* set the status of one participant for a given recurrence or for all recurrences since now ( includes recur_date = 0 )
*
* @ param int $cal_id
2006-12-25 13:16:40 +01:00
* @ param char $user_type 'u' regular user , 'r' resource , 'c' contact
2015-08-17 16:07:25 +02:00
* @ param int | string $user_id
2009-07-15 22:35:56 +02:00
* @ param int | char $status numeric status ( defines ) or 1 - char code : 'R' , 'U' , 'T' or 'A'
2015-01-14 14:52:34 +01:00
* @ param int $recur_date = 0 date to change , or 0 = all since now
* @ param string $role = null role to set if ! is_null ( $role )
2015-08-17 16:07:25 +02:00
* @ param string $attendee = null extra attendee information to set for all types ( incl . accounts ! )
2005-11-09 00:15:14 +01:00
* @ return int number of changed recurrences
*/
2015-08-17 16:07:25 +02:00
function set_status ( $cal_id , $user_type , $user_id , $status , $recur_date = 0 , $role = null , $attendee = null )
2005-11-09 00:15:14 +01:00
{
static $status_code_short = array (
REJECTED => 'R' ,
NO_RESPONSE => 'U' ,
TENTATIVE => 'T' ,
2010-02-17 14:29:28 +01:00
ACCEPTED => 'A' ,
DELEGATED => 'D'
2005-11-09 00:15:14 +01:00
);
2009-03-24 09:06:05 +01:00
if ( ! ( int ) $cal_id || ! ( int ) $user_id && $user_type != 'e' )
{
return false ;
}
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
if ( is_numeric ( $status )) $status = $status_code_short [ $status ];
2006-03-29 09:01:18 +02:00
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $user_type , $user_id );
$user_id_md5 = null ;
self :: split_user ( $uid , $user_type , $user_id_md5 , true );
2013-10-17 14:02:24 +02:00
2005-11-09 00:15:14 +01:00
$where = array (
'cal_id' => $cal_id ,
2013-10-17 14:02:24 +02:00
'cal_user_type' => $user_type ,
2015-08-17 16:07:25 +02:00
'cal_user_id' => $user_id_md5 ,
2005-11-09 00:15:14 +01:00
);
if (( int ) $recur_date )
{
$where [ 'cal_recur_date' ] = $recur_date ;
}
else
{
$where [] = '(cal_recur_date=0 OR cal_recur_date >= ' . time () . ')' ;
}
2009-08-17 16:45:42 +02:00
2005-11-23 15:21:20 +01:00
if ( $status == 'G' ) // remove group invitations, as we dont store them in the db
{
2008-03-15 15:10:20 +01:00
$this -> db -> delete ( $this -> user_table , $where , __LINE__ , __FILE__ , 'calendar' );
2013-10-17 14:02:24 +02:00
$ret = $this -> db -> affected_rows ();
2005-11-23 15:21:20 +01:00
}
else
{
2009-08-04 19:14:16 +02:00
$set = array ( 'cal_status' => $status );
2015-08-17 16:07:25 +02:00
if ( $user_type == 'e' || $attendee ) $set [ 'cal_user_attendee' ] = $attendee ? $attendee : $user_id ;
2009-11-26 19:36:19 +01:00
if ( ! is_null ( $role ) && $role != 'REQ-PARTICIPANT' ) $set [ 'cal_role' ] = $role ;
2009-08-04 19:14:16 +02:00
$this -> db -> insert ( $this -> user_table , $set , $where , __LINE__ , __FILE__ , 'calendar' );
2013-10-17 14:02:24 +02:00
// for new or changed group-invitations, remove previously deleted members, so they show up again
if (( $ret = $this -> db -> affected_rows ()) && $user_type == 'u' && $user_id < 0 )
{
$where [ 'cal_user_id' ] = $GLOBALS [ 'egw' ] -> accounts -> members ( $user_id , true );
$where [ 'cal_status' ] = 'X' ;
$this -> db -> delete ( $this -> user_table , $where , __LINE__ , __FILE__ , 'calendar' );
//error_log(__METHOD__."($cal_id,$user_type,$user_id,$status,$recur_date) = $ret, db->delete('$this->user_table', ".array2string($where).") affected ".$this->db->affected_rows().' rows');
}
2005-11-23 15:21:20 +01:00
}
2012-09-25 13:54:41 +02:00
// update modified and modifier in main table
2013-10-17 14:02:24 +02:00
if ( $ret )
2012-09-25 13:54:41 +02:00
{
2013-02-26 09:48:50 +01:00
$this -> updateModified ( $cal_id , true ); // true = update series master too
2012-09-25 13:54:41 +02:00
}
2009-07-15 22:35:56 +02:00
//error_log(__METHOD__."($cal_id,$user_type,$user_id,$status,$recur_date) = $ret");
2009-08-04 19:14:16 +02:00
return $ret ;
2005-11-09 00:15:14 +01:00
}
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
/**
* creates or update a recurrence in the dates and users table
*
2006-03-29 09:01:18 +02:00
* @ param int $cal_id
2005-11-09 00:15:14 +01:00
* @ param int $start
* @ param int $end
* @ param array $participants uid => status pairs
2015-01-14 14:52:34 +01:00
* @ param boolean $exception = null true or false to set recure_exception flag , null leave it unchanged ( new are by default no exception )
2005-11-09 00:15:14 +01:00
*/
2013-08-15 14:23:38 +02:00
function recurrence ( $cal_id , $start , $end , $participants , $exception = null )
2005-11-09 00:15:14 +01:00
{
2013-08-15 14:23:38 +02:00
//error_log(__METHOD__."($cal_id, $start, $end, ".array2string($participants).", ".array2string($exception));
$update = array ( 'cal_end' => $end );
if ( isset ( $exception )) $update [ 'recur_exception' ] = $exception ;
$this -> db -> insert ( $this -> dates_table , $update , array (
2005-11-09 00:15:14 +01:00
'cal_id' => $cal_id ,
'cal_start' => $start ,
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2006-03-29 09:01:18 +02:00
2011-11-09 18:53:42 +01:00
if ( ! is_array ( $participants ))
{
error_log ( __METHOD__ . " ( $cal_id , $start , $end , " . array2string ( $participants ) . " ) participants is NO array! " . function_backtrace ());
}
2013-08-15 14:23:38 +02:00
if ( $exception !== true )
2005-11-09 00:15:14 +01:00
{
2013-08-15 14:23:38 +02:00
foreach ( $participants as $uid => $status )
{
if ( $status == 'G' ) continue ; // dont save group-invitations
$type = '' ;
$id = null ;
2015-08-17 16:07:25 +02:00
self :: split_user ( $uid , $type , $id , true );
2015-01-14 14:52:34 +01:00
$quantity = $role = null ;
2013-08-15 14:23:38 +02:00
self :: split_status ( $status , $quantity , $role );
$this -> db -> insert ( $this -> user_table , array (
'cal_status' => $status ,
'cal_quantity' => $quantity ,
2015-08-17 16:07:25 +02:00
'cal_role' => $role ,
2015-11-03 14:41:16 +01:00
'cal_user_attendee' => $type == 'e' ? substr ( $uid , 1 ) : null ,
2013-08-15 14:23:38 +02:00
), array (
'cal_id' => $cal_id ,
'cal_recur_date' => $start ,
'cal_user_type' => $type ,
'cal_user_id' => $id ,
), __LINE__ , __FILE__ , 'calendar' );
}
2005-11-09 00:15:14 +01:00
}
}
/**
* Get all unfinished recuring events ( or all users ) after a given time
*
2006-03-29 09:01:18 +02:00
* @ param int $time
2005-11-09 00:15:14 +01:00
* @ return array with cal_id => max ( cal_start ) pairs
*/
function unfinished_recuring ( $time )
{
2008-03-15 15:10:20 +01:00
$ids = array ();
2012-09-19 12:27:28 +02:00
foreach ( $rs = $this -> db -> select ( $this -> repeats_table , " $this->repeats_table .cal_id,MAX(cal_start) AS cal_start " ,
'(range_end IS NULL OR range_end > ' . ( int ) $time . ')' ,
__LINE__ , __FILE__ , false , " GROUP BY $this->repeats_table .cal_id,range_end " , 'calendar' , 0 ,
" JOIN $this->cal_table ON $this->repeats_table .cal_id= $this->cal_table .cal_id " .
" JOIN $this->dates_table ON $this->repeats_table .cal_id= $this->dates_table .cal_id " ) as $row )
2005-11-09 00:15:14 +01:00
{
$ids [ $row [ 'cal_id' ]] = $row [ 'cal_start' ];
}
2012-09-19 12:27:28 +02:00
//error_log(__METHOD__."($time) query='$rs->sql' --> ids=".array2string($ids));
2005-11-09 00:15:14 +01:00
return $ids ;
}
/**
* deletes an event incl . all recurrences , participants and alarms
*
* @ param int $cal_id
*/
function delete ( $cal_id )
{
//echo "<p>socal::delete($cal_id)</p>\n";
$this -> delete_alarms ( $cal_id );
2013-02-26 09:48:50 +01:00
// update timestamp of series master, updates own timestamp too, which does not hurt ;-)
$this -> updateModified ( $cal_id , true );
2005-11-09 00:15:14 +01:00
foreach ( $this -> all_tables as $table )
{
2008-03-15 15:10:20 +01:00
$this -> db -> delete ( $table , array ( 'cal_id' => $cal_id ), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
}
2010-06-14 13:45:00 +02:00
/**
* Delete all events that were before the given date .
*
* Recurring events that finished before the date will be deleted .
* Recurring events that span the date will be ignored . Non - recurring
* events before the date will be deleted .
*
* @ param int $date
*/
function purge ( $date )
{
2012-09-19 12:27:28 +02:00
// with new range_end we simple delete all with range_end < $date (range_end NULL is never returned)
foreach ( $this -> db -> select ( $this -> cal_table , 'cal_id' , 'range_end < ' . ( int ) $date , __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2010-06-14 13:45:00 +02:00
{
2010-07-06 10:20:53 +02:00
//echo __METHOD__." About to delete".$row['cal_id']."\r\n";
foreach ( $this -> all_tables as $table )
{
$this -> db -> delete ( $table , array ( 'cal_id' => $row [ 'cal_id' ]), __LINE__ , __FILE__ , 'calendar' );
}
// handle sync
2010-09-10 09:01:41 +02:00
$this -> db -> update ( 'egw_api_content_history' , array (
'sync_deleted' => time (),
), array (
'sync_appname' => 'calendar' ,
'sync_contentid' => $row [ 'cal_id' ], // sync_contentid is varchar(60)!
), __LINE__ , __FILE__ );
2010-07-06 10:20:53 +02:00
// handle links
2016-05-01 19:47:59 +02:00
Link :: unlink ( '' , 'calendar' , $row [ 'cal_id' ]);
2010-06-14 13:45:00 +02:00
}
}
2005-11-09 00:15:14 +01:00
/**
2013-05-16 17:59:25 +02:00
* Caches all alarms read from async table to not re - read them in same request
*
* @ var array cal_id => array ( async_id => data )
*/
static $alarm_cache ;
/**
* read the alarms of one or more calendar - event ( s ) specified by $cal_id
2005-11-09 00:15:14 +01:00
*
* alarm - id is a string of 'cal:' . $cal_id . ':' . $alarm_nr , it is used as the job - id too
*
2013-05-16 17:59:25 +02:00
* @ param int | array $cal_id
2015-01-14 14:52:34 +01:00
* @ param boolean $update_cache = null true : re - read given $cal_id , false : delete given $cal_id
2013-05-16 17:59:25 +02:00
* @ return array of ( cal_id => array of ) alarms with alarm - id as key
2005-11-09 00:15:14 +01:00
*/
2013-05-16 17:59:25 +02:00
function read_alarms ( $cal_id , $update_cache = null )
2005-11-09 00:15:14 +01:00
{
2013-05-16 17:59:25 +02:00
if ( ! isset ( self :: $alarm_cache ) && is_array ( $cal_id ))
{
self :: $alarm_cache = array ();
2015-01-14 14:52:34 +01:00
if (( $jobs = $this -> async -> read ( 'cal:%' )))
2013-05-16 17:59:25 +02:00
{
foreach ( $jobs as $id => $job )
{
$alarm = $job [ 'data' ]; // text, enabled
$alarm [ 'id' ] = $id ;
$alarm [ 'time' ] = $job [ 'next' ];
self :: $alarm_cache [ $alarm [ 'cal_id' ]][ $id ] = $alarm ;
}
}
unset ( $update_cache ); // just done
}
2005-11-09 00:15:14 +01:00
$alarms = array ();
2013-05-16 17:59:25 +02:00
if ( isset ( self :: $alarm_cache ))
{
if ( isset ( $update_cache ))
{
foreach (( array ) $cal_id as $id )
{
if ( $update_cache === false )
{
unset ( self :: $alarm_cache [ $cal_id ]);
}
elseif ( $update_cache === true )
{
self :: $alarm_cache [ $cal_id ] = $this -> read_alarms_nocache ( $cal_id );
}
}
}
if ( ! is_array ( $cal_id ))
{
$alarms = ( array ) self :: $alarm_cache [ $cal_id ];
}
else
{
foreach ( $cal_id as $id )
{
$alarms [ $id ] = ( array ) self :: $alarm_cache [ $id ];
}
}
2013-05-16 18:02:18 +02:00
//error_log(__METHOD__."(".array2string($cal_id).", ".array2string($update_cache).") returning from cache ".array2string($alarms));
2013-05-16 17:59:25 +02:00
return $alarms ;
}
return $this -> read_alarms_nocache ( $cal_id );
}
private function read_alarms_nocache ( $cal_id )
{
2015-01-14 14:52:34 +01:00
if (( $jobs = $this -> async -> read ( 'cal:' . ( int ) $cal_id . ':%' )))
2005-11-09 00:15:14 +01:00
{
foreach ( $jobs as $id => $job )
{
$alarm = $job [ 'data' ]; // text, enabled
$alarm [ 'id' ] = $id ;
$alarm [ 'time' ] = $job [ 'next' ];
$alarms [ $id ] = $alarm ;
}
}
2013-05-16 18:02:18 +02:00
//error_log(__METHOD__."(".array2string($cal_id).") returning ".array2string($alarms));
2016-12-07 14:16:52 +01:00
return $alarms ? $alarms : array ();
2005-11-09 00:15:14 +01:00
}
/**
* read a single alarm specified by it ' s $id
*
* @ param string $id alarm - id is a string of 'cal:' . $cal_id . ':' . $alarm_nr , it is used as the job - id too
* @ return array with data of the alarm
*/
function read_alarm ( $id )
{
if ( ! ( $jobs = $this -> async -> read ( $id )))
{
return False ;
}
2015-01-14 14:52:34 +01:00
list ( $alarm_id , $job ) = each ( $jobs );
2005-11-09 00:15:14 +01:00
$alarm = $job [ 'data' ]; // text, enabled
2015-01-14 14:52:34 +01:00
$alarm [ 'id' ] = $alarm_id ;
2005-11-09 00:15:14 +01:00
$alarm [ 'time' ] = $job [ 'next' ];
//echo "<p>read_alarm('$id')="; print_r($alarm); echo "</p>\n";
return $alarm ;
}
/**
* saves a new or updated alarm
*
* @ param int $cal_id Id of the calendar - entry
* @ param array $alarm array with fields : text , owner , enabled , ..
2015-01-14 14:52:34 +01:00
* @ param boolean $update_modified = true call update modified , default true
2005-11-09 00:15:14 +01:00
* @ return string id of the alarm
*/
2013-02-26 09:48:50 +01:00
function save_alarm ( $cal_id , $alarm , $update_modified = true )
2005-11-09 00:15:14 +01:00
{
2013-08-15 16:56:34 +02:00
//error_log(__METHOD__."($cal_id, ".array2string($alarm).', '.array2string($update_modified).') '.function_backtrace());
2005-11-09 00:15:14 +01:00
if ( ! ( $id = $alarm [ 'id' ]))
{
$alarms = $this -> read_alarms ( $cal_id ); // find a free alarm#
$n = count ( $alarms );
do
{
$id = 'cal:' . ( int ) $cal_id . ':' . $n ;
++ $n ;
}
while ( @ isset ( $alarms [ $id ]));
}
else
{
$this -> async -> cancel_timer ( $id );
}
$alarm [ 'cal_id' ] = $cal_id ; // we need the back-reference
2015-06-25 22:39:53 +02:00
// add an alarm uid, if none is given
if ( empty ( $alarm [ 'uid' ]) && class_exists ( 'Horde_Support_Uuid' )) $alarm [ 'uid' ] = ( string ) new Horde_Support_Uuid ;
2012-12-07 15:10:51 +01:00
//error_log(__METHOD__.__LINE__.' Save Alarm for CalID:'.$cal_id.'->'.array2string($alarm).'-->'.$id.'#'.function_backtrace());
2011-03-21 15:13:42 +01:00
// allways store job with the alarm owner as job-owner to get eg. the correct from address
if ( ! $this -> async -> set_timer ( $alarm [ 'time' ], $id , 'calendar.calendar_boupdate.send_alarm' , $alarm , $alarm [ 'owner' ]))
2005-11-09 00:15:14 +01:00
{
return False ;
}
2009-07-15 22:35:56 +02:00
// update the modification information of the related event
2013-02-26 09:48:50 +01:00
if ( $update_modified ) $this -> updateModified ( $cal_id , true );
2009-08-06 13:29:05 +02:00
2013-05-16 17:59:25 +02:00
// update cache, if used
if ( isset ( self :: $alarm_cache )) $this -> read_alarms ( $cal_id , true );
2005-11-09 00:15:14 +01:00
return $id ;
}
/**
2013-02-26 09:48:50 +01:00
* Delete all alarms of a calendar - entry
*
* Does not update timestamps of series master , therefore private !
2005-11-09 00:15:14 +01:00
*
* @ param int $cal_id Id of the calendar - entry
* @ return int number of alarms deleted
*/
2013-02-26 09:48:50 +01:00
private function delete_alarms ( $cal_id )
2005-11-09 00:15:14 +01:00
{
2013-08-15 16:56:34 +02:00
//error_log(__METHOD__."($cal_id) ".function_backtrace());
2013-09-02 08:49:10 +02:00
if (( $alarms = $this -> read_alarms ( $cal_id )))
2005-11-09 00:15:14 +01:00
{
2015-01-14 14:52:34 +01:00
foreach ( array_keys ( $alarms ) as $id )
2013-09-02 08:49:10 +02:00
{
$this -> async -> cancel_timer ( $id );
}
// update cache, if used
if ( isset ( self :: $alarm_cache )) $this -> read_alarms ( $cal_id , false );
2005-11-09 00:15:14 +01:00
}
return count ( $alarms );
}
/**
* delete one alarms identified by its id
*
* @ param string $id alarm - id is a string of 'cal:' . $cal_id . ':' . $alarm_nr , it is used as the job - id too
* @ return int number of alarms deleted
*/
2013-02-26 09:48:50 +01:00
function delete_alarm ( $id )
2005-11-09 00:15:14 +01:00
{
2013-08-15 16:56:34 +02:00
//error_log(__METHOD__."('$id') ".function_backtrace());
2009-07-15 22:35:56 +02:00
// update the modification information of the related event
list (, $cal_id ) = explode ( ':' , $id );
2009-08-06 13:29:05 +02:00
if ( $cal_id )
{
2013-02-26 09:48:50 +01:00
$this -> updateModified ( $cal_id , true );
2009-07-15 22:35:56 +02:00
}
2013-05-16 17:59:25 +02:00
$ret = $this -> async -> cancel_timer ( $id );
// update cache, if used
if ( isset ( self :: $alarm_cache )) $this -> read_alarms ( $cal_id , true );
return $ret ;
2005-11-09 00:15:14 +01:00
}
2006-03-29 09:01:18 +02:00
2008-06-07 19:45:33 +02:00
/**
* Delete account hook
*
* @ param array | int $old_user integer old user or array with keys 'account_id' and 'new_owner' as the deleteaccount hook uses it
2015-01-14 14:52:34 +01:00
* @ param int $new_user = null
2008-06-07 19:45:33 +02:00
*/
2015-01-14 14:52:34 +01:00
function deleteaccount ( $old_user , $new_user = null )
2005-11-09 00:15:14 +01:00
{
2008-06-07 19:45:33 +02:00
if ( is_array ( $old_user ))
{
$new_user = $old_user [ 'new_owner' ];
$old_user = $old_user [ 'account_id' ];
}
2005-11-09 00:15:14 +01:00
if ( ! ( int ) $new_user )
{
2009-07-15 22:35:56 +02:00
$user_type = '' ;
$user_id = null ;
2009-11-29 15:03:45 +01:00
self :: split_user ( $old_user , $user_type , $user_id );
2006-03-29 09:01:18 +02:00
2005-11-09 00:15:14 +01:00
if ( $user_type == 'u' ) // only accounts can be owners of events
{
2008-03-15 15:10:20 +01:00
foreach ( $this -> db -> select ( $this -> cal_table , 'cal_id' , array ( 'cal_owner' => $old_user ), __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2005-11-09 00:15:14 +01:00
{
2008-03-15 15:10:20 +01:00
$this -> delete ( $row [ 'cal_id' ]);
2005-11-09 00:15:14 +01:00
}
}
$this -> db -> delete ( $this -> user_table , array (
'cal_user_type' => $user_type ,
'cal_user_id' => $user_id ,
2008-03-17 10:06:08 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2006-03-29 09:01:18 +02:00
2006-03-21 11:05:45 +01:00
// delete calendar entries without participants (can happen if the deleted user is the only participants, but not the owner)
2008-03-15 15:10:20 +01:00
foreach ( $this -> db -> select ( $this -> cal_table , " DISTINCT $this->cal_table .cal_id " , 'cal_user_id IS NULL' , __LINE__ , __FILE__ ,
False , '' , 'calendar' , 0 , " LEFT JOIN $this->user_table ON $this->cal_table .cal_id= $this->user_table .cal_id " ) as $row )
2006-03-21 11:05:45 +01:00
{
2008-03-15 15:10:20 +01:00
$this -> delete ( $row [ 'cal_id' ]);
2006-03-21 11:05:45 +01:00
}
2005-11-09 00:15:14 +01:00
}
else
{
2008-03-15 15:10:20 +01:00
$this -> db -> update ( $this -> cal_table , array ( 'cal_owner' => $new_user ), array ( 'cal_owner' => $old_user ), __LINE__ , __FILE__ , 'calendar' );
2007-05-07 10:27:50 +02:00
// delete participation of old user, if new user is already a participant
2008-03-15 15:10:20 +01:00
$ids = array ();
foreach ( $this -> db -> select ( $this -> user_table , 'cal_id' , array ( // MySQL does NOT allow to run this as delete!
2007-05-07 10:27:50 +02:00
'cal_user_type' => 'u' ,
'cal_user_id' => $old_user ,
2011-03-11 08:37:34 +01:00
" cal_id IN (SELECT cal_id FROM $this->user_table other WHERE other.cal_id=cal_id AND other.cal_user_id= " . $this -> db -> quote ( $new_user ) . " AND cal_user_type='u') " ,
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
2007-05-07 10:27:50 +02:00
{
$ids [] = $row [ 'cal_id' ];
}
if ( $ids ) $this -> db -> delete ( $this -> user_table , array (
'cal_user_type' => 'u' ,
'cal_user_id' => $old_user ,
'cal_id' => $ids ,
2008-03-15 15:10:20 +01:00
), __LINE__ , __FILE__ , 'calendar' );
2007-05-07 10:27:50 +02:00
// now change participant in the rest to contain new user instead of old user
2008-03-15 15:10:20 +01:00
$this -> db -> update ( $this -> user_table , array (
'cal_user_id' => $new_user ,
), array (
'cal_user_type' => 'u' ,
'cal_user_id' => $old_user ,
), __LINE__ , __FILE__ , 'calendar' );
2005-11-09 00:15:14 +01:00
}
}
2009-07-15 22:35:56 +02:00
/**
* get stati of all recurrences of an event for a specific participant
*
* @ param int $cal_id
2015-01-14 14:52:34 +01:00
* @ param int $uid = null participant uid ; if == null return only the recur dates
* @ param int $start = 0 if != 0 : startdate of the search / list ( servertime )
* @ param int $end = 0 if != 0 : enddate of the search / list ( servertime )
2009-11-29 15:03:45 +01:00
*
2009-07-15 22:35:56 +02:00
* @ return array recur_date => status pairs ( index 0 => main status )
*/
2010-02-23 19:19:12 +01:00
function get_recurrences ( $cal_id , $uid = null , $start = 0 , $end = 0 )
2009-07-15 22:35:56 +02:00
{
$participant_status = array ();
$where = array ( 'cal_id' => $cal_id );
2009-11-29 15:03:45 +01:00
if ( $start != 0 && $end == 0 ) $where [] = '(cal_recur_date = 0 OR cal_recur_date >= ' . ( int ) $start . ')' ;
2009-11-29 22:02:15 +01:00
if ( $start == 0 && $end != 0 ) $where [] = '(cal_recur_date = 0 OR cal_recur_date <= ' . ( int ) $end . ')' ;
2009-11-29 15:03:45 +01:00
if ( $start != 0 && $end != 0 )
{
$where [] = '(cal_recur_date = 0 OR (cal_recur_date >= ' . ( int ) $start .
' AND cal_recur_date <= ' . ( int ) $end . '))' ;
}
2009-07-15 22:35:56 +02:00
foreach ( $this -> db -> select ( $this -> user_table , 'DISTINCT cal_recur_date' , $where , __LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
// inititalize the array
$participant_status [ $row [ 'cal_recur_date' ]] = null ;
}
2010-02-23 19:19:12 +01:00
if ( is_null ( $uid )) return $participant_status ;
$user_type = $user_id = null ;
2015-08-17 16:07:25 +02:00
self :: split_user ( $uid , $user_type , $user_id , true );
2015-01-14 14:52:34 +01:00
$where2 = array (
2009-07-15 22:35:56 +02:00
'cal_id' => $cal_id ,
'cal_user_type' => $user_type ? $user_type : 'u' ,
'cal_user_id' => $user_id ,
);
2015-01-14 14:52:34 +01:00
if ( $start != 0 && $end == 0 ) $where2 [] = '(cal_recur_date = 0 OR cal_recur_date >= ' . ( int ) $start . ')' ;
if ( $start == 0 && $end != 0 ) $where2 [] = '(cal_recur_date = 0 OR cal_recur_date <= ' . ( int ) $end . ')' ;
2009-11-29 15:03:45 +01:00
if ( $start != 0 && $end != 0 )
{
2015-01-14 14:52:34 +01:00
$where2 [] = '(cal_recur_date = 0 OR (cal_recur_date >= ' . ( int ) $start .
2009-11-29 15:03:45 +01:00
' AND cal_recur_date <= ' . ( int ) $end . '))' ;
}
2015-01-14 14:52:34 +01:00
foreach ( $this -> db -> select ( $this -> user_table , 'cal_recur_date,cal_status,cal_quantity,cal_role' , $where2 ,
2009-07-15 22:35:56 +02:00
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
2009-11-26 21:21:16 +01:00
$status = self :: combine_status ( $row [ 'cal_status' ], $row [ 'cal_quantity' ], $row [ 'cal_role' ]);
$participant_status [ $row [ 'cal_recur_date' ]] = $status ;
2009-07-15 22:35:56 +02:00
}
return $participant_status ;
}
/**
* get all participants of an event
*
* @ param int $cal_id
2015-01-14 14:52:34 +01:00
* @ param int $recur_date = 0 gives participants of this recurrence , default 0 = all
2009-07-15 22:35:56 +02:00
*
* @ return array participants
*/
2015-08-17 16:07:25 +02:00
/* seems NOT to be used anywhere , NOT ported to new md5 - email schema !
2009-07-15 22:35:56 +02:00
function get_participants ( $cal_id , $recur_date = 0 )
{
$participants = array ();
$where = array ( 'cal_id' => $cal_id );
if ( $recur_date )
{
$where [ 'cal_recur_date' ] = $recur_date ;
}
foreach ( $this -> db -> select ( $this -> user_table , 'DISTINCT cal_user_type,cal_user_id' , $where ,
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
2009-11-29 15:03:45 +01:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ]);
2009-07-15 22:35:56 +02:00
$id = $row [ 'cal_user_type' ] . $row [ 'cal_user_id' ];
$participants [ $id ][ 'type' ] = $row [ 'cal_user_type' ];
$participants [ $id ][ 'id' ] = $row [ 'cal_user_id' ];
$participants [ $id ][ 'uid' ] = $uid ;
}
return $participants ;
2015-08-17 16:07:25 +02:00
} */
2009-07-15 22:35:56 +02:00
/**
* get all releated events
*
* @ param int $uid UID of the series
*
* @ return array of event exception ids for all events which share $uid
*/
function get_related ( $uid )
{
$where = array (
'cal_uid' => $uid ,
);
$related = array ();
foreach ( $this -> db -> select ( $this -> cal_table , 'cal_id,cal_reference' , $where ,
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
2010-01-22 20:47:32 +01:00
if ( $row [ 'cal_reference' ] != 0 )
2009-07-15 22:35:56 +02:00
{
2010-01-22 20:47:32 +01:00
// not the series master
$related [] = $row [ 'cal_id' ];
2009-07-15 22:35:56 +02:00
}
}
return $related ;
}
/**
* Gets the exception days of a given recurring event caused by
2010-01-14 18:01:30 +01:00
* irregular participant stati or timezone transitions
2009-07-15 22:35:56 +02:00
*
2009-11-19 11:13:17 +01:00
* @ param array $event Recurring Event .
* @ param string tz_id = null timezone for exports ( null for event ' s timezone )
2015-01-14 14:52:34 +01:00
* @ param int $start = 0 if != 0 : startdate of the search / list ( servertime )
* @ param int $end = 0 if != 0 : enddate of the search / list ( servertime )
* @ param string $filter = 'all' string filter - name : all ( not rejected ),
2010-02-17 14:29:28 +01:00
* accepted , unknown , tentative , rejected , delegated
2010-01-29 22:42:54 +01:00
* rrule return array of remote exceptions in servertime
* tz_rrule / tz_only , return ( only by ) timezone transition affected entries
* map return array of dates with no pseudo exception
* key remote occurrence date
* tz_map return array of all dates with no tz pseudo exception
2009-07-15 22:35:56 +02:00
*
* @ return array Array of exception days ( false for non - recurring events ) .
*/
2010-01-29 22:42:54 +01:00
function get_recurrence_exceptions ( $event , $tz_id = null , $start = 0 , $end = 0 , $filter = 'all' )
2009-07-15 22:35:56 +02:00
{
2010-01-29 22:42:54 +01:00
if ( ! is_array ( $event )) return false ;
2009-07-15 22:35:56 +02:00
$cal_id = ( int ) $event [ 'id' ];
2010-01-29 22:42:54 +01:00
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// "($cal_id, $tz_id, $filter): " . $event['tzid']);
2009-07-15 22:35:56 +02:00
if ( ! $cal_id || $event [ 'recur_type' ] == MCAL_RECUR_NONE ) return false ;
$days = array ();
2009-11-19 11:13:17 +01:00
2010-01-29 22:42:54 +01:00
$expand_all = ( ! $this -> isWholeDay ( $event ) && $tz_id && $tz_id != $event [ 'tzid' ]);
if ( $filter == 'tz_only' && ! $expand_all ) return $days ;
$remote = in_array ( $filter , array ( 'tz_rrule' , 'rrule' ));
$egw_rrule = calendar_rrule :: event2rrule ( $event , false );
2010-02-17 14:29:28 +01:00
$egw_rrule -> current = clone $egw_rrule -> time ;
2010-01-29 22:42:54 +01:00
if ( $expand_all )
2009-11-19 11:13:17 +01:00
{
2011-05-03 19:28:54 +02:00
unset ( $event [ 'recur_exception' ]);
2010-01-29 22:42:54 +01:00
$remote_rrule = calendar_rrule :: event2rrule ( $event , false , $tz_id );
2010-02-17 14:29:28 +01:00
$remote_rrule -> current = clone $remote_rrule -> time ;
2010-01-29 22:42:54 +01:00
}
while ( $egw_rrule -> valid ())
{
2010-02-17 14:29:28 +01:00
while ( $egw_rrule -> exceptions &&
in_array ( $egw_rrule -> current -> format ( 'Ymd' ), $egw_rrule -> exceptions ))
{
if ( in_array ( $filter , array ( 'map' , 'tz_map' , 'rrule' , 'tz_rrule' )))
{
// real exception
2016-05-01 19:47:59 +02:00
$locts = ( int ) Api\DateTime :: to ( $egw_rrule -> current (), 'server' );
2010-02-17 14:29:28 +01:00
if ( $expand_all )
{
2016-05-01 19:47:59 +02:00
$remts = ( int ) Api\DateTime :: to ( $remote_rrule -> current (), 'server' );
2010-02-17 14:29:28 +01:00
if ( $remote )
{
$days [ $locts ] = $remts ;
}
else
{
$days [ $remts ] = $locts ;
}
}
else
{
$days [ $locts ] = $locts ;
}
}
if ( $expand_all )
{
$remote_rrule -> next_no_exception ();
}
$egw_rrule -> next_no_exception ();
if ( ! $egw_rrule -> valid ()) return $days ;
}
2010-01-29 22:42:54 +01:00
$day = $egw_rrule -> current ();
2016-05-01 19:47:59 +02:00
$locts = ( int ) Api\DateTime :: to ( $day , 'server' );
2010-01-29 22:42:54 +01:00
$tz_exception = ( $filter == 'tz_rrule' );
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// '()[EVENT Server]: ' . $day->format('Ymd\THis') . " ($locts)");
if ( $expand_all )
2009-11-19 11:13:17 +01:00
{
2010-01-29 22:42:54 +01:00
$remote_day = $remote_rrule -> current ();
2016-05-01 19:47:59 +02:00
$remts = ( int ) Api\DateTime :: to ( $remote_day , 'server' );
2010-01-29 22:42:54 +01:00
// error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// '()[EVENT Device]: ' . $remote_day->format('Ymd\THis') . " ($remts)");
2009-11-19 11:13:17 +01:00
}
2009-07-15 22:35:56 +02:00
2010-01-29 22:42:54 +01:00
if ( ! ( $end && $end < $locts ) && $start <= $locts )
2009-07-15 22:35:56 +02:00
{
2010-01-29 22:42:54 +01:00
// we are within the relevant time period
if ( $expand_all && $day -> format ( 'U' ) != $remote_day -> format ( 'U' ))
{
$tz_exception = true ;
if ( $filter != 'map' && $filter != 'tz_map' )
2009-07-15 22:35:56 +02:00
{
2010-01-29 22:42:54 +01:00
// timezone pseudo exception
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// '() tz exception: ' . $day->format('Ymd\THis'));
if ( $remote )
2009-07-15 22:35:56 +02:00
{
2010-01-29 22:42:54 +01:00
$days [ $locts ] = $remts ;
}
else
{
$days [ $remts ] = $locts ;
}
}
}
if ( $filter != 'tz_map' && ( ! $tz_exception || $filter == 'tz_only' ) &&
$this -> status_pseudo_exception ( $event [ 'id' ], $locts , $filter ))
{
// status pseudo exception
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// '() status exception: ' . $day->format('Ymd\THis'));
if ( $expand_all )
{
if ( $filter == 'tz_only' )
{
unset ( $days [ $remts ]);
}
else
{
if ( $filter != 'map' )
2010-01-14 18:01:30 +01:00
{
2010-01-29 22:42:54 +01:00
if ( $remote )
2010-01-14 18:01:30 +01:00
{
2010-01-29 22:42:54 +01:00
$days [ $locts ] = $remts ;
}
else
{
$days [ $remts ] = $locts ;
2010-01-14 18:01:30 +01:00
}
2009-11-19 11:13:17 +01:00
}
2009-07-15 22:35:56 +02:00
}
}
2010-01-29 22:42:54 +01:00
elseif ( $filter != 'map' )
{
$days [ $locts ] = $locts ;
}
}
elseif (( $filter == 'map' || filter == 'tz_map' ) &&
! $tz_exception )
{
// no pseudo exception date
if ( $expand_all )
{
$days [ $remts ] = $locts ;
}
else
{
$days [ $locts ] = $locts ;
}
}
}
2010-02-17 14:29:28 +01:00
if ( $expand_all )
2010-01-29 22:42:54 +01:00
{
2010-02-17 14:29:28 +01:00
$remote_rrule -> next_no_exception ();
2009-07-15 22:35:56 +02:00
}
2010-02-17 14:29:28 +01:00
$egw_rrule -> next_no_exception ();
2009-07-15 22:35:56 +02:00
}
return $days ;
}
2010-01-29 22:42:54 +01:00
/**
* Checks for status only pseudo exceptions
*
* @ param int $cal_id event id
* @ param int $recur_date occurrence to check
* @ param string $filter status filter criteria for user
*
* @ return boolean true , if stati don ' t match with defaults
*/
function status_pseudo_exception ( $cal_id , $recur_date , $filter )
{
2015-01-14 14:52:34 +01:00
static $recurrence_zero = null ;
static $cached_id = null ;
static $user = null ;
2010-01-29 22:42:54 +01:00
if ( ! isset ( $cached_id ) || $cached_id != $cal_id )
{
// get default stati
$recurrence_zero = array ();
$user = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
2015-08-17 16:07:25 +02:00
$where = array (
'cal_id' => $cal_id ,
'cal_recur_date' => 0 ,
);
foreach ( $this -> db -> select ( $this -> user_table , 'cal_user_type,cal_user_id,cal_user_attendee,cal_status' , $where ,
2010-01-29 22:42:54 +01:00
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
switch ( $row [ 'cal_user_type' ])
{
case 'u' : // account
case 'c' : // contact
case 'e' : // email address
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2010-01-29 22:42:54 +01:00
$recurrence_zero [ $uid ] = $row [ 'cal_status' ];
}
}
$cached_id = $cal_id ;
}
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// "($cal_id, $recur_date, $filter)[DEFAULTS]: " .
// array2string($recurrence_zero));
$participants = array ();
2015-08-17 16:07:25 +02:00
$where = array (
'cal_id' => $cal_id ,
'cal_recur_date' => $recur_date ,
);
foreach ( $this -> db -> select ( $this -> user_table , 'cal_user_type,cal_user_id,cal_user_attendee,cal_status' , $where ,
2010-01-29 22:42:54 +01:00
__LINE__ , __FILE__ , false , '' , 'calendar' ) as $row )
{
switch ( $row [ 'cal_user_type' ])
{
case 'u' : // account
case 'c' : // contact
case 'e' : // email address
2015-08-17 16:07:25 +02:00
$uid = self :: combine_user ( $row [ 'cal_user_type' ], $row [ 'cal_user_id' ], $row [ 'cal_user_attendee' ]);
2010-01-29 22:42:54 +01:00
$participants [ $uid ] = $row [ 'cal_status' ];
}
}
if ( empty ( $participants )) return false ; // occurrence does not exist at all yet
foreach ( $recurrence_zero as $uid => $status )
{
if ( $uid == $user )
{
// handle filter for current user
switch ( $filter )
{
case 'unknown' :
if ( $status != 'U' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
case 'accepted' :
if ( $status != 'A' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
case 'tentative' :
if ( $status != 'T' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
case 'rejected' :
if ( $status != 'R' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
2010-02-17 14:29:28 +01:00
case 'delegated' :
if ( $status != 'D' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
2010-01-29 22:42:54 +01:00
case 'default' :
if ( $status == 'R' )
{
unset ( $participants [ $uid ]);
continue ;
}
break ;
default :
// All entries
}
}
if ( ! isset ( $participants [ $uid ])
|| $participants [ $uid ] != $status )
return true ;
unset ( $participants [ $uid ]);
}
return ( ! empty ( $participants ));
}
/**
* Check if the event is the whole day
*
* @ param array $event event ( all timestamps in servertime )
* @ return boolean true if whole day event within its timezone , false othwerwise
*/
function isWholeDay ( $event )
{
if ( ! isset ( $event [ 'start' ]) || ! isset ( $event [ 'end' ])) return false ;
if ( empty ( $event [ 'tzid' ]))
{
2016-05-01 19:47:59 +02:00
$timezone = Api\DateTime :: $server_timezone ;
2010-01-29 22:42:54 +01:00
}
else
{
if ( ! isset ( self :: $tz_cache [ $event [ 'tzid' ]]))
{
self :: $tz_cache [ $event [ 'tzid' ]] = calendar_timezones :: DateTimeZone ( $event [ 'tzid' ]);
}
$timezone = self :: $tz_cache [ $event [ 'tzid' ]];
}
2016-05-01 19:47:59 +02:00
$start_time = new Api\DateTime ( $event [ 'start' ], Api\DateTime :: $server_timezone );
2015-01-14 14:52:34 +01:00
$start_time -> setTimezone ( $timezone );
2016-05-01 19:47:59 +02:00
$end_time = new Api\DateTime ( $event [ 'end' ], Api\DateTime :: $server_timezone );
2015-01-14 14:52:34 +01:00
$end_time -> setTimezone ( $timezone );
2010-01-29 22:42:54 +01:00
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
// '(): ' . $start . '-' . $end);
2016-05-01 19:47:59 +02:00
$start = Api\DateTime :: to ( $start_time , 'array' );
$end = Api\DateTime :: to ( $end_time , 'array' );
2010-01-29 22:42:54 +01:00
return ! $start [ 'hour' ] && ! $start [ 'minute' ] && $end [ 'hour' ] == 23 && $end [ 'minute' ] == 59 ;
}
/**
2010-02-19 18:10:15 +01:00
* Moves a datetime to the beginning of the day within timezone
2010-01-29 22:42:54 +01:00
*
2016-05-01 19:47:59 +02:00
* @ param Api\DateTime $time the datetime entry
2010-01-29 22:42:54 +01:00
* @ param string tz_id timezone
*
* @ return DateTime
*/
2016-05-01 19:47:59 +02:00
function & startOfDay ( Api\DateTime $time , $tz_id = null )
2010-01-29 22:42:54 +01:00
{
if ( empty ( $tz_id ))
{
2016-05-01 19:47:59 +02:00
$timezone = Api\DateTime :: $server_timezone ;
2010-01-29 22:42:54 +01:00
}
else
{
if ( ! isset ( self :: $tz_cache [ $tz_id ]))
{
self :: $tz_cache [ $tz_id ] = calendar_timezones :: DateTimeZone ( $tz_id );
}
$timezone = self :: $tz_cache [ $tz_id ];
}
2016-05-01 19:47:59 +02:00
return new Api\DateTime ( $time -> format ( 'Y-m-d 00:00:00' ), $timezone );
2010-01-29 22:42:54 +01:00
}
2010-10-28 11:22:01 +02:00
2010-09-11 20:08:48 +02:00
/**
2013-02-26 09:48:50 +01:00
* Updates the modification timestamp to force an etag , ctag and sync - token change
2010-09-11 20:08:48 +02:00
*
2013-02-26 09:48:50 +01:00
* @ param int $id event id
2015-01-14 14:52:34 +01:00
* @ param int | boolean $update_master = false id of series master or true , to update series master too
* @ param int $time = null new timestamp , default current ( server - ) time
* @ param int $modifier = null uid of the modifier , default current user
2010-09-11 20:08:48 +02:00
*/
2013-02-26 09:48:50 +01:00
function updateModified ( $id , $update_master = false , $time = null , $modifier = null )
2010-09-11 20:08:48 +02:00
{
2013-02-26 09:48:50 +01:00
if ( is_null ( $time ) || ! $time ) $time = time ();
2012-09-28 09:53:14 +02:00
if ( is_null ( $modifier )) $modifier = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
2010-09-11 20:08:48 +02:00
$this -> db -> update ( $this -> cal_table ,
array ( 'cal_modified' => $time , 'cal_modifier' => $modifier ),
array ( 'cal_id' => $id ), __LINE__ , __FILE__ , 'calendar' );
2013-02-26 09:48:50 +01:00
// if event is an exception: update modified of master, to force etag, ctag and sync-token change
if ( $update_master )
{
if ( $update_master !== true || ( $update_master = $this -> db -> select ( $this -> cal_table , 'cal_reference' , array ( 'cal_id' => $id ), __LINE__ , __FILE__ ) -> fetchColumn ()))
{
$this -> updateModified ( $update_master , false , $time , $modifier );
}
}
2010-09-11 20:08:48 +02:00
}
2004-11-07 15:38:00 +01:00
}