1<?php 2/** 3 * A script for returning a feed (currently Atom) of recent changes to a calendar collection 4 * 5 * @package davical 6 * @subpackage feed 7 * @author Leho Kraav <leho@kraav.com> 8 * @author Andrew McMillan <andrew@morphoss.com> 9 * @license GPL v2 or later 10 */ 11require_once("./always.php"); 12dbg_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 :-(")) ); 13dbg_log_array( "headers", '_SERVER', $_SERVER, true ); 14 15require_once('AwlCache.php'); 16 17require_once("HTTPAuthSession.php"); 18$session = new HTTPAuthSession(); 19 20require_once('CalDAVRequest.php'); 21$request = new CalDAVRequest(); 22 23require_once("vComponent.php"); 24require_once("DAVResource.php"); 25 26 27/** 28 * Function for creating anchor links out of plain text. 29 * Source: http://stackoverflow.com/questions/1960461/convert-plain-text-hyperlinks-into-html-hyperlinks-in-php 30 */ 31function hyperlink( $text ) { 32 return preg_replace( '@(https?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@', '<a href="$1" target="_blank">$1</a>', htmlspecialchars($text) ); 33} 34 35function caldav_get_feed( $request, $collection ) { 36 global $c, $session; 37 38 dbg_error_log("feed", "GET method handler"); 39 40 $collection->NeedPrivilege( array('DAV::read') ); 41 42 if ( ! $collection->Exists() ) { 43 $request->DoResponse( 404, translate("Resource Not Found.") ); 44 } 45 46 if ( !$collection->IsCollection() 47 || !$collection->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections) ) { 48 $request->DoResponse( 405, translate("Feeds are only supported for calendars at present.") ); 49 } 50 51 // Try and pull the answer out of a hat 52 $cache = getCacheInstance(); 53 $cache_ns = 'collection-'.$collection->dav_name(); 54 $cache_key = 'feed'.$session->user_no; 55 $response = $cache->get( $cache_ns, $cache_key ); 56 if ( $response !== false ) return $response; 57 58 $principal = $collection->GetProperty('principal'); 59 60 /** 61 * The CalDAV specification does not define GET on a collection, but typically this is 62 * used as a .ics download for the whole collection, which is what we do also. 63 */ 64 $sql = 'SELECT caldav_data, caldav_type, caldav_data.user_no, caldav_data.dav_name,'; 65 $sql .= ' caldav_data.modified, caldav_data.created, '; 66 $sql .= ' summary, dtstart, dtend, calendar_item.description '; 67 $sql .= ' FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING ( dav_id ) WHERE '; 68 if ( isset($c->get_includes_subcollections) && $c->get_includes_subcollections ) { 69 $sql .= ' (collection.dav_name ~ :path_match '; 70 $sql .= ' OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; 71 $params = array( ':path_match' => '^'.$request->path ); 72 } 73 else { 74 $sql .= ' caldav_data.collection_id = :collection_id '; 75 $params = array( ':collection_id' => $collection->resource_id() ); 76 } 77 $sql .= ' ORDER BY caldav_data.created DESC'; 78 $sql .= ' LIMIT '.(isset($c->feed_item_limit) ? $c->feed_item_limit : 15); 79 $qry = new AwlQuery( $sql, $params ); 80 if ( !$qry->Exec("GET",__LINE__,__FILE__) ) { 81 $request->DoResponse( 500, translate("Database Error") ); 82 } 83 84 /** 85 * Here we are constructing the feed response for this collection, including 86 * the timezones that are referred to by the events we have selected. 87 * Library used: http://framework.zend.com/manual/en/zend.feed.writer.html 88 */ 89 require_once('AtomFeed.php'); 90 $feed = new AtomFeed(); 91 92 $feed->setTitle('DAViCal Atom Feed: '. $collection->GetProperty('displayname')); 93 $url = $c->protocol_server_port . $collection->url(); 94 $url = preg_replace( '{/$}', '.ics', $url); 95 $feed->setLink($url); 96 $feed->setFeedLink($c->protocol_server_port_script . $request->path, 'atom'); 97 $feed->addAuthor(array( 98 'name' => $principal->GetProperty('displayname'), 99 'email' => $principal->GetProperty('email'), 100 'uri' => $c->protocol_server_port . $principal->url(), 101 )); 102 $feed_description = $collection->GetProperty('description'); 103 if ( isset($feed_description) && $feed_description != '' ) $feed->setDescription($feed_description); 104 105 require_once('RRule.php'); 106 107 $need_zones = array(); 108 $timezones = array(); 109 while( $event = $qry->Fetch() ) { 110 if ( $event->caldav_type != 'VEVENT' && $event->caldav_type != 'VTODO' && $event->caldav_type != 'VJOURNAL') { 111 dbg_error_log( 'feed', 'Skipping peculiar "%s" component in VCALENDAR', $event->caldav_type ); 112 continue; 113 } 114 $is_todo = ($event->caldav_type == 'VTODO'); 115 116 $ical = new vComponent( $event->caldav_data ); 117 $event_data = $ical->GetComponents('VTIMEZONE', false); 118 119 $item = $feed->createEntry(); 120 $item->setId( $c->protocol_server_port_script . ConstructURL($event->dav_name) ); 121 122 $dt_created = new RepeatRuleDateTime( $event->created ); 123 $item->setDateCreated( $dt_created->epoch() ); 124 125 $dt_modified = new RepeatRuleDateTime( $event->modified ); 126 $item->setDateModified( $dt_modified->epoch() ); 127 128 $summary = $event->summary; 129 $p_title = ($summary != '' ? $summary : translate('No summary')); 130 if ( $is_todo ) $p_title = "TODO: " . $p_title; 131 $item->setTitle($p_title); 132 133 $content = ""; 134 135 $dt_start = new RepeatRuleDateTime($event->dtstart); 136 if ( $dt_start != null ) { 137 $p_time = '<strong>' . translate('Time') . ':</strong> ' . strftime(translate('%F %T'), $dt_start->epoch()); 138 139 $dt_end = new RepeatRuleDateTime($event->dtend); 140 if ( $dt_end != null ) { 141 $p_time .= ' - ' . ( $dt_end->AsDate() == $dt_start->AsDate() 142 # Translators: his is the formatting of just the time. See http://php.net/manual/en/function.strftime.php 143 ? strftime(translate('%T'), $dt_end->epoch()) 144 # Translators: this is the formatting of a date with time. See http://php.net/manual/en/function.strftime.php 145 : strftime(translate('%F %T'), $dt_end->epoch()) 146 ); 147 } 148 $content .= $p_time; 149 } 150 151 $p_location = $event_data[0]->GetProperty('LOCATION'); 152 if ( $p_location != null ) 153 $content .= '<br />' 154 .'<strong>' . translate('Location') . '</strong>: ' . hyperlink($p_location->Value()); 155 156 $p_attach = $event_data[0]->GetProperty('ATTACH'); 157 if ( $p_attach != null ) 158 $content .= '<br />' 159 .'<strong>' . translate('Attachment') . '</strong>: ' . hyperlink($p_attach->Value()); 160 161 $p_url = $event_data[0]->GetProperty('URL'); 162 if ( $p_url != null ) 163 $content .= '<br />' 164 .'<strong>' . translate('URL') . '</strong>: ' . hyperlink($p_url->Value()); 165 166 $p_cat = $event_data[0]->GetProperty('CATEGORIES'); 167 if ( $p_cat != null ) { 168 $content .= '<br />' .'<strong>' . translate('Categories') . '</strong>: ' . $p_cat->Value(); 169 $categories = explode(',',$p_cat->Value()); 170 foreach( $categories AS $category ) { 171 $item->addCategory( array('term' => trim($category)) ); 172 } 173 } 174 175 $p_description = $event->description; 176 if ( $p_description != '' ) { 177 $content .= '<br />' 178 .'<br />' 179 .'<strong>' . translate('Description') . '</strong>:<br />' . ( nl2br(hyperlink($p_description)) ) 180 ; 181 $item->setDescription($p_description); 182 } 183 184 $item->setContent($content); 185 $feed->addEntry($item); 186 //break; 187 } 188 $last_modified = new RepeatRuleDateTime($collection->GetProperty('modified')); 189 $feed->setDateModified($last_modified->epoch()); 190 $response = $feed->export('atom'); 191 $cache->set( $cache_ns, $cache_key, $response ); 192 return $response; 193} 194 195if ( $request->method == 'GET' ) { 196 $collection = new DAVResource($request->path); 197 $response = caldav_get_feed( $request, $collection ); 198 header( 'Content-Length: '.strlen($response) ); 199 header( 'Etag: '.$collection->unique_tag() ); 200 $request->DoResponse( 200, ($request->method == 'HEAD' ? '' : $response), 'text/xml; charset="utf-8"' ); 201} 202else { 203 dbg_error_log( 'feed', 'Unhandled request method >>%s<<', $request->method ); 204 dbg_log_array( 'feed', '_SERVER', $_SERVER, true ); 205 dbg_error_log( 'feed', 'RAW: %s', str_replace("\n", '',str_replace("\r", '', $request->raw_post)) ); 206} 207 208$request->DoResponse( 500, translate('The application program does not understand that request.') ); 209 210/* vim: set ts=2 sw=2 tw=0 :*/ 211