2012-06-03 20:19:28 +02:00
< ? php
2012-08-11 10:07:19 +02:00
use Sabre\VObject ;
2012-06-03 20:19:28 +02:00
/**
2018-01-20 17:01:59 +01:00
* CalDAV addon
2012-06-03 20:19:28 +02:00
*
2018-01-20 17:01:59 +01:00
* This addon provides functionality added by CalDAV ( RFC 4791 )
2012-06-03 20:19:28 +02:00
* It implements new reports , and the MKCALENDAR method .
*
* @ package Sabre
* @ subpackage CalDAV
* @ copyright Copyright ( C ) 2007 - 2012 Rooftop Solutions . All rights reserved .
* @ author Evert Pot ( http :// www . rooftopsolutions . nl / )
* @ license http :// code . google . com / p / sabredav / wiki / License Modified BSD License
*/
class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* This is the official CalDAV namespace
*/
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav' ;
/**
* This is the namespace for the proprietary calendarserver extensions
*/
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/' ;
/**
* The hardcoded root for calendar objects . It is unfortunate
* that we ' re stuck with it , but it will have to do for now
*/
const CALENDAR_ROOT = 'calendars' ;
/**
* Reference to server object
*
* @ var Sabre_DAV_Server
*/
private $server ;
/**
* The email handler for invites and other scheduling messages .
*
* @ var Sabre_CalDAV_Schedule_IMip
*/
protected $imipHandler ;
/**
* Sets the iMIP handler .
*
* iMIP = The email transport of iCalendar scheduling messages . Setting
* this is optional , but if you want the server to allow invites to be sent
* out , you must set a handler .
*
* Specifically iCal will plain assume that the server supports this . If
* the server doesn ' t , iCal will display errors when inviting people to
* events .
*
* @ param Sabre_CalDAV_Schedule_IMip $imipHandler
* @ return void
*/
public function setIMipHandler ( Sabre_CalDAV_Schedule_IMip $imipHandler ) {
$this -> imipHandler = $imipHandler ;
}
/**
2018-01-20 17:01:59 +01:00
* Use this method to tell the server this addon defines additional
2012-06-03 20:19:28 +02:00
* HTTP methods .
*
* This method is passed a uri . It should only return HTTP methods that are
* available for the specified uri .
*
* @ param string $uri
* @ return array
*/
public function getHTTPMethods ( $uri ) {
// The MKCALENDAR is only available on unmapped uri's, whose
// parents extend IExtendedCollection
list ( $parent , $name ) = Sabre_DAV_URLUtil :: splitPath ( $uri );
$node = $this -> server -> tree -> getNodeForPath ( $parent );
if ( $node instanceof Sabre_DAV_IExtendedCollection ) {
try {
$node -> getChild ( $name );
} catch ( Sabre_DAV_Exception_NotFound $e ) {
return array ( 'MKCALENDAR' );
}
}
return array ();
}
/**
* Returns a list of features for the DAV : HTTP header .
*
* @ return array
*/
public function getFeatures () {
return array ( 'calendar-access' , 'calendar-proxy' );
}
/**
2018-01-20 17:01:59 +01:00
* Returns a addon name .
2012-06-03 20:19:28 +02:00
*
2018-01-20 17:01:59 +01:00
* Using this name other addons will be able to access other addons
2012-06-03 20:19:28 +02:00
* using Sabre_DAV_Server :: getPlugin
*
* @ return string
*/
public function getPluginName () {
return 'caldav' ;
}
/**
2018-01-20 17:01:59 +01:00
* Returns a list of reports this addon supports .
2012-06-03 20:19:28 +02:00
*
* This will be used in the { DAV : } supported - report - set property .
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @ param string $uri
* @ return array
*/
public function getSupportedReportSet ( $uri ) {
$node = $this -> server -> tree -> getNodeForPath ( $uri );
$reports = array ();
if ( $node instanceof Sabre_CalDAV_ICalendar || $node instanceof Sabre_CalDAV_ICalendarObject ) {
$reports [] = '{' . self :: NS_CALDAV . '}calendar-multiget' ;
$reports [] = '{' . self :: NS_CALDAV . '}calendar-query' ;
}
if ( $node instanceof Sabre_CalDAV_ICalendar ) {
$reports [] = '{' . self :: NS_CALDAV . '}free-busy-query' ;
}
return $reports ;
}
/**
2018-01-20 17:01:59 +01:00
* Initializes the addon
2012-06-03 20:19:28 +02:00
*
* @ param Sabre_DAV_Server $server
* @ return void
*/
public function initialize ( Sabre_DAV_Server $server ) {
$this -> server = $server ;
$server -> subscribeEvent ( 'unknownMethod' , array ( $this , 'unknownMethod' ));
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
$server -> subscribeEvent ( 'report' , array ( $this , 'report' ));
$server -> subscribeEvent ( 'beforeGetProperties' , array ( $this , 'beforeGetProperties' ));
$server -> subscribeEvent ( 'onHTMLActionsPanel' , array ( $this , 'htmlActionsPanel' ));
$server -> subscribeEvent ( 'onBrowserPostAction' , array ( $this , 'browserPostAction' ));
$server -> subscribeEvent ( 'beforeWriteContent' , array ( $this , 'beforeWriteContent' ));
$server -> subscribeEvent ( 'beforeCreateFile' , array ( $this , 'beforeCreateFile' ));
2012-07-08 19:12:58 +02:00
$server -> subscribeEvent ( 'beforeMethod' , array ( $this , 'beforeMethod' ));
2012-06-03 20:19:28 +02:00
$server -> xmlNamespaces [ self :: NS_CALDAV ] = 'cal' ;
$server -> xmlNamespaces [ self :: NS_CALENDARSERVER ] = 'cs' ;
$server -> propertyMap [ '{' . self :: NS_CALDAV . '}supported-calendar-component-set' ] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet' ;
$server -> resourceTypeMapping [ 'Sabre_CalDAV_ICalendar' ] = '{urn:ietf:params:xml:ns:caldav}calendar' ;
$server -> resourceTypeMapping [ 'Sabre_CalDAV_Schedule_IOutbox' ] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox' ;
$server -> resourceTypeMapping [ 'Sabre_CalDAV_Principal_ProxyRead' ] = '{http://calendarserver.org/ns/}calendar-proxy-read' ;
$server -> resourceTypeMapping [ 'Sabre_CalDAV_Principal_ProxyWrite' ] = '{http://calendarserver.org/ns/}calendar-proxy-write' ;
2012-07-08 19:12:58 +02:00
$server -> resourceTypeMapping [ 'Sabre_CalDAV_Notifications_ICollection' ] = '{' . self :: NS_CALENDARSERVER . '}notifications' ;
$server -> resourceTypeMapping [ 'Sabre_CalDAV_Notifications_INode' ] = '{' . self :: NS_CALENDARSERVER . '}notification' ;
2012-06-03 20:19:28 +02:00
array_push ( $server -> protectedProperties ,
'{' . self :: NS_CALDAV . '}supported-calendar-component-set' ,
'{' . self :: NS_CALDAV . '}supported-calendar-data' ,
'{' . self :: NS_CALDAV . '}max-resource-size' ,
'{' . self :: NS_CALDAV . '}min-date-time' ,
'{' . self :: NS_CALDAV . '}max-date-time' ,
'{' . self :: NS_CALDAV . '}max-instances' ,
'{' . self :: NS_CALDAV . '}max-attendees-per-instance' ,
'{' . self :: NS_CALDAV . '}calendar-home-set' ,
'{' . self :: NS_CALDAV . '}supported-collation-set' ,
'{' . self :: NS_CALDAV . '}calendar-data' ,
// scheduling extension
'{' . self :: NS_CALDAV . '}schedule-inbox-URL' ,
'{' . self :: NS_CALDAV . '}schedule-outbox-URL' ,
'{' . self :: NS_CALDAV . '}calendar-user-address-set' ,
'{' . self :: NS_CALDAV . '}calendar-user-type' ,
// CalendarServer extensions
'{' . self :: NS_CALENDARSERVER . '}getctag' ,
'{' . self :: NS_CALENDARSERVER . '}calendar-proxy-read-for' ,
2012-07-08 19:12:58 +02:00
'{' . self :: NS_CALENDARSERVER . '}calendar-proxy-write-for' ,
'{' . self :: NS_CALENDARSERVER . '}notification-URL' ,
'{' . self :: NS_CALENDARSERVER . '}notificationtype'
2012-06-03 20:19:28 +02:00
);
}
/**
* This function handles support for the MKCALENDAR method
*
* @ param string $method
* @ param string $uri
* @ return bool
*/
public function unknownMethod ( $method , $uri ) {
switch ( $method ) {
case 'MKCALENDAR' :
$this -> httpMkCalendar ( $uri );
// false is returned to stop the propagation of the
// unknownMethod event.
return false ;
case 'POST' :
// Checking if we're talking to an outbox
try {
$node = $this -> server -> tree -> getNodeForPath ( $uri );
} catch ( Sabre_DAV_Exception_NotFound $e ) {
return ;
}
if ( ! $node instanceof Sabre_CalDAV_Schedule_IOutbox )
return ;
$this -> outboxRequest ( $node );
return false ;
}
}
/**
* This functions handles REPORT requests specific to CalDAV
*
* @ param string $reportName
* @ param DOMNode $dom
* @ return bool
*/
public function report ( $reportName , $dom ) {
switch ( $reportName ) {
case '{' . self :: NS_CALDAV . '}calendar-multiget' :
$this -> calendarMultiGetReport ( $dom );
return false ;
case '{' . self :: NS_CALDAV . '}calendar-query' :
$this -> calendarQueryReport ( $dom );
return false ;
case '{' . self :: NS_CALDAV . '}free-busy-query' :
$this -> freeBusyQueryReport ( $dom );
return false ;
}
}
/**
* This function handles the MKCALENDAR HTTP method , which creates
* a new calendar .
*
* @ param string $uri
* @ return void
*/
public function httpMkCalendar ( $uri ) {
// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
// for clients matching iCal in the user agent
//$ua = $this->server->httpRequest->getHeader('User-Agent');
//if (strpos($ua,'iCal/')!==false) {
// throw new Sabre_DAV_Exception_Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
//}
$body = $this -> server -> httpRequest -> getBody ( true );
$properties = array ();
if ( $body ) {
$dom = Sabre_DAV_XMLUtil :: loadDOMDocument ( $body );
foreach ( $dom -> firstChild -> childNodes as $child ) {
if ( Sabre_DAV_XMLUtil :: toClarkNotation ( $child ) !== '{DAV:}set' ) continue ;
foreach ( Sabre_DAV_XMLUtil :: parseProperties ( $child , $this -> server -> propertyMap ) as $k => $prop ) {
$properties [ $k ] = $prop ;
}
}
}
$resourceType = array ( '{DAV:}collection' , '{urn:ietf:params:xml:ns:caldav}calendar' );
$this -> server -> createCollection ( $uri , $resourceType , $properties );
$this -> server -> httpResponse -> sendStatus ( 201 );
$this -> server -> httpResponse -> setHeader ( 'Content-Length' , 0 );
}
/**
* beforeGetProperties
*
* This method handler is invoked before any after properties for a
* resource are fetched . This allows us to add in any CalDAV specific
* properties .
*
* @ param string $path
* @ param Sabre_DAV_INode $node
* @ param array $requestedProperties
* @ param array $returnedProperties
* @ return void
*/
public function beforeGetProperties ( $path , Sabre_DAV_INode $node , & $requestedProperties , & $returnedProperties ) {
if ( $node instanceof Sabre_DAVACL_IPrincipal ) {
// calendar-home-set property
$calHome = '{' . self :: NS_CALDAV . '}calendar-home-set' ;
if ( in_array ( $calHome , $requestedProperties )) {
$principalId = $node -> getName ();
$calendarHomePath = self :: CALENDAR_ROOT . '/' . $principalId . '/' ;
unset ( $requestedProperties [ $calHome ]);
$returnedProperties [ 200 ][ $calHome ] = new Sabre_DAV_Property_Href ( $calendarHomePath );
}
// schedule-outbox-URL property
$scheduleProp = '{' . self :: NS_CALDAV . '}schedule-outbox-URL' ;
if ( in_array ( $scheduleProp , $requestedProperties )) {
$principalId = $node -> getName ();
$outboxPath = self :: CALENDAR_ROOT . '/' . $principalId . '/outbox' ;
unset ( $requestedProperties [ $scheduleProp ]);
$returnedProperties [ 200 ][ $scheduleProp ] = new Sabre_DAV_Property_Href ( $outboxPath );
}
// calendar-user-address-set property
$calProp = '{' . self :: NS_CALDAV . '}calendar-user-address-set' ;
if ( in_array ( $calProp , $requestedProperties )) {
$addresses = $node -> getAlternateUriSet ();
$addresses [] = $this -> server -> getBaseUri () . $node -> getPrincipalUrl ();
unset ( $requestedProperties [ $calProp ]);
$returnedProperties [ 200 ][ $calProp ] = new Sabre_DAV_Property_HrefList ( $addresses , false );
}
// These two properties are shortcuts for ical to easily find
// other principals this principal has access to.
$propRead = '{' . self :: NS_CALENDARSERVER . '}calendar-proxy-read-for' ;
$propWrite = '{' . self :: NS_CALENDARSERVER . '}calendar-proxy-write-for' ;
if ( in_array ( $propRead , $requestedProperties ) || in_array ( $propWrite , $requestedProperties )) {
$membership = $node -> getGroupMembership ();
$readList = array ();
$writeList = array ();
foreach ( $membership as $group ) {
$groupNode = $this -> server -> tree -> getNodeForPath ( $group );
// If the node is either ap proxy-read or proxy-write
// group, we grab the parent principal and add it to the
// list.
if ( $groupNode instanceof Sabre_CalDAV_Principal_ProxyRead ) {
list ( $readList []) = Sabre_DAV_URLUtil :: splitPath ( $group );
}
if ( $groupNode instanceof Sabre_CalDAV_Principal_ProxyWrite ) {
list ( $writeList []) = Sabre_DAV_URLUtil :: splitPath ( $group );
}
}
if ( in_array ( $propRead , $requestedProperties )) {
unset ( $requestedProperties [ $propRead ]);
$returnedProperties [ 200 ][ $propRead ] = new Sabre_DAV_Property_HrefList ( $readList );
}
if ( in_array ( $propWrite , $requestedProperties )) {
unset ( $requestedProperties [ $propWrite ]);
$returnedProperties [ 200 ][ $propWrite ] = new Sabre_DAV_Property_HrefList ( $writeList );
}
}
2012-07-08 19:12:58 +02:00
// notification-URL property
$notificationUrl = '{' . self :: NS_CALENDARSERVER . '}notification-URL' ;
if (( $index = array_search ( $notificationUrl , $requestedProperties )) !== false ) {
$principalId = $node -> getName ();
$calendarHomePath = 'calendars/' . $principalId . '/notifications/' ;
unset ( $requestedProperties [ $index ]);
$returnedProperties [ 200 ][ $notificationUrl ] = new Sabre_DAV_Property_Href ( $calendarHomePath );
}
2012-06-03 20:19:28 +02:00
} // instanceof IPrincipal
2012-07-08 19:12:58 +02:00
if ( $node instanceof Sabre_CalDAV_Notifications_INode ) {
$propertyName = '{' . self :: NS_CALENDARSERVER . '}notificationtype' ;
if (( $index = array_search ( $propertyName , $requestedProperties )) !== false ) {
$returnedProperties [ 200 ][ $propertyName ] =
$node -> getNotificationType ();
unset ( $requestedProperties [ $index ]);
}
} // instanceof Notifications_INode
2012-06-03 20:19:28 +02:00
if ( $node instanceof Sabre_CalDAV_ICalendarObject ) {
// The calendar-data property is not supposed to be a 'real'
// property, but in large chunks of the spec it does act as such.
// Therefore we simply expose it as a property.
$calDataProp = '{' . Sabre_CalDAV_Plugin :: NS_CALDAV . '}calendar-data' ;
if ( in_array ( $calDataProp , $requestedProperties )) {
unset ( $requestedProperties [ $calDataProp ]);
$val = $node -> get ();
if ( is_resource ( $val ))
$val = stream_get_contents ( $val );
// Taking out \r to not screw up the xml output
$returnedProperties [ 200 ][ $calDataProp ] = str_replace ( " \r " , " " , $val );
}
}
}
/**
* This function handles the calendar - multiget REPORT .
*
* This report is used by the client to fetch the content of a series
* of urls . Effectively avoiding a lot of redundant requests .
*
* @ param DOMNode $dom
* @ return void
*/
public function calendarMultiGetReport ( $dom ) {
$properties = array_keys ( Sabre_DAV_XMLUtil :: parseProperties ( $dom -> firstChild ));
2012-07-23 23:15:47 +02:00
$hrefElems = $dom -> getElementsByTagNameNS ( 'DAV:' , 'href' );
2012-06-03 20:19:28 +02:00
$xpath = new DOMXPath ( $dom );
$xpath -> registerNameSpace ( 'cal' , Sabre_CalDAV_Plugin :: NS_CALDAV );
2012-07-23 23:15:47 +02:00
$xpath -> registerNameSpace ( 'dav' , 'DAV:' );
2012-06-03 20:19:28 +02:00
$expand = $xpath -> query ( '/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand' );
if ( $expand -> length > 0 ) {
$expandElem = $expand -> item ( 0 );
$start = $expandElem -> getAttribute ( 'start' );
$end = $expandElem -> getAttribute ( 'end' );
if ( ! $start || ! $end ) {
throw new Sabre_DAV_Exception_BadRequest ( 'The "start" and "end" attributes are required for the CALDAV:expand element' );
}
2012-08-11 10:07:19 +02:00
$start = VObject\DateTimeParser :: parseDateTime ( $start );
$end = VObject\DateTimeParser :: parseDateTime ( $end );
2012-06-03 20:19:28 +02:00
if ( $end <= $start ) {
throw new Sabre_DAV_Exception_BadRequest ( 'The end-date must be larger than the start-date in the expand element.' );
}
$expand = true ;
} else {
$expand = false ;
}
foreach ( $hrefElems as $elem ) {
$uri = $this -> server -> calculateUri ( $elem -> nodeValue );
list ( $objProps ) = $this -> server -> getPropertiesForPath ( $uri , $properties );
if ( $expand && isset ( $objProps [ 200 ][ '{' . self :: NS_CALDAV . '}calendar-data' ])) {
2012-08-11 10:07:19 +02:00
$vObject = VObject\Reader :: read ( $objProps [ 200 ][ '{' . self :: NS_CALDAV . '}calendar-data' ]);
2012-06-03 20:19:28 +02:00
$vObject -> expand ( $start , $end );
$objProps [ 200 ][ '{' . self :: NS_CALDAV . '}calendar-data' ] = $vObject -> serialize ();
}
$propertyList [] = $objProps ;
}
$this -> server -> httpResponse -> sendStatus ( 207 );
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml; charset=utf-8' );
$this -> server -> httpResponse -> sendBody ( $this -> server -> generateMultiStatus ( $propertyList ));
}
/**
* This function handles the calendar - query REPORT
*
* This report is used by clients to request calendar objects based on
* complex conditions .
*
* @ param DOMNode $dom
* @ return void
*/
public function calendarQueryReport ( $dom ) {
$parser = new Sabre_CalDAV_CalendarQueryParser ( $dom );
$parser -> parse ();
$node = $this -> server -> tree -> getNodeForPath ( $this -> server -> getRequestUri ());
$depth = $this -> server -> getHTTPDepth ( 0 );
// The default result is an empty array
$result = array ();
// The calendarobject was requested directly. In this case we handle
// this locally.
if ( $depth == 0 && $node instanceof Sabre_CalDAV_ICalendarObject ) {
$requestedCalendarData = true ;
$requestedProperties = $parser -> requestedProperties ;
if ( ! in_array ( '{urn:ietf:params:xml:ns:caldav}calendar-data' , $requestedProperties )) {
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties [] = '{urn:ietf:params:xml:ns:caldav}calendar-data' ;
// If calendar-data wasn't explicitly requested, we need to remove
// it after processing.
$requestedCalendarData = false ;
}
$properties = $this -> server -> getPropertiesForPath (
$this -> server -> getRequestUri (),
$requestedProperties ,
0
);
// This array should have only 1 element, the first calendar
// object.
$properties = current ( $properties );
// If there wasn't any calendar-data returned somehow, we ignore
// this.
if ( isset ( $properties [ 200 ][ '{urn:ietf:params:xml:ns:caldav}calendar-data' ])) {
$validator = new Sabre_CalDAV_CalendarQueryValidator ();
2012-08-11 10:07:19 +02:00
$vObject = VObject\Reader :: read ( $properties [ 200 ][ '{urn:ietf:params:xml:ns:caldav}calendar-data' ]);
2012-06-03 20:19:28 +02:00
if ( $validator -> validate ( $vObject , $parser -> filters )) {
// If the client didn't require the calendar-data property,
// we won't give it back.
if ( ! $requestedCalendarData ) {
unset ( $properties [ 200 ][ '{urn:ietf:params:xml:ns:caldav}calendar-data' ]);
} else {
if ( $parser -> expand ) {
$vObject -> expand ( $parser -> expand [ 'start' ], $parser -> expand [ 'end' ]);
$properties [ 200 ][ '{' . self :: NS_CALDAV . '}calendar-data' ] = $vObject -> serialize ();
}
}
$result = array ( $properties );
}
}
}
// If we're dealing with a calendar, the calendar itself is responsible
// for the calendar-query.
if ( $node instanceof Sabre_CalDAV_ICalendar && $depth = 1 ) {
$nodePaths = $node -> calendarQuery ( $parser -> filters );
foreach ( $nodePaths as $path ) {
list ( $properties ) =
$this -> server -> getPropertiesForPath ( $this -> server -> getRequestUri () . '/' . $path , $parser -> requestedProperties );
if ( $parser -> expand ) {
// We need to do some post-processing
2012-08-11 10:07:19 +02:00
$vObject = VObject\Reader :: read ( $properties [ 200 ][ '{urn:ietf:params:xml:ns:caldav}calendar-data' ]);
2012-06-03 20:19:28 +02:00
$vObject -> expand ( $parser -> expand [ 'start' ], $parser -> expand [ 'end' ]);
$properties [ 200 ][ '{' . self :: NS_CALDAV . '}calendar-data' ] = $vObject -> serialize ();
}
$result [] = $properties ;
}
}
$this -> server -> httpResponse -> sendStatus ( 207 );
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml; charset=utf-8' );
$this -> server -> httpResponse -> sendBody ( $this -> server -> generateMultiStatus ( $result ));
}
/**
* This method is responsible for parsing the request and generating the
* response for the CALDAV : free - busy - query REPORT .
*
* @ param DOMNode $dom
* @ return void
*/
protected function freeBusyQueryReport ( DOMNode $dom ) {
$start = null ;
$end = null ;
foreach ( $dom -> firstChild -> childNodes as $childNode ) {
$clark = Sabre_DAV_XMLUtil :: toClarkNotation ( $childNode );
if ( $clark == '{' . self :: NS_CALDAV . '}time-range' ) {
$start = $childNode -> getAttribute ( 'start' );
$end = $childNode -> getAttribute ( 'end' );
break ;
}
}
if ( $start ) {
2012-08-11 10:07:19 +02:00
$start = VObject\DateTimeParser :: parseDateTime ( $start );
2012-06-03 20:19:28 +02:00
}
if ( $end ) {
2012-08-11 10:07:19 +02:00
$end = VObject\DateTimeParser :: parseDateTime ( $end );
2012-06-03 20:19:28 +02:00
}
if ( ! $start && ! $end ) {
throw new Sabre_DAV_Exception_BadRequest ( 'The freebusy report must have a time-range filter' );
}
$acl = $this -> server -> getPlugin ( 'acl' );
if ( ! $acl ) {
2018-01-20 17:01:59 +01:00
throw new Sabre_DAV_Exception ( 'The ACL addon must be loaded for free-busy queries to work' );
2012-06-03 20:19:28 +02:00
}
$uri = $this -> server -> getRequestUri ();
$acl -> checkPrivileges ( $uri , '{' . self :: NS_CALDAV . '}read-free-busy' );
$calendar = $this -> server -> tree -> getNodeForPath ( $uri );
if ( ! $calendar instanceof Sabre_CalDAV_ICalendar ) {
throw new Sabre_DAV_Exception_NotImplemented ( 'The free-busy-query REPORT is only implemented on calendars' );
}
$objects = array_map ( function ( $child ) {
$obj = $child -> get ();
if ( is_resource ( $obj )) {
$obj = stream_get_contents ( $obj );
}
return $obj ;
}, $calendar -> getChildren ());
2012-08-11 10:07:19 +02:00
$generator = new VObject\FreeBusyGenerator ();
2012-06-03 20:19:28 +02:00
$generator -> setObjects ( $objects );
$generator -> setTimeRange ( $start , $end );
$result = $generator -> getResult ();
$result = $result -> serialize ();
$this -> server -> httpResponse -> sendStatus ( 200 );
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'text/calendar' );
$this -> server -> httpResponse -> setHeader ( 'Content-Length' , strlen ( $result ));
$this -> server -> httpResponse -> sendBody ( $result );
}
/**
* This method is triggered before a file gets updated with new content .
*
2018-01-20 17:01:59 +01:00
* This addon uses this method to ensure that CalDAV objects receive
2012-06-03 20:19:28 +02:00
* valid calendar data .
*
* @ param string $path
* @ param Sabre_DAV_IFile $node
* @ param resource $data
* @ return void
*/
public function beforeWriteContent ( $path , Sabre_DAV_IFile $node , & $data ) {
if ( ! $node instanceof Sabre_CalDAV_ICalendarObject )
return ;
2012-07-08 19:12:58 +02:00
$this -> validateICalendar ( $data , $path );
2012-06-03 20:19:28 +02:00
}
/**
* This method is triggered before a new file is created .
*
2018-01-20 17:01:59 +01:00
* This addon uses this method to ensure that newly created calendar
2012-06-03 20:19:28 +02:00
* objects contain valid calendar data .
*
* @ param string $path
* @ param resource $data
* @ param Sabre_DAV_ICollection $parentNode
* @ return void
*/
public function beforeCreateFile ( $path , & $data , Sabre_DAV_ICollection $parentNode ) {
if ( ! $parentNode instanceof Sabre_CalDAV_Calendar )
return ;
2012-07-08 19:12:58 +02:00
$this -> validateICalendar ( $data , $path );
}
/**
* This event is triggered before any HTTP request is handled .
*
* We use this to intercept GET calls to notification nodes , and return the
* proper response .
*
* @ param string $method
* @ param string $path
* @ return void
*/
public function beforeMethod ( $method , $path ) {
if ( $method !== 'GET' ) return ;
try {
$node = $this -> server -> tree -> getNodeForPath ( $path );
} catch ( Sabre_DAV_Exception_NotFound $e ) {
return ;
}
if ( ! $node instanceof Sabre_CalDAV_Notifications_INode )
return ;
$dom = new DOMDocument ( '1.0' , 'UTF-8' );
$dom -> formatOutput = true ;
$root = $dom -> createElement ( 'cs:notification' );
foreach ( $this -> server -> xmlNamespaces as $namespace => $prefix ) {
$root -> setAttribute ( 'xmlns:' . $prefix , $namespace );
}
$dom -> appendChild ( $root );
$node -> getNotificationType () -> serializeBody ( $this -> server , $root );
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml' );
$this -> server -> httpResponse -> sendStatus ( 200 );
$this -> server -> httpResponse -> sendBody ( $dom -> saveXML ());
return false ;
2012-06-03 20:19:28 +02:00
}
/**
* Checks if the submitted iCalendar data is in fact , valid .
*
* An exception is thrown if it ' s not .
*
* @ param resource | string $data
2012-07-08 19:12:58 +02:00
* @ param string $path
2012-06-03 20:19:28 +02:00
* @ return void
*/
2012-07-08 19:12:58 +02:00
protected function validateICalendar ( & $data , $path ) {
2012-06-03 20:19:28 +02:00
// If it's a stream, we convert it to a string first.
if ( is_resource ( $data )) {
$data = stream_get_contents ( $data );
}
// Converting the data to unicode, if needed.
$data = Sabre_DAV_StringUtil :: ensureUTF8 ( $data );
try {
2012-08-11 10:07:19 +02:00
$vobj = VObject\Reader :: read ( $data );
2012-06-03 20:19:28 +02:00
2012-08-11 10:07:19 +02:00
} catch ( VObject\ParseException $e ) {
2012-06-03 20:19:28 +02:00
throw new Sabre_DAV_Exception_UnsupportedMediaType ( 'This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e -> getMessage ());
}
if ( $vobj -> name !== 'VCALENDAR' ) {
throw new Sabre_DAV_Exception_UnsupportedMediaType ( 'This collection can only support iCalendar objects.' );
}
2012-07-08 19:12:58 +02:00
// Get the Supported Components for the target calendar
list ( $parentPath , $object ) = Sabre_Dav_URLUtil :: splitPath ( $path );
$calendarProperties = $this -> server -> getProperties ( $parentPath , array ( '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' ));
$supportedComponents = $calendarProperties [ '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' ] -> getValue ();
2012-06-03 20:19:28 +02:00
$foundType = null ;
$foundUID = null ;
foreach ( $vobj -> getComponents () as $component ) {
switch ( $component -> name ) {
case 'VTIMEZONE' :
continue 2 ;
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
if ( is_null ( $foundType )) {
$foundType = $component -> name ;
2012-07-08 19:12:58 +02:00
if ( ! in_array ( $foundType , $supportedComponents )) {
throw new Sabre_CalDAV_Exception_InvalidComponentType ( 'This calendar only supports ' . implode ( ', ' , $supportedComponents ) . '. We found a ' . $foundType );
}
2012-06-03 20:19:28 +02:00
if ( ! isset ( $component -> UID )) {
throw new Sabre_DAV_Exception_BadRequest ( 'Every ' . $component -> name . ' component must have an UID' );
}
$foundUID = ( string ) $component -> UID ;
} else {
if ( $foundType !== $component -> name ) {
throw new Sabre_DAV_Exception_BadRequest ( 'A calendar object must only contain 1 component. We found a ' . $component -> name . ' as well as a ' . $foundType );
}
if ( $foundUID !== ( string ) $component -> UID ) {
throw new Sabre_DAV_Exception_BadRequest ( 'Every ' . $component -> name . ' in this object must have identical UIDs' );
}
}
break ;
default :
throw new Sabre_DAV_Exception_BadRequest ( 'You are not allowed to create components of type: ' . $component -> name . ' here' );
}
}
if ( ! $foundType )
throw new Sabre_DAV_Exception_BadRequest ( 'iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL' );
}
/**
* This method handles POST requests to the schedule - outbox
*
* @ param Sabre_CalDAV_Schedule_IOutbox $outboxNode
* @ return void
*/
public function outboxRequest ( Sabre_CalDAV_Schedule_IOutbox $outboxNode ) {
$originator = $this -> server -> httpRequest -> getHeader ( 'Originator' );
$recipients = $this -> server -> httpRequest -> getHeader ( 'Recipient' );
if ( ! $originator ) {
throw new Sabre_DAV_Exception_BadRequest ( 'The Originator: header must be specified when making POST requests' );
}
if ( ! $recipients ) {
throw new Sabre_DAV_Exception_BadRequest ( 'The Recipient: header must be specified when making POST requests' );
}
2012-07-08 19:12:58 +02:00
if ( ! preg_match ( '/^mailto:(.*)@(.*)$/i' , $originator )) {
2012-06-03 20:19:28 +02:00
throw new Sabre_DAV_Exception_BadRequest ( 'Originator must start with mailto: and must be valid email address' );
}
$originator = substr ( $originator , 7 );
$recipients = explode ( ',' , $recipients );
foreach ( $recipients as $k => $recipient ) {
$recipient = trim ( $recipient );
2012-07-08 19:12:58 +02:00
if ( ! preg_match ( '/^mailto:(.*)@(.*)$/i' , $recipient )) {
2012-06-03 20:19:28 +02:00
throw new Sabre_DAV_Exception_BadRequest ( 'Recipients must start with mailto: and must be valid email address' );
}
$recipient = substr ( $recipient , 7 );
$recipients [ $k ] = $recipient ;
}
// We need to make sure that 'originator' matches one of the email
// addresses of the selected principal.
$principal = $outboxNode -> getOwner ();
$props = $this -> server -> getProperties ( $principal , array (
'{' . self :: NS_CALDAV . '}calendar-user-address-set' ,
));
$addresses = array ();
if ( isset ( $props [ '{' . self :: NS_CALDAV . '}calendar-user-address-set' ])) {
$addresses = $props [ '{' . self :: NS_CALDAV . '}calendar-user-address-set' ] -> getHrefs ();
}
if ( ! in_array ( 'mailto:' . $originator , $addresses )) {
throw new Sabre_DAV_Exception_Forbidden ( 'The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header' );
}
try {
2012-08-11 10:07:19 +02:00
$vObject = VObject\Reader :: read ( $this -> server -> httpRequest -> getBody ( true ));
} catch ( VObject\ParseException $e ) {
2012-06-03 20:19:28 +02:00
throw new Sabre_DAV_Exception_BadRequest ( 'The request body must be a valid iCalendar object. Parse error: ' . $e -> getMessage ());
}
// Checking for the object type
$componentType = null ;
foreach ( $vObject -> getComponents () as $component ) {
if ( $component -> name !== 'VTIMEZONE' ) {
$componentType = $component -> name ;
break ;
}
}
if ( is_null ( $componentType )) {
throw new Sabre_DAV_Exception_BadRequest ( 'We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component' );
}
// Validating the METHOD
$method = strtoupper (( string ) $vObject -> METHOD );
if ( ! $method ) {
throw new Sabre_DAV_Exception_BadRequest ( 'A METHOD property must be specified in iTIP messages' );
}
if ( in_array ( $method , array ( 'REQUEST' , 'REPLY' , 'ADD' , 'CANCEL' )) && $componentType === 'VEVENT' ) {
2012-07-08 19:12:58 +02:00
$result = $this -> iMIPMessage ( $originator , $recipients , $vObject , $principal );
2012-06-03 20:19:28 +02:00
$this -> server -> httpResponse -> sendStatus ( 200 );
2012-07-08 19:12:58 +02:00
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml' );
$this -> server -> httpResponse -> sendBody ( $this -> generateScheduleResponse ( $result ));
2012-06-03 20:19:28 +02:00
} else {
throw new Sabre_DAV_Exception_NotImplemented ( 'This iTIP method is currently not implemented' );
}
}
/**
* Sends an iMIP message by email .
*
2012-07-08 19:12:58 +02:00
* This method must return an array with status codes per recipient .
* This should look something like :
*
* array (
* 'user1@example.org' => '2.0;Success'
* )
*
* Formatting for this status code can be found at :
* https :// tools . ietf . org / html / rfc5545 #section-3.8.8.3
*
* A list of valid status codes can be found at :
* https :// tools . ietf . org / html / rfc5546 #section-3.6
*
2012-06-03 20:19:28 +02:00
* @ param string $originator
* @ param array $recipients
2012-08-11 10:07:19 +02:00
* @ param Sabre\VObject\Component $vObject
2012-07-08 19:12:58 +02:00
* @ return array
2012-06-03 20:19:28 +02:00
*/
2012-08-11 10:07:19 +02:00
protected function iMIPMessage ( $originator , array $recipients , VObject\Component $vObject , $principal ) {
2012-06-03 20:19:28 +02:00
if ( ! $this -> imipHandler ) {
2012-07-08 19:12:58 +02:00
$resultStatus = '5.2;This server does not support this operation' ;
} else {
$this -> imipHandler -> sendMessage ( $originator , $recipients , $vObject , $principal );
$resultStatus = '2.0;Success' ;
}
$result = array ();
foreach ( $recipients as $recipient ) {
$result [ $recipient ] = $resultStatus ;
}
return $result ;
}
/**
* Generates a schedule - response XML body
*
* The recipients array is a key -> value list , containing email addresses
* and iTip status codes . See the iMIPMessage method for a description of
* the value .
*
* @ param array $recipients
* @ return string
*/
public function generateScheduleResponse ( array $recipients ) {
$dom = new DOMDocument ( '1.0' , 'utf-8' );
$dom -> formatOutput = true ;
$xscheduleResponse = $dom -> createElement ( 'cal:schedule-response' );
$dom -> appendChild ( $xscheduleResponse );
foreach ( $this -> server -> xmlNamespaces as $namespace => $prefix ) {
$xscheduleResponse -> setAttribute ( 'xmlns:' . $prefix , $namespace );
}
foreach ( $recipients as $recipient => $status ) {
$xresponse = $dom -> createElement ( 'cal:response' );
$xrecipient = $dom -> createElement ( 'cal:recipient' );
$xrecipient -> appendChild ( $dom -> createTextNode ( $recipient ));
$xresponse -> appendChild ( $xrecipient );
$xrequestStatus = $dom -> createElement ( 'cal:request-status' );
$xrequestStatus -> appendChild ( $dom -> createTextNode ( $status ));
$xresponse -> appendChild ( $xrequestStatus );
$xscheduleResponse -> appendChild ( $xresponse );
2012-06-03 20:19:28 +02:00
}
2012-07-08 19:12:58 +02:00
return $dom -> saveXML ();
2012-06-03 20:19:28 +02:00
}
/**
* This method is used to generate HTML output for the
* Sabre_DAV_Browser_Plugin . This allows us to generate an interface users
* can use to create new calendars .
*
* @ param Sabre_DAV_INode $node
* @ param string $output
* @ return bool
*/
public function htmlActionsPanel ( Sabre_DAV_INode $node , & $output ) {
if ( ! $node instanceof Sabre_CalDAV_UserCalendars )
return ;
$output .= ' < tr >< td colspan = " 2 " >< form method = " post " action = " " >
< h3 > Create new calendar </ h3 >
< input type = " hidden " name = " sabreAction " value = " mkcalendar " />
< label > Name ( uri ) :</ label > < input type = " text " name = " name " />< br />
< label > Display name :</ label > < input type = " text " name = " { DAV:}displayname " />< br />
< input type = " submit " value = " create " />
</ form >
</ td ></ tr > ' ;
return false ;
}
/**
* This method allows us to intercept the 'mkcalendar' sabreAction . This
2018-01-20 17:01:59 +01:00
* action enables the user to create new calendars from the browser addon .
2012-06-03 20:19:28 +02:00
*
* @ param string $uri
* @ param string $action
* @ param array $postVars
* @ return bool
*/
public function browserPostAction ( $uri , $action , array $postVars ) {
if ( $action !== 'mkcalendar' )
return ;
$resourceType = array ( '{DAV:}collection' , '{urn:ietf:params:xml:ns:caldav}calendar' );
$properties = array ();
if ( isset ( $postVars [ '{DAV:}displayname' ])) {
$properties [ '{DAV:}displayname' ] = $postVars [ '{DAV:}displayname' ];
}
$this -> server -> createCollection ( $uri . '/' . $postVars [ 'name' ], $resourceType , $properties );
return false ;
}
}