diff --git a/htdocs/feed.php b/htdocs/feed.php index c59a2f53..f1773516 100644 --- a/htdocs/feed.php +++ b/htdocs/feed.php @@ -9,12 +9,18 @@ require_once("./always.php"); dbg_error_log( "feed", " User agent: %s", ((isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "Unfortunately Mulberry and Chandler don't send a 'User-agent' header with their requests :-(")) ); dbg_log_array( "headers", '_SERVER', $_SERVER, true ); +require_once('AWLCache.php'); + require_once("HTTPAuthSession.php"); $session = new HTTPAuthSession(); require_once('CalDAVRequest.php'); $request = new CalDAVRequest(); +require_once("vComponent.php"); +require_once("DAVResource.php"); + + /** * Function for creating anchor links out of plain text. * Source: http://stackoverflow.com/questions/1960461/convert-plain-text-hyperlinks-into-html-hyperlinks-in-php @@ -23,165 +29,170 @@ function hyperlink( $text ) { return preg_replace( '@(https?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@', '$1', htmlspecialchars($text) ); } -function caldav_get_feed( $request ) { - global $c; +function caldav_get_feed( $request, $collection ) { + global $c, $session; dbg_error_log("feed", "GET method handler"); - require_once("vComponent.php"); - require_once("DAVResource.php"); - - $collection = new DAVResource($request->path); $collection->NeedPrivilege( array('DAV::read') ); if ( ! $collection->Exists() ) { $request->DoResponse( 404, translate("Resource Not Found.") ); } - if ( $collection->IsCollection() ) { - if ( ! $collection->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections) ) { - $request->DoResponse( 405, translate("Feeds are only supported for calendars at present.") ); - } - - $principal = $collection->GetProperty('principal'); - - /** - * The CalDAV specification does not define GET on a collection, but typically this is - * used as a .ics download for the whole collection, which is what we do also. - */ - $sql = 'SELECT caldav_data, caldav_type, caldav_data.user_no, caldav_data.dav_name,'; - $sql .= ' caldav_data.modified, caldav_data.created, '; - $sql .= ' summary, dtstart, dtend, calendar_item.description '; - $sql .= ' FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING ( dav_id ) WHERE '; - if ( isset($c->get_includes_subcollections) && $c->get_includes_subcollections ) { - $sql .= ' (collection.dav_name ~ :path_match '; - $sql .= ' OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; - $params = array( ':path_match' => '^'.$request->path ); - } - else { - $sql .= ' caldav_data.collection_id = :collection_id '; - $params = array( ':collection_id' => $collection->resource_id() ); - } - $sql .= ' ORDER BY caldav_data.created DESC'; - $sql .= ' LIMIT '.(isset($c->feed_item_limit) ? $c->feed_item_limit : 15); - $qry = new AwlQuery( $sql, $params ); - if ( !$qry->Exec("GET",__LINE__,__FILE__) ) { - $request->DoResponse( 500, translate("Database Error") ); - } - - /** - * Here we are constructing the feed response for this collection, including - * the timezones that are referred to by the events we have selected. - * Library used: http://framework.zend.com/manual/en/zend.feed.writer.html - */ - require_once('AtomFeed.php'); - $feed = new AtomFeed(); - - $feed->setTitle('DAViCal Atom Feed: '. $collection->GetProperty('displayname')); - $url = $c->protocol_server_port . $collection->url(); - $url = preg_replace( '{/$}', '.ics', $url); - $feed->setLink($url); - $feed->setFeedLink($c->protocol_server_port_script . $request->path, 'atom'); - $feed->addAuthor(array( - 'name' => $principal->GetProperty('displayname'), - 'email' => $principal->GetProperty('email'), - 'uri' => $c->protocol_server_port . $principal->url(), - )); - $feed_description = $collection->GetProperty('description'); - if ( isset($feed_description) && $feed_description != '' ) $feed->setDescription($feed_description); - - require_once('RRule-v2.php'); - - $need_zones = array(); - $timezones = array(); - while( $event = $qry->Fetch() ) { - if ( $event->caldav_type != 'VEVENT' && $event->caldav_type != 'VTODO' && $event->caldav_type != 'VJOURNAL') { - dbg_error_log( 'feed', 'Skipping peculiar "%s" component in VCALENDAR', $event->caldav_type ); - continue; - } - $is_todo = ($event->caldav_type == 'VTODO'); - - $ical = new vComponent( $event->caldav_data ); - $event_data = $ical->GetComponents('VTIMEZONE', false); - - $item = $feed->createEntry(); - $item->setId( $c->protocol_server_port_script . ConstructURL($event->dav_name) ); - - $dt_created = new RepeatRuleDateTime( $event->created ); - $item->setDateCreated( $dt_created->epoch() ); - - $dt_modified = new RepeatRuleDateTime( $event->modified ); - $item->setDateModified( $dt_modified->epoch() ); - - $summary = $event->summary; - $p_title = ($summary != '' ? $summary : translate('No summary')); - if ( $is_todo ) $p_title = "TODO: " . $p_title; - $item->setTitle($p_title); - - $content = ""; - - $dt_start = new RepeatRuleDateTime($event->dtstart); - if ( $dt_start != null ) { - $p_time = '' . translate('Time') . ': ' . strftime(translate('%F %T'), $dt_start->epoch()); - - $dt_end = new RepeatRuleDateTime($event->dtend); - if ( $dt_end != null ) { - $p_time .= ' - ' . ( $dt_end->AsDate() == $dt_start->AsDate() - ? strftime(translate('%T'), $dt_end->epoch()) - : strftime(translate('%F %T'), $dt_end->epoch()) - ); - } - $content .= $p_time; - } - - $p_location = $event_data[0]->GetProperty('LOCATION'); - if ( $p_location != null ) - $content .= '
' - .'' . translate('Location') . ': ' . hyperlink($p_location->Value()); - - $p_attach = $event_data[0]->GetProperty('ATTACH'); - if ( $p_attach != null ) - $content .= '
' - .'' . translate('Attachment') . ': ' . hyperlink($p_attach->Value()); - - $p_url = $event_data[0]->GetProperty('URL'); - if ( $p_url != null ) - $content .= '
' - .'' . translate('URL') . ': ' . hyperlink($p_url->Value()); - - $p_cat = $event_data[0]->GetProperty('CATEGORIES'); - if ( $p_cat != null ) { - $content .= '
' .'' . translate('Categories') . ': ' . $p_cat->Value(); - $categories = explode(',',$p_cat->Value()); - foreach( $categories AS $category ) { - $item->addCategory( array('term' => trim($category)) ); - } - } - - $p_description = $event->description; - if ( $p_description != '' ) { - $content .= '
' - .'
' - .'' . translate('Description') . ':
' . ( nl2br(hyperlink($p_description)) ) - ; - $item->setDescription($p_description); - } - - $item->setContent($content); - $feed->addEntry($item); - //break; - } - $last_modified = new RepeatRuleDateTime($collection->GetProperty('modified')); - $feed->setDateModified($last_modified->epoch()); - $response = $feed->export('atom'); - header( 'Content-Length: '.strlen($response) ); - header( 'Etag: '.$collection->unique_tag() ); - $request->DoResponse( 200, ($request->method == 'HEAD' ? '' : $response), 'text/xml; charset="utf-8"' ); + if ( !$collection->IsCollection() + || !$collection->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections) ) { + $request->DoResponse( 405, translate("Feeds are only supported for calendars at present.") ); } + + // Try and pull the answer out of a hat + $cache = getCacheInstance(); + $cache_ns = 'collection-'.$collection->dav_name(); + $cache_key = 'feed'.$session->user_no; + $response = $cache->get( $cache_ns, $cache_key ); + if ( $response !== false ) return $response; + + $principal = $collection->GetProperty('principal'); + + /** + * The CalDAV specification does not define GET on a collection, but typically this is + * used as a .ics download for the whole collection, which is what we do also. + */ + $sql = 'SELECT caldav_data, caldav_type, caldav_data.user_no, caldav_data.dav_name,'; + $sql .= ' caldav_data.modified, caldav_data.created, '; + $sql .= ' summary, dtstart, dtend, calendar_item.description '; + $sql .= ' FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING ( dav_id ) WHERE '; + if ( isset($c->get_includes_subcollections) && $c->get_includes_subcollections ) { + $sql .= ' (collection.dav_name ~ :path_match '; + $sql .= ' OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; + $params = array( ':path_match' => '^'.$request->path ); + } + else { + $sql .= ' caldav_data.collection_id = :collection_id '; + $params = array( ':collection_id' => $collection->resource_id() ); + } + $sql .= ' ORDER BY caldav_data.created DESC'; + $sql .= ' LIMIT '.(isset($c->feed_item_limit) ? $c->feed_item_limit : 15); + $qry = new AwlQuery( $sql, $params ); + if ( !$qry->Exec("GET",__LINE__,__FILE__) ) { + $request->DoResponse( 500, translate("Database Error") ); + } + + /** + * Here we are constructing the feed response for this collection, including + * the timezones that are referred to by the events we have selected. + * Library used: http://framework.zend.com/manual/en/zend.feed.writer.html + */ + require_once('AtomFeed.php'); + $feed = new AtomFeed(); + + $feed->setTitle('DAViCal Atom Feed: '. $collection->GetProperty('displayname')); + $url = $c->protocol_server_port . $collection->url(); + $url = preg_replace( '{/$}', '.ics', $url); + $feed->setLink($url); + $feed->setFeedLink($c->protocol_server_port_script . $request->path, 'atom'); + $feed->addAuthor(array( + 'name' => $principal->GetProperty('displayname'), + 'email' => $principal->GetProperty('email'), + 'uri' => $c->protocol_server_port . $principal->url(), + )); + $feed_description = $collection->GetProperty('description'); + if ( isset($feed_description) && $feed_description != '' ) $feed->setDescription($feed_description); + + require_once('RRule-v2.php'); + + $need_zones = array(); + $timezones = array(); + while( $event = $qry->Fetch() ) { + if ( $event->caldav_type != 'VEVENT' && $event->caldav_type != 'VTODO' && $event->caldav_type != 'VJOURNAL') { + dbg_error_log( 'feed', 'Skipping peculiar "%s" component in VCALENDAR', $event->caldav_type ); + continue; + } + $is_todo = ($event->caldav_type == 'VTODO'); + + $ical = new vComponent( $event->caldav_data ); + $event_data = $ical->GetComponents('VTIMEZONE', false); + + $item = $feed->createEntry(); + $item->setId( $c->protocol_server_port_script . ConstructURL($event->dav_name) ); + + $dt_created = new RepeatRuleDateTime( $event->created ); + $item->setDateCreated( $dt_created->epoch() ); + + $dt_modified = new RepeatRuleDateTime( $event->modified ); + $item->setDateModified( $dt_modified->epoch() ); + + $summary = $event->summary; + $p_title = ($summary != '' ? $summary : translate('No summary')); + if ( $is_todo ) $p_title = "TODO: " . $p_title; + $item->setTitle($p_title); + + $content = ""; + + $dt_start = new RepeatRuleDateTime($event->dtstart); + if ( $dt_start != null ) { + $p_time = '' . translate('Time') . ': ' . strftime(translate('%F %T'), $dt_start->epoch()); + + $dt_end = new RepeatRuleDateTime($event->dtend); + if ( $dt_end != null ) { + $p_time .= ' - ' . ( $dt_end->AsDate() == $dt_start->AsDate() + ? strftime(translate('%T'), $dt_end->epoch()) + : strftime(translate('%F %T'), $dt_end->epoch()) + ); + } + $content .= $p_time; + } + + $p_location = $event_data[0]->GetProperty('LOCATION'); + if ( $p_location != null ) + $content .= '
' + .'' . translate('Location') . ': ' . hyperlink($p_location->Value()); + + $p_attach = $event_data[0]->GetProperty('ATTACH'); + if ( $p_attach != null ) + $content .= '
' + .'' . translate('Attachment') . ': ' . hyperlink($p_attach->Value()); + + $p_url = $event_data[0]->GetProperty('URL'); + if ( $p_url != null ) + $content .= '
' + .'' . translate('URL') . ': ' . hyperlink($p_url->Value()); + + $p_cat = $event_data[0]->GetProperty('CATEGORIES'); + if ( $p_cat != null ) { + $content .= '
' .'' . translate('Categories') . ': ' . $p_cat->Value(); + $categories = explode(',',$p_cat->Value()); + foreach( $categories AS $category ) { + $item->addCategory( array('term' => trim($category)) ); + } + } + + $p_description = $event->description; + if ( $p_description != '' ) { + $content .= '
' + .'
' + .'' . translate('Description') . ':
' . ( nl2br(hyperlink($p_description)) ) + ; + $item->setDescription($p_description); + } + + $item->setContent($content); + $feed->addEntry($item); + //break; + } + $last_modified = new RepeatRuleDateTime($collection->GetProperty('modified')); + $feed->setDateModified($last_modified->epoch()); + $response = $feed->export('atom'); + $cache->set( $cache_ns, $cache_key ); + return $response; } if ( $request->method == 'GET' ) { - caldav_get_feed( $request ); + $collection = new DAVResource($request->path); + $response = caldav_get_feed( $request, $collection ); + header( 'Content-Length: '.strlen($response) ); + header( 'Etag: '.$collection->unique_tag() ); + $request->DoResponse( 200, ($request->method == 'HEAD' ? '' : $response), 'text/xml; charset="utf-8"' ); } else { dbg_error_log( 'feed', 'Unhandled request method >>%s<<', $request->method ); diff --git a/inc/caldav-PROPPATCH.php b/inc/caldav-PROPPATCH.php index 1f380ffa..690c2f00 100644 --- a/inc/caldav-PROPPATCH.php +++ b/inc/caldav-PROPPATCH.php @@ -10,6 +10,7 @@ */ dbg_error_log("PROPPATCH", "method handler"); +require_once('AWLCache.php'); require_once('iCalendar.php'); require_once('DAVResource.php'); @@ -306,6 +307,19 @@ if ( count($failure) > 0 ) { */ ; if ( $qry->Commit() ) { + + $cache = getCacheInstance(); + $cache_ns = null; + if ( $dav_resource->IsPrincipal() ) { + $cache_ns = 'principal-'.$dav_resource->dav_name(); + } + else if ( $dav_resource->IsCollection() ) { + // Uncache anything to do with the collection + $cache_ns = 'collection-'.$dav_resource->dav_name(); + } + + if ( isset($cache_ns) ) $cache->delete( $cache_ns, null ); + $url = ConstructURL($request->path); $href = new XMLElement('href', $url ); $desc = new XMLElement('responsedescription', translate("All requested changes were made.") ); diff --git a/inc/caldav-PUT-functions.php b/inc/caldav-PUT-functions.php index 59743bed..5543f0bc 100644 --- a/inc/caldav-PUT-functions.php +++ b/inc/caldav-PUT-functions.php @@ -15,6 +15,7 @@ * return true if it's a whole calendar */ +require_once('AWLCache.php'); require_once('iCalendar.php'); require_once('WritableCollection.php'); @@ -627,6 +628,11 @@ EOSQL; if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) { if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path); } + + // Uncache anything to do with the collection + $cache = getCacheInstance(); + $cache_ns = 'collection-'.preg_replace( '{/.*$}', '/', $path); + $cache->delete( $cache_ns, null ); } @@ -987,6 +993,11 @@ EOSQL; $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $path ) ); $qry->Commit(); + // Uncache anything to do with the collection + $cache = getCacheInstance(); + $cache_ns = 'collection-'.preg_replace( '{/.*$}', '/', $path); + $cache->delete( $cache_ns, null ); + dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return true; // Success!