aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--default_config.php1
-rw-r--r--functions/date_functions.php115
-rw-r--r--functions/ical_parser.php188
3 files changed, 237 insertions, 67 deletions
diff --git a/default_config.php b/default_config.php
index eab6212..19c3549 100644
--- a/default_config.php
+++ b/default_config.php
@@ -55,6 +55,7 @@ class Configs{
// Calendar Caching (decreases page load times)
$this->save_parsed_cals = 'no'; // Saves a copy of the cal in /tmp after it's been parsed. Improves performance.
$this->tmp_dir = '/tmp'; // The temporary directory on your system (/tmp is fine for UNIXes including Mac OS X). Any php-writable folder works.
+ $this->webcal_hours = '24'; // Number of hours to cache webcals. Setting to '0' will always re-parse webcals if they've been modified.
// Webdav style publishing
$this->phpicalendar_publishing = '0'; // Set to '1' to enable remote webdav style publish. See 'calendars/publish.php' for complete information;
diff --git a/functions/date_functions.php b/functions/date_functions.php
index 43a15a0..cd82fd5 100644
--- a/functions/date_functions.php
+++ b/functions/date_functions.php
@@ -4,28 +4,101 @@ require_once(BASE."functions/is_daylight.php");
// functions for returning or comparing dates
-// get remote file last modification date (returns unix timestamp)
-function remote_filemtime($url) {
- $fp = fopen($url, 'r');
- if (!$fp) return 0;
- $metadata = stream_get_meta_data($fp);
- fclose($fp);
-
- $unixtime = 0;
- foreach ($metadata['wrapper_data'] as $response) {
- // case: redirection
- // WARNING: does not handle relative redirection
- if (substr(strtolower($response), 0, 10) == 'location: ') {
- return GetRemoteLastModified(substr($response, 10));
- }
- // case: last-modified
- else if (substr(strtolower($response), 0, 15) == 'last-modified: ') {
- $unixtime = strtotime(substr($response, 15));
- break;
+/*
+ * Get remote file last modification date (returns unix timestamp)
+ * Supports HTTPS, HTTP basic authentication, and location: redirects
+ * FIXME: Basic auth should not be sent over unencrypted connections
+ * unless an HTTP 401 Unauthorized is returned
+ */
+function remote_filemtime($url, $recurse = 0) {
+ // We hate infinite loops!
+ if (++$recurse > 5) return 0;
+
+ // Caching the remote mtime is a Really Good Idea.
+ static $remote_files = array();
+ if (isset($remote_files[$url])) return $remote_files[$url];
+
+ $uri = parse_url($url);
+ $uri['proto'] = (
+ (isset($uri['proto']) && ($uri['proto'] == 'https')) ?
+ 'ssl://' :
+ ''
+ );
+ $uri['port'] = isset($uri['port']) ? $uri['port'] : 80;
+ $path = (
+ (isset($uri['path']) || isset($uri['query'])) ?
+ (@$uri['path'] . @$uri['query']) :
+ '/'
+ );
+ $auth = (
+ (isset($uri['user']) || isset($uri['pass'])) ?
+ ('Authentication: Basic ' . base64_encode(@$uri['user'] . ':' . @$uri['pass']) . "\r\n") :
+ ''
+ );
+
+ $handle = @fsockopen($uri['proto'] . $uri['host'], $uri['port']);
+ if (!$handle) {
+ $remote_files[$url] = 0;
+ return 0;
+ }
+
+ fputs($handle, "HEAD {$path} HTTP/1.1\r\nHost: {$uri['host']}\r\n{$auth}Connection: close\r\n\r\n");
+ $headers = array();
+ while (!feof($handle)) {
+ $line = trim(fgets($handle, 1024));
+ if (empty($line)) break;
+ $headers[] = $line;
+ }
+ fclose($handle);
+
+ $result = 0;
+ array_shift($headers);
+ foreach ($headers as $header) {
+ list($key, $value) = explode(':', $header, 2);
+ $value = trim($value);
+
+ switch (strtolower(trim($key))) {
+ case 'location': // Redirect
+ $result = remote_filemtime(resolve_path($url, $value), $recurse);
+ break;
+
+ case 'last-modified': // Got it!
+ $result = strtotime($value);
+ break;
}
}
- return $unixtime;
+ $remote_files[$url] = $result;
+ return $result;
+}
+
+/*
+ * Resolve relative paths
+ * Utility function for remote_filemtime()
+ */
+function resolve_path($url, $rel_path) {
+ $uri = parse_url($url);
+
+ $uri['proto'] = (isset($uri['proto']) ? $uri['proto'] : 'http://');
+ $uri['port'] = (isset($uri['port']) ? (':' . $uri['port']) : '');
+ $auth = (
+ (isset($uri['user']) || isset($uri['pass'])) ?
+ (urlencode(@$uri['user']) . ':' . urlencode(@$uri['pass']) . '@') :
+ ''
+ );
+
+ if (parse_url($rel_path) === false) {
+ // Path is relative to this domain
+ $rel_path = str_replace('\\', '/', $rel_path);
+
+ if ($rel_path{0} == '/')
+ return $uri['proto'] . '://' . $auth . $uri['host'] . $uri['port'] . $rel_path;
+
+ return $uri['proto'] . '://' . $auth . $uri['host'] . $uri['port'] . $uri['path'] . '/' . $rel_path;
+ }
+
+ // Path is absolute
+ return $rel_path;
}
// takes iCalendar 2 day format and makes it into 3 characters
@@ -235,10 +308,10 @@ function openevent($event_date, $time, $uid, $arr, $lines = 0, $length = 0, $lin
# for iCal pseudo tag <http> comptability
if (ereg('<([[:alpha:]]+://)([^<>[:space:]]+)>',$event_text,$matches)) {
$full_event_text = $matches[1] . $matches[2];
- $event_text = $matches[2];
+ $event_text = $matches[2];
} else {
$full_event_text = $event_text;
- $event_text = strip_tags($event_text, '<b><i><u><img>');
+ $event_text = strip_tags($event_text, '<b><i><u><img>');
}
if (!empty($link_class)) $link_class = ' class="'.$link_class.'"';
diff --git a/functions/ical_parser.php b/functions/ical_parser.php
index 74a281a..36b2b3e 100644
--- a/functions/ical_parser.php
+++ b/functions/ical_parser.php
@@ -8,37 +8,59 @@ include_once(BASE.'functions/timezones.php');
include_once(BASE.'functions/parse/recur_functions.php');
// reading the file if it's allowed
+$realcal_mtime = time();
$parse_file = true;
-if ($phpiCal_config->save_parsed_cals == 'yes') {
+if ($phpiCal_config->save_parsed_cals == 'yes') {
if (sizeof ($cal_filelist) > 1) {
+ // This is a special case for "all calendars combined"
$parsedcal = $phpiCal_config->tmp_dir.'/parsedcal-'.urlencode($cpath.'::'.$phpiCal_config->ALL_CALENDARS_COMBINED).'-'.$this_year;
if (file_exists($parsedcal)) {
$fd = fopen($parsedcal, 'r');
$contents = fread($fd, filesize($parsedcal));
fclose($fd);
$master_array = unserialize($contents);
- $z=1;
$y=0;
+ // Check the calendars' last-modified time to determine if any need to be re-parsed
if (sizeof($master_array['-4']) == (sizeof($cal_filelist))) {
foreach ($master_array['-4'] as $temp_array) {
- $mtime = $master_array['-4'][$z]['mtime'];
- $fname = $master_array['-4'][$z]['filename'];
- $wcalc = $master_array['-4'][$z]['webcal'];
-
- if ($wcalc == 'no') $realcal_mtime = filemtime($fname);
- else $realcal_mtime = remote_filemtime($fname);
+ $mtime = $temp_array['mtime'];
+ $fname = $temp_array['filename'];
+ $wcalc = $temp_array['webcal'];
+
+ if ($wcalc == 'no') {
+ /*
+ * Getting local file mtime is "fairly cheap"
+ * (disk I/O is expensive, but *much* cheaper than going to the network for remote files)
+ */
+ $realcal_mtime = filemtime($fname);
+ }
+ else if ((time() - $mtime) >= $phpiCal_config->webcal_hours * 60 * 60) {
+ /*
+ * We limit remote file mtime checks based on the magic webcal_hours config variable
+ * This allows highly volatile web calendars to be cached for a period of time before
+ * downloading them again
+ */
+ $realcal_mtime = remote_filemtime($fname);
+ }
+ else {
+ // This is our fallback, for the case where webcal_hours is taking effect
+ $realcal_mtime = $mtime;
+ }
- if ($mtime == $realcal_mtime) {
+ // If this calendar is up-to-date, the $y magic number will be incremented...
+ if ($mtime >= $realcal_mtime) {
$y++;
}
- $z++;
}
+
foreach ($master_array['-3'] as $temp_array) {
if (isset($temp_array) && $temp_array !='') $caldisplaynames[] = $temp_array;
}
+ // And the $y magic number is used here to determine if all calendars are up-to-date
if ($y == sizeof($cal_filelist)) {
if ($master_array['-1'] == 'valid cal file') {
+ // At this point, all calendars are up-to-date, so we can simply used the pre-parsed data
$parse_file = false;
$calendar_name = $master_array['calendar_name'];
$calendar_tz = $master_array['calendar_tz'];
@@ -46,20 +68,25 @@ if ($phpiCal_config->save_parsed_cals == 'yes') {
}
}
}
- if ($parse_file == true) unset($master_array);
+ if ($parse_file == true) {
+ // We need to re-parse at least one calendar, so unset master_array
+ unset($master_array);
+ }
} else {
foreach ($cal_filelist as $filename) {
- if (substr($filename, 0, 7) == 'http://' || substr($filename, 0, 8) == 'https://' || substr($filename, 0, 9) == 'webcal://') {
- $realcal_mtime = remote_filemtime($filename);
- }
- else {
- $realcal_mtime = filemtime($filename);
- }
-
$parsedcal = $phpiCal_config->tmp_dir.'/parsedcal-'.urlencode($cpath.'::'.$cal_filename).'-'.$this_year;
if (file_exists($parsedcal)) {
$parsedcal_mtime = filemtime($parsedcal);
- if ($realcal_mtime == $parsedcal_mtime) {
+
+ if (((time() - $parsedcal_mtime) >= $phpiCal_config->webcal_hours * 60 * 60) &&
+ (substr($filename, 0, 7) == 'http://' || substr($filename, 0, 8) == 'https://' || substr($filename, 0, 9) == 'webcal://')) {
+ $realcal_mtime = remote_filemtime($filename);
+ }
+ else {
+ $realcal_mtime = $parsedcal_mtime;
+ }
+
+ if ($parsedcal_mtime >= $realcal_mtime) {
$fd = fopen($parsedcal, 'r');
$contents = fread($fd, filesize($parsedcal));
fclose($fd);
@@ -95,15 +122,16 @@ foreach ($cal_filelist as $cal_key=>$filename) {
$cal_httpsPrefix = str_replace(array('http://', 'webcal://'), 'https://', $filename);
$filename = $cal_httpPrefix;
$master_array['-4'][$calnumber]['webcal'] = 'yes';
- $actual_mtime = @remote_filemtime($filename);
+ $actual_mtime = remote_filemtime($filename);
} else {
- $actual_mtime = @filemtime($filename);
+ $actual_mtime = filemtime($filename);
}
- include(BASE.'functions/parse/parse_tzs.php');
+ $is_std = false;
+ $is_daylight = false;
- $ifile = @fopen($filename, "r");
+ $ifile = @fopen($filename, 'r');
if ($ifile == FALSE) exit(error($lang['l_error_cantopen'], $filename));
$nextline = fgets($ifile, 1024);
#if (trim($nextline) != 'BEGIN:VCALENDAR') exit(error($lang['l_error_invalidcal'], $filename));
@@ -120,17 +148,55 @@ foreach ($cal_filelist as $cal_key=>$filename) {
while (!feof($ifile)) {
$line = $nextline;
$nextline = fgets($ifile, 1024);
- $nextline = ereg_replace("[\r\n]", "", $nextline);
+ $nextline = ereg_replace("[\r\n]", '', $nextline);
#handle continuation lines that start with either a space or a tab (MS Outlook)
- while (isset($nextline{0}) && ($nextline{0} == " " || $nextline{0} == "\t")) {
+ while (isset($nextline{0}) && ($nextline{0} == ' ' || $nextline{0} == "\t")) {
$line = $line . substr($nextline, 1);
$nextline = fgets($ifile, 1024);
- $nextline = ereg_replace("[\r\n]", "", $nextline);
+ $nextline = ereg_replace("[\r\n]", '', $nextline);
}
- $line = str_replace('\n',"\n",$line);
- $line = str_replace('\t',"\t",$line);
+ $line = str_replace('\n', "\n", $line);
+ $line = str_replace('\t', "\t", $line);
$line = trim(stripslashes($line));
switch ($line) {
+ // Begin VTIMEZONE Parsing
+ //
+ case 'BEGIN:VTIMEZONE':
+ unset($tz_name, $offset_from, $offset_to, $tz_id);
+ break;
+ case 'BEGIN:STANDARD':
+ unset ($offset_s);
+ $is_std = true;
+ $is_daylight = false;
+ break;
+ case 'END:STANDARD':
+ $offset_s = $offset_to;
+ $is_std = false;
+ break;
+ case 'BEGIN:DAYLIGHT':
+ unset ($offset_d);
+ $is_daylight = true;
+ $is_std = false;
+ break;
+ case 'END:DAYLIGHT':
+ $offset_d = $offset_to;
+ $is_daylight = false;
+ break;
+ case 'END:VTIMEZONE':
+ if (!isset($offset_d) && isset($offset_s)) $offset_d = $offset_s;
+ $tz_array[$tz_id] = array(
+ 0 => @$offset_s,
+ 1 => @$offset_d,
+ 'dt_start' => @$begin_daylight,
+ 'st_start' => @$begin_std,
+ 'st_name' => @$st_name,
+ 'dt_name' => @$dt_name
+
+ ); #echo "<pre>$tz_id"; print_r($tz_array[$tz_id]);echo"</pre>";
+ break;
+
+ // Begin VFREEBUSY/VEVENT Parsing
+ //
case 'BEGIN:VFREEBUSY':
case 'BEGIN:VEVENT':
// each of these vars were being set to an empty string
@@ -175,8 +241,11 @@ foreach ($cal_filelist as $cal_key=>$filename) {
break;
case 'END:VFREEBUSY':
case 'END:VEVENT':
- include BASE."functions/parse/end_vevent.php";
+ include BASE.'functions/parse/end_vevent.php';
break;
+
+ // Begin VTODO Parsing
+ //
case 'END:VTODO':
if (($vtodo_priority == '') && ($status == 'COMPLETED')) {
$vtodo_sort = 11;
@@ -233,6 +302,9 @@ foreach ($cal_filelist as $cal_key=>$filename) {
$class = '';
$description = '';
break;
+
+ // Begin VALARM Parsing
+ //
case 'BEGIN:VALARM':
$valarm_set = TRUE;
break;
@@ -251,7 +323,24 @@ foreach ($cal_filelist as $cal_key=>$filename) {
if ($prop_pos !== false) $property = substr($property,0,$prop_pos);
switch ($property) {
-
+ // Start TZ Parsing
+ //
+ case 'TZID':
+ $tz_id = $data;
+ break;
+ case 'TZOFFSETFROM':
+ $offset_from = $data;
+ break;
+ case 'TZOFFSETTO':
+ $offset_to = $data;
+ break;
+ case 'TZNAME':
+ if ($is_std) $st_name = $data;
+ if ($is_daylight) $dt_name = $data;
+ break;
+ //
+ // End TZ Parsing
+
// Start VTODO Parsing
//
case 'DUE':
@@ -286,19 +375,26 @@ foreach ($cal_filelist as $cal_key=>$filename) {
$vtodo_categories = "$data";
break;
//
- // End VTODO Parsing
+ // End VTODO Parsing
case 'DTSTART':
$datetime = extractDateTime($data, $property, $field);
$start_unixtime = $datetime[0];
$start_date = $datetime[1];
- $start_time = $datetime[2];
- $allday_start = $datetime[3];
- $start_tz = $datetime[4];
- preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
- $vevent_start_date = $regs[1] . $regs[2] . $regs[3];
- $day_offset = dayCompare($start_date, $vevent_start_date);
- #echo date("Ymd Hi", $start_unixtime)." $start_date $start_time $vevent_start_date $day_offset<br>";
+ if ($is_std || $is_daylight) {
+ $year = substr($start_date, 0, 4);
+ if ($is_std) $begin_std[$year] = $data;
+ if ($is_daylight) $begin_daylight[$year] = $data;
+ }
+ else {
+ $start_time = $datetime[2];
+ $allday_start = $datetime[3];
+ $start_tz = $datetime[4];
+ preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
+ $vevent_start_date = $regs[1] . $regs[2] . $regs[3];
+ $day_offset = dayCompare($start_date, $vevent_start_date);
+ #echo date("Ymd Hi", $start_unixtime)." $start_date $start_time $vevent_start_date $day_offset<br>";
+ }
break;
case 'DTEND':
@@ -310,7 +406,7 @@ foreach ($cal_filelist as $cal_key=>$filename) {
break;
case 'EXDATE':
- $data = split(",", $data);
+ $data = split(',', $data);
foreach ($data as $exdata) {
$exdata = str_replace('T', '', $exdata);
$exdata = str_replace('Z', '', $exdata);
@@ -424,11 +520,11 @@ foreach ($cal_filelist as $cal_key=>$filename) {
}
break;
case 'ATTENDEE':
- $email = preg_match("/mailto:(.*)/i", $data, $matches1);
- $name = preg_match("/CN=([^;]*)/i", $field, $matches2);
- $rsvp = preg_match("/RSVP=([^;]*)/i", $field, $matches3);
- $partstat = preg_match("/PARTSTAT=([^;]*)/i", $field, $matches4);
- $role = preg_match("/ROLE=([^;]*)/i", $field, $matches5);
+ $email = preg_match('/mailto:(.*)/i', $data, $matches1);
+ $name = preg_match('/CN=([^;]*)/i', $field, $matches2);
+ $rsvp = preg_match('/RSVP=([^;]*)/i', $field, $matches3);
+ $partstat = preg_match('/PARTSTAT=([^;]*)/i', $field, $matches4);
+ $role = preg_match('/ROLE=([^;]*)/i', $field, $matches5);
$email = ($email ? $matches1[1] : '');
$name = ($name ? $matches2[1] : $email);
@@ -447,8 +543,8 @@ foreach ($cal_filelist as $cal_key=>$filename) {
);
break;
case 'ORGANIZER':
- $email = preg_match("/mailto:(.*)/i", $data, $matches1);
- $name = preg_match("/CN=([^;]*)/i", $field, $matches2);
+ $email = preg_match('/mailto:(.*)/i', $data, $matches1);
+ $name = preg_match('/CN=([^;]*)/i', $field, $matches2);
$email = ($email ? $matches1[1] : '');
$name = ($name ? $matches2[1] : $email);

© 2014-2024 Faster IT GmbH | imprint | privacy policy