diff --git a/config/example-config.php b/config/example-config.php
index 22fb8e97..f0c2e851 100644
--- a/config/example-config.php
+++ b/config/example-config.php
@@ -188,6 +188,16 @@ $c->collections_always_exist = false;
$c->schedule_private_key = 'PRIVATE-KEY-BASE-64-DATA';
*/
+/*
+* External subscription (BIND) minimum refresh interval
+* Required if you want to enable remote binding ( webcal subscriptions )
+* Default: none
+*/
+/*
+$c->external_refresh = 60;
+*/
+
+
/***************************************************************************
diff --git a/inc/DAVResource.php b/inc/DAVResource.php
index 2b445b72..7c7834d4 100644
--- a/inc/DAVResource.php
+++ b/inc/DAVResource.php
@@ -97,6 +97,11 @@ class DAVResource
*/
private $_is_binding;
+ /**
+ * @var True if this resource is a binding to an external resource
+ */
+ private $_is_external;
+
/**
* @var True if this resource is an addressbook collection
*/
@@ -158,6 +163,7 @@ class DAVResource
$this->_is_principal = false;
$this->_is_calendar = false;
$this->_is_binding = false;
+ $this->_is_external = false;
$this->_is_addressbook = false;
$this->_is_proxy_request = false;
if ( isset($parameters) && is_object($parameters) ) {
@@ -400,7 +406,8 @@ EOSQL;
SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
time_zone.tz_spec, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
- dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to
+ dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to,
+ dav_binding.external_url AS external_url, dav_binding.type AS external_type, dav_binding.bind_id AS bind_id
FROM dav_binding
LEFT JOIN collection ON (collection.collection_id=bound_source_id)
LEFT JOIN principal p USING (user_no)
@@ -431,7 +438,16 @@ EOSQL;
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else
$this->collection->type = 'collection';
-
+ if ( strlen($row->external_url) > 8 ) {
+ $this->collection->bound_from = $row->external_url;
+ $this->_is_external = true;
+ if ( $row->external_type == 'calendar' )
+ $this->collection->type = 'calendar';
+ else if ( $row->external_type == 'addressbook' )
+ $this->collection->type = 'addressbook';
+ else
+ $this->collection->type = 'collection';
+ }
$this->_is_binding = true;
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
if ( isset($row->access_ticket_id) ) {
@@ -1047,6 +1063,14 @@ EOQRY;
}
+ /**
+ * Checks whether this resource is a bind to an external resource
+ */
+ function IsExternal() {
+ return $this->_is_external;
+ }
+
+
/**
* Checks whether this resource actually exists, in the virtual sense, within the hierarchy
*/
diff --git a/inc/caldav-BIND.php b/inc/caldav-BIND.php
index fc82f15b..674451bd 100644
--- a/inc/caldav-BIND.php
+++ b/inc/caldav-BIND.php
@@ -47,45 +47,88 @@ if ( $destination->Exists() ) {
$request->PreconditionFailed(403,'DAV::can-overwrite',translate('A resource already exists at the destination.'));
}
-$source = new DAVResource( $href );
-if ( !$source->Exists() ) {
- $request->PreconditionFailed(403,'DAV::bind-source-exists',translate('The BIND Request MUST identify an existing resource.'));
-}
+if ( preg_match ( '{^https?://[A-Za-z][^/]*/.+$}', $href ) ) {
+ require_once('external-fetch.php');
+ $qry = new AwlQuery( );
+ $qry->QDo('SELECT collection_id FROM collection WHERE dav_name = :dav_name ', array( ':dav_name' => '/.external/'. md5($href) ));
+ if ( $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
+ $dav_id = $row->dav_id;
+ }
+ else {
+ create_external ( '/.external/'. md5($href) ,true,false );
+ $qry->QDo('SELECT collection_id FROM collection WHERE dav_name = :dav_name ', array( ':dav_name' => '/.external/'. md5($href) ));
+ if ( $qry->rows() != 1 || !($row = $qry->Fetch()) )
+ $request->DoResponse(500,translate('Database Error1'));
+ $dav_id = $row->collection_id;
+ }
-if ( $source->IsPrincipal() || !$source->IsCollection() ) {
- $request->PreconditionFailed(403,'DAV::binding-allowed',translate('DAViCal only allows BIND requests for collections at present.'));
-}
-
-/*
- bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY,
- bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE,
- access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL,
- parent_container TEXT NOT NULL,
- dav_name TEXT UNIQUE NOT NULL,
- dav_displayname TEXT
-*/
-
-$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname )
-VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )';
-$params = array(
- ':target_id' => $source->GetProperty('collection_id'),
- ':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null),
- ':parent_container' => $parent->dav_name(),
- ':session_principal' => $session->principal_id,
- ':dav_name' => $destination_path,
- ':displayname' => $source->GetProperty('displayname')
-);
-$qry = new AwlQuery( $sql, $params );
-if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
- header('Location: '. ConstructURL($destination_path) );
-
- // Uncache anything to do with the target
- $cache = getCacheInstance();
- $cache_ns = 'collection-'.$destination_path;
- $cache->delete( $cache_ns, null );
-
- $request->DoResponse(201);
-}
+ $sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname, external_url, type )
+ VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname, :external_url, :external_type )';
+ $params = array(
+ ':target_id' => $dav_id,
+ ':ticket_id' => null,
+ ':parent_container' => $parent->dav_name(),
+ ':session_principal' => $session->principal_id,
+ ':dav_name' => $destination_path,
+ ':displayname' => $segment,
+ ':external_url' => $href,
+ ':external_type' => 'calendar'
+ );
+ $qry = new AwlQuery( $sql, $params );
+ if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
+ $qry = new AwlQuery( 'SELECT bind_id from dav_binding where dav_name = :dav_name', array( ':dav_name' => $destination_path ) );
+ if ( ! $qry->Exec('BIND',__LINE__,__FILE__) || $qry->rows() != 1 || !($row = $qry->Fetch()) )
+ $request->DoResponse(500,translate('Database Error1'));
+ fetch_external ( $row->bind_id, '' );
+ $request->DoResponse(201);
+ }
+ else {
+ $request->DoResponse(500,translate('Database Error2'));
+ }
+}
else {
- $request->DoResponse(500,translate('Database Error'));
-}
\ No newline at end of file
+ $source = new DAVResource( $href );
+ if ( !$source->Exists() ) {
+ $request->PreconditionFailed(403,'DAV::bind-source-exists',translate('The BIND Request MUST identify an existing resource.'));
+ }
+
+ if ( $source->IsPrincipal() || !$source->IsCollection() ) {
+ $request->PreconditionFailed(403,'DAV::binding-allowed',translate('DAViCal only allows BIND requests for collections at present.'));
+ }
+
+ /*
+ bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY,
+ bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL,
+ parent_container TEXT NOT NULL,
+ dav_name TEXT UNIQUE NOT NULL,
+ dav_displayname TEXT,
+ external_url TEXT,
+ type TEXT
+ */
+
+ $sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname )
+ VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )';
+ $params = array(
+ ':target_id' => $source->GetProperty('collection_id'),
+ ':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null),
+ ':parent_container' => $parent->dav_name(),
+ ':session_principal' => $session->principal_id,
+ ':dav_name' => $destination_path,
+ ':displayname' => $source->GetProperty('displayname')
+ );
+ $qry = new AwlQuery( $sql, $params );
+ if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
+ header('Location: '. ConstructURL($destination_path) );
+
+ // Uncache anything to do with the target
+ $cache = getCacheInstance();
+ $cache_ns = 'collection-'.$destination_path;
+ $cache->delete( $cache_ns, null );
+
+ $request->DoResponse(201);
+ }
+ else {
+ $request->DoResponse(500,translate('Database Error'));
+ }
+}
diff --git a/inc/caldav-GET.php b/inc/caldav-GET.php
index 685d2604..d6885b18 100644
--- a/inc/caldav-GET.php
+++ b/inc/caldav-GET.php
@@ -15,6 +15,9 @@ require_once("DAVResource.php");
$dav_resource = new DAVResource($request->path);
$dav_resource->NeedPrivilege( array('urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read') );
+if ( $dav_resource->IsExternal() ) {
+ update_external ( $dav_resource );
+}
if ( ! $dav_resource->Exists() ) {
$request->DoResponse( 404, translate("Resource Not Found.") );
diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php
index e2a6d633..c2d77b2f 100644
--- a/inc/caldav-REPORT.php
+++ b/inc/caldav-REPORT.php
@@ -61,9 +61,15 @@ switch( $xmltree->GetTag() ) {
include("caldav-REPORT-pps-set.php");
exit; // Not that it should return anyway.
case 'DAV::sync-collection':
+ require_once("external-fetch.php");
+ if ( $target->IsExternal() )
+ update_external ( $target );
include("caldav-REPORT-sync-collection.php");
exit; // Not that it should return anyway.
case 'DAV::expand-property':
+ require_once("external-fetch.php");
+ if ( $target->IsExternal() )
+ update_external ( $target );
include("caldav-REPORT-expand-property.php");
exit; // Not that it should return anyway.
case 'DAV::principal-match':
@@ -313,6 +319,10 @@ function component_to_xml( $properties, $item ) {
return $response;
}
+require_once("external-fetch.php");
+if ( $target->IsExternal() )
+ update_external ( $target );
+
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) {
$calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*");
include("caldav-REPORT-calquery.php");
diff --git a/inc/external-fetch.php b/inc/external-fetch.php
new file mode 100644
index 00000000..731f0089
--- /dev/null
+++ b/inc/external-fetch.php
@@ -0,0 +1,73 @@
+
+* @copyright Rob Ostensen
+* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
+*/
+
+function create_external ( $path,$is_calendar,$is_addressbook )
+{
+ global $request;
+ $resourcetypes = '';
+ if ($is_calendar) $resourcetypes .= '';
+ $qry = new AwlQuery();
+ if ( ! $qry->QDo( 'INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname,
+ is_calendar, is_addressbook, resourcetypes, created, modified )
+ VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname,
+ :is_calendar, :is_addressbook, :resourcetypes, current_timestamp, current_timestamp )',
+ array(
+ ':user_no' => $request->user_no,
+ ':parent_container' => '/.external/',
+ ':dav_name' => $path,
+ ':dav_etag' => md5($request->user_no. $path),
+ ':dav_displayname' => $path,
+ ':is_calendar' => ($is_calendar ? 't' : 'f'),
+ ':is_addressbook' => ($is_addressbook ? 't' : 'f'),
+ ':resourcetypes' => $resourcetypes
+ ) ) ) {
+ $request->DoResponse( 500, translate('Error writing calendar details to database.') );
+ }
+}
+
+function fetch_external ( $bind_id, $min_age )
+{
+ $sql = 'SELECT collection.*, collection.dav_name as path, dav_binding.external_url as external_url FROM dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) WHERE bind_id = :bind_id';
+ $params = array( ':bind_id' => $bind_id );
+ if ( strlen ( $min_age ) > 2 ) {
+ $sql .= ' and collection.modified + interval :interval > NOW()';
+ $params[':interval'] = $min_age;
+ }
+ $qry = new AwlQuery( $sql, $params );
+ if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $row = $qry->Fetch() ) {
+ $curl = curl_init ( $row->external_url );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, true );
+ $ics = curl_exec ( $curl );
+ curl_close ( $curl );
+ if ( is_string ( $ics ) && strlen ( $ics ) > 20 ) {
+ $qry = new AwlQuery( 'UPDATE collection SET modified=NOW() where collection_id = :cid', array ( ':cid' => $row->collection_id ) );
+ $qry->Exec('DAVResource');
+ require_once ( 'caldav-PUT-functions.php');
+ import_collection ( $ics , $row->user_no, $row->path, 'External Fetch' , false ) ;
+ return true;
+ }
+ }
+ return false;
+}
+
+function update_external ( $request )
+{
+ global $c;
+ if ( $c->external_refresh < 1 )
+ return ;
+ $sql = 'SELECT bind_id from dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) collection where dav_name = :dav_name and collection.modified + interval :interval < NOW()';
+ $qry = new AwlQuery( $sql, array ( ':dav_name' => $request->dav_name, ':interval' => $c->external_refresh + ' minutes' ) );
+ if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $row = $qry->Fetch() ) {
+ if ( $row->bind_id != 0 )
+ fetch_external ( $row->bind_id );
+ }
+}