mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-05-25 02:34:17 +00:00
Basic script to sync from another caldav calendar.
This commit is contained in:
parent
6d6ea5a503
commit
c4c05bd46d
@ -151,6 +151,15 @@ class CalDAVClient {
|
|||||||
$this->headers[] = "Content-type: $type";
|
$this->headers[] = "Content-type: $type";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the calendar_url we will be using for a while.
|
||||||
|
*
|
||||||
|
* @param string $url The calendar_url
|
||||||
|
*/
|
||||||
|
function SetCalendar( $url ) {
|
||||||
|
$this->calendar_url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split response into httpResponse and xmlResponse
|
* Split response into httpResponse and xmlResponse
|
||||||
*
|
*
|
||||||
@ -429,6 +438,27 @@ class CalDAVClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the href containing this property
|
||||||
|
*
|
||||||
|
* @param string $tagname The tag name of the property to find the href for
|
||||||
|
* @param integer $which Which instance of the tag should we use
|
||||||
|
*/
|
||||||
|
function HrefForProp( $tagname, $i = 0 ) {
|
||||||
|
if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
|
||||||
|
$j = $this->xmltags[$tagname][$i];
|
||||||
|
while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::prop' );
|
||||||
|
if ( $j > 0 ) {
|
||||||
|
while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' );
|
||||||
|
if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
|
||||||
|
return $this->xmlnodes[$j]['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the href which has a resourcetype of the specified type
|
* Return the href which has a resourcetype of the specified type
|
||||||
*
|
*
|
||||||
@ -489,11 +519,13 @@ class CalDAVClient {
|
|||||||
$xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
|
$xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
|
||||||
'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
|
'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
|
||||||
|
|
||||||
$principal_url = $this->HrefForResourcetype('DAV::principal');
|
$principal_url = $this->HrefForProp('DAV::principal');
|
||||||
|
|
||||||
foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $v ) {
|
if ( !isset($principal_url) ) {
|
||||||
if ( !isset($principal_url) ) {
|
foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) {
|
||||||
$principal_url = $this->HrefValueInside($v);
|
if ( !isset($principal_url) ) {
|
||||||
|
$principal_url = $this->HrefValueInside($href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +577,7 @@ class CalDAVClient {
|
|||||||
if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) {
|
if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) {
|
||||||
$calendar_urls = array();
|
$calendar_urls = array();
|
||||||
foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
|
foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
|
||||||
$calendar_urls[$this->HrefForResourcetype('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
|
$calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
|
foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
|
||||||
@ -576,6 +608,59 @@ class CalDAVClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all etags for a calendar
|
||||||
|
*/
|
||||||
|
function GetCollectionETags( $url = null ) {
|
||||||
|
if ( isset($url) ) $this->SetCalendar($url);
|
||||||
|
|
||||||
|
$this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1);
|
||||||
|
|
||||||
|
$etags = array();
|
||||||
|
if ( isset($this->xmltags['DAV::getetag']) ) {
|
||||||
|
foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) {
|
||||||
|
$events[$this->HrefForProp('DAV::getetag', $k)] = $v['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $etags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a bunch of events for a calendar with a calendar-multiget report
|
||||||
|
*/
|
||||||
|
function CalendarMultiget( $event_hrefs, $url = null ) {
|
||||||
|
|
||||||
|
if ( isset($url) ) $this->SetCalendar($url);
|
||||||
|
|
||||||
|
$hrefs = array();
|
||||||
|
foreach( $event_hrefs AS $k => $href ) {
|
||||||
|
$hrefs .= '<href>'.rawurlencode($url).'</href>';
|
||||||
|
}
|
||||||
|
$this->body = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<prop><getetag/><C:calendar-data/></prop>
|
||||||
|
$hrefs
|
||||||
|
</C:calendar-query>
|
||||||
|
EOXML;
|
||||||
|
|
||||||
|
$this->requestMethod = "REPORT";
|
||||||
|
$this->SetContentType("text/xml");
|
||||||
|
$this->DoRequest( $this->calendar_url );
|
||||||
|
|
||||||
|
$etags = array();
|
||||||
|
if ( isset($this->xmltags['DAV::getetag']) ) {
|
||||||
|
foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) {
|
||||||
|
$events[$this->HrefForProp('DAV::getetag', $k)] = $v['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $etags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given XML for a calendar query, return an array of the events (/todos) in the
|
* Given XML for a calendar query, return an array of the events (/todos) in the
|
||||||
* response. Each event in the array will have a 'href', 'etag' and '$response_type'
|
* response. Each event in the array will have a 'href', 'etag' and '$response_type'
|
||||||
@ -583,16 +668,17 @@ class CalDAVClient {
|
|||||||
* definition of the calendar data in iCalendar format.
|
* definition of the calendar data in iCalendar format.
|
||||||
*
|
*
|
||||||
* @param string $filter XML fragment which is the <filter> element of a calendar-query
|
* @param string $filter XML fragment which is the <filter> element of a calendar-query
|
||||||
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
|
* @param string $url The URL of the calendar, or null to use the 'current' calendar_url
|
||||||
* @param string $report_type Used as a name for the array element containing the calendar data. @deprecated
|
|
||||||
*
|
*
|
||||||
* @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
|
* @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
|
||||||
* be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
|
* be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
|
||||||
* etag (which only varies when the data changes) and the calendar data in iCalendar format.
|
* etag (which only varies when the data changes) and the calendar data in iCalendar format.
|
||||||
*/
|
*/
|
||||||
function DoCalendarQuery( $filter, $relative_url = '' ) {
|
function DoCalendarQuery( $filter, $url = null ) {
|
||||||
|
|
||||||
$xml = <<<EOXML
|
if ( isset($url) ) $this->SetCalendar($url);
|
||||||
|
|
||||||
|
$this->body = <<<EOXML
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
<D:prop>
|
<D:prop>
|
||||||
@ -602,17 +688,14 @@ class CalDAVClient {
|
|||||||
</C:calendar-query>
|
</C:calendar-query>
|
||||||
EOXML;
|
EOXML;
|
||||||
|
|
||||||
$this->DoXMLRequest( 'REPORT', $xml, $relative_url );
|
$this->requestMethod = "REPORT";
|
||||||
$xml_parser = xml_parser_create_ns('UTF-8');
|
$this->SetContentType("text/xml");
|
||||||
$this->xml_tags = array();
|
$this->DoRequest( $this->calendar_url );
|
||||||
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
|
|
||||||
xml_parse_into_struct( $xml_parser, $this->xmlResponse, $this->xml_tags );
|
|
||||||
xml_parser_free($xml_parser);
|
|
||||||
|
|
||||||
$report = array();
|
$report = array();
|
||||||
foreach( $this->xml_tags as $k => $v ) {
|
foreach( $this->xmltags as $k => $v ) {
|
||||||
switch( $v['tag'] ) {
|
switch( $v['tag'] ) {
|
||||||
case 'DAV::RESPONSE':
|
case 'DAV::response':
|
||||||
if ( $v['type'] == 'open' ) {
|
if ( $v['type'] == 'open' ) {
|
||||||
$response = array();
|
$response = array();
|
||||||
}
|
}
|
||||||
@ -620,13 +703,13 @@ EOXML;
|
|||||||
$report[] = $response;
|
$report[] = $response;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'DAV::HREF':
|
case 'DAV::href':
|
||||||
$response['href'] = basename( $v['value'] );
|
$response['href'] = basename( $v['value'] );
|
||||||
break;
|
break;
|
||||||
case 'DAV::GETETAG':
|
case 'DAV::getetag':
|
||||||
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
|
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
|
||||||
break;
|
break;
|
||||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
|
case 'urn:ietf:params:xml:ns:caldav:calendar-data':
|
||||||
$response['data'] = $v['value'];
|
$response['data'] = $v['value'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
174
scripts/sync-pull.php
Executable file
174
scripts/sync-pull.php
Executable file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if ( @file_exists('../../awl/inc/AWLUtilities.php') ) {
|
||||||
|
set_include_path('../inc:../../awl/inc');
|
||||||
|
}
|
||||||
|
else if ( @file_exists('../awl/inc/AWLUtilities.php') ) {
|
||||||
|
set_include_path('inc:../awl/inc:.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_include_path('../inc:/usr/share/awl/inc');
|
||||||
|
}
|
||||||
|
include('always.php');
|
||||||
|
require_once('AwlQuery.php');
|
||||||
|
require_once('caldav-PUT-functions.php');
|
||||||
|
|
||||||
|
include('caldav-client-v2.php');
|
||||||
|
|
||||||
|
$args = (object) null;
|
||||||
|
$args->sync_all = false;
|
||||||
|
|
||||||
|
function parse_arguments() {
|
||||||
|
global $args;
|
||||||
|
|
||||||
|
$opts = getopt( 'u:U:p:a' );
|
||||||
|
foreach( $opts AS $k => $v ) {
|
||||||
|
switch( $k ) {
|
||||||
|
case 'u': $args->url = $v; break;
|
||||||
|
case 'U': $args->user = $v; break;
|
||||||
|
case 'p': $args->pass = $v; break;
|
||||||
|
case 'a': $args->sync_all = 1; break;
|
||||||
|
case 'c': $args->local_collection_path = $v; break;
|
||||||
|
default: $args->{$k} = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_arguments();
|
||||||
|
|
||||||
|
// E.g.
|
||||||
|
// sync-pull.php -U andrew@example.net -p 53cret -u https://www.google.com/calendar/dav/andrew@example.net/events -c /andrew/gsync/
|
||||||
|
//
|
||||||
|
|
||||||
|
if ( !preg_match( '{/$}', $args->url ) ) $args->url .= '/';
|
||||||
|
|
||||||
|
$caldav = new CalDAVClient( $args->url, $args->user, $args->pass );
|
||||||
|
|
||||||
|
// This will find the 'Principal URL' which we can query for user-related
|
||||||
|
// properties.
|
||||||
|
$principal_url = $caldav->FindPrincipal($args->url);
|
||||||
|
|
||||||
|
// This will find the 'Calendar Home URL' which will be the folder(s) which
|
||||||
|
// contain all of the user's calendars
|
||||||
|
$calendar_home_set = $caldav->FindCalendarHome();
|
||||||
|
|
||||||
|
$calendar = null;
|
||||||
|
|
||||||
|
// This will go through the calendar_home_set and find all of the users
|
||||||
|
// calendars on the remote server.
|
||||||
|
$calendars = $caldav->FindCalendars();
|
||||||
|
if ( count($calendars) < 1 ) {
|
||||||
|
printf( "No calendars found based on '%s'\n", $args->url );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have all of the remote calendars, we will look for the URL that
|
||||||
|
// matches what we were originally supplied. While this seems laborious
|
||||||
|
// because we already have it, it means we could provide a match in some
|
||||||
|
// other way (e.g. on displayname) and we could also present a list to
|
||||||
|
// the user which is built from following the above process.
|
||||||
|
foreach( $calendars AS $k => $a_calendar ) {
|
||||||
|
if ( rawurldecode($a_calendar->url) == rawurldecode($args->url) ) $calendar = $a_calendar;
|
||||||
|
}
|
||||||
|
if ( !isset($calendar) ) $calendar = $calendars[0];
|
||||||
|
|
||||||
|
// In reality we could have omitted all of the above parts, If we really do
|
||||||
|
// know the correct URL at the start.
|
||||||
|
printf( "Calendar '%s' is at %s\n", $calendar->displayname, $calendar->url );
|
||||||
|
|
||||||
|
// Generate a consistent filename for our synchronisation cache
|
||||||
|
$sync_cache_filename = md5($args->url . $args->user . $calendar->url);
|
||||||
|
|
||||||
|
// Do we just need to sync everything across and overwrite all the local stuff?
|
||||||
|
$sync_all = ( !file_exists($sync_cache_filename) || $args->sync_all);
|
||||||
|
|
||||||
|
if ( ! $sync_all ) {
|
||||||
|
/**
|
||||||
|
* Read a structure out of the cache file containing:
|
||||||
|
* server_getctag - A collection tag (string)
|
||||||
|
* server_etags - An array of event tags (strings) keyed on filename, from the server
|
||||||
|
* local_etags - An array of event tags (strings) keyed on filename, from local DAViCal
|
||||||
|
*/
|
||||||
|
$cache = unserialize( file_get_contents($sync_cache_filename) );
|
||||||
|
|
||||||
|
// First compare the ctag for the calendar
|
||||||
|
if ( isset($cache) && isset($cache->server_ctag) && isset($calendar->getctag) && $calendar->getctag == $cache->server_ctag ) {
|
||||||
|
printf( 'No changes to calendar "%s"'."\n", $args->url );
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !isset($cache) || !isset($cache->server_ctag) ) $sync_all = true;
|
||||||
|
|
||||||
|
// Everything now will be at our calendar URL
|
||||||
|
$caldav->SetCalendar($calendar->url);
|
||||||
|
|
||||||
|
// So it seems we do need to sync. We now need to check each individual event
|
||||||
|
// which might have changed, so we pull a list of event etags from the server.
|
||||||
|
$server_etags = $caldav->GetCollectionETags($calendar->url);
|
||||||
|
|
||||||
|
$newcache = (object) array( 'server_ctag' => $calendar->getctag, 'server_etags' => array(), 'local_etags' => array() );
|
||||||
|
|
||||||
|
if ( $sync_all ) {
|
||||||
|
// The easy case. Sync them all, delete nothing
|
||||||
|
$insert_urls = array_flip($server_etags);
|
||||||
|
$update_urls = array();
|
||||||
|
$delete_urls = array();
|
||||||
|
foreach( $server_etags AS $href => $etag ) {
|
||||||
|
$fname = preg_replace('{^.*/}', '', $href);
|
||||||
|
$newcache->server_etags[$fname] = $etag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Only sync the ones where the etag has changed. Delete any that are no
|
||||||
|
// longer present at the remote end.
|
||||||
|
$insert_urls = array();
|
||||||
|
$update_urls = array();
|
||||||
|
foreach( $server_etags AS $href => $etag ) {
|
||||||
|
$fname = preg_replace('{^.*/}', '', $href);
|
||||||
|
$newcache->server_etags[$fname] = $etag;
|
||||||
|
if ( isset($cache->server_etags[$fname]) ) {
|
||||||
|
$cache_etag = $cache->server_etags[$fname];
|
||||||
|
unset($cache->server_etags[$fname]);
|
||||||
|
if ( $cache_etag == $etag ) continue;
|
||||||
|
$update_urls[] = $href;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$insert_urls[] = $href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$delete_urls = array_flip($cache->server_etags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch the calendar data
|
||||||
|
$events = $caldav->CalendarMultiget( array_merge( $insert_urls, $update_urls) );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @TODO: We should really check for collisions locally in case the local ETag is
|
||||||
|
* also different to the one we saved earlier.
|
||||||
|
*/
|
||||||
|
// Update the local system with these events
|
||||||
|
foreach( $events AS $href => $event ) {
|
||||||
|
// Do what we need to write $v into the local calendar we are syncing to
|
||||||
|
// at the
|
||||||
|
$fname = preg_replace('{^.*/}', '', $href);
|
||||||
|
$local_fname = $args->local_collection_path . $fname;
|
||||||
|
simple_write_resource( $local_fname, $event, (isset($insert_urls[$href]) ? 'INSERT' : 'UPDATE') );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @TODO: We should not delete locally in the case that the local ETag is different
|
||||||
|
* to the one we saved earlier.
|
||||||
|
*/
|
||||||
|
// Delete any events which were present in our cache, but are not on the master server
|
||||||
|
foreach( $delete_urls AS $k => $v ) {
|
||||||
|
$fname = preg_replace('{^.*/}', '', $href);
|
||||||
|
$local_fname = $args->local_collection_path . $fname;
|
||||||
|
$qry = new AwlQuery('DELETE FROM caldav_data WHERE dav_name = :dav_name', array( ':dav_name' => $local_fname ) );
|
||||||
|
$qry->Exec('sync_pull',__LINE__,__FILE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now (re)write the cache file reflecting the current state.
|
||||||
|
$cache_file = fopen($sync_cache_filename, 'w');
|
||||||
|
fwrite( $cache_file, serialize($newcache) );
|
||||||
|
fclose($cache_file);
|
||||||
Loading…
x
Reference in New Issue
Block a user