2010-09-18 14:34:58 +02:00
< ? php
/**
* eGroupWare - Calendar ' s buisness - object - access + update
*
* @ link http :// www . egroupware . org
* @ package calendar
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ author Joerg Lehrke < jlehrke @ noc . de >
* @ copyright ( c ) 2005 - 9 by RalfBecker - At - outdoor - training . de
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ version $Id $
*/
// types of messsages send by calendar_boupdate::send_update
define ( 'MSG_DELETED' , 0 );
define ( 'MSG_MODIFIED' , 1 );
define ( 'MSG_ADDED' , 2 );
define ( 'MSG_REJECTED' , 3 );
define ( 'MSG_TENTATIVE' , 4 );
define ( 'MSG_ACCEPTED' , 5 );
define ( 'MSG_ALARM' , 6 );
define ( 'MSG_DISINVITE' , 7 );
define ( 'MSG_DELEGATED' , 8 );
/**
* Class to access AND manipulate all calendar data ( business object )
*
* The new UI , BO and SO classes have a strikt definition , in which time - zone they operate :
* 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
* SO operates only on server - time
*
* As this BO class deals with dates / times of several types and timezone , each variable should have a postfix
* appended , telling with type it is : _s = seconds , _su = secs in user - time , _ss = secs in server - time , _h = hours
*
* All new BO code ( should be true for eGW in general ) NEVER use any $_REQUEST ( $_POST or $_GET ) vars itself .
* Nor does it store the state of any UI - elements ( eg . cat - id selectbox ) . All this is the task of the UI class ( es ) !!!
*
* All permanent debug messages of the calendar - code should done via the debug - message method of the bocal class ! ! !
*/
class calendar_boupdate extends calendar_bo
{
/**
* Category ACL allowing to add a given category
*/
const CAT_ACL_ADD = 512 ;
/**
* Category ACL allowing to change status of a participant
*/
const CAT_ACL_STATUS = 1024 ;
/**
* name of method to debug or level of debug - messages :
* False = Off as higher as more messages you get ; - )
* 1 = function - calls incl . parameters to general functions like search , read , write , delete
* 2 = function - calls to exported helper - functions like check_perms
* 4 = function - calls to exported conversation - functions like date2ts , date2array , ...
* 5 = function - calls to private functions
* @ var mixed
*/
var $debug ;
/**
* Set Logging
*
* @ var boolean
*/
var $log = false ;
var $logfile = '/tmp/log-calendar-boupdate' ;
/**
* Cached timezone data
*
* @ var array id => data
*/
protected static $tz_cache = array ();
/**
* Constructor
*/
function __construct ()
{
if ( $this -> debug > 0 ) $this -> debug_message ( 'calendar_boupdate::__construct() started' , True );
parent :: __construct (); // calling the parent constructor
if ( $this -> debug > 0 ) $this -> debug_message ( 'calendar_boupdate::__construct() finished' , True );
}
/**
* updates or creates an event , it ( optionaly ) checks for conflicts and sends the necessary notifications
*
* @ param array & $event event - array , on return some values might be changed due to set defaults
* @ param boolean $ignore_conflicts = false just ignore conflicts or do a conflict check and return the conflicting events
2011-04-07 10:09:20 +02:00
* @ param boolean $touch_modified = true NOT USED ANYMORE ( was only used in old csv - import ), modified & modifier is always updated !
2010-09-18 14:34:58 +02:00
* @ param boolean $ignore_acl = false should we ignore the acl
* @ param boolean $updateTS = true update the content history of the event
* @ param array & $messages = null messages about because of missing ACL removed participants or categories
* @ return mixed on success : int $cal_id > 0 , on error false or array with conflicting events ( only if $check_conflicts )
* Please note : the events are not garantied to be readable by the user ( no read grant or private ) !
2010-11-11 09:55:35 +01:00
*
* @ ToDo current conflict checking code does NOT cope quantity - wise correct with multiple non - overlapping
* events overlapping the event to store : the quantity sum is used , even as the events dont overlap !
*
* ++++++++ ++++++++
* + + + B + If A get checked for conflicts , the check used for resource quantity is
* + + ++++++++
* + A + quantity ( A , resource ) + quantity ( B , resource ) + quantity ( C , resource ) > maxBookable ( resource )
* + + ++++++++
* + + + C + which is clearly wrong for everything with a maximum quantity > 1
* ++++++++ ++++++++
2010-09-18 14:34:58 +02:00
*/
function update ( & $event , $ignore_conflicts = false , $touch_modified = true , $ignore_acl = false , $updateTS = true , & $messages = null )
{
//error_log(__METHOD__."(".array2string($event).",$ignore_conflicts,$touch_modified,$ignore_acl)");
2010-11-11 09:55:35 +01:00
2010-09-18 14:34:58 +02:00
if ( $this -> debug > 1 || $this -> debug == 'update' )
{
$this -> debug_message ( 'calendar_boupdate::update(%1,ignore_conflict=%2,touch_modified=%3,ignore_acl=%4)' ,
false , $event , $ignore_conflicts , $touch_modified , $ignore_acl );
}
// check some minimum requirements:
// - new events need start, end and title
// - updated events cant set start, end or title to empty
if ( ! $event [ 'id' ] && ( ! $event [ 'start' ] || ! $event [ 'end' ] || ! $event [ 'title' ]) ||
$event [ 'id' ] && ( isset ( $event [ 'start' ]) && ! $event [ 'start' ] || isset ( $event [ 'end' ]) && ! $event [ 'end' ] ||
isset ( $event [ 'title' ]) && ! $event [ 'title' ]))
{
return false ;
}
if (( $new_event = ! $event [ 'id' ])) // some defaults for new entries
{
// if no owner given, set user to owner
if ( ! $event [ 'owner' ]) $event [ 'owner' ] = $this -> user ;
// set owner as participant if none is given
if ( ! is_array ( $event [ 'participants' ]) || ! count ( $event [ 'participants' ]))
{
$status = $event [ 'owner' ] == $this -> user ? 'A' : 'U' ;
$status = calendar_so :: combine_status ( $status , 1 , 'CHAIR' );
$event [ 'participants' ] = array ( $event [ 'owner' ] => $status );
}
}
// check if user has the permission to update / create the event
if ( ! $ignore_acl && ( ! $new_event && ! $this -> check_perms ( EGW_ACL_EDIT , $event [ 'id' ]) ||
$new_event && ! $this -> check_perms ( EGW_ACL_EDIT , 0 , $event [ 'owner' ])) &&
! $this -> check_perms ( EGW_ACL_ADD , 0 , $event [ 'owner' ]))
{
return false ;
}
if ( $new_event )
{
$old_event = array ();
}
else
{
$old_event = $this -> read (( int ) $event [ 'id' ], null , $ignore_acl );
}
// do we need to check, if user is allowed to invite the invited participants
if ( $this -> require_acl_invite && ( $removed = $this -> remove_no_acl_invite ( $event , $old_event )))
{
// report removed participants back to user
foreach ( $removed as $key => $account_id )
{
$removed [ $key ] = $this -> participant_name ( $account_id );
}
$messages [] = lang ( '%1 participants removed because of missing invite grants' , count ( $removed )) .
': ' . implode ( ', ' , $removed );
}
// check category based ACL
if ( $event [ 'category' ])
{
if ( ! is_array ( $event [ 'category' ])) $event [ 'category' ] = explode ( ',' , $event [ 'category' ]);
if ( ! $old_event || ! isset ( $old_event [ 'category' ]))
{
$old_event [ 'category' ] = array ();
}
elseif ( ! is_array ( $old_event [ 'category' ]))
{
$old_event [ 'category' ] = explode ( ',' , $old_event [ 'category' ]);
}
foreach ( $event [ 'category' ] as $key => $cat_id )
{
// check if user is allowed to update event categories
if (( ! $old_event || ! in_array ( $cat_id , $old_event [ 'category' ])) &&
self :: has_cat_right ( self :: CAT_ACL_ADD , $cat_id , $this -> user ) === false )
{
unset ( $event [ 'category' ][ $key ]);
// report removed category to user
$removed_cats [ $cat_id ] = $this -> categories -> id2name ( $cat_id );
continue ; // no further check, as cat was removed
}
// for new or moved events check status of participants, if no category status right --> set all status to 'U' = unknown
if ( ! $status_reset_to_unknown &&
self :: has_cat_right ( self :: CAT_ACL_STATUS , $cat_id , $this -> user ) === false &&
( ! $old_event || $old_event [ 'start' ] != $event [ 'start' ] || $old_event [ 'end' ] != $event [ 'end' ]))
{
foreach (( array ) $event [ 'participants' ] as $uid => $status )
{
calendar_so :: split_status ( $status , $q , $r );
if ( $status != 'U' )
{
$event [ 'participants' ][ $uid ] = calendar_so :: combine_status ( 'U' , $q , $r );
// todo: report reset status to user
}
}
$status_reset_to_unknown = true ; // once is enough
}
}
if ( $removed_cats )
{
$messages [] = lang ( 'Category %1 removed because of missing rights' , implode ( ', ' , $removed_cats ));
}
if ( $status_reset_to_unknown )
{
$messages [] = lang ( 'Status of participants set to unknown because of missing category rights' );
}
}
// check for conflicts only happens !$ignore_conflicts AND if start + end date are given
if ( ! $ignore_conflicts && ! $event [ 'non_blocking' ] && isset ( $event [ 'start' ]) && isset ( $event [ 'end' ]))
{
$types_with_quantity = array ();
foreach ( $this -> resources as $type => $data )
{
if ( $data [ 'max_quantity' ]) $types_with_quantity [] = $type ;
}
// get all NOT rejected participants and evtl. their quantity
$quantity = $users = array ();
foreach ( $event [ 'participants' ] as $uid => $status )
{
calendar_so :: split_status ( $status , $q , $r );
if ( $status [ 0 ] == 'R' ) continue ; // ignore rejected participants
if ( $uid < 0 ) // group, check it's members too
{
$users += $GLOBALS [ 'egw' ] -> accounts -> members ( $uid , true );
$users = array_unique ( $users );
}
$users [] = $uid ;
if ( in_array ( $uid [ 0 ], $types_with_quantity ))
{
$quantity [ $uid ] = $q ;
}
}
$overlapping_events =& $this -> search ( array (
'start' => $event [ 'start' ],
'end' => $event [ 'end' ],
'users' => $users ,
'ignore_acl' => true , // otherwise we get only events readable by the user
'enum_groups' => true , // otherwise group-events would not block time
));
if ( $this -> debug > 2 || $this -> debug == 'update' )
{
$this -> debug_message ( 'calendar_boupdate::update() checking for potential overlapping events for users %1 from %2 to %3' , false , $users , $event [ 'start' ], $event [ 'end' ]);
}
$max_quantity = $possible_quantity_conflicts = $conflicts = array ();
foreach (( array ) $overlapping_events as $k => $overlap )
{
if ( $overlap [ 'id' ] == $event [ 'id' ] || // that's the event itself
$overlap [ 'id' ] == $event [ 'reference' ] || // event is an exception of overlap
$overlap [ 'non_blocking' ]) // that's a non_blocking event
{
continue ;
}
if ( $this -> debug > 3 || $this -> debug == 'update' )
{
$this -> debug_message ( 'calendar_boupdate::update() checking overlapping event %1' , false , $overlap );
}
// check if the overlap is with a rejected participant or within the allowed quantity
$common_parts = array_intersect ( $users , array_keys ( $overlap [ 'participants' ]));
foreach ( $common_parts as $n => $uid )
{
2010-11-11 09:55:35 +01:00
$status = $overlap [ 'participants' ][ $uid ];
calendar_so :: split_status ( $status , $q , $r );
if ( $status == 'R' )
2010-09-18 14:34:58 +02:00
{
unset ( $common_parts [ $n ]);
continue ;
}
if ( is_numeric ( $uid ) || ! in_array ( $uid [ 0 ], $types_with_quantity ))
{
continue ; // no quantity check: quantity allways 1 ==> conflict
}
if ( ! isset ( $max_quantity [ $uid ]))
{
$res_info = $this -> resource_info ( $uid );
$max_quantity [ $uid ] = $res_info [ $this -> resources [ $uid [ 0 ]][ 'max_quantity' ]];
}
2010-11-11 09:55:35 +01:00
$quantity [ $uid ] += $q ;
2010-09-18 14:34:58 +02:00
if ( $quantity [ $uid ] <= $max_quantity [ $uid ])
{
$possible_quantity_conflicts [ $uid ][] =& $overlapping_events [ $k ]; // an other event can give the conflict
unset ( $common_parts [ $n ]);
continue ;
}
// now we have a quantity conflict for $uid
}
if ( count ( $common_parts ))
{
if ( $this -> debug > 3 || $this -> debug == 'update' )
{
$this -> debug_message ( 'calendar_boupdate::update() conflicts with the following participants found %1' , false , $common_parts );
}
$conflicts [ $overlap [ 'id' ] . '-' . $this -> date2ts ( $overlap [ 'start' ])] =& $overlapping_events [ $k ];
}
}
// check if we are withing the allowed quantity and if not add all events using that resource
// seems this function is doing very strange things, it gives empty conflicts
foreach ( $max_quantity as $uid => $max )
{
if ( $quantity [ $uid ] > $max )
{
foreach (( array ) $possible_quantity_conflicts [ $uid ] as $conflict )
{
$conflicts [ $conflict [ 'id' ] . '-' . $this -> date2ts ( $conflict [ 'start' ])] =& $possible_quantity_conflicts [ $k ];
}
}
}
unset ( $possible_quantity_conflicts );
if ( count ( $conflicts ))
{
foreach ( $conflicts as $key => $conflict )
{
$conflict [ 'participants' ] = array_intersect_key ( $conflict [ 'participants' ], $event [ 'participants' ]);
if ( ! $this -> check_perms ( EGW_ACL_READ , $conflict ))
{
$conflicts [ $key ] = array (
'id' => $conflict [ 'id' ],
'title' => lang ( 'busy' ),
'participants' => $conflict [ 'participants' ],
'start' => $conflict [ 'start' ],
'end' => $conflict [ 'end' ],
);
}
}
if ( $this -> debug > 2 || $this -> debug == 'update' )
{
$this -> debug_message ( 'calendar_boupdate::update() %1 conflicts found %2' , false , count ( $conflicts ), $conflicts );
}
return $conflicts ;
}
}
//echo "saving $event[id]="; _debug_array($event);
$event2save = $event ;
if ( ! ( $cal_id = $this -> save ( $event , $ignore_acl , $updateTS )))
{
return $cal_id ;
}
$event = $this -> read ( $cal_id ); // we re-read the event, in case only partial information was update and we need the full info for the notifies
//echo "new $cal_id="; _debug_array($event);
if ( $this -> log_file )
{
$this -> log2file ( $event2save , $event , $old_event );
}
// send notifications
if ( $new_event )
{
$this -> send_update ( MSG_ADDED , $event [ 'participants' ], '' , $event );
}
else // update existing event
{
$this -> check4update ( $event , $old_event );
}
// notify the link-class about the update, as other apps may be subscribt to it
egw_link :: notify_update ( 'calendar' , $cal_id , $event );
return $cal_id ;
}
/**
* Remove participants current user has no right to invite
*
* @ param array & $event new event
* @ param array $old_event = null old event with already invited participants
* @ return array removed participants because of missing invite grants
*/
public function remove_no_acl_invite ( array & $event , array $old_event = null )
{
if ( ! $this -> require_acl_invite )
{
return array (); // nothing to check, everyone can invite everyone else
}
if ( $event [ 'id' ] && is_null ( $old_event ))
{
$old_event = $this -> read ( $event [ 'id' ]);
}
$removed = array ();
foreach ( $event [ 'participants' ] as $uid => $status )
{
if (( is_null ( $old_event ) || ! isset ( $old_event [ 'participants' ][ $uid ])) && ! $this -> check_acl_invite ( $uid ))
{
unset ( $event [ 'participants' ][ $uid ]); // remove participant
$removed [] = $uid ;
}
}
//echo "<p>".__METHOD__."($event[title],".($old_event?'$old_event':'NULL').") returning ".array2string($removed)."</p>";
return $removed ;
}
/**
* Check if current user is allowed to invite a given participant
*
* @ param int | string $uid
* @ return boolean
*/
public function check_acl_invite ( $uid )
{
if ( ! is_numeric ( $uid )) return true ; // nothing implemented for resources so far
if ( ! $this -> require_acl_invite )
{
$ret = true ; // no grant required
}
elseif ( $this -> require_acl_invite == 'groups' && $GLOBALS [ 'egw' ] -> accounts -> get_type ( $uid ) != 'g' )
{
$ret = true ; // grant only required for groups
}
else
{
$ret = $this -> check_perms ( EGW_ACL_INVITE , 0 , $uid );
}
//error_log(__METHOD__."($uid) = ".array2string($ret));
//echo "<p>".__METHOD__."($uid) require_acl_invite=$this->require_acl_invite returning ".array2string($ret)."</p>\n";
return $ret ;
}
/**
* Check for added , modified or deleted participants AND notify them
*
* @ param array $new_event the updated event
* @ param array $old_event the event before the update
* @ todo check if there is a real change , not assume every save is a change
*/
function check4update ( $new_event , $old_event )
{
//error_log(__METHOD__."($new_event[title])");
$modified = $added = $deleted = array ();
//echo "<p>calendar_boupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n";
// Find modified and deleted participants ...
foreach ( $old_event [ 'participants' ] as $old_userid => $old_status )
{
if ( isset ( $new_event [ 'participants' ][ $old_userid ]))
{
$modified [ $old_userid ] = $new_event [ 'participants' ][ $old_userid ];
}
else
{
$deleted [ $old_userid ] = $old_status ;
}
}
// Find new participants ...
foreach (( array ) $new_event [ 'participants' ] as $new_userid => $new_status )
{
if ( ! isset ( $old_event [ 'participants' ][ $new_userid ]))
{
$added [ $new_userid ] = 'U' ;
}
}
//echo "<p>calendar_boupdate::check4update() added=".print_r($added,true).", modified=".print_r($modified,true).", deleted=".print_r($deleted,true)."</p>\n";
if ( count ( $added ) || count ( $modified ) || count ( $deleted ))
{
if ( count ( $added ))
{
$this -> send_update ( MSG_ADDED , $added , $old_event , $new_event );
}
if ( count ( $modified ))
{
$this -> send_update ( MSG_MODIFIED , $modified , $old_event , $new_event );
}
if ( count ( $deleted ))
{
$this -> send_update ( MSG_DISINVITE , $deleted , $new_event );
}
}
}
/**
* checks if $userid has requested ( in $part_prefs ) updates for $msg_type
*
* @ param int $userid numerical user - id
* @ param array $part_prefs preferces of the user $userid
* @ param int $msg_type type of the notification : MSG_ADDED , MSG_MODIFIED , MSG_ACCEPTED , ...
* @ param array $old_event Event before the change
* @ param array $new_event Event after the change
* @ return boolean true = update requested , flase otherwise
*/
function update_requested ( $userid , $part_prefs , $msg_type , $old_event , $new_event )
{
if ( $msg_type == MSG_ALARM )
{
return True ; // always True for now
}
$want_update = 0 ;
// the following switch falls through all cases, as each included the following too
//
$msg_is_response = $msg_type == MSG_REJECTED || $msg_type == MSG_ACCEPTED || $msg_type == MSG_TENTATIVE || $msg_type == MSG_DELEGATED ;
switch ( $ru = $part_prefs [ 'calendar' ][ 'receive_updates' ])
{
case 'responses' :
++ $want_update ;
case 'modifications' :
if ( ! $msg_is_response )
{
++ $want_update ;
}
case 'time_change_4h' :
case 'time_change' :
$diff = max ( abs ( $this -> date2ts ( $old_event [ 'start' ]) - $this -> date2ts ( $new_event [ 'start' ])),
abs ( $this -> date2ts ( $old_event [ 'end' ]) - $this -> date2ts ( $new_event [ 'end' ])));
$check = $ru == 'time_change_4h' ? 4 * 60 * 60 - 1 : 0 ;
if ( $msg_type == MSG_MODIFIED && $diff > $check )
{
++ $want_update ;
}
case 'add_cancel' :
if ( $old_event [ 'owner' ] == $userid && $msg_is_response ||
$msg_type == MSG_DELETED || $msg_type == MSG_ADDED || $msg_type == MSG_DISINVITE )
{
++ $want_update ;
}
break ;
case 'no' :
break ;
}
//error_log(__METHOD__."(userid=$userid,,msg_type=$msg_type,...) msg_is_response=$msg_is_response, want_update=$want_update");
return $want_update > 0 ;
}
/**
* sends update - messages to certain participants of an event
*
* @ param int $msg_type type of the notification : MSG_ADDED , MSG_MODIFIED , MSG_ACCEPTED , ...
* @ param array $to_notify numerical user - ids as keys ( ! ) ( value is not used )
* @ param array $old_event Event before the change
* @ param array $new_event = null Event after the change
* @ param int $user = 0 User who started the notify , default current user
* @ return bool true / false
*/
function send_update ( $msg_type , $to_notify , $old_event , $new_event = null , $user = 0 )
{
//error_log(__METHOD__."($msg_type,".array2string($to_notify).",...)");
if ( ! is_array ( $to_notify ))
{
$to_notify = array ();
}
$disinvited = $msg_type == MSG_DISINVITE ? array_keys ( $to_notify ) : array ();
$owner = $old_event ? $old_event [ 'owner' ] : $new_event [ 'owner' ];
if ( $owner && ! isset ( $to_notify [ $owner ]) && $msg_type != MSG_ALARM )
{
$to_notify [ $owner ] = 'owner' ; // always include the event-owner
}
$version = $GLOBALS [ 'egw_info' ][ 'apps' ][ 'calendar' ][ 'version' ];
// ignore events in the past (give a tolerance of 10 seconds for the script)
if ( $old_event != False && $this -> date2ts ( $old_event [ 'start' ]) < ( $this -> now_su - 10 ))
{
return False ;
}
$temp_user = $GLOBALS [ 'egw_info' ][ 'user' ]; // save user-date of the enviroment to restore it after
if ( ! $user )
{
$user = $temp_user [ 'account_id' ];
}
if ( $GLOBALS [ 'egw' ] -> preferences -> account_id != $user )
{
$GLOBALS [ 'egw' ] -> preferences -> __construct ( $user );
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ] = $GLOBALS [ 'egw' ] -> preferences -> read_repository ();
}
2010-11-01 20:18:13 +01:00
$senderid = $this -> user ;
2010-09-18 14:34:58 +02:00
$event = $msg_type == MSG_ADDED || $msg_type == MSG_MODIFIED ? $new_event : $old_event ;
switch ( $msg_type )
{
case MSG_DELETED :
$action = lang ( 'Canceled' );
$msg = 'Canceled' ;
$msgtype = '"calendar";' ;
$method = 'CANCEL' ;
break ;
case MSG_MODIFIED :
$action = lang ( 'Modified' );
$msg = 'Modified' ;
$msgtype = '"calendar"; Version="' . $version . '"; Id="' . $new_event [ 'id' ] . '"' ;
$method = 'REQUEST' ;
break ;
case MSG_DISINVITE :
$action = lang ( 'Disinvited' );
$msg = 'Disinvited' ;
$msgtype = '"calendar";' ;
$method = 'CANCEL' ;
break ;
case MSG_ADDED :
$action = lang ( 'Added' );
$msg = 'Added' ;
$msgtype = '"calendar"; Version="' . $version . '"; Id="' . $new_event [ 'id' ] . '"' ;
$method = 'REQUEST' ;
break ;
case MSG_REJECTED :
$action = lang ( 'Rejected' );
$msg = 'Response' ;
$msgtype = '"calendar";' ;
$method = 'REPLY' ;
break ;
case MSG_TENTATIVE :
$action = lang ( 'Tentative' );
$msg = 'Response' ;
$msgtype = '"calendar";' ;
$method = 'REPLY' ;
break ;
case MSG_ACCEPTED :
$action = lang ( 'Accepted' );
$msg = 'Response' ;
$msgtype = '"calendar";' ;
$method = 'REPLY' ;
break ;
case MSG_DELEGATED :
$action = lang ( 'Delegated' );
$msg = 'Response' ;
$msgtype = '"calendar";' ;
$method = 'REPLY' ;
break ;
case MSG_ALARM :
$action = lang ( 'Alarm' );
$msg = 'Alarm' ;
$msgtype = '"calendar";' ;
$method = 'PUBLISH' ; // duno if thats right
break ;
default :
$method = 'PUBLISH' ;
}
$notify_msg = $this -> cal_prefs [ 'notify' . $msg ];
if ( empty ( $notify_msg ))
{
$notify_msg = $this -> cal_prefs [ 'notifyAdded' ]; // use a default
}
$details = $this -> _get_event_details ( $event , $action , $event_arr , $disinvited );
// add all group-members to the notification, unless they are already participants
foreach ( $to_notify as $userid => $statusid )
{
if ( is_numeric ( $userid ) && $GLOBALS [ 'egw' ] -> accounts -> get_type ( $userid ) == 'g' &&
( $members = $GLOBALS [ 'egw' ] -> accounts -> member ( $userid )))
{
foreach ( $members as $member )
{
$member = $member [ 'account_id' ];
if ( ! isset ( $to_notify [ $member ]))
{
$to_notify [ $member ] = 'G' ; // Group-invitation
}
}
}
}
$user_prefs = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ];
2010-11-01 20:18:13 +01:00
$startdate = new egw_time ( $event [ 'start' ]);
2010-11-16 14:41:25 +01:00
$enddate = new egw_time ( $event [ 'end' ]);
2010-11-01 20:18:13 +01:00
$modified = new egw_time ( $event [ 'modified' ]);
if ( $old_event != False ) $olddate = new egw_time ( $old_event [ 'start' ]);
2010-09-18 14:34:58 +02:00
foreach ( $to_notify as $userid => $statusid )
{
if ( $this -> debug > 0 ) error_log ( __METHOD__ . " trying to notify $userid , with $statusid " );
if ( ! is_numeric ( $userid ))
{
$res_info = $this -> resource_info ( $userid );
$userid = $res_info [ 'responsible' ];
if ( ! isset ( $userid )) continue ;
}
if ( $statusid == 'R' || $GLOBALS [ 'egw' ] -> accounts -> get_type ( $userid ) == 'g' )
{
continue ; // dont notify rejected participants or groups
}
if ( $userid != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] ||
( $userid == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] &&
$user_prefs [ 'calendar' ][ 'receive_own_updates' ] == 1 ) ||
$msg_type == MSG_ALARM )
{
$preferences = CreateObject ( 'phpgwapi.preferences' , $userid );
$part_prefs = $preferences -> read_repository ();
if ( ! $this -> update_requested ( $userid , $part_prefs , $msg_type , $old_event , $new_event ))
{
continue ;
}
$GLOBALS [ 'egw' ] -> accounts -> get_account_name ( $userid , $lid , $details [ 'to-firstname' ], $details [ 'to-lastname' ]);
$details [ 'to-fullname' ] = $GLOBALS [ 'egw' ] -> common -> display_fullname ( '' , $details [ 'to-firstname' ], $details [ 'to-lastname' ]);
// event is in user-time of current user, now we need to calculate the tz-difference to the notified user and take it into account
2010-11-01 20:18:13 +01:00
if ( ! isset ( $part_prefs [ 'common' ][ 'tz' ])) $part_prefs [ 'common' ][ 'tz' ] = $GLOBALS [ 'egw_info' ][ 'server' ][ 'server_timezone' ];
$timezone = new DateTimeZone ( $part_prefs [ 'common' ][ 'tz' ]);
$timeformat = $part_prefs [ 'common' ][ 'timeformat' ];
switch ( $timeformat )
{
case '24' :
$timeformat = 'H:i' ;
break ;
case '12' :
$timeformat = 'h:i a' ;
break ;
}
$timeformat = $part_prefs [ 'common' ][ 'dateformat' ] . ', ' . $timeformat ;
2010-11-11 09:55:35 +01:00
2010-11-01 20:18:13 +01:00
$startdate -> setTimezone ( $timezone );
$details [ 'startdate' ] = $startdate -> format ( $timeformat );
2010-11-11 09:55:35 +01:00
2010-11-01 20:18:13 +01:00
$enddate -> setTimezone ( $timezone );
$details [ 'enddate' ] = $enddate -> format ( $timeformat );
2010-11-11 09:55:35 +01:00
2010-11-01 20:18:13 +01:00
$modified -> setTimezone ( $timezone );
$details [ 'updated' ] = $modified -> format ( $timeformat ) . ', ' . common :: grab_owner_name ( $event [ 'modifier' ]);
2010-11-11 09:55:35 +01:00
2010-11-01 20:18:13 +01:00
if ( $old_event != False )
{
$olddate -> setTimezone ( $timezone );
$details [ 'olddate' ] = $olddate -> format ( $timeformat );
}
2010-09-18 14:34:58 +02:00
list ( $subject , $body ) = explode ( " \n " , $GLOBALS [ 'egw' ] -> preferences -> parse_notify ( $notify_msg , $details ), 2 );
switch ( $part_prefs [ 'calendar' ][ 'update_format' ])
{
case 'ical' :
if ( $method == 'REQUEST' )
{
2011-04-11 13:03:13 +02:00
if ( is_null ( $ics ))
{
$calendar_ical = new calendar_ical ();
$calendar_ical -> setSupportedFields ( 'groupdav' ); // full iCal fields+event TZ
$ics = $calendar_ical -> exportVCal ( $event [ 'id' ], '2.0' , $method );
unset ( $calendar_ical );
}
2010-09-18 14:34:58 +02:00
$attachment = array ( 'string' => $ics ,
'filename' => 'cal.ics' ,
'encoding' => '8bit' ,
'type' => 'text/calendar; method=' . $method ,
);
}
// fall through
case 'extended' :
$body .= " \n \n " . lang ( 'Event Details follow' ) . " : \n " ;
foreach ( $event_arr as $key => $val )
{
if ( strlen ( $details [ $key ])) {
switch ( $key ){
case 'access' :
case 'priority' :
case 'link' :
break ;
default :
$body .= sprintf ( " %-20s %s \n " , $val [ 'field' ] . ':' , $details [ $key ]);
break ;
}
}
}
break ;
}
// send via notification_app
if ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'notifications' ][ 'enabled' ]) {
try {
$notification = new notifications ();
$notification -> set_receivers ( array ( $userid ));
$notification -> set_message ( $body );
$notification -> set_sender ( $senderid );
$notification -> set_subject ( $subject );
$notification -> set_links ( array ( $details [ 'link_arr' ]));
if ( is_array ( $attachment )) { $notification -> set_attachments ( array ( $attachment )); }
$notification -> send ();
}
catch ( Exception $exception ) {
error_log ( __METHOD__ . ' error while notifying user ' . $userid . ':' . $exception -> getMessage ());
continue ;
}
} else {
error_log ( __METHOD__ . ' cannot send any notifications because notifications is not installed' );
}
}
}
// restore the enviroment (preferences->read_repository() sets the timezone!)
$GLOBALS [ 'egw_info' ][ 'user' ] = $temp_user ;
2010-11-01 20:18:13 +01:00
if ( $GLOBALS [ 'egw' ] -> preferences -> account_id != $temp_user [ 'account_id' ])
2010-09-18 14:34:58 +02:00
{
$GLOBALS [ 'egw' ] -> preferences -> __construct ( $temp_user [ 'account_id' ]);
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ] = $GLOBALS [ 'egw' ] -> preferences -> read_repository ();
//echo "<p>".__METHOD__."() restored enviroment of #$temp_user[account_id] $temp_user[account_fullname]: tz={$GLOBALS['egw_info']['user']['preferences']['common']['tz']}</p>\n";
}
return true ;
}
function get_update_message ( $event , $added )
{
$details = $this -> _get_event_details ( $event , $added ? lang ( 'Added' ) : lang ( 'Modified' ), $nul );
$notify_msg = $this -> cal_prefs [ $added || empty ( $this -> cal_prefs [ 'notifyModified' ]) ? 'notifyAdded' : 'notifyModified' ];
return explode ( " \n " , $GLOBALS [ 'egw' ] -> preferences -> parse_notify ( $notify_msg , $details ), 2 );
}
/**
* Function called via async service , when an alarm is to be send
*
* @ param array $alarm array with keys owner , cal_id , all
* @ return boolean
*/
function send_alarm ( $alarm )
{
//echo "<p>bocalendar::send_alarm("; print_r($alarm); echo ")</p>\n";
$GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] = $this -> owner = $alarm [ 'owner' ];
$event_time_user = egw_time :: server2user ( $alarm [ 'time' ] + $alarm [ 'offset' ]); // alarm[time] is in server-time, read requires user-time
if ( ! $alarm [ 'owner' ] || ! $alarm [ 'cal_id' ] || ! ( $event = $this -> read ( $alarm [ 'cal_id' ], $event_time_user )))
{
return False ; // event not found
}
if ( $alarm [ 'all' ])
{
$to_notify = $event [ 'participants' ];
}
elseif ( $this -> check_perms ( EGW_ACL_READ , $event )) // checks agains $this->owner set to $alarm[owner]
{
$to_notify [ $alarm [ 'owner' ]] = 'A' ;
}
else
{
return False ; // no rights
}
$ret = $this -> send_update ( MSG_ALARM , $to_notify , $event , False , $alarm [ 'owner' ]);
// create a new alarm for recuring events for the next event, if one exists
if ( $event [ 'recur_type' ] && ( $event = $this -> read ( $alarm [ 'cal_id' ], $event_time_user + 1 )))
{
$alarm [ 'time' ] = $this -> date2ts ( $event [ 'start' ]) - $alarm [ 'offset' ];
$this -> save_alarm ( $alarm [ 'cal_id' ], $alarm );
}
return $ret ;
}
/**
* saves an event to the database , does NOT do any notifications , see calendar_boupdate :: update for that
*
* This methode converts from user to server time and handles the insertion of users and dates of repeating events
*
* @ param array $event
* @ param boolean $ignore_acl = false should we ignore the acl
* @ param boolean $updateTS = true update the content history of the event
* @ return int | boolean $cal_id > 0 or false on error ( eg . permission denied )
*/
function save ( $event , $ignore_acl = false , $updateTS = true )
{
2011-04-07 10:09:20 +02:00
//error_log(__METHOD__.'('.array2string($event).", $ignore_acl, $updateTS)");
2010-11-11 09:55:35 +01:00
2010-09-18 14:34:58 +02:00
// check if user has the permission to update / create the event
if ( ! $ignore_acl && ( $event [ 'id' ] && ! $this -> check_perms ( EGW_ACL_EDIT , $event [ 'id' ]) ||
! $event [ 'id' ] && ! $this -> check_perms ( EGW_ACL_EDIT , 0 , $event [ 'owner' ]) &&
! $this -> check_perms ( EGW_ACL_ADD , 0 , $event [ 'owner' ])))
{
return false ;
}
// invalidate the read-cache if it contains the event we store now
if ( $event [ 'id' ] && $event [ 'id' ] == self :: $cached_event [ 'id' ]) self :: $cached_event = array ();
$save_event = $event ;
// we run all dates through date2ts, to adjust to server-time and the possible date-formats
foreach ( array ( 'start' , 'end' , 'modified' , 'created' , 'recur_enddate' , 'recurrence' ) as $ts )
{
// we convert here from user-time to timestamps in server-time!
if ( isset ( $event [ $ts ])) $event [ $ts ] = $event [ $ts ] ? $this -> date2ts ( $event [ $ts ], true ) : 0 ;
}
// convert tzid name to integer tz_id, of set user default
if ( empty ( $event [ 'tzid' ]) || ! ( $event [ 'tz_id' ] = calendar_timezones :: tz2id ( $event [ 'tzid' ])))
{
$event [ 'tz_id' ] = calendar_timezones :: tz2id ( $event [ 'tzid' ] = egw_time :: $user_timezone -> getName ());
}
// same with the recur exceptions
if ( isset ( $event [ 'recur_exception' ]) && is_array ( $event [ 'recur_exception' ]))
{
foreach ( $event [ 'recur_exception' ] as $n => $date )
{
$event [ 'recur_exception' ][ $n ] = $this -> date2ts ( $date , true );
}
}
// same with the alarms
if ( isset ( $event [ 'alarm' ]) && is_array ( $event [ 'alarm' ]))
{
foreach ( $event [ 'alarm' ] as $id => $alarm )
{
2011-03-07 14:50:25 +01:00
// recalculate alarms to also cope with moved events (beside server time adjustment)
$event [ 'alarm' ][ $id ][ 'time' ] = $event [ 'start' ] - $alarm [ 'offset' ];
}
}
// update all existing alarm times, in case alarm got moved and alarms are not include in $event
if ( $old_event && is_array ( $old_event [ 'alarm' ]))
{
foreach ( $old_event [ 'alarm' ] as $alarm )
{
$alarm [ 'time' ] = $event [ 'start' ] - $alarm [ 'offset' ];
$this -> so -> save_alarm ( $event [ 'id' ], $alarm , $this -> now );
2010-09-18 14:34:58 +02:00
}
}
2011-04-07 10:09:20 +02:00
// always update modification time (ctag depends on it!)
$event [ 'modified' ] = $this -> now ;
$event [ 'modifier' ] = $this -> user ;
2010-09-18 14:34:58 +02:00
if ( empty ( $event [ 'id' ]) && ( ! isset ( $event [ 'created' ]) || $event [ 'created' ] > $this -> now ))
{
$event [ 'created' ] = $this -> now ;
$event [ 'creator' ] = $this -> user ;
}
$set_recurrences = false ;
$set_recurrences_start = 0 ;
if (( $cal_id = $this -> so -> save ( $event , $set_recurrences , $set_recurrences_start , 0 , $event [ 'etag' ])) && $set_recurrences && $event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
$save_event [ 'id' ] = $cal_id ;
// unset participants to enforce the default stati for all added recurrences
unset ( $save_event [ 'participants' ]);
$this -> set_recurrences ( $save_event , $set_recurrences_start );
}
if ( $updateTS ) $GLOBALS [ 'egw' ] -> contenthistory -> updateTimeStamp ( 'calendar' , $cal_id , $event [ 'id' ] ? 'modify' : 'add' , time ());
return $cal_id ;
}
/**
* Check if the current user has the necessary ACL rights to change the status of $uid
*
* For contacts we use edit rights of the owner of the event ( aka . edit rights of the event ) .
*
* @ param int | string $uid account_id or 1 - char type - identifer plus id ( eg . c15 for addressbook entry #15)
* @ param array | int $event event array or id of the event
* @ return boolean
*/
function check_status_perms ( $uid , $event )
{
if ( $uid [ 0 ] == 'c' || $uid [ 0 ] == 'e' ) // for contact we use the owner of the event
{
if ( ! is_array ( $event ) && ! ( $event = $this -> read ( $event ))) return false ;
return $this -> check_perms ( EGW_ACL_EDIT , 0 , $event [ 'owner' ]);
}
// check if we have a category acl for the event or not (null)
$access = $this -> check_cat_acl ( self :: CAT_ACL_STATUS , $event );
if ( ! is_null ( $access ))
{
return $access ;
}
// no access or denied access because of category acl --> regular check
if ( ! is_numeric ( $uid )) // this is eg. for resources (r123)
{
$resource = $this -> resource_info ( $uid );
return EGW_ACL_EDIT & $resource [ 'rights' ];
}
// regular user and groups
return $this -> check_perms ( EGW_ACL_EDIT , 0 , $uid );
}
/**
* Check if current user has a certain right on the categories of an event
*
* Not having the given right for a single category , means not having it !
*
* @ param int $right self :: CAT_ACL_ { ADD | STATUS }
* @ param int | array $event
* @ return boolean true if use has the right , false if not
* @ return boolean false = access denied because of cat acl , true access granted because of cat acl ,
* null = cat has no acl
*/
function check_cat_acl ( $right , $event )
{
if ( ! is_array ( $event )) $event = $this -> read ( $event );
$ret = null ;
if ( $event [ 'category' ])
{
foreach ( is_array ( $event [ 'category' ]) ? $event [ 'category' ] : explode ( ',' , $event [ 'category' ]) as $cat_id )
{
$access = self :: has_cat_right ( $right , $cat_id , $this -> user );
if ( $access === true )
{
$ret = true ;
break ;
}
if ( $access === false )
{
$ret = false ; // cat denies access --> check further cats
}
}
}
//echo "<p>".__METHOD__."($event[id]: $event[title], $right) = ".array2string($ret)."</p>\n";
return $ret ;
}
/**
* Array with $cat_id => $rights pairs for current user ( no entry means , cat is not limited by ACL ! )
*
* @ var array
*/
private static $cat_rights_cache ;
/**
* Get rights for a given category id
*
* @ param int $cat_id = null null to return array with all cats
* @ return array with account_id => right pairs
*/
public static function get_cat_rights ( $cat_id = null )
{
if ( ! isset ( self :: $cat_rights_cache ))
{
self :: $cat_rights_cache = egw_cache :: getSession ( 'calendar' , 'cat_rights' ,
array ( $GLOBALS [ 'egw' ] -> acl , 'get_location_grants' ), array ( 'L%' , 'calendar' ));
}
//echo "<p>".__METHOD__."($cat_id) = ".array2string($cat_id ? self::$cat_rights_cache['L'.$cat_id] : self::$cat_rights_cache)."</p>\n";
return $cat_id ? self :: $cat_rights_cache [ 'L' . $cat_id ] : self :: $cat_rights_cache ;
}
/**
* Set rights for a given single category and user
*
* @ param int $cat_id
* @ param int $user
* @ param int $rights self :: CAT_ACL_ { ADD | STATUS } or ' ed together
*/
public static function set_cat_rights ( $cat_id , $user , $rights )
{
//echo "<p>".__METHOD__."($cat_id,$user,$rights)</p>\n";
if ( ! isset ( self :: $cat_rights_cache )) self :: get_cat_rights ( $cat_id );
if (( int ) $rights != ( int ) self :: $cat_rights_cache [ 'L' . $cat_id ][ $user ])
{
if ( $rights )
{
self :: $cat_rights_cache [ 'L' . $cat_id ][ $user ] = $rights ;
$GLOBALS [ 'egw' ] -> acl -> add_repository ( 'calendar' , 'L' . $cat_id , $user , $rights );
}
else
{
unset ( self :: $cat_rights_cache [ 'L' . $cat_id ][ $user ]);
if ( ! self :: $cat_rights_cache [ 'L' . $cat_id ]) unset ( self :: $cat_rights_cache [ 'L' . $cat_id ]);
$GLOBALS [ 'egw' ] -> acl -> delete_repository ( 'calendar' , 'L' . $cat_id , $user );
}
egw_cache :: setSession ( 'calendar' , 'cat_rights' , self :: $cat_rights_cache );
}
}
/**
* Check if current user has a given right on a category ( if it ' s restricted ! )
*
* @ param int $cat_id
* @ return boolean false = access denied because of cat acl , true access granted because of cat acl ,
* null = cat has no acl
*/
public static function has_cat_right ( $right , $cat_id , $user )
{
static $cache ;
if ( ! isset ( $cache [ $cat_id ]))
{
$all = $own = 0 ;
$cat_rights = self :: get_cat_rights ( $cat_id );
if ( ! is_null ( $cat_rights ))
{
static $memberships ;
if ( is_null ( $memberships ))
{
$memberships = $GLOBALS [ 'egw' ] -> accounts -> memberships ( $user , true );
$memberships [] = $user ;
}
foreach ( $cat_rights as $uid => $value )
{
$all |= $value ;
if ( in_array ( $uid , $memberships )) $own |= $value ;
}
}
foreach ( array ( self :: CAT_ACL_ADD , self :: CAT_ACL_STATUS ) as $mask )
{
$cache [ $cat_id ][ $mask ] = ! ( $all & $mask ) ? null : !! ( $own & $mask );
}
}
//echo "<p>".__METHOD__."($right,$cat_id) all=$all, own=$own returning ".array2string($cache[$cat_id][$right])."</p>\n";
return $cache [ $cat_id ][ $right ];
}
/**
* set the status of one participant for a given recurrence or for all recurrences since now ( includes recur_date = 0 )
*
* @ param int | array $event event - array or id of the event
* @ param string | int $uid account_id or 1 - char type - identifer plus id ( eg . c15 for addressbook entry #15)
* @ param int | char $status numeric status ( defines ) or 1 - char code : 'R' , 'U' , 'T' or 'A'
* @ param int $recur_date = 0 date to change , or 0 = all since now
* @ param boolean $ignore_acl = false do not check the permisions for the $uid , if true
* @ param boolean $updateTS = true update the content history of the event
* @ return int number of changed recurrences
*/
function set_status ( $event , $uid , $status , $recur_date = 0 , $ignore_acl = false , $updateTS = true )
{
$cal_id = is_array ( $event ) ? $event [ 'id' ] : $event ;
//echo "<p>calendar_boupdate::set_status($cal_id,$uid,$status,$recur_date)</p>\n";
if ( ! $cal_id || ( ! $ignore_acl && ! $this -> check_status_perms ( $uid , $event )))
{
return false ;
}
calendar_so :: split_status ( $status , $quantity , $role );
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" ( $cal_id , $uid , $status , $recur_date ) \n " , 3 , $this -> logfile );
}
if (( $Ok = $this -> so -> set_status ( $cal_id , is_numeric ( $uid ) ? 'u' : $uid [ 0 ],
is_numeric ( $uid ) ? $uid : substr ( $uid , 1 ), $status ,
$recur_date ? $this -> date2ts ( $recur_date , true ) : 0 , $role )))
{
if ( $updateTS ) $GLOBALS [ 'egw' ] -> contenthistory -> updateTimeStamp ( 'calendar' , $cal_id , 'modify' , time ());
static $status2msg = array (
'R' => MSG_REJECTED ,
'T' => MSG_TENTATIVE ,
'A' => MSG_ACCEPTED ,
'D' => MSG_DELEGATED ,
);
if ( isset ( $status2msg [ $status ]))
{
if ( ! is_array ( $event )) $event = $this -> read ( $cal_id );
if ( isset ( $recur_date )) $event = $this -> read ( $event [ 'id' ], $recur_date ); //re-read the actually edited recurring event
$this -> send_update ( $status2msg [ $status ], $event [ 'participants' ], $event );
}
}
return $Ok ;
}
/**
* update the status of all participant for a given recurrence or for all recurrences since now ( includes recur_date = 0 )
*
* @ param array $new_event event - array with the new stati
* @ param array $old_event event - array with the old stati
* @ param int $recur_date = 0 date to change , or 0 = all since now
*/
function update_status ( $new_event , $old_event , $recur_date = 0 )
{
if ( ! isset ( $new_event [ 'participants' ])) return ;
// check the old list against the new list
foreach ( $old_event [ 'participants' ] as $userid => $status )
{
if ( ! isset ( $new_event [ 'participants' ][ $userid ])){
// Attendee will be deleted this way
$new_event [ 'participants' ][ $userid ] = 'G' ;
}
elseif ( $new_event [ 'participants' ][ $userid ] == $status )
{
// Same status -- nothing to do.
unset ( $new_event [ 'participants' ][ $userid ]);
}
}
// write the changes
foreach ( $new_event [ 'participants' ] as $userid => $status )
{
$this -> set_status ( $old_event , $userid , $status , $recur_date , true , false );
}
}
/**
* deletes an event
*
* @ param int $cal_id id of the event to delete
* @ param int $recur_date = 0 if a single event from a series should be deleted , its date
* @ param boolean $ignore_acl = false true for no ACL check , default do ACL check
* @ return boolean true on success , false on error ( usually permission denied )
*/
function delete ( $cal_id , $recur_date = 0 , $ignore_acl = false )
{
if ( ! ( $event = $this -> read ( $cal_id , $recur_date )) ||
! $ignore_acl && ! $this -> check_perms ( EGW_ACL_DELETE , $event ))
{
return false ;
}
$this -> send_update ( MSG_DELETED , $event [ 'participants' ], $event );
if ( ! $recur_date || $event [ 'recur_type' ] == MCAL_RECUR_NONE )
{
$this -> so -> delete ( $cal_id );
$GLOBALS [ 'egw' ] -> contenthistory -> updateTimeStamp ( 'calendar' , $cal_id , 'delete' , time ());
// delete all links to the event
egw_link :: unlink ( 0 , 'calendar' , $cal_id );
}
2011-04-07 10:09:20 +02:00
else // delete an exception
2010-09-18 14:34:58 +02:00
{
$event [ 'recur_exception' ][] = $recur_date = $this -> date2ts ( $event [ 'start' ]);
unset ( $event [ 'start' ]);
unset ( $event [ 'end' ]);
$this -> save ( $event ); // updates the content-history
}
if ( $event [ 'reference' ])
{
// evtl. delete recur_exception $event['recurrence'] from event with cal_id=$event['reference']
}
return true ;
}
/**
* helper for send_update and get_update_message
* @ internal
*/
function _get_event_details ( $event , $action , & $event_arr , $disinvited = array ())
{
$details = array ( // event-details for the notify-msg
'id' => $event [ 'id' ],
'action' => $action ,
);
$event_arr = $this -> event2array ( $event );
foreach ( $event_arr as $key => $val )
{
$details [ $key ] = $val [ 'data' ];
}
$details [ 'participants' ] = $details [ 'participants' ] ? implode ( " \n " , $details [ 'participants' ]) : '' ;
$event_arr [ 'link' ][ 'field' ] = lang ( 'URL' );
$eventStart_arr = $this -> date2array ( $event [ 'start' ]); // give this as 'date' to the link to pick the right recurrence for the participants state
$link = $GLOBALS [ 'egw_info' ][ 'server' ][ 'webserver_url' ] . '/index.php?menuaction=calendar.calendar_uiforms.edit&cal_id=' . $event [ 'id' ] . '&date=' . $eventStart_arr [ 'full' ] . '&no_popup=1' ;
// if url is only a path, try guessing the rest ;-)
if ( $link [ 0 ] == '/' )
{
$link = ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'enforce_ssl' ] || $_SERVER [ 'HTTPS' ] ? 'https://' : 'http://' ) .
( $GLOBALS [ 'egw_info' ][ 'server' ][ 'hostname' ] ? $GLOBALS [ 'egw_info' ][ 'server' ][ 'hostname' ] : $_SERVER [ 'HTTP_HOST' ]) .
$link ;
}
$event_arr [ 'link' ][ 'data' ] = $details [ 'link' ] = $link ;
/* this is needed for notification - app
* notification - app creates the link individual for
* every user , so we must provide a neutral link - style
* if calendar implements tracking in near future , this part can be deleted
*/
$link_arr = array ();
$link_arr [ 'text' ] = $event [ 'title' ];
$link_arr [ 'view' ] = array ( 'menuaction' => 'calendar.calendar_uiforms.edit' ,
'cal_id' => $event [ 'id' ],
'date' => $eventStart_arr [ 'full' ],
);
$link_arr [ 'popup' ] = '750x400' ;
$details [ 'link_arr' ] = $link_arr ;
$dis = array ();
foreach ( $disinvited as $uid )
{
$dis [] = $this -> participant_name ( $uid );
}
$details [ 'disinvited' ] = implode ( ', ' , $dis );
return $details ;
}
/**
* create array with name , translated name and readable content of each attributes of an event
*
* old function , so far only used by send_update ( therefor it ' s in bocalupdate and not bocal )
*
* @ param array $event event to use
* @ returns array of attributes with fieldname as key and array with the 'field' = translated name 'data' = readable content ( for participants this is an array ! )
*/
function event2array ( $event )
{
$var [ 'title' ] = Array (
'field' => lang ( 'Title' ),
'data' => $event [ 'title' ]
);
$var [ 'description' ] = Array (
'field' => lang ( 'Description' ),
'data' => $event [ 'description' ]
);
foreach ( explode ( ',' , $event [ 'category' ]) as $cat_id )
{
list ( $cat ) = $GLOBALS [ 'egw' ] -> categories -> return_single ( $cat_id );
$cat_string [] = stripslashes ( $cat [ 'name' ]);
}
$var [ 'category' ] = Array (
'field' => lang ( 'Category' ),
'data' => implode ( ', ' , $cat_string )
);
$var [ 'location' ] = Array (
'field' => lang ( 'Location' ),
'data' => $event [ 'location' ]
);
$var [ 'startdate' ] = Array (
'field' => lang ( 'Start Date/Time' ),
'data' => $this -> format_date ( $event [ 'start' ]),
);
$var [ 'enddate' ] = Array (
'field' => lang ( 'End Date/Time' ),
'data' => $this -> format_date ( $event [ 'end' ]),
);
$pri = Array (
0 => '' ,
1 => lang ( 'Low' ),
2 => lang ( 'Normal' ),
3 => lang ( 'High' )
);
$var [ 'priority' ] = Array (
'field' => lang ( 'Priority' ),
'data' => $pri [ $event [ 'priority' ]]
);
$var [ 'owner' ] = Array (
'field' => lang ( 'Owner' ),
2010-11-01 20:18:13 +01:00
'data' => common :: grab_owner_name ( $event [ 'owner' ])
2010-09-18 14:34:58 +02:00
);
$var [ 'updated' ] = Array (
'field' => lang ( 'Updated' ),
2010-11-01 20:18:13 +01:00
'data' => $this -> format_date ( $event [ 'modtime' ]) . ', ' . common :: grab_owner_name ( $event [ 'modifier' ])
2010-09-18 14:34:58 +02:00
);
$var [ 'access' ] = Array (
'field' => lang ( 'Access' ),
'data' => $event [ 'public' ] ? lang ( 'Public' ) : lang ( 'Private' )
);
if ( isset ( $event [ 'participants' ]) && is_array ( $event [ 'participants' ]))
{
$participants = $this -> participants ( $event , true );
}
$var [ 'participants' ] = Array (
'field' => lang ( 'Participants' ),
'data' => $participants
);
// Repeated Events
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
$var [ 'recur_type' ] = Array (
'field' => lang ( 'Repetition' ),
'data' => $this -> recure2string ( $event ),
);
}
return $var ;
}
/**
* log all updates to a file
*
* @ param array $event2save event - data before calling save
* @ param array $event_saved event - data read back from the DB
* @ param array $old_event = null event - data in the DB before calling save
* @ param string $type = 'update'
*/
function log2file ( $event2save , $event_saved , $old_event = null , $type = 'update' )
{
if ( ! ( $f = fopen ( $this -> log_file , 'a' )))
{
echo " <p>error opening ' $this->log_file ' !!!</p> \n " ;
return false ;
}
fwrite ( $f , $type . ': ' . $GLOBALS [ 'egw' ] -> common -> grab_owner_name ( $this -> user ) . ': ' . date ( 'r' ) . " \n " );
fwrite ( $f , " Time: time to save / saved time read back / old time before save \n " );
foreach ( array ( 'start' , 'end' ) as $name )
{
fwrite ( $f , $name . ': ' . ( isset ( $event2save [ $name ]) ? $this -> format_date ( $event2save [ $name ]) : 'not set' ) . ' / ' .
$this -> format_date ( $event_saved [ $name ]) . ' / ' .
( is_null ( $old_event ) ? 'no old event' : $this -> format_date ( $old_event [ $name ])) . " \n " );
}
foreach ( array ( 'event2save' , 'event_saved' , 'old_event' ) as $name )
{
fwrite ( $f , $name . ' = ' . print_r ( $$name , true ));
}
fwrite ( $f , " \n " );
fclose ( $f );
return true ;
}
/**
* saves a new or updated alarm
*
* @ param int $cal_id Id of the calendar - entry
* @ param array $alarm array with fields : text , owner , enabled , ..
* @ return string id of the alarm , or false on error ( eg . no perms )
*/
function save_alarm ( $cal_id , $alarm )
{
if ( ! $cal_id || ! $this -> check_perms ( EGW_ACL_EDIT , $alarm [ 'all' ] ? $cal_id : 0 , ! $alarm [ 'all' ] ? $alarm [ 'owner' ] : 0 ))
{
//echo "<p>no rights to save the alarm=".print_r($alarm,true)." to event($cal_id)</p>";
return false ; // no rights to add the alarm
}
$alarm [ 'time' ] = $this -> date2ts ( $alarm [ 'time' ], true ); // user to server-time
$GLOBALS [ 'egw' ] -> contenthistory -> updateTimeStamp ( 'calendar' , $cal_id , 'modify' , time ());
return $this -> so -> save_alarm ( $cal_id , $alarm , $this -> now_su );
}
/**
* 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 , false on error ( eg . no perms )
*/
function delete_alarm ( $id )
{
list (, $cal_id ) = explode ( ':' , $id );
if ( ! ( $alarm = $this -> so -> read_alarm ( $id )) || ! $cal_id || ! $this -> check_perms ( EGW_ACL_EDIT , $alarm [ 'all' ] ? $cal_id : 0 , ! $alarm [ 'all' ] ? $alarm [ 'owner' ] : 0 ))
{
return false ; // no rights to delete the alarm
}
$GLOBALS [ 'egw' ] -> contenthistory -> updateTimeStamp ( 'calendar' , $cal_id , 'modify' , time ());
return $this -> so -> delete_alarm ( $id , $this -> now_su );
}
/**
* Find existing categories in database by name or add categories that do not exist yet
* currently used for ical / sif import
*
* @ param array $catname_list names of the categories which should be found or added
* @ param int $cal_id =- 1 match against existing event and expand the returned category ids
* by the ones the user normally does not see due to category permissions - used to preserve categories
* @ return array category ids ( found , added and preserved categories )
*/
function find_or_add_categories ( $catname_list , $cal_id =- 1 )
{
if ( $cal_id && $cal_id > 0 )
{
// preserve categories without users read access
$old_event = $this -> read ( $cal_id );
$old_categories = explode ( ',' , $old_event [ 'category' ]);
$old_cats_preserve = array ();
if ( is_array ( $old_categories ) && count ( $old_categories ) > 0 )
{
foreach ( $old_categories as $cat_id )
{
if ( ! $this -> categories -> check_perms ( EGW_ACL_READ , $cat_id ))
{
$old_cats_preserve [] = $cat_id ;
}
}
}
}
$cat_id_list = array ();
foreach (( array ) $catname_list as $cat_name )
{
$cat_name = trim ( $cat_name );
$cat_id = $this -> categories -> name2id ( $cat_name , 'X-' );
if ( ! $cat_id )
{
// some SyncML clients (mostly phones) add an X- to the category names
if ( strncmp ( $cat_name , 'X-' , 2 ) == 0 )
{
$cat_name = substr ( $cat_name , 2 );
}
$cat_id = $this -> categories -> add ( array ( 'name' => $cat_name , 'descr' => $cat_name , 'access' => 'private' ));
}
if ( $cat_id )
{
$cat_id_list [] = $cat_id ;
}
}
if ( is_array ( $old_cats_preserve ) && count ( $old_cats_preserve ) > 0 )
{
$cat_id_list = array_merge ( $cat_id_list , $old_cats_preserve );
}
if ( count ( $cat_id_list ) > 1 )
{
$cat_id_list = array_unique ( $cat_id_list );
sort ( $cat_id_list , SORT_NUMERIC );
}
return $cat_id_list ;
}
function get_categories ( $cat_id_list )
{
if ( ! is_array ( $cat_id_list ))
{
$cat_id_list = explode ( ',' , $cat_id_list );
}
$cat_list = array ();
foreach ( $cat_id_list as $cat_id )
{
if ( $cat_id && $this -> categories -> check_perms ( EGW_ACL_READ , $cat_id ) &&
( $cat_name = $this -> categories -> id2name ( $cat_id )) && $cat_name != '--' )
{
$cat_list [] = $cat_name ;
}
}
return $cat_list ;
}
/**
* Try to find a matching db entry
*
* @ param array $event the vCalendar data we try to find
* @ param string filter = 'exact' exact -> find the matching entry
* check -> check ( consitency ) for identical matches
* relax -> be more tolerant
* master -> try to find a releated series master
* @ return array calendar_ids of matching entries
*/
function find_event ( $event , $filter = 'exact' )
{
$matchingEvents = array ();
$query = array ();
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" ( $filter )[EVENT]: " . array2string ( $event ) . " \n " , 3 , $this -> logfile );
}
if ( $filter == 'master' )
{
$query [] = 'recur_type!=' . MCAL_RECUR_NONE ;
$query [ 'cal_recurrence' ] = 0 ;
}
2010-11-11 09:55:35 +01:00
2010-09-18 14:34:58 +02:00
if ( ! isset ( $event [ 'recurrence' ])) $event [ 'recurrence' ] = 0 ;
if ( $event [ 'id' ])
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'(' . $event [ 'id' ] . " )[EventID] \n " , 3 , $this -> logfile );
}
if (( $egwEvent = $this -> read ( $event [ 'id' ], 0 , false , 'server' )))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'()[FOUND]:' . array2string ( $egwEvent ) . " \n " , 3 , $this -> logfile );
}
if ( $egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE &&
( empty ( $event [ 'uid' ]) || $event [ 'uid' ] == $egwEvent [ 'uid' ]))
{
if ( $filter == 'master' )
{
$matchingEvents [] = $egwEvent [ 'id' ]; // we found the master
}
if ( $event [ 'recur_type' ] == $egwEvent [ 'recur_type' ])
{
$matchingEvents [] = $egwEvent [ 'id' ]; // we found the event
}
elseif ( $event [ 'recur_type' ] == MCAL_RECUR_NONE &&
$event [ 'recurrence' ] != 0 )
{
$exceptions = $this -> so -> get_recurrence_exceptions ( $egwEvent , $event [ 'tzid' ]);
if ( in_array ( $event [ 'recurrence' ], $exceptions ))
{
$matchingEvents [] = $egwEvent [ 'id' ] . ':' . ( int ) $event [ 'recurrence' ];
}
}
} elseif ( $filter != 'master' && ( $filter == 'exact' ||
$event [ 'recur_type' ] == $egwEvent [ 'recur_type' ] &&
strpos ( $egwEvent [ 'title' ], $event [ 'title' ]) === 0 ))
{
$matchingEvents [] = $egwEvent [ 'id' ]; // we found the event
}
}
if ( ! empty ( $matchingEvents ) || $filter == 'exact' ) return $matchingEvents ;
}
unset ( $event [ 'id' ]);
// No chance to find a master without [U]ID
if ( $filter == 'master' && empty ( $event [ 'uid' ])) return $matchingEvents ;
// only query calendars of users, we have READ-grants from
$users = array ();
foreach ( array_keys ( $this -> grants ) as $user )
{
$user = trim ( $user );
if ( $this -> check_perms ( EGW_ACL_READ | EGW_ACL_READ_FOR_PARTICIPANTS | EGW_ACL_FREEBUSY , 0 , $user ))
{
if ( $user && ! in_array ( $user , $users )) // already added?
{
$users [] = $user ;
}
}
elseif ( $GLOBALS [ 'egw' ] -> accounts -> get_type ( $user ) != 'g' )
{
continue ; // for non-groups (eg. users), we stop here if we have no read-rights
}
// the further code is only for real users
if ( ! is_numeric ( $user )) continue ;
// for groups we have to include the members
if ( $GLOBALS [ 'egw' ] -> accounts -> get_type ( $user ) == 'g' )
{
$members = $GLOBALS [ 'egw' ] -> accounts -> member ( $user );
if ( is_array ( $members ))
{
foreach ( $members as $member )
{
// use only members which gave the user a read-grant
if ( ! in_array ( $member [ 'account_id' ], $users ) &&
$this -> check_perms ( EGW_ACL_READ | EGW_ACL_FREEBUSY , 0 , $member [ 'account_id' ]))
{
$users [] = $member [ 'account_id' ];
}
}
}
}
else // for users we have to include all the memberships, to get the group-events
{
$memberships = $GLOBALS [ 'egw' ] -> accounts -> membership ( $user );
if ( is_array ( $memberships ))
{
foreach ( $memberships as $group )
{
if ( ! in_array ( $group [ 'account_id' ], $users ))
{
$users [] = $group [ 'account_id' ];
}
}
}
}
}
if ( $filter != 'master' && ( $filter != 'exact' || empty ( $event [ 'uid' ])))
{
if ( ! empty ( $event [ 'whole_day' ]))
{
if ( $filter == 'relax' )
{
$delta = 1800 ;
}
else
{
$delta = 60 ;
}
// check length with some tolerance
$length = $event [ 'end' ] - $event [ 'start' ] - $delta ;
$query [] = ( '(cal_end-cal_start)>' . $length );
$length += 2 * $delta ;
$query [] = ( '(cal_end-cal_start)<' . $length );
$query [] = ( 'cal_start>' . ( $event [ 'start' ] - 86400 ));
$query [] = ( 'cal_start<' . ( $event [ 'start' ] + 86400 ));
}
elseif ( isset ( $event [ 'start' ]))
{
if ( $filter == 'relax' )
{
$query [] = ( 'cal_start>' . ( $event [ 'start' ] - 3600 ));
$query [] = ( 'cal_start<' . ( $event [ 'start' ] + 3600 ));
}
else
{
// we accept a tiny tolerance
$query [] = ( 'cal_start>' . ( $event [ 'start' ] - 2 ));
$query [] = ( 'cal_start<' . ( $event [ 'start' ] + 2 ));
}
}
if ( $filter == 'relax' )
{
$matchFields = array ();
}
else
{
$matchFields = array ( 'priority' , 'public' );
}
$matchFields [] = 'recurrence' ;
foreach ( $matchFields as $key )
{
if ( isset ( $event [ $key ])) $query [ 'cal_' . $key ] = $event [ $key ];
}
}
if ( ! empty ( $event [ 'uid' ]))
{
$query [ 'cal_uid' ] = $event [ 'uid' ];
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'(' . $event [ 'uid' ] . " )[EventUID] \n " , 3 , $this -> logfile );
}
}
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'[QUERY]: ' . array2string ( $query ) . " \n " , 3 , $this -> logfile );
}
if ( ! count ( $users ) || ! ( $foundEvents =
$this -> so -> search ( null , null , $users , 0 , 'owner' , $query )))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" [NO MATCH] \n " , 3 , $this -> logfile );
}
return $matchingEvents ;
}
$pseudos = array ();
foreach ( $foundEvents as $egwEvent )
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'[FOUND]: ' . array2string ( $egwEvent ) . " \n " , 3 , $this -> logfile );
}
2010-11-11 09:55:35 +01:00
2010-09-18 14:34:58 +02:00
if ( in_array ( $egwEvent [ 'id' ], $matchingEvents )) continue ;
// convert timezone id of event to tzid (iCal id like 'Europe/Berlin')
if ( ! $egwEvent [ 'tz_id' ] || ! ( $egwEvent [ 'tzid' ] = calendar_timezones :: id2tz ( $egwEvent [ 'tz_id' ])))
{
$egwEvent [ 'tzid' ] = egw_time :: $server_timezone -> getName ();
}
if ( ! isset ( self :: $tz_cache [ $egwEvent [ 'tzid' ]]))
{
self :: $tz_cache [ $egwEvent [ 'tzid' ]] = calendar_timezones :: DateTimeZone ( $egwEvent [ 'tzid' ]);
}
if ( ! $event [ 'tzid' ])
{
$event [ 'tzid' ] = egw_time :: $server_timezone -> getName ();
}
if ( ! isset ( self :: $tz_cache [ $event [ 'tzid' ]]))
{
self :: $tz_cache [ $event [ 'tzid' ]] = calendar_timezones :: DateTimeZone ( $event [ 'tzid' ]);
}
if ( ! empty ( $event [ 'uid' ]))
{
if ( $filter == 'master' )
{
// We found the master
$matchingEvents = array ( $egwEvent [ 'id' ]);;
break ;
2010-11-11 09:55:35 +01:00
}
if ( $filter == 'exact' )
2010-09-18 14:34:58 +02:00
{
// UID found
if ( empty ( $event [ 'recurrence' ]))
{
$egwstart = new egw_time ( $egwEvent [ 'start' ], egw_time :: $server_timezone );
$egwstart -> setTimezone ( self :: $tz_cache [ $egwEvent [ 'tzid' ]]);
$dtstart = new egw_time ( $event [ 'start' ], egw_time :: $server_timezone );
$dtstart -> setTimezone ( self :: $tz_cache [ $event [ 'tzid' ]]);
if ( $egwEvent [ 'recur_type' ] == MCAL_RECUR_NONE &&
2010-11-11 09:55:35 +01:00
$event [ 'recur_type' ] == MCAL_RECUR_NONE ||
2010-09-18 14:34:58 +02:00
$egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE &&
$event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
if ( $egwEvent [ 'recur_type' ] == MCAL_RECUR_NONE &&
$egwstart -> format ( 'Ymd' ) == $dtstart -> format ( 'Ymd' ) ||
$egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE )
{
// We found an exact match
$matchingEvents = array ( $egwEvent [ 'id' ]);
break ;
}
else
{
2010-11-11 09:55:35 +01:00
$matchingEvents [] = $egwEvent [ 'id' ];
2010-09-18 14:34:58 +02:00
}
}
continue ;
}
elseif ( $egwEvent [ 'recurrence' ] == $event [ 'recurrence' ])
{
// We found an exact match
$matchingEvents = array ( $egwEvent [ 'id' ]);
break ;
}
if ( $egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE &&
$event [ 'recur_type' ] == MCAL_RECUR_NONE &&
! $egwEvent [ 'recurrence' ] && $event [ 'recurrence' ])
{
$exceptions = $this -> so -> get_recurrence_exceptions ( $egwEvent , $event [ 'tzid' ]);
if ( in_array ( $event [ 'recurrence' ], $exceptions ))
{
// We found a pseudo exception
$matchingEvents = array ( $egwEvent [ 'id' ] . ':' . ( int ) $event [ 'recurrence' ]);
break ;
}
}
continue ;
}
}
// check times
if ( $filter != 'relax' )
{
if ( empty ( $event [ 'whole_day' ]))
{
if ( abs ( $event [ 'end' ] - $egwEvent [ 'end' ]) >= 120 )
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () egwEvent length does not match! \n " , 3 , $this -> logfile );
}
continue ;
}
}
else
{
if ( ! $this -> so -> isWholeDay ( $egwEvent ))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () egwEvent is not a whole-day event! \n " , 3 , $this -> logfile );
}
continue ;
}
}
}
// check for real match
$matchFields = array ( 'title' , 'description' );
if ( $filter != 'relax' )
{
$matchFields [] = 'location' ;
}
foreach ( $matchFields as $key )
{
if ( ! empty ( $event [ $key ]) && ( empty ( $egwEvent [ $key ])
|| strpos ( str_replace ( " \r \n " , " \n " , $egwEvent [ $key ]), $event [ $key ]) !== 0 ))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () event[ $key ] differ: ' " . $event [ $key ] .
" ' <> ' " . $egwEvent [ $key ] . " ' \n " , 3 , $this -> logfile );
}
continue 2 ; // next foundEvent
}
}
if ( is_array ( $event [ 'category' ]))
{
// check categories
$egwCategories = explode ( ',' , $egwEvent [ 'category' ]);
foreach ( $egwCategories as $cat_id )
{
if ( $this -> categories -> check_perms ( EGW_ACL_READ , $cat_id ) &&
! in_array ( $cat_id , $event [ 'category' ]))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () egwEvent category $cat_id is missing! \n " , 3 , $this -> logfile );
}
continue 2 ;
}
}
$newCategories = array_diff ( $event [ 'category' ], $egwCategories );
if ( ! empty ( $newCategories ))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'() event has additional categories:'
. array2string ( $newCategories ) . " \n " , 3 , $this -> logfile );
}
continue ;
}
}
if ( $filter != 'relax' )
{
// check participants
if ( is_array ( $event [ 'participants' ]))
{
foreach ( $event [ 'participants' ] as $attendee => $status )
{
if ( ! isset ( $egwEvent [ 'participants' ][ $attendee ]) &&
$attendee != $egwEvent [ 'owner' ]) // ||
//(!$relax && $egw_event['participants'][$attendee] != $status))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () additional event['participants']: $attendee\n " , 3 , $this -> logfile );
}
continue 2 ;
}
else
{
unset ( $egwEvent [ 'participants' ][ $attendee ]);
}
}
// ORGANIZER is maybe missing
unset ( $egwEvent [ 'participants' ][ $egwEvent [ 'owner' ]]);
if ( ! empty ( $egwEvent [ 'participants' ]))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'() missing event[participants]: ' .
array2string ( $egwEvent [ 'participants' ]) . " \n " , 3 , $this -> logfile );
}
continue ;
}
}
}
if ( $event [ 'recur_type' ] == MCAL_RECUR_NONE )
{
if ( $egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE )
{
// We found a pseudo Exception
$pseudos [] = $egwEvent [ 'id' ] . ':' . $event [ 'start' ];
continue ;
}
}
elseif ( $filter != 'relax' )
{
// check exceptions
// $exceptions[$remote_ts] = $egw_ts
$exceptions = $this -> so -> get_recurrence_exceptions ( $egwEvent , $event [ '$tzid' ], 0 , 0 , 'map' );
if ( is_array ( $event [ 'recur_exception' ]))
{
foreach ( $event [ 'recur_exception' ] as $key => $day )
{
if ( isset ( $exceptions [ $day ]))
{
unset ( $exceptions [ $day ]);
}
else
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () additional event['recur_exception']: $day\n " , 3 , $this -> logfile );
}
continue 2 ;
}
}
if ( ! empty ( $exceptions ))
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'() missing event[recur_exception]: ' .
array2string ( $event [ 'recur_exception' ]));
}
continue ;
}
}
// check recurrence information
foreach ( array ( 'recur_type' , 'recur_interval' , 'recur_enddate' ) as $key )
{
if ( isset ( $event [ $key ])
&& $event [ $key ] != $egwEvent [ $key ])
{
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () events[ $key ] differ: " . $event [ $key ] .
' <> ' . $egwEvent [ $key ] . " \n " , 3 , $this -> logfile );
}
continue 2 ;
}
}
}
$matchingEvents [] = $egwEvent [ 'id' ]; // exact match
}
if ( ! empty ( $event [ 'uid' ]) &&
count ( $matchingEvents ) > 1 || $filter != 'master' &&
$egwEvent [ 'recur_type' ] != MCAL_RECUR_NONE &&
empty ( $event [ 'recur_type' ]))
{
// Unknown exception for existing series
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" () new exception for series found. \n " , 3 , $this -> logfile );
}
$matchingEvents = array ();
}
2010-11-11 09:55:35 +01:00
2010-09-18 14:34:58 +02:00
// append pseudos as last entries
$matchingEvents = array_merge ( $matchingEvents , $pseudos );
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'[MATCHES]:' . array2string ( $matchingEvents ) . " \n " , 3 , $this -> logfile );
}
return $matchingEvents ;
}
/**
* classifies an incoming event from the eGW point - of - view
*
* exceptions : unlike other calendar apps eGW does not create an event exception
* if just the participant state changes - therefore we have to distinguish between
* real exceptions and status only exceptions
*
* @ param array $event the event to check
*
* @ return array
* type =>
* SINGLE a single event
* SERIES - MASTER the series master
* SERIES - EXCEPTION event is a real exception
* SERIES - PSEUDO - EXCEPTION event is a status only exception
* SERIES - EXCEPTION - PROPAGATE event was a status only exception in the past and is now a real exception
* stored_event => if event already exists in the database array with event data or false
* master_event => for event type SERIES - EXCEPTION , SERIES - PSEUDO - EXCEPTION or SERIES - EXCEPTION - PROPAGATE
* the corresponding series master event array
* NOTE : this param is false if event is of type SERIES - MASTER
*/
function get_event_info ( $event )
{
$type = 'SINGLE' ; // default
$master_event = false ; //default
$stored_event = false ;
$recurrence_event = false ;
$wasPseudo = false ;
if (( $foundEvents = $this -> find_event ( $event , 'exact' )))
{
// We found the exact match
$eventID = array_shift ( $foundEvents );
if ( strstr ( $eventID , ':' ))
{
$type = 'SERIES-PSEUDO-EXCEPTION' ;
$wasPseudo = true ;
list ( $eventID , $recur_date ) = explode ( ':' , $eventID );
$recur_date = $this -> date2usertime ( $recur_date );
$stored_event = $this -> read ( $eventID , $recur_date , false , 'server' );
$master_event = $this -> read ( $eventID , 0 , false , 'server' );
$recurrence_event = $stored_event ;
}
else
{
$stored_event = $this -> read ( $eventID , 0 , false , 'server' );
}
if ( ! empty ( $stored_event [ 'uid' ]) && empty ( $event [ 'uid' ]))
{
$event [ 'uid' ] = $stored_event [ 'uid' ]; // restore the UID if it was not delivered
}
}
if ( $event [ 'recur_type' ] != MCAL_RECUR_NONE )
{
$type = 'SERIES-MASTER' ;
}
if ( $type == 'SINGLE' &&
( $foundEvents = $this -> find_event ( $event , 'master' )))
{
// SINGLE, SERIES-EXCEPTION OR SERIES-EXCEPTON-STATUS
foreach ( $foundEvents as $eventID )
{
// Let's try to find a related series
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
" ()[MASTER]: $eventID\n " , 3 , $this -> logfile );
}
$type = 'SERIES-EXCEPTION' ;
if (( $master_event = $this -> read ( $eventID , 0 , false , 'server' )))
{
if ( isset ( $stored_event [ 'id' ]) &&
$master_event [ 'id' ] != $stored_event [ 'id' ])
{
break ; // this is an existing exception
}
elseif ( isset ( $event [ 'recurrence' ]) &&
in_array ( $event [ 'recurrence' ], $master_event [ 'recur_exception' ]))
{
$type = 'SERIES-PSEUDO-EXCEPTION' ; // could also be a real one
$recurrence_event = $master_event ;
$recurrence_event [ 'start' ] = $event [ 'recurrence' ];
$recurrence_event [ 'end' ] -= $master_event [ 'start' ] - $event [ 'recurrence' ];
break ;
}
elseif ( in_array ( $event [ 'start' ], $master_event [ 'recur_exception' ]))
{
$type = 'SERIES-PSEUDO-EXCEPTION' ; // new pseudo exception?
$recurrence_event = $master_event ;
$recurrence_event [ 'start' ] = $event [ 'start' ];
$recurrence_event [ 'end' ] -= $master_event [ 'start' ] - $event [ 'start' ];
break ;
}
else
{
// try to find a suitable pseudo exception date
$egw_rrule = calendar_rrule :: event2rrule ( $master_event , false );
$egw_rrule -> current = clone $egw_rrule -> time ;
while ( $egw_rrule -> valid ())
{
$occurrence = egw_time :: to ( $egw_rrule -> current (), 'server' );
if ( $this -> log )
{
error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ .
'() try occurrence ' . $egw_rrule -> current ()
. " ( $occurrence ) \n " , 3 , $this -> logfile );
}
if ( $event [ 'start' ] == $occurrence )
{
$type = 'SERIES-PSEUDO-EXCEPTION' ; // let's try a pseudo exception
$recurrence_event = $master_event ;
$recurrence_event [ 'start' ] = $occurrence ;
$recurrence_event [ 'end' ] -= $master_event [ 'start' ] - $occurrence ;
break 2 ;
}
if ( isset ( $event [ 'recurrence' ]) && $event [ 'recurrence' ] == $occurrence )
{
$type = 'SERIES-EXCEPTION-PROPAGATE' ;
if ( $stored_event )
{
unset ( $stored_event [ 'id' ]); // signal the true exception
$stored_event [ 'recur_type' ] = MCAL_RECUR_NONE ;
}
break 2 ;
}
$egw_rrule -> next_no_exception ();
}
}
}
}
}
// check pseudo exception propagation
if ( $recurrence_event )
{
// default if we cannot find a proof for a fundamental change
// the recurrence_event is the master event with start and end adjusted to the recurrence
// check for changed data
foreach ( array ( 'start' , 'end' , 'uid' , 'title' , 'location' , 'description' ,
'priority' , 'public' , 'special' , 'non_blocking' ) as $key )
{
if ( ! empty ( $event [ $key ]) && $recurrence_event [ $key ] != $event [ $key ])
{
if ( $wasPseudo )
{
// We started with a pseudo exception
$type = 'SERIES-EXCEPTION-PROPAGATE' ;
}
else
{
$type = 'SERIES-EXCEPTION' ;
}
if ( $stored_event )
{
unset ( $stored_event [ 'id' ]); // signal the true exception
$stored_event [ 'recur_type' ] = MCAL_RECUR_NONE ;
}
break ;
}
}
// the event id here is always the id of the master event
// unset it to prevent confusion of stored event and master event
unset ( $event [ 'id' ]);
}
// check ACL
if ( is_array ( $master_event ))
{
$acl_edit = $this -> check_perms ( EGW_ACL_EDIT , $master_event [ 'id' ]);
}
else
{
if ( is_array ( $stored_event ))
{
$acl_edit = $this -> check_perms ( EGW_ACL_EDIT , $stored_event [ 'id' ]);
}
else
{
$acl_edit = true ; // new event
}
}
return array (
'type' => $type ,
'acl_edit' => $acl_edit ,
'stored_event' => $stored_event ,
'master_event' => $master_event ,
);
}
/**
* Translates all timestamps for a given event from server - time to user - time .
* The update () and save () methods expect timestamps in user - time .
* @ param & $event the event we are working on
*
*/
function server2usertime ( & $event )
{
// we run all dates through date2usertime, to adjust to user-time
foreach ( array ( 'start' , 'end' , 'recur_enddate' , 'recurrence' ) as $ts )
{
// we convert here from server-time to timestamps in user-time!
if ( isset ( $event [ $ts ])) $event [ $ts ] = $event [ $ts ] ? $this -> date2usertime ( $event [ $ts ]) : 0 ;
}
// same with the recur exceptions
if ( isset ( $event [ 'recur_exception' ]) && is_array ( $event [ 'recur_exception' ]))
{
foreach ( $event [ 'recur_exception' ] as $n => $date )
{
$event [ 'recur_exception' ][ $n ] = $this -> date2usertime ( $date );
}
}
// same with the alarms
if ( isset ( $event [ 'alarm' ]) && is_array ( $event [ 'alarm' ]))
{
foreach ( $event [ 'alarm' ] as $id => $alarm )
{
$event [ 'alarm' ][ $id ][ 'time' ] = $this -> date2usertime ( $alarm [ 'time' ]);
}
}
}
}