2008-05-08 22:31:32 +02:00
< ? php
/**
2016-04-02 10:40:34 +02:00
* EGroupware : CalDAV / CardDAV / 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
2016-05-01 19:47:59 +02:00
* @ subpackage caldav
2008-05-08 22:31:32 +02:00
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2016-04-02 10:40:34 +02:00
* @ copyright ( c ) 2007 - 16 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-05-08 22:31:32 +02:00
* @ version $Id $
*/
2016-04-02 12:44:17 +02:00
use EGroupware\Api ;
2016-05-01 19:47:59 +02:00
use EGroupware\Api\Acl ;
2016-04-02 12:44:17 +02:00
2008-05-08 22:31:32 +02:00
/**
2016-04-02 12:44:17 +02:00
* CalDAV / CardDAV / GroupDAV access : Calendar handler
2012-02-21 21:04:45 +01:00
*
2016-04-02 12:44:17 +02:00
* Permanent error_log () calls should use $this -> caldav -> log ( $str ) instead , to be send to PHP error_log ()
2012-02-21 21:04:45 +01:00
* and our request - log ( prefixed with " ### " after request and response , like exceptions ) .
2012-10-08 13:14:07 +02:00
*
* @ ToDo : new properties on calendars and it ' s ressources specially from sharing :
* - for the invite property : 5.2 . 2 in https :// trac . calendarserver . org / browser / CalendarServer / trunk / doc / Extensions / caldav - sharing . txt
* - https :// trac . calendarserver . org / browser / CalendarServer / trunk / doc / Extensions / caldav - schedulingchanges . txt
2008-05-08 22:31:32 +02:00
*/
2016-04-02 12:44:17 +02:00
class calendar_groupdav extends Api\CalDAV\Handler
2008-05-08 22:31:32 +02:00
{
/**
* 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
*
2015-06-22 18:20:15 +02: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
*
2016-04-02 12:44:17 +02:00
* Get 's set in constructor to ' caldav_name ' and self::$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'
2016-04-02 12:44:17 +02:00
* @ param Api\CalDAV $caldav calling class
2008-05-17 15:00:34 +02:00
*/
2016-04-02 12:44:17 +02:00
function __construct ( $app , Api\CalDAV $caldav )
2008-05-08 22:31:32 +02:00
{
2016-04-02 12:44:17 +02:00
parent :: __construct ( $app , $caldav );
2008-05-08 22:31:32 +02:00
2009-06-08 18:21:14 +02:00
$this -> bo = new calendar_boupdate ();
2015-06-22 18:20:15 +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' ;
2016-04-02 12:44:17 +02:00
self :: $path_extension = '' ;
2011-04-06 21:26:10 +02:00
}
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
}
2016-04-02 12:44:17 +02:00
$name .= self :: $path_extension ;
//error_log(__METHOD__.'('.array2string($event).") path_attr='".self::$path_attr."', path_extension='".self::$path_extension."' returning ".array2string($name));
2011-04-06 21:26:10 +02:00
return $name ;
2008-11-18 07:11:12 +01:00
}
2013-03-14 18:13:59 +01:00
const PAST_LIMIT = 100 ;
const FUTURE_LIMIT = 365 ;
2008-05-08 22:31:32 +02:00
/**
* Handle propfind in the calendar folder
*
* @ param string $path
2012-06-27 22:08:56 +02:00
* @ param array & $options
2008-05-08 22:31:32 +02:00
* @ param array & $files
* @ param int $user account_id
2015-06-25 22:39:53 +02:00
* @ param string $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' )
*/
2012-06-27 22:08:56 +02:00
function propfind ( $path , & $options , & $files , $user , $id = '' )
2008-05-08 22:31:32 +02:00
{
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 );
}
2015-11-13 16:23:36 +01:00
if ( isset ( $_GET [ 'download' ]))
2015-11-08 13:33:35 +01:00
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> propfind_options [ 'props' ] = array ( array (
'xmlns' => Api\CalDAV :: CALDAV ,
2015-11-08 13:33:35 +01:00
'name' => 'calendar-data' ,
));
}
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 ,
'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)
2015-06-25 22:39:53 +02:00
'cfs' => array (), // return custom-fields, as we use them to store X- attributes
2008-05-08 22:31:32 +02:00
);
2013-03-14 18:13:59 +01:00
foreach ( array (
'start' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'calendar-past-limit' ],
'end' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'calendar-future-limit' ],
) as $name => $value )
{
if ( ! is_numeric ( $value ))
{
$value = $name == 'start' ? self :: PAST_LIMIT : self :: FUTURE_LIMIT ;
}
2013-05-16 14:26:59 +02:00
$filter [ $name ] = $this -> bo -> now + 24 * 3600 * ( $name == 'start' ? - 1 : 1 ) * abs ( $value );
2013-03-14 18:13:59 +01: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/' )
{
2015-10-09 19:47:10 +02:00
$filter [ 'filter' ] = 'owner' ;
2010-05-01 18:24:05 +02:00
}
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
2014-02-20 18:46:15 +01:00
$nresults = null ;
2012-09-26 16:30:47 +02:00
if (( $id || $options [ 'root' ][ 'name' ] != 'propfind' ) && ! $this -> _report_filters ( $options , $filter , $id , $nresults ))
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
2012-09-26 16:30:47 +02:00
// rfc 6578 sync-collection report: filter for sync-token is already set in _report_filters
if ( $options [ 'root' ][ 'name' ] == 'sync-collection' )
{
// callback to query sync-token, after propfind_callbacks / iterator is run and
// stored max. modification-time in $this->sync_collection_token
$files [ 'sync-token' ] = array ( $this , 'get_sync_collection_token' );
$files [ 'sync-token-params' ] = array ( $path , $user );
$this -> sync_collection_token = null ;
2014-02-20 20:26:02 +01:00
$filter [ 'order' ] = 'cal_modified ASC' ; // return oldest modifications first
$filter [ 'sync-collection' ] = true ;
// no end-date / limit into the future, as unchanged entries would never be transferted later on
unset ( $filter [ 'end' ]);
2012-09-26 16:30:47 +02:00
}
2011-04-05 17:32:20 +02:00
2012-09-26 16:30:47 +02:00
if ( isset ( $nresults ))
{
2014-02-20 18:46:15 +01:00
unset ( $filter [ 'no_total' ]); // we need the total!
2012-09-26 16:30:47 +02:00
$files [ 'files' ] = $this -> propfind_callback ( $path , $filter , array ( 0 , ( int ) $nresults ));
// hack to support limit with sync-collection report: events are returned in modified ASC order (oldest first)
// if limit is smaller then full result, return modified-1 as sync-token, so client requests next chunk incl. modified
// (which might contain further entries with identical modification time)
if ( $options [ 'root' ][ 'name' ] == 'sync-collection' && $this -> bo -> total > $nresults )
{
-- $this -> sync_collection_token ;
2014-02-20 18:46:15 +01:00
$files [ 'sync-token-params' ][] = true ; // tel get_sync_collection_token that we have more entries
2012-09-26 16:30:47 +02:00
}
}
else
{
// return iterator, calling ourself to return result in chunks
2016-04-02 12:44:17 +02:00
$files [ 'files' ] = new Api\CalDAV\PropfindIterator ( $this , $path , $filter , $files [ 'files' ]);
2012-09-26 16:30:47 +02:00
}
2015-11-13 16:23:36 +01:00
if ( isset ( $_GET [ 'download' ]))
2015-11-08 13:33:35 +01:00
{
$this -> output_vcalendar ( $files [ 'files' ]);
}
2010-03-15 10:55:16 +01:00
return true ;
}
2011-08-03 18:13:56 +02:00
2015-11-08 13:33:35 +01:00
/**
* Download whole calendar as big ics file
*
* @ param iterator | array $files
*/
function output_vcalendar ( $files )
{
// todo ETag logic with CTag to not download unchanged calendar again
2016-05-01 19:47:59 +02:00
Api\Header\Content :: type ( 'calendar.ics' , 'text/calendar' );
2015-11-08 13:33:35 +01:00
$n = 0 ;
foreach ( $files as $file )
{
if ( ! $n ++ ) continue ; // first entry is collection itself
$icalendar = $file [ 'props' ][ 'calendar-data' ][ 'val' ];
if (( $start = strpos ( $icalendar , 'BEGIN:VEVENT' )) !== false &&
( $end = strrpos ( $icalendar , 'END:VCALENDAR' )) !== false )
{
if ( $n === 2 )
{
2015-11-13 16:23:36 +01:00
// skip X-CALENDARSERVER-ACCESS:CONFIDENTIAL, as it is on VCALENDAR not VEVENT level
if (( $x_calendarserver_access = strpos ( $icalendar , 'X-CALENDARSERVER-ACCESS:' )) !== false )
{
echo substr ( $icalendar , 0 , $x_calendarserver_access );
}
2015-11-08 13:33:35 +01:00
// skip timezones, as we would need to collect them from all events
// ans most clients understand timezone by reference anyway
2015-11-13 16:23:36 +01:00
elseif (( $tz = strpos ( $icalendar , 'BEGIN:VTIMEZONE' )) !== false )
2015-11-08 13:33:35 +01:00
{
echo substr ( $icalendar , 0 , $tz );
}
else
{
echo substr ( $icalendar , 0 , $start );
}
}
echo substr ( $icalendar , $start , $end - $start );
}
}
if ( $icalendar && $end )
{
echo " END:VCALENDAR \n " ;
}
2016-05-01 19:47:59 +02:00
exit ();
2015-11-08 13:33:35 +01:00
}
2010-03-15 10:55:16 +01:00
/**
* Callback for profind interator
*
* @ param string $path
* @ param array $filter
2015-06-25 22:39:53 +02:00
* @ param array | boolean $start = false false = return all or array ( start , num )
2010-03-15 10:55:16 +01:00
* @ 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 );
2016-04-02 12:44:17 +02:00
$calendar_data = $this -> caldav -> prop_requested ( 'calendar-data' , Api\CalDAV :: CALDAV , true );
2012-01-24 06:04:35 +01:00
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 ];
}
2013-10-01 13:38:34 +02:00
$requested_multiget_ids = ( array ) $filter [ 'query' ][ self :: $path_attr ];
2014-02-20 20:26:02 +01:00
$sync_collection = $filter [ 'sync-collection' ];
2013-01-25 18:52:28 +01:00
2010-03-15 10:55:16 +01:00
$events =& $this -> bo -> search ( $filter );
2013-09-25 12:27:41 +02:00
2009-12-27 05:21:33 +01:00
if ( $events )
2008-05-08 22:31:32 +02:00
{
2010-03-16 21:44:55 +01:00
foreach ( $events as $event )
2008-05-08 22:31:32 +02:00
{
2013-01-25 18:52:28 +01:00
// remove event from requested multiget ids, to be able to report not found urls
if ( $requested_multiget_ids && ( $k = array_search ( $event [ self :: $path_attr ], $requested_multiget_ids )) !== false )
{
unset ( $requested_multiget_ids [ $k ]);
}
2012-09-26 16:30:47 +02:00
// sync-collection report: deleted entries need to be reported without properties, same for rejected or deleted invitations
if ( $sync_collection && ( $event [ 'deleted' ] || in_array ( $event [ 'participants' ][ $filter [ 'users' ]][ 0 ], array ( 'R' , 'X' ))))
{
$files [] = array ( 'path' => $path . urldecode ( $this -> get_path ( $event )));
continue ;
}
2015-06-25 22:39:53 +02:00
$schedule_tag = null ;
2011-10-20 22:10:04 +02:00
$etag = $this -> get_etag ( $event , $schedule_tag );
2012-10-08 13:14:07 +02:00
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 . '"' ,
2012-09-25 13:54:41 +02:00
'getlastmodified' => $event [ 'modified' ],
2012-10-08 13:14:07 +02:00
// user and timestamp of creation or last modification of event, used in calendarserver only for shared calendars
2016-04-02 12:44:17 +02:00
'created-by' => Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'created-by' ,
2012-10-08 13:14:07 +02:00
$this -> _created_updated_by_prop ( $event [ 'creator' ], $event [ 'created' ])),
2016-04-02 12:44:17 +02:00
'updated-by' => Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'updated-by' ,
2012-10-08 13:14:07 +02:00
$this -> _created_updated_by_prop ( $event [ 'modifier' ], $event [ 'modified' ])),
2008-05-08 22:31:32 +02:00
);
2011-11-08 22:09:06 +01:00
if ( $this -> use_schedule_tag )
{
2016-04-02 12:44:17 +02:00
$props [ 'schedule-tag' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'schedule-tag' , '"' . $schedule_tag . '"' );
2011-11-08 22:09:06 +01:00
}
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 );
2016-04-02 12:44:17 +02:00
$props [ 'calendar-data' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: 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
2016-04-02 12:44:17 +02:00
if ( strpos ( $path , '/inbox/' ) !== false && $this -> caldav -> prop_requested ( 'schedule-changes' ))
2011-10-20 22:10:04 +02:00
{
2016-04-02 12:44:17 +02:00
$props [ 'schedule-changes' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'schedule-changes' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'dtstamp' , gmdate ( 'Ymd\THis' , $event [ 'created' ]) . 'Z' ),
Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'action' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'create' , '' ),
2011-10-20 22:10:04 +02:00
)),
));
} */
2011-09-21 22:08:21 +02:00
$files [] = $this -> add_resource ( $path , $event , $props );
2008-05-08 22:31:32 +02:00
}
2013-09-25 12:27:41 +02:00
}
// report not found multiget urls
if ( $requested_multiget_ids )
{
foreach ( $requested_multiget_ids as $id )
2012-09-26 16:30:47 +02:00
{
2013-09-25 12:27:41 +02:00
$files [] = array ( 'path' => $path . $id . self :: $path_extension );
2012-09-26 16:30:47 +02:00
}
2008-05-08 22:31:32 +02:00
}
2013-09-25 12:27:41 +02:00
// sync-collection report --> return modified of last contact as sync-token
2014-02-20 18:46:15 +01:00
if ( $sync_collection )
2013-09-25 12:27:41 +02:00
{
$this -> sync_collection_token = $event [ 'modified' ];
}
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
}
2012-10-08 13:14:07 +02:00
/**
* Return Calendarserver : ( created | updated ) - by sub - properties for a given user and time
*
* < created - by xmlns = 'http://calendarserver.org/ns/' >
* < first - name > Ralf </ first - name >
* < last - name > Becker </ last - name >
* < dtstamp > 20121002 T092006Z </ dtstamp >
* < href xmlns = 'DAV:' > mailto : farktronix @ me . com </ href >
* </ created - by >
*
* @ param int $user
* @ param int $time
* @ return array with subprops
*/
private function _created_updated_by_prop ( $user , $time )
{
$props = array ();
foreach ( array (
'first-name' => 'account_firstname' ,
'last-name' => 'account_lastname' ,
'href' => 'account_email' ,
) as $prop => $name )
{
if ( $user && ( $val = $this -> accounts -> id2name ( $user , $name )))
{
2016-04-02 12:44:17 +02:00
$ns = Api\CalDAV :: CALENDARSERVER ;
2012-10-08 13:14:07 +02:00
if ( $prop == 'href' )
{
$ns = '' ;
$val = 'mailto:' . $val ;
}
2016-04-02 12:44:17 +02:00
$props [ $prop ] = $ns ? Api\CalDAV :: mkprop ( $ns , $prop , $val ) : Api\CalDAV :: mkprop ( $prop , $val );
2012-10-08 13:14:07 +02:00
}
}
if ( $time )
{
2016-04-02 12:44:17 +02:00
$props [ 'dtstamp' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALENDARSERVER , 'dtstamp' , gmdate ( 'Ymd\\This\\Z' , $time ));
2012-10-08 13:14:07 +02:00
}
//error_log(__METHOD__."($user, $time) returning ".array2string($props));
return $props ? $props : '' ;
}
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
2012-09-26 16:30:47 +02:00
* @ param int & $nresult on return limit for number or results or unchanged / null
* @ return boolean true if filter could be processed
2008-05-08 22:31:32 +02:00
*/
2012-09-26 16:30:47 +02:00
function _report_filters ( $options , & $cal_filters , $id , & $nresults )
2008-05-08 22:31:32 +02:00
{
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
2012-09-26 16:30:47 +02:00
// parse limit from $options['other']
/* Example limit
< B : limit >
< B : nresults > 10 </ B : nresults >
</ B : limit >
*/
foreach (( array ) $options [ 'other' ] as $option )
{
switch ( $option [ 'name' ])
{
case 'nresults' :
$nresults = ( int ) $option [ 'data' ];
//error_log(__METHOD__."(...) options[other]=".array2string($options['other'])." --> nresults=$nresults");
break ;
case 'limit' :
break ;
case 'href' :
break ; // from addressbook-multiget, handled below
// rfc 6578 sync-report
case 'sync-token' :
if ( ! empty ( $option [ 'data' ]))
{
$parts = explode ( '/' , $option [ 'data' ]);
$sync_token = array_pop ( $parts );
$cal_filters [ 'query' ][] = 'cal_modified>' . ( int ) $sync_token ;
$cal_filters [ 'filter' ] = 'everything' ; // to return deleted entries too
// no standard time-range!
unset ( $cal_filters [ 'start' ]);
}
break ;
case 'sync-level' :
if ( $option [ 'data' ] != '1' )
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( __METHOD__ . " (...) only sync-level { $option [ 'data' ] } requested, but only 1 supported! options[other]= " . array2string ( $options [ 'other' ]));
2012-09-26 16:30:47 +02:00
}
break ;
default :
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( __METHOD__ . " (...) unknown xml tag ' { $option [ 'name' ] } ': options[other]= " . array2string ( $options [ 'other' ]));
2012-09-26 16:30:47 +02:00
break ;
}
}
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 )
{
2016-04-02 12:44:17 +02:00
$cal_filters [ 'query' ][ self :: $path_attr ] = self :: $path_extension ?
basename ( $id , self :: $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' ]);
2014-02-20 16:11:27 +01:00
if (( $id = urldecode ( array_pop ( $parts ))))
2010-11-08 10:25:58 +01:00
{
2016-04-02 12:44:17 +02:00
$cal_filters [ 'query' ][ self :: $path_attr ][] = self :: $path_extension ?
basename ( $id , self :: $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
2015-06-25 22:39:53 +02: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' );
2015-06-25 22:39:53 +02:00
$schedule_tag = null ;
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
2015-06-25 22:39:53 +02:00
* @ param int $user = null account_id of calendar to display
* @ param string $method = null eg . 'PUBLISH' for inbox , nothing anywhere else
* @ 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' ]);
}
2014-11-10 21:04:37 +01:00
$events =& self :: get_series ( $event [ 'uid' ], $this -> bo , $expand , $user );
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
2015-06-25 22:39:53 +02:00
* @ param calendar_bo $bo = null calendar_bo object to reuse for search call
* @ param boolean | array $expand = false true or array with values for 'start' , 'end' to expand recurrences
* @ param int $user = null account_id of calendar to display , to remove master , if current user does not participate in
2009-12-27 05:21:33 +01:00
* @ return array
*/
2014-11-10 21:04:37 +01:00
private static function & get_series ( $uid , calendar_bo $bo = null , $expand = false , $user = null )
2009-12-27 05:21:33 +01:00
{
2010-02-17 14:29:28 +01:00
if ( is_null ( $bo )) $bo = new calendar_bopdate ();
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' ,
2015-06-25 22:39:53 +02:00
'cfs' => array (), // read cfs as we use them to store X- attributes
2012-01-24 06:04:35 +01:00
);
if ( is_array ( $expand )) $params += $expand ;
2015-10-09 19:37:01 +02:00
if ( ! ( $events =& $bo -> search ( $params )))
{
return array ();
}
2012-01-24 06:04:35 +01:00
2015-01-27 19:12:10 +01:00
// find master, which is not always first event, eg. when first event is an exception
2014-11-10 21:04:37 +01:00
$master = null ;
2015-10-09 19:37:01 +02:00
$exceptions = array ();
2009-12-27 05:21:33 +01:00
foreach ( $events as $k => & $recurrence )
{
2015-01-27 19:12:10 +01:00
if ( $recurrence [ 'recur_type' ])
2014-11-10 21:04:37 +01:00
{
$master = $recurrence ;
$exceptions =& $master [ 'recur_exception' ];
unset ( $events [ $k ]);
2015-01-27 19:12:10 +01:00
break ;
2014-11-10 21:04:37 +01:00
}
2015-01-27 19:12:10 +01:00
}
2015-02-04 11:37:52 +01:00
// if recurring event starts in future behind horizont, nothing will be returned by bo::search()
2015-10-09 19:37:01 +02:00
if ( ! isset ( $master )) $master = $bo -> read ( $uid );
2015-01-27 19:12:10 +01:00
foreach ( $events as $k => & $recurrence )
{
2012-01-24 06:04:35 +01:00
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($uid)[$k]:" . array2string($recurrence));
2015-10-09 19:37:01 +02:00
if ( ! $master || $recurrence [ 'id' ] != $master [ 'id' ]) // real exception
2009-12-27 05:21:33 +01:00
{
2014-11-10 21:04:37 +01:00
// user is NOT participating in this exception
2015-01-27 19:12:10 +01:00
if ( $user && ! self :: isParticipant ( $recurrence , $user ))
2014-11-10 21:04:37 +01:00
{
// if he is NOT in master, delete this exception
2015-10-09 19:37:01 +02:00
if ( ! $master || ! self :: isParticipant ( $master , $user ))
2014-11-10 21:04:37 +01:00
{
unset ( $events [ $k ]);
continue ;
}
// otherwise mark him in this exception as rejected
$recurrence [ 'participants' ][ $user ] = 'R' ;
}
2009-12-27 05:21:33 +01:00
//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
}
2015-06-25 22:39:53 +02:00
// alarms are reported on recurrences --> move them to master
2015-10-09 19:37:01 +02:00
if ( $master )
2015-06-25 22:39:53 +02:00
{
2015-10-09 19:37:01 +02:00
foreach ( $recurrence [ 'alarm' ] as $alarm )
{
$master [ 'alarm' ][] = $alarm ;
}
$recurrence [ 'alarm' ] = array ();
2015-06-25 22:39:53 +02:00
}
2009-12-27 05:21:33 +01:00
// now we need to check if this recurrence is an exception
2015-10-09 19:37:01 +02:00
if ( ! $expand && $master && $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' ];
2015-10-09 19:37:01 +02:00
if ( $master ) $recurrence [ 'reference' ] = $master [ 'id' ];
2009-12-27 05:21:33 +01:00
$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'];
}
2014-11-10 21:04:37 +01:00
// only add master if we are not expanding and current user participates in master (and not just some exceptions)
2015-10-09 19:37:01 +02:00
if ( ! $expand && $master && ( ! $user || self :: isParticipant ( $master , $user )))
2012-01-24 06:04:35 +01:00
{
$events = array_merge ( array ( $master ), $events );
}
2009-12-27 05:21:33 +01:00
return $events ;
}
2008-05-08 22:31:32 +02:00
2015-01-27 19:12:10 +01:00
/**
* Check if $user is a participant of given $event incl . group - invitations
*
* @ param array $event
* @ param int | string $user
* @ return boolean
*/
public static function isParticipant ( array $event , $user )
{
return isset ( $event [ 'participants' ][ $user ]) ||
// for group-invitations we need to check memberships of $user too
array_intersect ( array_keys ( $event [ 'participants' ]), $GLOBALS [ 'egw' ] -> accounts -> memberships ( $user , true ));
}
2008-05-08 22:31:32 +02:00
/**
* Handle put request for an event
*
* @ param array & $options
* @ param int $id
2015-06-25 22:39:53 +02:00
* @ param int $user = null account_id of owner , default null
* @ 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
2016-05-01 19:47:59 +02:00
if ( is_null ( $oldEvent ) && ( $user >= 0 && ! $this -> bo -> check_perms ( Acl :: ADD , 0 , $user ) ||
2013-01-24 16:32:56 +01:00
// if we require an extra invite grant, we fail if that does not exist (bind privilege is not given in that case)
$this -> bo -> require_acl_invite && $user && $user != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] &&
! $this -> bo -> check_acl_invite ( $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
2012-02-21 21:04:45 +01:00
//client specified a CalDAV Scheduling schedule-tag AND an etag If-Match precondition
if ( $this -> use_schedule_tag && isset ( $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ]) &&
isset ( $_SERVER [ 'HTTP_IF_MATCH' ]))
{
if ( $oldEvent [ 'owner' ] == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( " Both If-Match and If-Schedule-Tag-Match header given: If-Schedule-Tag-Match ignored for event owner! " );
2012-02-21 21:04:45 +01:00
unset ( $_SERVER [ 'HTTP_IF_SCHEDULE_TAG_MATCH' ]);
}
else
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( " Both If-Match and If-Schedule-Tag-Match header given: If-Schedule-Tag-Match takes precedence for participants! " );
2012-02-21 21:04:45 +01:00
}
}
2012-10-23 13:35:07 +02:00
// check CalDAV Scheduling schedule-tag precondition
2011-11-08 22:09:06 +01:00
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 );
2015-06-25 22:39:53 +02:00
$schedule_tag = null ;
2011-10-20 22:10:04 +02:00
$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 ' " );
2013-09-25 09:09:44 +02:00
// honor Prefer: return=representation for 412 too (no need for client to explicitly reload)
$this -> check_return_representation ( $options , $id , $user );
2011-10-20 22:10:04 +02:00
return '412 Precondition Failed' ;
}
2012-10-23 13:35:07 +02:00
}
// if no edit-rights (aka no organizer), update only attendee stuff: status and alarms
2016-05-01 19:47:59 +02:00
if ( ! $this -> check_access ( Acl :: EDIT , $oldEvent ))
2012-10-23 13:35:07 +02:00
{
2013-01-15 14:12:10 +01:00
$user_and_memberships = $GLOBALS [ 'egw' ] -> accounts -> memberships ( $user , true );
$user_and_memberships [] = $user ;
2015-10-12 13:55:20 +02:00
if ( ! array_intersect ( array_keys ( $oldEvent [ 'participants' ]), $user_and_memberships ) &&
// above can be true, if current user is not in master but just a recurrence
( ! $oldEvent [ 'recur_type' ] || ! ( $series = self :: get_series ( $oldEvent [ 'uid' ], $this -> bo ))))
2012-10-23 13:35:07 +02:00
{
if ( $this -> debug ) error_log ( __METHOD__ . " (,, $user ) user $user is NOT an attendee! " );
return '403 Forbidden' ;
}
2011-10-20 22:10:04 +02:00
// update only participant status and alarms of current user
2012-10-23 13:35:07 +02:00
if (( $events = $handler -> icaltoegw ( $vCalendar )))
2011-10-20 22:10:04 +02:00
{
2012-08-15 17:27:11 +02:00
$modified = 0 ;
foreach ( $events as $n => $event )
2011-10-20 22:10:04 +02:00
{
2012-08-15 17:27:11 +02:00
// for recurrances of event series, we need to read correct recurrence (or if series master is no first event)
2015-10-12 13:55:20 +02:00
if ( $event [ 'recurrence' ] || $n && ! $event [ 'recurrence' ] || isset ( $series ))
2012-08-15 17:27:11 +02:00
{
// first try reading (virtual and real) exceptions
if ( ! isset ( $series ))
{
$series = self :: get_series ( $event [ 'uid' ], $this -> bo );
//foreach($series as $s => $sEvent) error_log("series[$s]: ".array2string($sEvent));
}
foreach ( $series as $oldEvent )
{
if ( $oldEvent [ 'recurrence' ] == $event [ 'recurrence' ]) break ;
}
// if no exception found, check if it might be just a recurrence (no exception)
2015-10-12 13:55:20 +02:00
if ( $event [ 'recurrence' ] && $oldEvent [ 'recurrence' ] != $event [ 'recurrence' ])
2012-08-15 17:27:11 +02:00
{
if ( ! ( $oldEvent = $this -> bo -> read ( $eventId , $event [ 'recurrence' ], true )) ||
// virtual exceptions have recurrence=0 and recur_date=recurrence (series master or real exceptions have recurence=0)
! ( $oldEvent [ 'recur_date' ] == $event [ 'recurrence' ] || ! $event [ 'recurrence' ] && ! $oldEvent [ 'recurrence' ]))
{
// if recurrence not found --> log it and continue with other recurrence
2016-05-01 19:47:59 +02:00
$this -> caldav -> log ( __METHOD__ . " (,, $user ) could NOT find recurrence= $event[recurrence] = " . Api\DateTime :: to ( $event [ 'recurrence' ]) . ' of event series! event=' . array2string ( $event ));
2012-08-15 17:27:11 +02:00
continue ;
}
}
}
2016-05-01 19:47:59 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id , $user , ' $prefix ') eventId= $eventId ( $oldEvent[id] ), user= $user , old-status=' { $oldEvent [ 'participants' ][ $user ] } ', new-status=' { $event [ 'participants' ][ $user ] } ', recurrence= $event[recurrence] = " . Api\DateTime :: to ( $event [ 'recurrence' ]) . " , event= " . array2string ( $event ));
2013-01-25 18:52:28 +01:00
if ( isset ( $event [ 'participants' ]) && isset ( $event [ 'participants' ][ $user ]) &&
$event [ 'participants' ][ $user ] !== $oldEvent [ 'participants' ][ $user ])
2011-11-08 22:09:06 +01:00
{
2012-08-15 17:27:11 +02:00
if ( ! $this -> bo -> set_status ( $oldEvent [ 'id' ], $user , $event [ 'participants' ][ $user ],
// real (not virtual) exceptions use recurrence 0 in egw_cal_user.cal_recurrence!
$recurrence = $eventId == $oldEvent [ 'id' ] ? $event [ 'recurrence' ] : 0 ))
2011-11-08 22:09:06 +01:00
{
2016-05-01 19:47:59 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " (,, $user ) failed to set_status( $oldEvent[id] , $user , ' { $event [ 'participants' ][ $user ] } ', $recurrence = " . Api\DateTime :: to ( $recurrence ) . ')' );
2011-11-08 22:09:06 +01:00
return '403 Forbidden' ;
}
else
{
2012-08-15 17:27:11 +02:00
++ $modified ;
2016-05-01 19:47:59 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " () set_status( $oldEvent[id] , $user , { $event [ 'participants' ][ $user ] } , $recurrence = " . Api\DateTime :: to ( $recurrence ) . ')' );
2011-11-08 22:09:06 +01:00
}
}
// import alarms, if given and changed
if (( array ) $event [ 'alarm' ] !== ( array ) $oldEvent [ 'alarm' ])
{
2015-06-25 22:39:53 +02:00
$event [ 'id' ] = $oldEvent [ 'id' ];
$modified += $handler -> sync_alarms ( $event , ( array ) $oldEvent [ 'alarm' ], $user );
2011-10-20 22:10:04 +02:00
}
}
2012-08-15 17:27:11 +02:00
if ( ! $modified ) // NO modififictions, or none we understood --> log it and return Ok: "204 No Content"
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( __METHOD__ . " (,, $user ) NO changes for current user events= " . array2string ( $events ) . ', old-event=' . array2string ( $oldEvent ));
2012-08-15 17:27:11 +02:00
}
2012-10-29 13:23:17 +01:00
$this -> put_response_headers ( $eventId , $options [ 'path' ], '204 No Content' , self :: $path_attr == 'caldav_name' );
2011-10-20 22:10:04 +02:00
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
2016-05-01 19:47:59 +02:00
if ( $this -> agent == 'lightning' && ! $this -> check_access ( Acl :: EDIT , $oldEvent ) &&
2011-03-05 11:21:32 +01:00
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 ,
2016-04-02 12:44:17 +02:00
self :: etag2value ( $this -> http_if_match ), false , 0 , $this -> caldav -> 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
{
2013-09-25 09:09:44 +02:00
// honor Prefer: return=representation for 412 too (no need for client to explicitly reload)
$this -> check_return_representation ( $options , $id , $user );
2011-11-08 22:09:06 +01:00
return '412 Precondition Failed' ;
}
2010-05-18 16:41:22 +02:00
else
{
return '403 Forbidden' ;
}
2008-05-08 22:31:32 +02:00
}
2012-02-04 21:24:01 +01:00
// send evtl. necessary respose headers: Location, etag, ...
$this -> put_response_headers ( $cal_id , $options [ 'path' ], $retval , self :: $path_attr == 'caldav_name' );
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
2010-05-17 16:20:34 +02:00
/**
* Handle post request for a schedule entry
*
* @ param array & $options
* @ param int $id
2015-06-25 22:39:53 +02:00
* @ param int $user = null account_id of owner , default null
2010-05-17 16:20:34 +02:00
* @ 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 )))
{
2015-06-25 22:39:53 +02:00
$id = array_shift ( $foundEvents );
list ( $eventId ) = explode ( ':' , $id );
2010-10-26 11:35:44 +02:00
2010-06-29 11:19:25 +02:00
if ( ! ( $cal_id = $handler -> importVCal ( $vCalendar , $eventId , null ,
2016-04-02 12:44:17 +02:00
false , 0 , $this -> caldav -> current_user_principal , $user , $charset )))
2010-06-29 11:19:25 +02:00
{
if ( $this -> debug ) error_log ( __METHOD__ . " () importVCal( $eventId ) returned false " );
}
2013-09-23 12:21:31 +02:00
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 )
{
2015-06-25 22:39:53 +02:00
unset ( $options ); // not used, but required by function signature
2015-06-22 18:20:15 +02:00
$vcal = new Horde_Icalendar ();
2011-09-22 17:22:52 +02:00
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 )) ||
2016-04-02 12:44:17 +02:00
! ( $event = $handler -> vevent2egw ( $component , $version , $handler -> supportedFields , $this -> caldav -> current_user_principal , 'Horde_Icalendar_Vfreebusy' )))
2011-09-22 17:22:52 +02:00
{
return '400 Bad request' ;
}
if ( $event [ 'owner' ] != $user )
{
2016-04-02 12:44:17 +02:00
$this -> caldav -> log ( __METHOD__ . " (' $ical ',, $user ) ORGANIZER is NOT principal! " );
2011-09-22 17:22:52 +02:00
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
2015-06-25 22:39:53 +02:00
$mask_uid = $component -> getAttributeDefault ( 'X-CALENDARSERVER-MASK-UID' , null );
2011-09-22 17:22:52 +02:00
header ( 'Content-type: text/xml; charset=UTF-8' );
$xml = new XMLWriter ;
$xml -> openMemory ();
2012-10-02 07:17:32 +02:00
$xml -> setIndent ( true );
2011-09-22 17:22:52 +02:00
$xml -> startDocument ( '1.0' , 'UTF-8' );
2016-04-02 12:44:17 +02:00
$xml -> startElementNs ( 'C' , 'schedule-response' , Api\CalDAV :: CALDAV );
2011-09-22 17:22:52 +02:00
2015-06-25 22:39:53 +02:00
foreach ( array_keys ( $event [ 'participants' ]) as $uid )
2011-09-22 17:22:52 +02:00
{
$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 )
{
2015-06-25 22:39:53 +02:00
unset ( $path ); // unused, but required by function signature
2016-05-01 19:47:59 +02:00
if ( ! $this -> bo -> check_perms ( calendar_bo :: ACL_FREEBUSY , 0 , $user ))
2011-09-22 21:49:01 +02:00
{
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 ());
2016-05-01 19:47:59 +02:00
exit (); // otherwise we get a 207 multistatus, not 200 Ok
2011-09-22 21:49:01 +02:00
}
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
2015-06-25 22:39:53 +02:00
* @ param int $user = null owner of the collection , default current user
2011-09-22 20:46:16 +02:00
* @ return array with privileges
*/
public function current_user_privileges ( $path , $user = null )
{
2012-10-02 07:17:32 +02:00
$privileges = parent :: current_user_privileges ( $path , $user );
//error_log(__METHOD__."('$path', $user) parent gave ".array2string($privileges));
2011-09-22 20:46:16 +02:00
2016-05-01 19:47:59 +02:00
if ( $this -> bo -> check_perms ( calendar_bo :: ACL_FREEBUSY , 0 , $user ))
2011-09-22 20:46:16 +02:00
{
2016-04-02 12:44:17 +02:00
$privileges [ 'read-free-busy' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'read-free-busy' , '' );
2011-09-22 20:46:16 +02:00
if ( substr ( $path , - 8 ) == '/outbox/' && $this -> bo -> check_acl_invite ( $user ))
{
2016-04-02 12:44:17 +02:00
$privileges [ 'schedule-send' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'schedule-send' , '' );
2011-09-22 20:46:16 +02:00
}
}
if ( substr ( $path , - 7 ) == '/inbox/' && $this -> bo -> check_acl_invite ( $user ))
{
2016-04-02 12:44:17 +02:00
$privileges [ 'schedule-deliver' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'schedule-deliver' , '' );
2011-09-22 20:46:16 +02:00
}
2016-05-01 19:47:59 +02:00
// remove bind privilege on other users or groups calendars, if calendar Api\Config require_acl_invite is set
2013-01-24 13:54:08 +01:00
// and current user has no invite grant
if ( $user && $user != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] && isset ( $privileges [ 'bind' ]) &&
2013-01-24 16:32:56 +01:00
! $this -> bo -> check_acl_invite ( $user ))
2013-01-24 13:54:08 +01:00
{
unset ( $privileges [ 'bind' ]);
}
2012-10-02 07:17:32 +02:00
//error_log(__METHOD__."('$path', $user) returning ".array2string($privileges));
return $privileges ;
2011-09-22 20:46:16 +02:00
}
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 );
2015-06-25 22:39:53 +02:00
foreach ( array_keys ( $event [ 'participants' ]) as $uid )
2011-10-05 09:39:11 +02:00
{
if ( $this -> bo -> user == $uid || in_array ( $uid , $memberships ))
{
2012-10-02 14:57:31 +02:00
$this -> bo -> set_status ( $event , $this -> bo -> user , 'R' );
$ret = true ;
2011-10-05 09:39:11 +02:00
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 ;
2013-03-14 15:47:06 +01:00
$event = $this -> bo -> read ( array ( $column => $id , 'cal_deleted IS NULL' , 'cal_reference=0' ), 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!
2016-05-01 19:47:59 +02:00
if ( ! ( $retval = $this -> bo -> check_perms ( calendar_bo :: ACL_FREEBUSY , $event , 0 , 'server' )) &&
2015-10-09 19:37:01 +02:00
// above can be true, if current user is not in master but just a recurrence
( ! $event [ 'recur_type' ] || ! ( $events = self :: get_series ( $event [ 'uid' ], $this -> bo ))))
2010-10-26 11:35:44 +02:00
{
if ( $this -> debug > 0 ) error_log ( __METHOD__ . " ( $id ) no READ or FREEBUSY rights returning " . array2string ( $retval ));
return $retval ;
}
2016-05-01 19:47:59 +02:00
if ( ! $this -> bo -> check_perms ( 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
}
2013-09-23 12:21:31 +02:00
/**
* Update etag , ctag and sync - token to reflect changed attachments
*
* @ param array | string | int $entry array with entry data from read , or id
*/
public function update_tags ( $entry )
{
if ( ! is_array ( $entry )) $entry = $this -> read ( $entry );
$this -> bo -> update ( $entry , true );
}
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
*
2015-06-25 22:39:53 +02:00
* @ param array | int $entry array with event or cal_id
* @ param string $schedule_tag = null on return schedule - tag
2011-10-20 22:10:04 +02:00
* @ 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
}
2012-10-29 13:23:17 +01:00
/**
* Send response - headers for a PUT ( or POST with add - member query parameter )
*
* Reimplemented to send
*
* @ param int | array $entry id or array of new created entry
* @ param string $path
* @ param int | string $retval
2015-06-25 22:39:53 +02:00
* @ param boolean $path_attr_is_name = true true : path_attr is ca ( l | rd ) dav_name , false : id ( GroupDAV needs Location header )
2012-10-29 13:23:17 +01:00
*/
function put_response_headers ( $entry , $path , $retval , $path_attr_is_name = true )
{
2015-06-25 22:39:53 +02:00
$schedule_tag = null ;
2012-10-29 13:23:17 +01:00
$etag = $this -> get_etag ( $entry , $schedule_tag );
if ( $this -> use_schedule_tag )
{
header ( 'Schedule-Tag: "' . $schedule_tag . '"' );
}
parent :: put_response_headers ( $entry , $path , $retval , $path_attr_is_name , $etag );
}
2008-05-08 22:31:32 +02:00
/**
* Check if user has the neccessary rights on an event
*
2016-05-01 19:47:59 +02:00
* @ param int $acl Acl :: READ , Acl :: EDIT or 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 )
{
2016-05-01 19:47:59 +02:00
if ( $acl == Acl :: READ )
2010-05-14 10:35:16 +02:00
{
2016-05-01 19:47:59 +02:00
// we need at least calendar_bo::ACL_FREEBUSY to get some information
$acl = calendar_bo :: ACL_FREEBUSY ;
2010-05-14 10:35:16 +02:00
}
2008-05-08 22:31:32 +02:00
return $this -> bo -> check_perms ( $acl , $event , 0 , 'server' );
}
/**
* Add extra properties for calendar collections
*
2016-05-01 19:47:59 +02:00
* @ param array $props regular props by the Api\CalDAV handler
2010-03-07 00:06:43 +01:00
* @ param string $displayname
2015-06-25 22:39:53 +02:00
* @ param string $base_uri = null base url of handler
* @ param int $user = null account_id of owner of current collection
* @ param string $path = null path of the collection
2008-05-08 22:31:32 +02:00
* @ return array
*/
2015-06-25 22:39:53 +02:00
public function extra_properties ( array $props , $displayname , $base_uri = null , $user = null , $path = null )
2008-05-08 22:31:32 +02:00
{
2015-06-25 22:39:53 +02:00
unset ( $base_uri ); // unused, but required by function signature
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
2016-04-02 12:44:17 +02:00
$props [ 'calendar-description' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'calendar-description' , $displayname );
2012-01-30 01:40:55 +01:00
}
2012-01-31 00:53:06 +01:00
$supported_components = array (
2016-04-02 12:44:17 +02:00
Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'comp' , array ( 'name' => 'VCALENDAR' )),
Api\CalDAV :: mkprop ( Api\CalDAV :: 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/' )
{
2016-04-02 12:44:17 +02:00
$supported_components [] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'comp' , array ( 'name' => 'VFREEBUSY' ));
2012-01-31 00:53:06 +01:00
}
2016-04-02 12:44:17 +02:00
$props [ 'supported-calendar-component-set' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV ,
2012-01-31 00:53:06 +01:00
'supported-calendar-component-set' , $supported_components );
2012-09-26 16:30:47 +02:00
// supported reports
2012-09-25 13:54:41 +02:00
$props [ 'supported-report-set' ] = array (
2016-04-02 12:44:17 +02:00
'calendar-query' => Api\CalDAV :: mkprop ( 'supported-report' , array (
Api\CalDAV :: mkprop ( 'report' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'calendar-query' , '' ))))),
'calendar-multiget' => Api\CalDAV :: mkprop ( 'supported-report' , array (
Api\CalDAV :: mkprop ( 'report' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'calendar-multiget' , '' ))))),
'free-busy-query' => Api\CalDAV :: mkprop ( 'supported-report' , array (
Api\CalDAV :: mkprop ( 'report' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'free-busy-query' , '' ))))),
2012-09-25 13:54:41 +02:00
);
2013-10-31 12:29:22 +01:00
// rfc 6578 sync-collection report for everything but outbox
2014-01-07 12:10:51 +01:00
// only if "delete-prevention" is switched on (deleted entries get marked deleted but not actualy deleted
if ( strpos ( $path , '/outbox/' ) === false && $GLOBALS [ 'egw_info' ][ 'server' ][ 'calendar_delete_history' ])
2012-10-02 12:35:12 +02:00
{
2016-04-02 12:44:17 +02:00
$props [ 'supported-report-set' ][ 'sync-collection' ] = Api\CalDAV :: mkprop ( 'supported-report' , array (
Api\CalDAV :: mkprop ( 'report' , array (
Api\CalDAV :: mkprop ( 'sync-collection' , '' )))));
2012-10-02 12:35:12 +02:00
}
2016-04-02 12:44:17 +02:00
$props [ 'supported-calendar-data' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'supported-calendar-data' , array (
Api\CalDAV :: mkprop ( Api\CalDAV :: CALDAV , 'calendar-data' , array ( 'content-type' => 'text/calendar' , 'version' => '2.0' )),
Api\CalDAV :: mkprop ( Api\CalDAV :: 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
2016-04-02 12:44:17 +02:00
if ( $this -> caldav -> prop_requested ( 'calendar-timezone' ))
2011-10-20 15:35:01 +02:00
{
2016-04-02 12:44:17 +02:00
$props [ 'calendar-timezone' ] = Api\CalDAV :: mkprop ( Api\CalDAV :: 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 );
2013-09-23 12:21:31 +02:00
$handler -> supportedFields [ 'attachments' ] = true ; // enabling attachments
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 ();
2015-06-25 22:39:53 +02:00
$pref = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'calendar-home-set' ];
$calendar_home_set = $pref ? explode ( ',' , $pref ) : array ();
2012-02-04 02:03:56 +01:00
// 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' ];
2012-03-01 14:28:38 +01:00
if ( $id && $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] != $id && // no current user
2012-02-04 02:03:56 +01:00
( in_array ( 'A' , $calendar_home_set ) || in_array (( string ) $id , $calendar_home_set )) &&
is_numeric ( $id ) && ( $owner = $this -> accounts -> id2name ( $id )))
{
2012-09-27 17:46:08 +02:00
$shared [ $id ] = 'calendar-' . $owner ;
}
}
// shared locations and resources
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'resources' ])
{
foreach ( array ( 'locations' , 'resources' ) as $res )
{
if (( $pref = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'calendar-home-set-' . $res ]))
{
foreach ( explode ( ',' , $pref ) as $res_id )
{
$is_location = $res == 'locations' ;
2016-04-02 12:44:17 +02:00
$shared [ 'r' . $res_id ] = str_replace ( 's/' , '-' , Api\CalDAV\Principals :: resource2name ( $res_id , $is_location ));
2012-09-27 17:46:08 +02:00
}
}
2012-02-04 02:03:56 +01:00
}
}
return $shared ;
}
2012-02-04 02:24:34 +01:00
/**
* Return appliction specific settings
*
2012-02-14 18:38:45 +01:00
* @ param array $hook_data
* @ return array of array with settings
2012-02-04 02:24:34 +01:00
*/
2012-02-14 18:38:45 +01:00
static function get_settings ( $hook_data )
2012-02-04 02:24:34 +01:00
{
2014-07-23 14:30:39 +02:00
$calendars = array (
'A' => lang ( 'All' ),
'G' => lang ( 'Primary Group' ),
);
if ( ! isset ( $hook_data [ 'setup' ]) && in_array ( $hook_data [ 'type' ], array ( 'user' , 'group' )))
2012-02-04 02:24:34 +01:00
{
2014-07-23 14:30:39 +02:00
$user = $hook_data [ 'account_id' ];
foreach ( calendar_bo :: list_calendars ( $user ) as $entry )
2012-02-04 02:24:34 +01:00
{
$calendars [ $entry [ 'grantor' ]] = $entry [ 'name' ];
}
2014-07-23 15:16:01 +02:00
if ( $user > 0 ) unset ( $calendars [ $user ]); // skip current user
2012-02-04 02:24:34 +01:00
}
$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 ,
);
2013-03-14 18:13:59 +01:00
$settings [ 'calendar-past-limit' ] = array (
'type' => 'input' ,
'label' => lang ( 'How many days to sync in the past (default %1)' , self :: PAST_LIMIT ),
'name' => 'calendar-past-limit' ,
'help' => 'Clients not explicitly stating a limit get limited to these many days. A too high limit may cause problems with some clients.' ,
'xmlrpc' => True ,
'admin' => False ,
);
$settings [ 'calendar-future-limit' ] = array (
'type' => 'input' ,
'label' => lang ( 'How many days to sync in the future (default %1)' , self :: FUTURE_LIMIT ),
'name' => 'calendar-future-limit' ,
'help' => 'Clients not explicitly stating a limit get limited to these many days. A too high limit may cause problems with some clients.' ,
'xmlrpc' => True ,
'admin' => False ,
);
2012-09-27 17:46:08 +02:00
// allow to subscribe to resources
2016-04-02 12:44:17 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'resources' ] && ( $all_resources = Api\CalDAV\Principals :: get_resources ()))
2012-09-27 17:46:08 +02:00
{
$resources = $locations = array ();
foreach ( $all_resources as $resource )
{
2016-04-02 12:44:17 +02:00
if ( Api\CalDAV\Principals :: resource_is_location ( $resource ))
2012-09-27 17:46:08 +02:00
{
$locations [ $resource [ 'res_id' ]] = $resource [ 'name' ];
}
else
{
$resources [ $resource [ 'res_id' ]] = $resource [ 'name' ];
}
}
foreach ( array (
'locations' => $locations ,
'resources' => $resources ,
) as $name => $options )
{
if ( $options )
{
natcasesort ( $options );
$settings [ 'calendar-home-set-' . $name ] = array (
'type' => 'multiselect' ,
'label' => lang ( '%1 to sync' , lang ( $name == 'locations' ? 'Location calendars' : 'Resource calendars' )),
'no_lang' => true ,
'name' => 'calendar-home-set-' . $name ,
'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' => $options ,
'xmlrpc' => True ,
'admin' => False ,
);
}
}
}
2012-02-04 02:24:34 +01:00
return $settings ;
}
2009-04-02 14:35:26 +02:00
}