2008-05-08 22:31:32 +02:00
< ? php
/**
2011-04-06 21:26:10 +02:00
* EGroupware : CalDAV / GroupDAV access : calendar handler
2008-05-08 22:31:32 +02:00
*
* @ link http :// www . egroupware . org
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package calendar
* @ subpackage groupdav
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2012-01-30 01:40:55 +01:00
* @ copyright ( c ) 2007 - 12 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-05-08 22:31:32 +02:00
* @ version $Id $
*/
2010-10-23 13:43:52 +02:00
require_once EGW_SERVER_ROOT . '/phpgwapi/inc/horde/lib/core.php' ;
2008-05-08 22:31:32 +02:00
/**
* eGroupWare : GroupDAV access : calendar handler
*/
class calendar_groupdav extends groupdav_handler
{
/**
* bo class of the application
*
2008-06-07 19:45:33 +02:00
* @ var calendar_boupdate
2008-05-08 22:31:32 +02:00
*/
var $bo ;
2010-11-08 10:25:58 +01:00
2010-10-23 13:43:52 +02:00
/**
* vCalendar Instance for parsing
*
2012-01-24 06:04:35 +01:00
* @ var Horde_iCalendar
2010-10-23 13:43:52 +02:00
*/
var $vCalendar ;
2010-11-08 10:25:58 +01:00
2008-05-08 22:31:32 +02:00
var $filter_prop2cal = array (
'SUMMARY' => 'cal_title' ,
'UID' => 'cal_uid' ,
'DTSTART' => 'cal_start' ,
'DTEND' => 'cal_end' ,
// 'DURATION'
//'RRULE' => 'recur_type',
//'RDATE' => 'cal_start',
//'EXRULE'
//'EXDATE'
//'RECURRENCE-ID'
);
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
/**
* Does client understand exceptions to be included in VCALENDAR component of series master sharing its UID
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* That also means no EXDATE for these exceptions !
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* Setting it to false , should give the old behavior used in 1.6 ( hopefully ) no client needs that .
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* @ var boolean
*/
var $client_shared_uid_exceptions = true ;
2011-11-08 22:09:06 +01:00
/**
* Enable or disable Schedule - Tag handling :
* - return Schedule - Tag header in PUT response
* - update only status and alarms of calendar owner , if If - Schedule - Tag - Match header in PUT
*
* Disabling Schedule - Tag for iCal , as current implementation seems to create too much trouble :- (
* - iCal on OS X always uses If - Schedule - Tag - Match , even if other stuff in event is changed ( eg . title )
* - iCal on iOS allways uses both If - Schedule - Tag - Match and If - Match ( ETag )
* - Lighting 1.0 is NOT using it
*
* @ var boolean
*/
2012-01-31 00:55:12 +01:00
var $use_schedule_tag = true ;
2011-11-08 22:09:06 +01:00
2009-12-27 05:21:33 +01:00
/**
2011-04-06 21:26:10 +02:00
* Are we using id , uid or caldav_name for the path / url
*
* Get 's set in constructor to ' caldav_name ' and groupdav_handler::$path_extension = ' ' !
2009-12-27 05:21:33 +01:00
*/
2011-04-06 21:26:10 +02:00
static $path_attr = 'id' ;
2008-05-08 22:31:32 +02:00
2008-05-17 15:00:34 +02:00
/**
* Constructor
*
* @ param string $app 'calendar' , 'addressbook' or 'infolog'
2011-09-18 12:56:56 +02:00
* @ param groupdav $groupdav calling class
2008-05-17 15:00:34 +02:00
*/
2011-09-18 12:56:56 +02:00
function __construct ( $app , groupdav $groupdav )
2008-05-08 22:31:32 +02:00
{
2011-09-18 12:56:56 +02:00
parent :: __construct ( $app , $groupdav );
2008-05-08 22:31:32 +02:00
2009-06-08 18:21:14 +02:00
$this -> bo = new calendar_boupdate ();
2010-10-23 13:43:52 +02:00
$this -> vCalendar = new Horde_iCalendar ;
2011-04-06 21:26:10 +02:00
// since 1.9.003 we allow clients to specify the URL when creating a new event, as specified by CalDAV
if ( version_compare ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'calendar' ][ 'version' ], '1.9.003' , '>=' ))
{
self :: $path_attr = 'caldav_name' ;
groupdav_handler :: $path_extension = '' ;
}
2008-05-08 22:31:32 +02:00
}
2008-11-18 07:11:12 +01:00
/**
* Create the path for an event
*
* @ param array | int $event
* @ return string
*/
2010-05-09 22:23:53 +02:00
function get_path ( $event )
2008-11-18 07:11:12 +01:00
{
2011-04-06 21:26:10 +02:00
if ( is_numeric ( $event ) && self :: $path_attr == 'id' )
2008-11-18 07:11:12 +01:00
{
$name = $event ;
}
else
{
if ( ! is_array ( $event )) $event = $this -> bo -> read ( $event );
2011-04-06 21:26:10 +02:00
$name = $event [ self :: $path_attr ];
2008-11-18 07:11:12 +01:00
}
2011-04-06 21:26:10 +02:00
$name .= groupdav_handler :: $path_extension ;
//error_log(__METHOD__.'('.array2string($event).") path_attr='".self::$path_attr."', path_extension='".groupdav_handler::$path_extension."' returning ".array2string($name));
return $name ;
2008-11-18 07:11:12 +01:00
}
2008-05-08 22:31:32 +02:00
/**
* Handle propfind in the calendar folder
*
* @ param string $path
* @ param array $options
* @ param array & $files
* @ param int $user account_id
* @ param string $id = ''
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
function propfind ( $path , $options , & $files , $user , $id = '' )
{
2010-02-23 19:19:12 +01:00
if ( $this -> debug )
{
error_log ( __METHOD__ . " ( $path , " . array2string ( $options ) . " ,, $user , $id ) " );
}
2009-10-16 10:36:28 +02:00
2011-09-22 21:49:01 +02:00
if ( $options [ 'root' ][ 'name' ] == 'free-busy-query' )
{
return $this -> free_busy_report ( $path , $options , $user );
}
2008-05-08 22:31:32 +02:00
// ToDo: add parameter to only return id & etag
2010-03-15 10:55:16 +01:00
$filter = array (
2008-05-08 22:31:32 +02:00
'users' => $user ,
2010-09-11 20:08:48 +02:00
'start' => $this -> bo -> now - 100 * 24 * 3600 , // default one month back -30 breaks all sync recurrences
'end' => $this -> bo -> now + 365 * 24 * 3600 , // default one year into the future +365
2008-05-08 22:31:32 +02:00
'enum_recuring' => false ,
'daywise' => false ,
'date_format' => 'server' ,
2011-04-05 17:32:20 +02:00
'no_total' => true , // we need no total number of rows (saves extra query)
2008-05-08 22:31:32 +02:00
);
2011-04-05 17:32:20 +02:00
if ( $this -> client_shared_uid_exceptions ) // do NOT return (non-virtual) exceptions
{
$filter [ 'query' ] = array ( 'cal_reference' => 0 );
}
2010-03-11 08:30:46 +01:00
2010-05-01 18:24:05 +02:00
if ( $path == '/calendar/' )
{
$filter [ 'filter' ] = 'owner' ;
}
2011-09-22 17:22:52 +02:00
// scheduling inbox, shows only not yet accepted or rejected events
elseif ( substr ( $path , - 7 ) == '/inbox/' )
{
$filter [ 'filter' ] = 'unknown' ;
2011-10-17 17:36:28 +02:00
$filter [ 'start' ] = $this -> bo -> now ; // only return future invitations
2011-09-22 17:22:52 +02:00
}
// ToDo: not sure what scheduling outbox is supposed to show, leave it empty for now
elseif ( substr ( $path , - 8 ) == '/outbox/' )
{
return true ;
}
2010-05-01 18:24:05 +02:00
else
{
$filter [ 'filter' ] = 'default' ; // not rejected
}
2010-03-11 08:30:46 +01:00
2008-05-08 22:31:32 +02:00
// process REPORT filters or multiget href's
2010-03-15 10:55:16 +01:00
if (( $id || $options [ 'root' ][ 'name' ] != 'propfind' ) && ! $this -> _report_filters ( $options , $filter , $id ))
2008-05-08 22:31:32 +02:00
{
2011-09-30 14:52:40 +02:00
// return empty collection, as iCal under iOS 5 had problems with returning "404 Not found" status
// when trying to request not supported components, eg. VTODO on a calendar collection
return true ;
2008-05-08 22:31:32 +02:00
}
2011-10-04 14:16:03 +02:00
if ( $id ) $path = dirname ( $path ) . '/' ; // caldav_name get's added anyway in the callback
2010-02-23 19:19:12 +01:00
if ( $this -> debug > 1 )
{
2011-04-05 17:32:20 +02:00
error_log ( __METHOD__ . " ( $path ,,, $user , $id ) filter= " . array2string ( $filter ));
2010-02-23 19:19:12 +01:00
}
2009-12-27 05:21:33 +01:00
2010-03-15 10:55:16 +01:00
// return iterator, calling ourself to return result in chunks
$files [ 'files' ] = new groupdav_propfind_iterator ( $this , $path , $filter , $files [ 'files' ]);
2011-04-05 17:32:20 +02:00
2010-03-15 10:55:16 +01:00
return true ;
}
2011-08-03 18:13:56 +02:00
2010-03-15 10:55:16 +01:00
/**
* Callback for profind interator
*
* @ param string $path
* @ param array $filter
* @ param array | boolean $start = false false = return all or array ( start , num )
* @ return array with " files " array with values for keys path and props
*/
2011-08-03 18:13:56 +02:00
function propfind_callback ( $path , array $filter , $start = false )
2010-03-15 10:55:16 +01:00
{
2010-08-05 19:11:13 +02:00
if ( $this -> debug ) $starttime = microtime ( true );
2012-01-24 06:04:35 +01:00
$calendar_data = $this -> groupdav -> prop_requested ( 'calendar-data' , groupdav :: CALDAV , true );
if ( ! is_array ( $calendar_data )) $calendar_data = false ; // not in allprop or autoindex
2011-04-05 17:32:20 +02:00
2010-03-15 10:55:16 +01:00
$files = array ();
if ( is_array ( $start ))
{
$filter [ 'offset' ] = $start [ 0 ];
$filter [ 'num_rows' ] = $start [ 1 ];
}
$events =& $this -> bo -> search ( $filter );
2009-12-27 05:21:33 +01:00
if ( $events )
2008-05-08 22:31:32 +02:00
{
2009-12-27 05:21:33 +01:00
// get all max user modified times at once
2010-03-16 21:44:55 +01:00
foreach ( $events as $k => $event )
2009-12-27 05:21:33 +01:00
{
2010-08-05 19:11:13 +02:00
if ( $this -> client_shared_uid_exceptions && $event [ 'reference' ])
2010-02-17 14:29:28 +01:00
{
2011-04-05 17:32:20 +02:00
throw new egw_exception_assertion_failed ( __METHOD__ . " () event= " . array2string ( $event ));
2010-02-17 14:29:28 +01:00
// this exception will be handled with the series master
unset ( $events [ $k ]);
continue ;
}
2009-12-27 05:21:33 +01:00
$ids [] = $event [ 'id' ];
}
$max_user_modified = $this -> bo -> so -> max_user_modified ( $ids );
2010-03-16 21:44:55 +01:00
foreach ( $events as $event )
2008-05-08 22:31:32 +02:00
{
2009-12-27 05:21:33 +01:00
$event [ 'max_user_modified' ] = $max_user_modified [ $event [ 'id' ]];
2011-10-20 22:10:04 +02:00
$etag = $this -> get_etag ( $event , $schedule_tag );
2008-06-04 13:07:45 +02:00
//header('X-EGROUPWARE-EVENT-'.$event['id'].': '.$event['title'].': '.date('Y-m-d H:i:s',$event['start']).' - '.date('Y-m-d H:i:s',$event['end']));
2008-05-08 22:31:32 +02:00
$props = array (
2011-10-20 22:10:04 +02:00
'getcontenttype' => $this -> agent != 'kde' ? 'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar' ,
'getetag' => '"' . $etag . '"' ,
2011-10-23 10:13:35 +02:00
'getlastmodified' => max ( $event [ 'modified' ], $event [ 'max_user_modified' ]),
2008-05-08 22:31:32 +02:00
);
2011-11-08 22:09:06 +01:00
if ( $this -> use_schedule_tag )
{
$props [ 'schedule-tag' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'schedule-tag' , '"' . $schedule_tag . '"' );
}
2009-04-02 14:35:26 +02:00
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
2008-05-08 22:31:32 +02:00
if ( $calendar_data )
{
2012-01-24 06:04:35 +01:00
$content = $this -> iCal ( $event , $filter [ 'users' ],
strpos ( $path , '/inbox/' ) !== false ? 'REQUEST' : null ,
! isset ( $calendar_data [ 'children' ][ 'expand' ]) ? false :
( $calendar_data [ 'children' ][ 'expand' ][ 'attrs' ] ? $calendar_data [ 'children' ][ 'expand' ][ 'attrs' ] : true ));
2011-09-21 22:08:21 +02:00
$props [ 'getcontentlength' ] = bytes ( $content );
2011-10-20 22:10:04 +02:00
$props [ 'calendar-data' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-data' , $content );
2008-05-20 11:07:03 +02:00
}
2011-10-20 22:10:04 +02:00
/* Calendarserver reports new events with schedule - changes : action : create , which iCal request
* adding it , unfortunately does not lead to showing the new event in the users inbox
if ( strpos ( $path , '/inbox/' ) !== false && $this -> groupdav -> prop_requested ( 'schedule-changes' ))
{
$props [ 'schedule-changes' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALENDARSERVER , 'schedule-changes' , array (
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALENDARSERVER , 'dtstamp' , gmdate ( 'Ymd\THis' , $event [ 'created' ]) . 'Z' ),
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALENDARSERVER , 'action' , array (
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALENDARSERVER , 'create' , '' ),
)),
));
} */
2011-09-21 22:08:21 +02:00
$files [] = $this -> add_resource ( $path , $event , $props );
2008-05-08 22:31:32 +02:00
}
}
2010-02-23 19:19:12 +01:00
if ( $this -> debug )
{
error_log ( __METHOD__ . " ( $path ) took " . ( microtime ( true ) - $starttime ) .
2010-08-05 19:11:13 +02:00
' to return ' . count ( $files [ 'files' ]) . ' items' );
2010-02-23 19:19:12 +01:00
}
2010-03-15 10:55:16 +01:00
return $files ;
2008-05-08 22:31:32 +02:00
}
/**
* Process the filters from the CalDAV REPORT request
*
* @ param array $options
* @ param array & $cal_filters
* @ param string $id
* @ return boolean true if filter could be processed , false for requesting not here supported VTODO items
*/
function _report_filters ( $options , & $cal_filters , $id )
{
if ( $options [ 'filters' ])
{
// unset default start & end
$cal_start = $cal_filters [ 'start' ]; unset ( $cal_filters [ 'start' ]);
$cal_end = $cal_filters [ 'end' ]; unset ( $cal_filters [ 'end' ]);
$num_filters = count ( $cal_filters );
foreach ( $options [ 'filters' ] as $filter )
{
switch ( $filter [ 'name' ])
{
case 'comp-filter' :
2009-12-27 05:21:33 +01:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) comp-filter=' { $filter [ 'attrs' ][ 'name' ] } ' " );
2009-07-14 21:51:03 +02:00
2008-05-08 22:31:32 +02:00
switch ( $filter [ 'attrs' ][ 'name' ])
{
case 'VTODO' :
return false ; // return nothing for now, todo: check if we can pass it on to the infolog handler
// todos are handled by the infolog handler
2009-12-27 05:21:33 +01:00
//$infolog_handler = new groupdav_infolog();
2010-03-07 00:06:43 +01:00
//return $infolog_handler->propfind($options['path'],$options,$options['files'],$user,$method);
2008-05-08 22:31:32 +02:00
case 'VCALENDAR' :
2009-04-02 14:35:26 +02:00
case 'VEVENT' :
2008-05-08 22:31:32 +02:00
break ; // that's our default anyway
}
break ;
case 'prop-filter' :
2009-12-27 05:21:33 +01:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) prop-filter=' { $filter [ 'attrs' ][ 'name' ] } ' " );
2008-05-08 22:31:32 +02:00
$prop_filter = $filter [ 'attrs' ][ 'name' ];
break ;
case 'text-match' :
2009-12-27 05:21:33 +01:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) text-match: $prop_filter =' { $filter [ 'data' ] } ' " );
2008-05-08 22:31:32 +02:00
if ( ! isset ( $this -> filter_prop2cal [ strtoupper ( $prop_filter )]))
{
2009-12-27 05:21:33 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] , " . array2string ( $options ) . " ,...) unknown property ' $prop_filter ' --> ignored " );
2008-05-08 22:31:32 +02:00
}
else
{
$cal_filters [ 'query' ][ $this -> filter_prop2cal [ strtoupper ( $prop_filter )]] = $filter [ 'data' ];
}
unset ( $prop_filter );
break ;
case 'param-filter' :
2009-12-27 05:21:33 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] ,...) param-filter=' { $filter [ 'attrs' ][ 'name' ] } ' not (yet) implemented! " );
2008-05-08 22:31:32 +02:00
break ;
case 'time-range' :
2009-12-27 05:21:33 +01:00
if ( $this -> debug > 1 ) error_log ( __FILE__ . __METHOD__ . " ( $options[path] ,...) time-range= { $filter [ 'attrs' ][ 'start' ] } - { $filter [ 'attrs' ][ 'end' ] } " );
2010-10-23 13:43:52 +02:00
if ( ! empty ( $filter [ 'attrs' ][ 'start' ]))
{
$cal_filters [ 'start' ] = $this -> vCalendar -> _parseDateTime ( $filter [ 'attrs' ][ 'start' ]);
}
if ( ! empty ( $filter [ 'attrs' ][ 'end' ]))
{
$cal_filters [ 'end' ] = $this -> vCalendar -> _parseDateTime ( $filter [ 'attrs' ][ 'end' ]);
}
2008-05-08 22:31:32 +02:00
break ;
default :
2009-12-27 05:21:33 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] , " . array2string ( $options ) . " ,...) unknown filter --> ignored " );
2008-05-08 22:31:32 +02:00
break ;
}
}
2008-06-04 13:07:45 +02:00
if ( count ( $cal_filters ) == $num_filters ) // no filters set --> restore default start and end time
2008-05-08 22:31:32 +02:00
{
$cal_filters [ 'start' ] = $cal_start ;
$cal_filters [ 'end' ] = $cal_end ;
}
}
2011-09-22 17:22:52 +02:00
2008-05-08 22:31:32 +02:00
// multiget or propfind on a given id
2009-04-02 14:35:26 +02:00
//error_log(__FILE__ . __METHOD__ . "multiget of propfind:");
2008-05-08 22:31:32 +02:00
if ( $options [ 'root' ][ 'name' ] == 'calendar-multiget' || $id )
{
// no standard time-range!
unset ( $cal_filters [ 'start' ]);
unset ( $cal_filters [ 'end' ]);
$ids = array ();
if ( $id )
{
2011-04-06 21:26:10 +02:00
$cal_filters [ 'query' ][ self :: $path_attr ] = groupdav_handler :: $path_extension ?
basename ( $id , groupdav_handler :: $path_extension ) : $id ;
2008-05-08 22:31:32 +02:00
}
else // fetch all given url's
{
foreach ( $options [ 'other' ] as $option )
{
if ( $option [ 'name' ] == 'href' )
{
$parts = explode ( '/' , $option [ 'data' ]);
2011-04-06 21:26:10 +02:00
if (( $id = array_pop ( $parts )))
2010-11-08 10:25:58 +01:00
{
2011-04-06 21:26:10 +02:00
$cal_filters [ 'query' ][ self :: $path_attr ][] = groupdav_handler :: $path_extension ?
basename ( $id , groupdav_handler :: $path_extension ) : $id ;
2010-11-08 10:25:58 +01:00
}
2008-05-08 22:31:32 +02:00
}
}
}
2009-07-14 21:51:03 +02:00
2010-11-08 10:25:58 +01:00
if ( $this -> debug > 1 ) error_log ( __FILE__ . __METHOD__ . " ( $options[path] ,..., $id ) calendar-multiget: ids= " . implode ( ',' , $ids ) . ', cal_filters=' . array2string ( $cal_filters ));
2008-05-08 22:31:32 +02:00
}
return true ;
}
/**
* Handle get request for an event
*
* @ param array & $options
* @ param int $id
2011-03-05 11:21:32 +01:00
* @ param int $user = null account_id
2008-05-08 22:31:32 +02:00
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
2011-03-05 11:21:32 +01:00
function get ( & $options , $id , $user = null )
2008-05-08 22:31:32 +02:00
{
if ( ! is_array ( $event = $this -> _common_get_put_delete ( 'GET' , $options , $id )))
{
return $event ;
}
2011-10-17 17:36:28 +02:00
2011-10-23 10:13:35 +02:00
$options [ 'data' ] = $this -> iCal ( $event , $user , strpos ( $options [ 'path' ], '/inbox/' ) !== false ? 'REQUEST' : null );
2008-05-08 22:31:32 +02:00
$options [ 'mimetype' ] = 'text/calendar; charset=utf-8' ;
header ( 'Content-Encoding: identity' );
2011-10-20 22:10:04 +02:00
header ( 'ETag: "' . $this -> get_etag ( $event , $schedule_tag ) . '"' );
2011-11-08 22:09:06 +01:00
if ( $this -> use_schedule_tag )
{
header ( 'Schedule-Tag: "' . $schedule_tag . '"' );
}
2008-05-08 22:31:32 +02:00
return true ;
}
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
/**
* Generate an iCal for the given event
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* Taking into account virtual an real exceptions for recuring events
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* @ param array $event
2011-03-05 11:21:32 +01:00
* @ param int $user = null account_id of calendar to display
2011-10-03 14:53:28 +02:00
* @ param string $method = null eg . 'PUBLISH' for inbox , nothing anywhere else
2012-01-24 06:04:35 +01:00
* @ param boolean | array $expand = false true or array with values for 'start' , 'end' to expand recurrences
2009-12-27 05:21:33 +01:00
* @ return string
*/
2012-01-24 06:04:35 +01:00
private function iCal ( array $event , $user = null , $method = null , $expand = false )
2009-12-27 05:21:33 +01:00
{
static $handler = null ;
if ( is_null ( $handler )) $handler = $this -> _get_handler ();
2010-02-11 21:50:35 +01:00
2011-03-05 11:21:32 +01:00
if ( ! $user ) $user = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
// only return alarms in own calendar, not other users calendars
if ( $user != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
{
//error_log(__METHOD__.'('.array2string($event).", $user) clearing alarms");
$event [ 'alarm' ] = array ();
}
2009-12-27 05:21:33 +01:00
$events = array ( $event );
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
// for recuring events we have to add the exceptions
if ( $this -> client_shared_uid_exceptions && $event [ 'recur_type' ] && ! empty ( $event [ 'uid' ]))
{
2012-01-24 06:04:35 +01:00
if ( is_array ( $expand ))
{
if ( isset ( $expand [ 'start' ])) $expand [ 'start' ] = $this -> vCalendar -> _parseDateTime ( $expand [ 'start' ]);
if ( isset ( $expand [ 'end' ])) $expand [ 'end' ] = $this -> vCalendar -> _parseDateTime ( $expand [ 'end' ]);
}
$events =& self :: get_series ( $event [ 'uid' ], $this -> bo , $expand );
2009-12-27 05:21:33 +01:00
}
elseif ( ! $this -> client_shared_uid_exceptions && $event [ 'reference' ])
{
$events [ 0 ][ 'uid' ] .= '-' . $event [ 'id' ]; // force a different uid
}
2011-10-03 14:53:28 +02:00
return $handler -> exportVCal ( $events , '2.0' , $method );
2009-12-27 05:21:33 +01:00
}
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
/**
* Get array with events of a series identified by its UID ( master and all exceptions )
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* Maybe that should be part of calendar_bo
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* @ param string $uid UID
* @ param calendar_bo $bo = null calendar_bo object to reuse for search call
2012-01-24 06:04:35 +01:00
* @ param boolean | array $expand = false true or array with values for 'start' , 'end' to expand recurrences
2009-12-27 05:21:33 +01:00
* @ return array
*/
2012-01-24 06:04:35 +01:00
private static function & get_series ( $uid , calendar_bo $bo = null , $expand = false )
2009-12-27 05:21:33 +01:00
{
2010-02-17 14:29:28 +01:00
if ( is_null ( $bo )) $bo = new calendar_bopdate ();
if ( ! ( $masterId = array_shift ( $bo -> find_event ( array ( 'uid' => $uid ), 'master' )))
|| ! ( $master = $bo -> read ( $masterId , 0 , false , 'server' )))
{
return array (); // should never happen
}
2012-01-24 06:04:35 +01:00
$exceptions =& $master [ 'recur_exception' ];
2010-02-17 14:29:28 +01:00
2012-01-24 06:04:35 +01:00
$params = array (
2009-12-27 05:21:33 +01:00
'query' => array ( 'cal_uid' => $uid ),
2010-05-19 17:25:07 +02:00
'filter' => 'owner' , // return all possible entries
2009-12-27 05:21:33 +01:00
'daywise' => false ,
'date_format' => 'server' ,
2012-01-24 06:04:35 +01:00
);
if ( is_array ( $expand )) $params += $expand ;
$events =& $bo -> search ( $params );
2009-12-27 05:21:33 +01:00
foreach ( $events as $k => & $recurrence )
{
2012-01-24 06:04:35 +01:00
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($uid)[$k]:" . array2string($recurrence));
2009-12-27 05:21:33 +01:00
if ( $recurrence [ 'id' ] != $master [ 'id' ]) // real exception
{
//error_log('real exception: '.array2string($recurrence));
// remove from masters recur_exception, as exception is include
2010-02-11 21:50:35 +01:00
// at least Lightning "understands" EXDATE as exception from what's included
2009-12-27 05:21:33 +01:00
// in the whole resource / VCALENDAR component
// not removing it causes Lightning to remove the exception itself
2010-02-17 14:29:28 +01:00
if (( $e = array_search ( $recurrence [ 'recurrence' ], $exceptions )) !== false )
2009-12-27 05:21:33 +01:00
{
2010-02-17 14:29:28 +01:00
unset ( $exceptions [ $e ]);
2009-12-27 05:21:33 +01:00
}
continue ; // nothing to change
}
// now we need to check if this recurrence is an exception
2012-01-24 06:04:35 +01:00
if ( ! $expand && $master [ 'participants' ] == $recurrence [ 'participants' ])
2009-12-27 05:21:33 +01:00
{
//error_log('NO exception: '.array2string($recurrence));
unset ( $events [ $k ]); // no exception --> remove it
continue ;
}
2010-02-17 14:29:28 +01:00
// this is a virtual exception now (no extra event/cal_id in DB)
2009-12-27 05:21:33 +01:00
//error_log('virtual exception: '.array2string($recurrence));
$recurrence [ 'recurrence' ] = $recurrence [ 'start' ];
$recurrence [ 'reference' ] = $master [ 'id' ];
$recurrence [ 'recur_type' ] = MCAL_RECUR_NONE ; // is set, as this is a copy of the master
// not for included exceptions (Lightning): $master['recur_exception'][] = $recurrence['start'];
}
2012-01-24 06:04:35 +01:00
if ( ! $expand )
{
$events = array_merge ( array ( $master ), $events );
}
2009-12-27 05:21:33 +01:00
return $events ;
}
2008-05-08 22:31:32 +02:00
/**
* Handle put request for an event
*
* @ param array & $options
* @ param int $id
* @ param int $user = null account_id of owner , default null
2010-10-26 11:35:44 +02:00
* @ param string $prefix = null user prefix from path ( eg . / ralf from / ralf / addressbook )
2008-05-08 22:31:32 +02:00
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
2010-10-26 11:35:44 +02:00
function put ( & $options , $id , $user = null , $prefix = null )
2008-05-08 22:31:32 +02:00
{
2010-02-23 19:19:12 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $id , $user ) " . print_r ( $options , true ));
2010-10-26 11:35:44 +02:00
if ( ! $prefix ) $user = null ; // /infolog/ does not imply setting the current user (for new entries it's done anyway)
2011-10-23 09:59:05 +02:00
// fix for iCal4OL using WinHTTP only supporting a certain header length
if ( isset ( $_SERVER [ 'HTTP_IF_SCHEDULE' ]) && ! isset ( $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ]))
{
$_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ] = $_SERVER [ 'HTTP_IF_SCHEDULE' ];
}
2010-05-18 12:03:21 +02:00
$return_no_access = true ; // as handled by importVCal anyway and allows it to set the status for participants
2011-10-20 22:10:04 +02:00
$oldEvent = $this -> _common_get_put_delete ( 'PUT' , $options , $id , $return_no_access ,
isset ( $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ])); // dont fail with 412 Precondition Failed in that case
2010-03-07 00:06:43 +01:00
if ( ! is_null ( $oldEvent ) && ! is_array ( $oldEvent ))
{
2010-06-29 11:19:25 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . ': ' . print_r ( $oldEvent , true ) . function_backtrace ());
2010-03-07 00:06:43 +01:00
return $oldEvent ;
}
2009-07-14 21:51:03 +02:00
2010-09-29 18:54:16 +02:00
if ( is_null ( $oldEvent ) && ( $user >= 0 ) && ! $this -> bo -> check_perms ( EGW_ACL_ADD , 0 , $user ))
2008-05-08 22:31:32 +02:00
{
2010-03-07 00:06:43 +01:00
// we have no add permission on this user's calendar
2010-10-26 11:35:44 +02:00
// ToDo: create event in current users calendar and invite only $user
if ( $this -> debug ) error_log ( __METHOD__ . " (,, $user ) we have not enough rights on this calendar " );
2010-03-07 00:06:43 +01:00
return '403 Forbidden' ;
2008-05-08 22:31:32 +02:00
}
2010-03-07 00:06:43 +01:00
2008-11-03 10:36:20 +01:00
$handler = $this -> _get_handler ();
2010-03-07 00:06:43 +01:00
$vCalendar = htmlspecialchars_decode ( $options [ 'content' ]);
2010-06-14 09:51:28 +02:00
$charset = null ;
if ( ! empty ( $options [ 'content_type' ]))
{
$content_type = explode ( ';' , $options [ 'content_type' ]);
if ( count ( $content_type ) > 1 )
{
array_shift ( $content_type );
foreach ( $content_type as $attribute )
{
trim ( $attribute );
list ( $key , $value ) = explode ( '=' , $attribute );
switch ( strtolower ( $key ))
{
case 'charset' :
$charset = strtoupper ( substr ( $value , 1 , - 1 ));
}
}
2010-10-26 11:35:44 +02:00
}
2010-06-14 09:51:28 +02:00
}
2010-02-17 14:29:28 +01:00
2010-03-07 00:06:43 +01:00
if ( is_array ( $oldEvent ))
2010-02-11 21:50:35 +01:00
{
2010-03-07 00:06:43 +01:00
$eventId = $oldEvent [ 'id' ];
2011-10-20 22:10:04 +02:00
2011-11-08 22:09:06 +01:00
//client specified a CalDAV Scheduling schedule-tag precondition
if ( $this -> use_schedule_tag && isset ( $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ]))
2011-10-20 22:10:04 +02:00
{
$schedule_tag_match = $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ];
if ( $schedule_tag_match [ 0 ] == '"' ) $schedule_tag_match = substr ( $schedule_tag_match , 1 , - 1 );
$this -> get_etag ( $oldEvent , $schedule_tag );
if ( $schedule_tag_match !== $schedule_tag )
{
2011-11-08 22:09:06 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " (,, $user ) schedule_tag missmatch: given ' $schedule_tag_match ' != ' $schedule_tag ' " );
2011-10-20 22:10:04 +02:00
return '412 Precondition Failed' ;
}
// update only participant status and alarms of current user
2011-11-08 22:09:06 +01:00
// fix for iCal on OS X, which uses only a schedule-tag (no etag), if event has no participants (only calendar owner)
// --> do regular calendar update as with matching etag (otherwise no updates possible)
if ( ! ( count ( $oldEvent [ 'participants' ]) == 1 && isset ( $oldEvent [ 'participants' ][ $user ])) &&
( $events = $handler -> icaltoegw ( $vCalendar )))
2011-10-20 22:10:04 +02:00
{
// todo check behavior for recuring events
foreach ( $events as $event )
{
2011-11-08 22:09:06 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id , $user , ' $prefix ') eventId= $eventId , user= $user , old-status=' { $oldEvent [ 'participants' ][ $user ] } ', new-status=' { $event [ 'participants' ][ $user ] } ', event= " . array2string ( $event ));
if ( isset ( $event [ 'participants' ]) && $event [ 'participants' ][ $user ] !== $oldEvent [ 'participants' ][ $user ])
{
if ( ! $this -> bo -> set_status ( $eventId , $user , $event [ 'participants' ][ $user ], $event [ 'recurrence' ]))
{
if ( $this -> debug ) error_log ( __METHOD__ . " (,, $user ) failed to set_status( $eventId , $user , ' { $event [ 'participants' ][ $user ] } ') " );
return '403 Forbidden' ;
}
else
{
if ( $this -> debug ) error_log ( __METHOD__ . " () set_status( $eventId , $user , " . array2string ( $event [ 'participants' ][ $user ]) . " , $event[recurrence] ) " );
}
}
// import alarms, if given and changed
if (( array ) $event [ 'alarm' ] !== ( array ) $oldEvent [ 'alarm' ])
{
$this -> sync_alarms ( $eventId , ( array ) $event [ 'alarm' ], ( array ) $oldEvent [ 'alarm' ], $user , $event [ 'start' ]);
}
elseif ( ! isset ( $event [ 'participants' ]) || $event [ 'participants' ][ $user ] === $oldEvent [ 'participants' ][ $user ])
2011-10-20 22:10:04 +02:00
{
2011-11-08 22:09:06 +01:00
error_log ( __METHOD__ . " (,, $user ) schedule-tag given, but NO change for current user event= " . array2string ( $event ) . ', old-event=' . array2string ( $oldEvent ));
return '412 Precondition Failed' ;
2011-10-20 22:10:04 +02:00
}
}
2011-11-08 22:09:06 +01:00
// we should not return an etag here, as we never store the PUT ical byte-by-byte
//header('ETag: "'.$etag.'"');
2011-10-20 22:10:04 +02:00
header ( 'Schedule-Tag: "' . $schedule_tag . '"' );
return '204 No Content' ;
}
2011-11-08 22:09:06 +01:00
if ( $this -> debug && ! isset ( $events )) error_log ( __METHOD__ . " (,, $user ) only schedule-tag given for event without participants (only calendar owner) --> handle as regular PUT " );
2011-10-20 22:10:04 +02:00
}
2010-03-07 00:06:43 +01:00
if ( $return_no_access )
{
$retval = true ;
}
else
{
2011-03-05 11:21:32 +01:00
$retval = '204 No Content' ;
// lightning will pop up the alarm, as long as the Sequence (etag) does NOT change
// --> update the etag alone, if user has no edit rights
if ( $this -> agent == 'lightning' && ! $this -> check_access ( EGW_ACL_EDIT , $oldEvent ) &&
isset ( $oldEvent [ 'participants' ][ $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ]]))
{
// just update etag in database
$GLOBALS [ 'egw' ] -> db -> update ( $this -> bo -> so -> cal_table , 'cal_etag=cal_etag+1' , array (
'cal_id' => $eventId ,
), __LINE__ , __FILE__ , 'calendar' );
}
2010-03-07 00:06:43 +01:00
}
}
else
{
2011-04-06 21:26:10 +02:00
// new entry
$eventId = - 1 ;
$retval = '201 Created' ;
2010-02-11 21:50:35 +01:00
}
2010-02-17 14:29:28 +01:00
2010-03-07 00:06:43 +01:00
if ( ! ( $cal_id = $handler -> importVCal ( $vCalendar , $eventId ,
2011-09-18 12:56:56 +02:00
self :: etag2value ( $this -> http_if_match ), false , 0 , $this -> groupdav -> current_user_principal , $user , $charset , $id )))
2008-05-08 22:31:32 +02:00
{
2011-11-08 22:09:06 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id ) eventId= $eventId : importVCal(' $options[content] ') returned " . array2string ( $cal_id ));
2010-05-18 16:41:22 +02:00
if ( $eventId && $cal_id === false )
{
// ignore import failures
$cal_id = $eventId ;
$retval = true ;
}
2011-11-08 22:09:06 +01:00
elseif ( $cal_id === 0 ) // etag failure
{
return '412 Precondition Failed' ;
}
2010-05-18 16:41:22 +02:00
else
{
return '403 Forbidden' ;
}
2008-05-08 22:31:32 +02:00
}
2011-11-08 22:09:06 +01:00
if ( $this -> use_schedule_tag )
{
$etag = $this -> get_etag ( $cal_id , $schedule_tag );
// we should not return an etag here, as we never store the PUT ical byte-by-byte
//header('ETag: "'.$etag.'"');
header ( 'Schedule-Tag: "' . $schedule_tag . '"' );
}
2011-04-06 21:26:10 +02:00
// send GroupDAV Location header only if we dont use caldav_name as path-attribute
if ( $retval !== true && self :: $path_attr != 'caldav_name' )
2008-05-08 22:31:32 +02:00
{
2010-03-07 00:06:43 +01:00
$path = preg_replace ( '|(.*)/[^/]*|' , '\1/' , $options [ 'path' ]);
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id , $user ) cal_id= $cal_id : $retval " );
2010-05-09 22:23:53 +02:00
header ( 'Location: ' . $this -> base_uri . $path . $this -> get_path ( $cal_id ));
2008-05-08 22:31:32 +02:00
}
2011-04-06 21:26:10 +02:00
return $retval ;
2008-05-08 22:31:32 +02:00
}
2010-02-11 21:50:35 +01:00
2011-10-20 22:10:04 +02:00
/**
* Sync alarms of current user : add alarms added on client and remove the ones removed
*
* @ param int $cal_id of event to set alarms
* @ param array $alarms
* @ param array $old_alarms
* @ param int $user account_id of user to create alarm for
* @ param int $start start - time of event
* @ ToDo store other alarm properties like : ACTION , DESCRIPTION , X - WR - ALARMUID
*/
private function sync_alarms ( $cal_id , array $alarms , array $old_alarms , $user , $start )
{
if ( $this -> debug ) error_log ( __METHOD__ . " ( $cal_id , " . array2string ( $alarms ) . ', ' . array2string ( $old_alarms ) . " , $user , $start ) " );
// todo import alarms
foreach ( $alarms as $alarm )
{
if ( $alarm [ 'owner' ] != $this -> user ) continue ; // only import alarms of current user
// check if alarm is already stored or from other users
foreach ( $old_alarms as $id => $old_alarm )
{
if ( $old_alarm [ 'owner' ] != $user || $alarm [ 'offset' ] == $old_alarm [ 'offset' ])
{
unset ( $old_alarms [ $id ]); // remove alarms of other user, or already existing alarms
}
}
// alarm not found --> add it
if ( $alarm [ 'offset' ] != $old_alarm [ 'offset' ] || $old_alarm [ 'owner' ] != $user )
{
$alarm [ 'owner' ] = $user ;
$alarm [ 'time' ] = $start - $alarm [ 'offset' ];
if ( $this -> debug ) error_log ( __METHOD__ . " () adding new alarm from client " . array2string ( $alarm ));
$this -> bo -> save_alarm ( $cal_id , $alarm );
}
}
// remove all old alarms left from current user
foreach ( $old_alarms as $id => $old_alarm )
{
if ( $this -> debug ) error_log ( __METHOD__ . " () deleting alarm ' $id ' deleted on client " . array2string ( $old_alarm ));
$this -> bo -> delete_alarm ( $id );
}
}
2010-05-17 16:20:34 +02:00
/**
* Handle post request for a schedule entry
*
* @ param array & $options
* @ param int $id
* @ param int $user = null account_id of owner , default null
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
function post ( & $options , $id , $user = null )
{
2010-06-29 11:19:25 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $id , $user ) " . print_r ( $options , true ));
2010-10-26 11:35:44 +02:00
2011-09-22 17:22:52 +02:00
$vCalendar = htmlspecialchars_decode ( $options [ 'content' ]);
$charset = null ;
if ( ! empty ( $options [ 'content_type' ]))
2010-06-28 19:34:57 +02:00
{
2011-09-22 17:22:52 +02:00
$content_type = explode ( ';' , $options [ 'content_type' ]);
if ( count ( $content_type ) > 1 )
2010-06-29 11:19:25 +02:00
{
2011-09-22 17:22:52 +02:00
array_shift ( $content_type );
foreach ( $content_type as $attribute )
2010-06-29 11:19:25 +02:00
{
2011-09-22 17:22:52 +02:00
trim ( $attribute );
list ( $key , $value ) = explode ( '=' , $attribute );
switch ( strtolower ( $key ))
2010-06-29 11:19:25 +02:00
{
2011-09-22 17:22:52 +02:00
case 'charset' :
$charset = strtoupper ( substr ( $value , 1 , - 1 ));
2010-06-29 11:19:25 +02:00
}
2010-10-26 11:35:44 +02:00
}
2010-06-29 11:19:25 +02:00
}
2011-09-22 17:22:52 +02:00
}
2010-10-26 11:35:44 +02:00
2011-09-22 17:22:52 +02:00
if ( substr ( $options [ 'path' ], - 8 ) == '/outbox/' )
{
if ( preg_match ( '/^METHOD:REQUEST(\r\n|\r|\n)(.*)^BEGIN:VFREEBUSY/ism' , $vCalendar ))
{
if ( $user != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
{
error_log ( __METHOD__ . " () freebusy request only allowed to own outbox! " );
return '403 Forbidden' ;
}
// do freebusy request
return $this -> outbox_freebusy_request ( $vCalendar , $charset , $user , $options );
}
else
{
// POST to deliver an invitation, containing http headers:
// Originator: mailto:<organizer-email>
// Recipient: mailto:<attendee-email>
// --> currently we simply ignore these posts, as EGroupware does it's own notifications based on user preferences
return '204 No Content' ;
}
}
if ( preg_match ( '/^METHOD:(PUBLISH|REQUEST)(\r\n|\r|\n)(.*)^BEGIN:VEVENT/ism' , $options [ 'content' ]))
{
$handler = $this -> _get_handler ();
2010-06-29 11:19:25 +02:00
if (( $foundEvents = $handler -> search ( $vCalendar , null , false , $charset )))
{
$eventId = array_shift ( $foundEvents );
list ( $eventId ) = explode ( ':' , $eventId );
2010-10-26 11:35:44 +02:00
2010-06-29 11:19:25 +02:00
if ( ! ( $cal_id = $handler -> importVCal ( $vCalendar , $eventId , null ,
2011-09-18 12:56:56 +02:00
false , 0 , $this -> groupdav -> current_user_principal , $user , $charset )))
2010-06-29 11:19:25 +02:00
{
if ( $this -> debug ) error_log ( __METHOD__ . " () importVCal( $eventId ) returned false " );
}
2011-10-05 10:15:24 +02:00
// we should not return an etag here, as we never store the ical byte-by-byte
//header('ETag: "'.$this->get_etag($eventId).'"');
2010-06-29 11:19:25 +02:00
}
2010-06-28 19:34:57 +02:00
}
2010-05-17 16:20:34 +02:00
return true ;
}
2011-09-22 17:22:52 +02:00
/**
* Handle outbox freebusy request
*
* @ param string $ical
* @ param string $charset of ical
* @ param int $user account_id of owner
* @ param array & $options
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
protected function outbox_freebusy_request ( $ical , $charset , $user , array & $options )
{
include_once EGW_SERVER_ROOT . '/phpgwapi/inc/horde/lib/core.php' ;
$vcal = new Horde_iCalendar ();
if ( ! $vcal -> parsevCalendar ( $ical , 'VCALENDAR' , $charset ))
{
return '400 Bad request' ;
}
$version = $vcal -> getAttribute ( 'VERSION' );
//echo $ical."\n";
$handler = $this -> _get_handler ();
$handler -> setSupportedFields ( 'groupdav' );
$handler -> calendarOwner = $handler -> user = 0 ; // to NOT default owner/organizer to something
if ( ! ( $component = $vcal -> getComponent ( 0 )) ||
! ( $event = $handler -> vevent2egw ( $component , $version , $handler -> supportedFields , $this -> groupdav -> current_user_principal , 'Horde_iCalendar_vfreebusy' )))
{
return '400 Bad request' ;
}
if ( $event [ 'owner' ] != $user )
{
error_log ( __METHOD__ . " (' $ical ',, $user ) ORGANIZER is NOT principal! " );
return '403 Forbidden' ;
}
//print_r($event);
$organizer = $component -> getAttribute ( 'ORGANIZER' );
2011-09-23 14:04:21 +02:00
$attendees = ( array ) $component -> getAttribute ( 'ATTENDEE' );
2011-09-22 17:22:52 +02:00
// X-CALENDARSERVER-MASK-UID specifies to exclude given event from busy-time
2011-09-22 21:49:01 +02:00
$mask_uid = $component -> getAttribute ( 'X-CALENDARSERVER-MASK-UID' );
2011-09-22 17:22:52 +02:00
header ( 'Content-type: text/xml; charset=UTF-8' );
$xml = new XMLWriter ;
$xml -> openMemory ();
$xml -> startDocument ( '1.0' , 'UTF-8' );
$xml -> startElementNs ( 'C' , 'schedule-response' , groupdav :: CALDAV );
foreach ( $event [ 'participants' ] as $uid => $status )
{
$xml -> startElementNs ( 'C' , 'response' , null );
$xml -> startElementNs ( 'C' , 'recipient' , null );
$xml -> writeElementNs ( 'D' , 'href' , 'DAV:' , $attendee = array_shift ( $attendees ));
$xml -> endElement (); // recipient
2011-10-17 17:36:28 +02:00
$xml -> writeElementNs ( 'C' , 'request-status' , null , '2.0;Success' );
$xml -> writeElementNs ( 'C' , 'calendar-data' , null ,
$handler -> freebusy ( $uid , $event [ 'end' ], true , 'utf-8' , $event [ 'start' ], 'REPLY' , array (
'UID' => $event [ 'uid' ],
'ORGANIZER' => $organizer ,
'ATTENDEE' => $attendee ,
) + ( empty ( $mask_uid ) || ! is_string ( $mask_uid ) ? array () : array (
'X-CALENDARSERVER-MASK-UID' => $mask_uid ,
))));
2011-09-22 17:22:52 +02:00
$xml -> endElement (); // response
}
$xml -> endElement (); // schedule-response
$xml -> endDocument ();
echo $xml -> outputMemory ();
return true ;
}
2011-09-22 21:49:01 +02:00
/**
* Handle free - busy - query report
*
* @ param string $path
* @ param array $options
* @ param int $user account_id
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
function free_busy_report ( $path , $options , $user )
{
if ( ! $this -> bo -> check_perms ( EGW_ACL_FREEBUSY , 0 , $user ))
{
return '403 Forbidden' ;
}
foreach ( $options [ 'other' ] as $filter )
{
if ( $filter [ 'name' ] == 'time-range' )
{
$start = $this -> vCalendar -> _parseDateTime ( $filter [ 'attrs' ][ 'start' ]);
$end = $this -> vCalendar -> _parseDateTime ( $filter [ 'attrs' ][ 'end' ]);
}
}
$handler = $this -> _get_handler ();
header ( 'Content-Type: text/calendar' );
echo $handler -> freebusy ( $user , $end , true , 'utf-8' , $start , 'REPLY' , array ());
common :: egw_exit (); // otherwise we get a 207 multistatus, not 200 Ok
}
2011-09-22 20:46:16 +02:00
/**
* Return priviledges for current user , default is read and read - current - user - privilege - set
*
* Reimplemented to add read - free - busy and schedule - deliver privilege
*
* @ param string $path path of collection
* @ param int $user = null owner of the collection , default current user
* @ return array with privileges
*/
public function current_user_privileges ( $path , $user = null )
{
$priviledes = parent :: current_user_privileges ( $user );
if ( $this -> bo -> check_perms ( EGW_ACL_FREEBUSY , 0 , $user ))
{
$priviledes [ 'read-free-busy' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'read-free-busy' , '' );
if ( substr ( $path , - 8 ) == '/outbox/' && $this -> bo -> check_acl_invite ( $user ))
{
$priviledes [ 'schedule-send' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'schedule-send' , '' );
}
}
if ( substr ( $path , - 7 ) == '/inbox/' && $this -> bo -> check_acl_invite ( $user ))
{
$priviledes [ 'schedule-deliver' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'schedule-deliver' , '' );
}
return $priviledes ;
}
2009-12-27 05:21:33 +01:00
/**
* Fix event series with exceptions , called by calendar_ical :: importVCal () :
* a ) only series master = first event got cal_id from URL
* b ) exceptions need to be checked if they are already in DB or new
* c ) recurrence - id of ( real not virtual ) exceptions need to be re - added to master
2010-02-11 21:50:35 +01:00
*
2009-12-27 05:21:33 +01:00
* @ param array & $events
*/
static function fix_series ( array & $events )
{
$bo = new calendar_boupdate ();
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
// get array with orginal recurrences indexed by recurrence-id
2010-02-17 14:29:28 +01:00
$org_recurrences = $exceptions = array ();
foreach ( self :: get_series ( $events [ 0 ][ 'uid' ], $bo ) as $k => $event )
2009-12-27 05:21:33 +01:00
{
2010-02-17 14:29:28 +01:00
if ( ! $k ) $master = $event ;
2009-12-27 05:21:33 +01:00
if ( $event [ 'recurrence' ])
{
$org_recurrences [ $event [ 'recurrence' ]] = $event ;
}
}
// assign cal_id's to already existing recurrences and evtl. re-add recur_exception to master
2010-02-17 14:29:28 +01:00
foreach ( $events as $k => & $recurrence )
2009-12-27 05:21:33 +01:00
{
2010-02-17 14:29:28 +01:00
if ( ! $recurrence [ 'recurrence' ])
{
// master
$recurrence [ 'id' ] = $master [ 'id' ];
$master =& $events [ $k ];
continue ;
}
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
// from now on we deal with exceptions
$org_recurrence = $org_recurrences [ $recurrence [ 'recurrence' ]];
if ( isset ( $org_recurrence )) // already existing recurrence
{
2010-05-19 18:13:37 +02:00
//error_log(__METHOD__.'() setting id #'.$org_recurrence['id']).' for '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']);
2009-12-27 05:21:33 +01:00
$recurrence [ 'id' ] = $org_recurrence [ 'id' ];
2010-02-11 21:50:35 +01:00
2009-12-27 05:21:33 +01:00
// re-add (non-virtual) exceptions to master's recur_exception
if ( $recurrence [ 'id' ] != $master [ 'id' ])
{
2010-05-19 18:13:37 +02:00
//error_log(__METHOD__.'() re-adding recur_exception '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']));
2010-02-17 14:29:28 +01:00
$exceptions [] = $recurrence [ 'recurrence' ];
2009-12-27 05:21:33 +01:00
}
// remove recurrence to be able to detect deleted exceptions
unset ( $org_recurrences [ $recurrence [ 'recurrence' ]]);
}
}
2010-02-17 14:29:28 +01:00
$master [ 'recur_exception' ] = array_merge ( $exceptions , $master [ 'recur_exception' ]);
2009-12-27 05:21:33 +01:00
// delete not longer existing recurrences
foreach ( $org_recurrences as $org_recurrence )
{
if ( $org_recurrence [ 'id' ] != $master [ 'id' ]) // non-virtual recurrence
{
2010-02-26 13:37:07 +01:00
//error_log(__METHOD__.'() deleting #'.$org_recurrence['id']);
2009-12-27 05:21:33 +01:00
$bo -> delete ( $org_recurrence [ 'id' ]); // might fail because of permissions
}
else // virtual recurrence
{
2010-05-19 18:13:37 +02:00
//error_log(__METHOD__.'() delete virtual exception '.$org_recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$org_recurrence['recurrence']));
2010-02-26 13:37:07 +01:00
$bo -> update_status ( $master , $org_recurrence , $org_recurrence [ 'recurrence' ]);
2009-12-27 05:21:33 +01:00
}
}
2010-02-26 13:37:07 +01:00
//foreach($events as $n => $event) error_log(__METHOD__." $n after: ".array2string($event));
2009-12-27 05:21:33 +01:00
}
2008-05-08 22:31:32 +02:00
/**
* Handle delete request for an event
*
2008-05-17 15:00:34 +02:00
* If current user has no right to delete the event , but is an attendee , we reject the event for him .
*
2009-12-27 05:21:33 +01:00
* @ todo remove ( non - virtual ) exceptions , if series master gets deleted
2008-05-08 22:31:32 +02:00
* @ param array & $options
* @ param int $id
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
function delete ( & $options , $id )
{
2011-09-22 17:22:52 +02:00
if ( strpos ( $options [ 'path' ], '/inbox/' ) !== false )
{
return true ; // simply ignore DELETE in inbox for now
}
2010-05-17 16:20:34 +02:00
$return_no_access = true ; // to allow to check if current use is a participant and reject the event for him
2008-05-17 15:00:34 +02:00
if ( ! is_array ( $event = $this -> _common_get_put_delete ( 'DELETE' , $options , $id , $return_no_access )) || ! $return_no_access )
2008-05-08 22:31:32 +02:00
{
2011-10-05 09:39:11 +02:00
if ( ! $return_no_access )
2008-05-17 15:00:34 +02:00
{
2011-10-05 09:39:11 +02:00
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
$ret = '403 Forbidden' ;
$memberships = $GLOBALS [ 'egw' ] -> accounts -> memberships ( $this -> bo -> user , true );
foreach ( $event [ 'participants' ] as $uid => $status )
{
if ( $this -> bo -> user == $uid || in_array ( $uid , $memberships ))
{
if ( $this -> bo -> set_status ( $event , $this -> bo -> user , 'R' )) $ret = true ;
break ;
}
}
2008-05-17 15:00:34 +02:00
}
2011-10-05 09:39:11 +02:00
else
{
$ret = $event ;
}
}
else
{
$ret = $this -> bo -> delete ( $event [ 'id' ]);
2008-05-08 22:31:32 +02:00
}
2011-10-05 09:39:11 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id ) return_no_access= $return_no_access , event[participants]= " . array2string ( is_array ( $event ) ? $event [ 'participants' ] : null ) . " , user= { $this -> bo -> user } --> return " . array2string ( $ret ));
return $ret ;
2008-05-08 22:31:32 +02:00
}
/**
* Read an entry
*
2011-10-04 16:18:35 +02:00
* We have to make sure to not return or even consider in read deleted events , as the might have
* the same UID and / or caldav_name as not deleted events and would block access to valid entries
*
2011-04-06 21:26:10 +02:00
* @ param string | id $id
* @ return array | boolean array with entry , false if no read rights , null if $id does not exist
2008-05-08 22:31:32 +02:00
*/
function read ( $id )
{
2011-04-06 21:26:10 +02:00
if ( strpos ( $column = self :: $path_attr , '_' ) === false ) $column = 'cal_' . $column ;
2011-10-04 16:18:35 +02:00
$event = $this -> bo -> read ( array ( $column => $id , 'cal_deleted IS NULL' ), null , true , 'server' );
2011-04-06 21:26:10 +02:00
if ( $event ) $event = array_shift ( $event ); // read with array as 1. param, returns an array of events!
2010-10-26 11:35:44 +02:00
if ( ! ( $retval = $this -> bo -> check_perms ( EGW_ACL_FREEBUSY , $event , 0 , 'server' )))
{
if ( $this -> debug > 0 ) error_log ( __METHOD__ . " ( $id ) no READ or FREEBUSY rights returning " . array2string ( $retval ));
return $retval ;
}
2010-06-01 11:22:06 +02:00
if ( ! $this -> bo -> check_perms ( EGW_ACL_READ , $event , 0 , 'server' ))
2010-05-14 10:35:16 +02:00
{
$this -> bo -> clear_private_infos ( $event , array ( $this -> bo -> user , $event [ 'owner' ]));
}
2011-06-19 10:54:06 +02:00
// handle deleted events, as not existing
if ( $event [ 'deleted' ]) $event = null ;
2010-10-26 11:35:44 +02:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $id ) returning " . array2string ( $event ));
2010-05-14 10:35:16 +02:00
return $event ;
2008-05-08 22:31:32 +02:00
}
2010-04-13 17:31:59 +02:00
/**
* Query ctag for calendar
*
* @ return string
*/
public function getctag ( $path , $user )
{
2010-12-02 23:27:32 +01:00
$ctag = $this -> bo -> get_ctag ( $user , $path == '/calendar/' ? 'owner' : 'default' ); // default = not rejected
2010-04-13 17:31:59 +02:00
2010-06-29 15:52:56 +02:00
if ( $this -> debug > 1 ) error_log ( __FILE__ . '[' . __LINE__ . '] ' . __METHOD__ . " ( $path )[ $user ] = $ctag " );
2010-10-26 11:35:44 +02:00
2011-10-08 13:34:55 +02:00
return $ctag ;
2010-04-13 17:31:59 +02:00
}
2008-05-08 22:31:32 +02:00
/**
2011-10-20 22:10:04 +02:00
* Get the etag for an entry
2008-05-08 22:31:32 +02:00
*
2011-10-20 22:10:04 +02:00
* @ param array | int $event array with event or cal_id
* @ return string | boolean string with etag or false
2008-05-08 22:31:32 +02:00
*/
2011-10-20 22:10:04 +02:00
function get_etag ( $entry , & $schedule_tag = null )
2008-05-08 22:31:32 +02:00
{
2011-10-20 22:10:04 +02:00
$etag = $this -> bo -> get_etag ( $entry , $schedule_tag , $this -> client_shared_uid_exceptions );
2009-12-27 05:21:33 +01:00
//error_log(__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
2011-10-08 13:34:55 +02:00
return $etag ;
2008-05-08 22:31:32 +02:00
}
/**
* Check if user has the neccessary rights on an event
*
* @ param int $acl EGW_ACL_READ , EGW_ACL_EDIT or EGW_ACL_DELETE
2011-10-20 22:10:04 +02:00
* @ param array | int $event event - array or id
2008-05-08 22:31:32 +02:00
* @ return boolean null if entry does not exist , false if no access , true if access permitted
*/
function check_access ( $acl , $event )
{
2010-05-14 10:35:16 +02:00
if ( $acl == EGW_ACL_READ )
{
// we need at least EGW_ACL_FREEBUSY to get some information
$acl = EGW_ACL_FREEBUSY ;
}
2008-05-08 22:31:32 +02:00
return $this -> bo -> check_perms ( $acl , $event , 0 , 'server' );
}
/**
* Add extra properties for calendar collections
*
* @ param array $props = array () regular props by the groupdav handler
2010-03-07 00:06:43 +01:00
* @ param string $displayname
* @ param string $base_uri = null base url of handler
2011-10-20 15:35:01 +02:00
* @ param int $user = null account_id of owner of current collection
2012-01-31 00:53:06 +01:00
* @ param string $path = null path of the collection
2008-05-08 22:31:32 +02:00
* @ return array
*/
2012-01-31 00:53:06 +01:00
public function extra_properties ( array $props = array (), $displayname , $base_uri = null , $user = null , $path = null )
2008-05-08 22:31:32 +02:00
{
2012-01-30 01:40:55 +01:00
if ( ! isset ( $props [ 'calendar-description' ]))
{
// default calendar description: can be overwritten via PROPPATCH, in which case it's already set
$props [ 'calendar-description' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-description' , $displayname );
}
2012-01-31 00:53:06 +01:00
$supported_components = array (
2010-04-13 17:31:59 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'comp' , array ( 'name' => 'VCALENDAR' )),
2009-07-14 21:51:03 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'comp' , array ( 'name' => 'VEVENT' )),
2012-01-31 00:53:06 +01:00
);
// outbox supports VFREEBUSY too, it is required from OS X iCal to autocomplete locations
if ( substr ( $path , - 8 ) == '/outbox/' )
{
$supported_components [] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'comp' , array ( 'name' => 'VFREEBUSY' ));
}
$props [ 'supported-calendar-component-set' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV ,
'supported-calendar-component-set' , $supported_components );
2011-09-22 17:22:52 +02:00
$props [ 'supported-report-set' ] = HTTP_WebDAV_Server :: mkprop ( 'supported-report-set' , array (
2010-03-07 00:06:43 +01:00
HTTP_WebDAV_Server :: mkprop ( 'supported-report' , array (
2011-09-24 23:10:53 +02:00
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-query' , '' ))),
2010-07-02 07:01:15 +02:00
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
2011-09-22 21:49:01 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-multiget' , '' ))),
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'free-busy-query' , '' ))),
))));
2011-09-22 17:22:52 +02:00
$props [ 'supported-calendar-data' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'supported-calendar-data' , array (
2010-04-13 17:31:59 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-data' , array ( 'content-type' => 'text/calendar' , 'version' => '2.0' )),
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-data' , array ( 'content-type' => 'text/x-calendar' , 'version' => '1.0' ))));
2010-03-07 00:06:43 +01:00
2011-10-20 15:35:01 +02:00
// get timezone of calendar
if ( $this -> groupdav -> prop_requested ( 'calendar-timezone' ))
{
$props [ 'calendar-timezone' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-timezone' ,
2011-10-20 16:01:16 +02:00
calendar_timezones :: user_timezone ( $user ));
2011-10-20 15:35:01 +02:00
}
2008-05-08 22:31:32 +02:00
return $props ;
}
2008-11-03 10:36:20 +01:00
/**
* Get the handler and set the supported fields
*
* @ return calendar_ical
*/
private function _get_handler ()
{
2009-06-08 18:21:14 +02:00
$handler = new calendar_ical ();
2008-11-03 10:36:20 +01:00
$handler -> setSupportedFields ( 'GroupDAV' , $this -> agent );
2010-08-15 08:42:05 +02:00
if ( $this -> debug > 1 ) error_log ( " ical Handler called: " . $this -> agent );
2008-11-03 10:36:20 +01:00
return $handler ;
}
2012-02-04 02:03:56 +01:00
/**
* Return calendars / addressbooks shared from other users with the current one
*
* return array account_id => account_lid pairs
*/
function get_shared ()
{
$shared = array ();
$calendar_home_set = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'calendar-home-set' ];
$calendar_home_set = $calendar_home_set ? explode ( ',' , $calendar_home_set ) : array ();
// replace symbolic id's with real nummeric id's
foreach ( array (
'G' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_primary_group' ],
) as $sym => $id )
{
if (( $key = array_search ( $sym , $calendar_home_set )) !== false )
{
$calendar_home_set [ $key ] = $id ;
}
}
foreach ( ExecMethod ( 'calendar.calendar_bo.list_cals' ) as $entry )
{
$id = $entry [ 'grantor' ];
if ( $id && $user != $id && // no current user and no accounts yet (todo)
( in_array ( 'A' , $calendar_home_set ) || in_array (( string ) $id , $calendar_home_set )) &&
is_numeric ( $id ) && ( $owner = $this -> accounts -> id2name ( $id )))
{
$shared [ $id ] = $owner ;
}
}
return $shared ;
}
2012-02-04 02:24:34 +01:00
/**
* Return appliction specific settings
*
* return array of array with settings
*/
static function get_settings ()
{
if ( $hook_data [ 'setup' ])
{
$calendars = array ();
}
else
{
$user = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
$cal_bo = new calendar_bo ();
foreach ( $cal_bo -> list_cals () as $entry )
{
$calendars [ $entry [ 'grantor' ]] = $entry [ 'name' ];
}
unset ( $calendars [ $user ]);
}
$calendars = array (
'A' => lang ( 'All' ),
'G' => lang ( 'Primary Group' ),
) + $calendars ;
$settings = array ();
$settings [ 'calendar-home-set' ] = array (
'type' => 'multiselect' ,
'label' => 'Calendars to sync in addition to personal calendar' ,
'name' => 'calendar-home-set' ,
'help' => lang ( 'Only supported by a few fully conformant clients (eg. from Apple). If you have to enter a URL, it will most likly not be suppored!' ) . '<br/>' . lang ( 'They will be sub-folders in users home (%1 attribute).' , 'CalDAV "calendar-home-set"' ),
'values' => $calendars ,
'xmlrpc' => True ,
'admin' => False ,
);
return $settings ;
}
2009-04-02 14:35:26 +02:00
}