Files
wordpress-preseed/wp-content/plugins/amr-ical-events-list/includes/amr-import-ical.php
2019-09-11 19:08:46 +02:00

1289 lines
45 KiB
PHP

<?php
/*
* This file incudes functions for parsing iCal data files duringan import.
/* It endeavours to parse as incluisive;y as much as possible.
/* It includes functions to cache the file
/* It is not a validator!
/* The function will return a nested array
properties
vevents
event1
parameters
repeatable parameters
repeat 1
repeat 2
event2
vtodos etc
*
* The iCal specification is available online at:
* http://www.ietf.org/rfc/rfc2445.txt
*
*/
include 'timezones/amr-windows-zones.php';
/* Return the full path to the cache file for the specified URL.*/
function get_cache_file($url) {
return get_cache_path() .'/'. amr_get_cache_filename($url);
}
/* Attempt to create the cache directory if it doesn't exist.
* Return the path if successful.
*/
function get_cache_path() {
global $amr_options;
$cache_path = (ICAL_EVENTS_CACHE_LOCATION. '/ical-events-cache');
if (!file_exists($cache_path)) { /* if there is no folder */
if (wp_mkdir_p($cache_path, 0777)) {
printf('<br />'.__('Your cache directory %s has been created','amr-ical-events-list'),'<code>'.$cache_path.'</code>');
}
else {
die( '<br />'.sprintf(__('Error creating cache directory %s. Please check permissions','amr-ical-events-list'),$cache_path));
}
}
return $cache_path;
}
/* Return the cache filename for the specified URL. */
function amr_get_cache_filename($url) {
$extension = ICAL_EVENTS_CACHE_DEFAULT_EXTENSION;
$matches = array();
if (preg_match('/\.(\w+)$/', $url, $matches)) {
$extension = $matches[1];
}
return md5($url) . ".$extension";
}
/* Cache the specified URL and return the name of the destination file. */
if( !class_exists( 'WP_Http' ) )
include_once( ABSPATH . WPINC. '/class-http.php' );
function amr_check_start_of_file ($data) {// check if the file looks like a icsfile
if (empty($data)) return false;
$checkstart = substr($data,0,15);
if (!($checkstart == 'BEGIN:VCALENDAR')) {
If (ICAL_EVENTS_DEBUG) {
echo '<br /> No VCALENDAR in file. Start has: '.$checkstart.' end';
}
echo '<a class="error" href="#" title="'
.__('Unexpected data contents. Please tell administrator.','amr-ical-events-list' ). ' '
.__('See comments in source for response received from ics server.','amr-ical-events-list' )
.'">!</a>';
echo '<!-- Some of the content returned is: '; var_dump(substr ($data,0,200)); echo ' end of dump -->';
return false;
}
return true;
}
function amr_set_http_timeout($val) {
global $amr_options;
if (!empty($amr_options['timeout']))
return ($amr_options['timeout']);
else
return $val;
}
function amr_cache_url($url, $cache=ICAL_EVENTS_CACHE_TTL) {
global $amr_lastcache;
global $amr_globaltz;
global $amr_options;
$text = '';
// if any args are sent then all must be sent - use wp defaults more or less
// so better to use filters
add_filter( 'http_request_timeout', 'amr_set_http_timeout' );
//add_filter( 'http_request_redirection_count', 'amr_' );
//'httpversion' => apply_filters( 'http_request_version', '1.0' ), //or 1.1
/*
curl_setopt($c, CURLOPT_RETURNTRANSFER, true); // just says to return rather than echo
curl_setopt($c, CURLOPT_USERAGENT, 'PHP/'.PHP_VERSION);
curl_setopt($c, CURLOPT_ENCODING, '');
if( strstr( $resource, 'https' ) !== FALSE ) {
curl_setopt($c, CURLOPT_SSLVERSION, 3);
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
}
curl_setopt($c, CURLOPT_COOKIESESSION, true);
curl_setopt($c, CURLOPT_HEADER, true);
if( !ini_get('safe_mode') ){
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
}
*/
// If (ICAL_EVENTS_DEBUG) echo '<hr />url before decode: '.$url.'<br />';
$url = html_entity_decode($url);
// If (ICAL_EVENTS_DEBUG) echo '<br />url decoded: '.$url.'<hr />';
$cachedfile = get_cache_file($url);
if ( file_exists($cachedfile) ) {
$c = filemtime($cachedfile);
if ($c)
$amr_lastcache = amr_newDateTime(strftime('%c',$c));
else
$amr_lastcache = '';
}
else {
$c = false;
$amr_lastcache = amr_newDateTime(strftime('%c',0));
}
// must we refresh ?
if ( isset($_REQUEST['nocache']) or isset($_REQUEST['refresh'])
or (!(file_exists($cachedfile))) or ((time() - ($c)) >= ($cache*60*60))) {
If (ICAL_EVENTS_DEBUG) echo '<br>Get ical file remotely, it is time to refresh or it is not cached: <br />';
amrical_mem_debug('We are going to refresh next');
//$url = urlencode($u); - do NOT encode - that gives an invalid URL response
$check = wp_remote_get($url);
// if use args, must set all - rather use filters and let wp do its thing
if (( is_wp_error($check) ) or (isset ($check['response']['code']) and !($check['response']['code'] == 200))
or (isset ($check[0]) and preg_match ('#404#', $check[0]))) {/* is this bit still meaningful or needed ? */
If (ICAL_EVENTS_DEBUG) { echo '<hr /><b>Http request failed </b><br /> Dumping response: ';
var_dump($check);
}
if (is_wp_error($check))
$text = '<br />'.$check->get_error_message().'</br>';
else $text = '';
$data = false;
}
elseif (!stristr($check['headers']['content-type'],'text/calendar')) {
if (amr_check_start_of_file ($data = $check['body'])) { // well wrong content type, but has the content!! - bad calendar provider
$data = $check['body'];
if (current_user_can('manage_options')) {
echo '<br />This message is only shown to the administrator, and only when we refresh the file.';
echo '<br />The ics url given is issuing an incorrect content type of text/html.'
.' It should be text/calendar. '
.' Luckily we persevere and check if the content looks like an ics file.'
.'Please inform the provider of the url. '
.'Their urls may not be recognised by browsers as ics files. <br />';
}
}
else {
if (ICAL_EVENTS_DEBUG) {
echo '<br />The url given is not returning a calendar file';
echo '<br />The response was '; var_dump($check['response']);
echo '<br />The content type is '.$check['headers']['content-type'];
echo '<br />The content type of an ics file should be text/calendar. <br />';
}
$data = false;
}
}
else $data = $check['body']; // from the http request
if (!amr_check_start_of_file ($data)) {
$text .= '&nbsp;'.sprintf(__('Error getting calendar file with htpp or curl %s','amr-ical-events-list'), $url);
if ( file_exists($cachedfile) ) { // Try use cached file if it exists
if (is_object($amr_lastcache))
$text .= '&nbsp;...'.sprintf(__('Using File last cached at %s','amr-ical-events-list'), $amr_lastcache->format('D c'));
else
$text .= '&nbsp;...'.__('File last cached time not available','amr-ical-events-list');
echo '<a class="error" href="#" title="'
.__('Warning: Events may be out of date. ','amr-ical-events-list' )
. $text.'">!</a>';
return($cachedfile); //return file not data
}
else {
echo '<a class="error" href="#" title="'
.__('No cached ical file for events','amr-ical-events-list' )
. $text.'">!</a>';
return (false);
}
return (false);
}
else If (ICAL_EVENTS_DEBUG) { echo '<br />We have vcalendar in start of file';}
// somebody wanted to pre process ics files that were not well generated?
// A filter could be added here, but I'm not keen - could add to support load?
if ($data) { /* now save it as a cached file */
$data = apply_filters('amr-ics-filter', $data, $url);
if ($dest = fopen($cachedfile, 'w')) {
if (!(fwrite($dest, $data)))
die ('Error writing cache file'.$dest);
fclose($dest);
$amr_lastcache = amr_newDateTime (date('Y-m-d H:i:s'));
}
else {
echo '<br />Error opening or creating the cached file <br />'.$cachedfile;
return (false);
}
}
else { echo '<br>Error opening remote file for refresh '.$url; return false;}
if (!isset($amr_lastcache)) $amr_lastcache = amr_newDateTime (date('Y-m-d H:i:s'));
}
else {}// no need to refresh, use the cached file
return ($cachedfile);
}
function amr_checkQuotedString($delimiter, $replace, $input) { // rfc 5545 says colons, semicoln and quoted strings can/must be inside doublequotes
$t = $input;
if (stristr($t, '="')) { //if have '="' then have a quoted parameter value
// get the substring within double quotes
if (preg_match_all('/"([^"]+)"/', $t, $m)) { // then we got our string
//echo '<br />PREG ALL';var_dump ($m[0]);
foreach ($m[0] as $i=> $quotedstring) {
$s = str_replace($delimiter, $replace, $quotedstring);
$t = str_replace($quotedstring, $s, $t); // swop colons only in the quoted string
//echo '<br />'.$i.$t;
}
}
}
return $t;
}
function amr_explodeByColon( $input ) { // rfc 5545 says colons, semicoln and quoted strings can/must be inside doublequotes
// maybe only required for parameter values? else to much overhead - actually maybe only needed for attendee stuff
// and then only if there are quoted strings
// NOTE PARAMETER VALUES may NOT contain quotes, so can have ="
// HOWEVER can have colon, semicolon or comma
// DESCRIPTION with colon does not have to be double quoted
// actually I think it's only the first non quoted colon we need ?
$t = amr_checkQuotedString(':','%3A', $input) ;
$t = str_replace('mailto:', 'mailto ', $t); //stop any other mailtos from exploding
//now we can explode by colon
//$arr = explode( ':', $t ); //only want the first unescaped colon, else colons ok in DESCRIPTION etc 20150814
$colon = stripos ( $t , ':' );
If (!$colon) return false;
$arr[0] = substr ( $t, 0, $colon );
$arr[1] = substr ( $t, $colon+1 );
return $arr;
}
function amr_explodeBySemi( $input ) { // rfc 5545 says colons, semicoln and quoted strings can/must be inside doublequotes
// maybe only required for parameter values? else to much overhead - actually maybe only needed for attendee stuff
// and then only if there are quoted strings
// NOTE PARAMETER VALUES may NOT contain quotes, so can have ="
// HOWEVER can have colon, semicolon or comma
$t = amr_checkQuotedString(';','%3B', $input) ;
//now we can explode by colon
$arr = explode( ';', $t ); //else acn treat normally
return $arr;
}
function amr_parseParameters($properties) { // anything from name separated by semicolons until the ':'
//echo '<br />Properties:';var_dump($properties);
$parameters = amr_explodeBYSemi ($properties);
//echo '<br />Parameters:';var_dump($parameters);
return $parameters;
}
function amr_parseAttendees ($arraybycolon) { /* receive full string parsed to array */
/*
ATTENDEE;X-NUM-GUESTS=0:mailto:1bfb88li8v385q41k6s7fnl9ls@group.calendar.go
ogle.com
ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="mailto:bob@
example.com";PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@
example.com
ATTENDEE;SENT-BY=mailto:jan_doe@example.com;CN=John Smith:
mailto:jsmith@example.com
NOT USING FOR NOW - INTERNAL ATTENDEES ONLY
*/
// the first one should start with ATTENDEE
$parameters = amr_parseParameters($arraybycolon[0]);
if ((!$parameters[0]) == 'ATTENDEE') { //garbage ?
echo '<br />Error: Bad attendee parameters <br />';
return false;
}
unset($parameters[0]);
foreach ($parameters as $param) {
$parts = explode('=',$param);
if (count($parts) == 2) { //X-NUM-GUESTS=0, CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Common Name;
$attendee[$parts[0]] = trim($parts[1],' "');
}
else { // ignore it ? bad parameter ?
}
}
$pos_email = trim($arraybycolon[1],' '); // 201710 allow for crap data
if (!stristr($pos_email, 'mailto')) {
$email = $pos_email;
}
else {
$email = amr_parseMailto($pos_email);
}
if ((!empty($email) and is_email($email)))
$attendee['MAILTO'] = $email;
// some files issue mailto's with an email address //201506 sometimes those mail addresses are not valid at all
//ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Common Name;X-NUM-GUESTS=0
else
$attendee['text'] = $email;
// set default values
if (!isset($attendee['PARTSTAT'])) // assume it needs action since not accepted
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
//echo' <br />Whata values ';var_dump($attendee);
//amr_parseMailto ($text)
return($attendee); // a single attendee
}
function amr_parseMailto ($text) { //mailto:ovcweb@uoguelph.ca return email
//2017 somehow we have a space instead of :
$text = str_replace('mailto','',$text);
$text = str_replace('%3A','',$text);
$text = (trim($text,' :"'));
return ($text);
}
function amr_parseOrganiser($arraybysemicolon) { /* receive full string parsed to array split by the semicolon
[0]=>ORGANIZER;SENT-BY="mailto
[1]=>dwood@uoguelph.ca":mailto:ovcweb@uoguelph.ca
or
[0]=>ORGANIZER;CN=Webmaster - OVC;SENT-BY="mailto
[1] => bagunn@uoguelph.ca":mailto:ovcweb@uoguelph.ca
ORGANIZER;CN="Cyrus Daboo":mailto:cyrus@example.com
*/
$org = array();
$p0 = explode(';',$arraybysemicolon[0]);
// we don't have a ':' for some reason to do with possible colon clash
$m = str_replace( 'mailto', '',$arraybysemicolon[1]);
$mailto = trim($m,' :'); //201710
//$m = explode(':',$arraybysemicolon[1]);
// if (ICAL_EVENTS_DEBUG) {echo '<br/>m : <br />'; var_dump($m); echo '<br/>p0 : <br />'; var_dump($p0);}
/* foreach ($m as $i => $m2) {
if (strtoupper($m2) == 'mailto') {
$mailto = rtrim($m[$i+1],'"');
}
}
*/
foreach ($p0 as $i => $p) {
$p1 = explode('=',$p);
if (isset ($p1[0])) {
$org['type'] = $p1[0]; /* if (!empty($p1[1])) $org['typevalue'] = $p1[1]; *** Parse this properly if we wantto handle complex attendees */
if ( ($p1[0] == 'SENT-BY') and (!empty($p1[1]))) {
$sentby = trim($m[0],'"');
$org['SENT-BY'] = $sentby;
}
else {
if (($p1[0] == 'CN') and (!empty($p1[1]))) {
$org['CN'] = trim( $p1[1], '"');
}
}
}
}
if (!empty($mailto))
$org['MAILTO'] = $mailto;
if (empty($org))
return ($arraybysemicolon);
return ($org);
}
/**
* Parse a Time Period field.
*/
function amr_parsePeriod($text,$tzobj) {
$periodParts = explode('/', $text);
if (!($start = amr_parseDateTime($periodParts[0], $tzobj))) return (false);
if ($duration = amr_parseDuration($periodParts[1])) return array('start' => $start, 'duration' => $duration);
else {
if (!($end = amr_parseDateTime($periodParts[1], $tzobj))) return (false);
else {
return array('start' => $start, 'end' => $end);
}
}
}
/**
* Parses a DateTime field and returns a datetime object, with either it's own tz if it has one, or the passed one
*/
function amr_parseDateTime($d, $tzobj) {
global $amr_globaltz;
global $utczobj;
/* 19970714T133000 ;Local time
19970714T173000Z ;UTC time
tz dealt with already ?*/
if (empty($d)) {
echo 'Unexpected error - empty date string to parse ';
return false;
}
if ((substr($d, strlen($d)-1, 1) === 'Z')) { /*datetime is specifed in UTC */
$tzobj = $utczobj;
$d = substr($d, 0, strlen($d)-1);
}
$date = substr($d,0, 4).'-'.substr($d,4, 2).'-'.substr($d,6, 2);
if (strlen ($d) > 8) {
$time = substr($d,9 ,2 ).':'.substr($d,11 ,2 ) ; /* has to at least have hours and mins */
}
else $time = '00:00';
if (strlen ($d) > 13) {
$time .= ':'.substr($d,13 ,2 );
}
else $time .= ':00';
/* Now create our date with the timezone in which it was defined , or if local, then in the plugin glovbal timezone */
$dt = amr_create_date_time ($date.' '.$time, $tzobj);
//if (!$dt) { amr_tell_admin_the_error ('Failed to parse'.$d); }
return ($dt);
}
/* Parses a Date field. */
function amr_parseRange($range, $daterange, $tzobj) { /*
For RECURRENCE-ID;
Strings like:
VALUE=DATE:19960401
RANGE=THISANDFUTURE:19960120T120000Z
RANGE=THISANDPRIOR:19960120T120000Z
*/
If (isset ($_REQUEST['debugexc'])) { echo '<br />Got Range '.$range.' with '.$daterange.'<br />'; }
$r = explode (':', $daterange);
if (!($thisanddate = amr_parseDateTime($r[1], $tzobj))) return (false);
If (isset ($_REQUEST['debugexc'])) { echo '<br />Got range '.$range.' "THISAND" date '.$thisanddate ->format('c').'<br />'; }
return (array('RANGE'=>$p[0],'DATE'=> $thisanddate));
}
/* Parses a Date field. */
function amr_parseDate($text, $tzobj) { /*
VALUE=DATE:
19970101,19970120,19970217,19970421
19970526,19970704,19970901,19971014,19971128,19971129,19971225
VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Berlin:20061223
*/
$p = explode (',',$text); /* if only a single will still return one array value */
foreach ($p as $i => $v) {
$datestring = substr($v, 0, 4).'-'.substr($v,4, 2).'-'.substr($v,6, 2);
$dates[] = amr_create_date_time ($datestring, $tzobj);
/*try {
// $dates[] = new DateTime(substr($v,0, 4).'-'.substr($v,4, 2).'-'.substr($v,6, 2), $tzobj);
//}
//catch(Exception $e) {
// echo '<br />Unable to create DateTime object from '.$text.' <br />'.$e->getMessage();
// return (false);
}*/
}
return ($dates);
}
/* ------------------------------------------------------------------ */
function amr_parseTZDate ($value, $tzid) {
$tzobj = amr_parseTZID($text);
if (!($d = amr_parseDateTime ($value, $tzobj))) {
return(false);
}
else
return ($d);
}
/* ------------------------------------------------------------------ */
function amr_deduceTZID($icstzid) {
// we have something that php didn't like, so we will try work something out
// Not great, really should use the filter function rather
global $amr_globaltz, $globaltzstring;
// $strip = array ('(',' ');
// $icstzid = str_replace($strip,'',$icstzid);
$gmtend = stripos($icstzid,')'); /* do we have a brackedt GMT ? */
//if (isset ($_REQUEST['tzdebug'])) {echo '<br/>Check for a bracketed gmt? = '.$gmtend.' in string '.$icstzid ; }
if (!empty ($gmtend) ) {
$icstzid = str_replace(')','/',$icstzid);
$icstzcities = explode ('/',$icstzid); /* could be commas, could be slashes */
if (isset ($_REQUEST['tzdebug'])) {echo '<br/>strip the gmt out '; print_r($icstzcities);}
$gmt = stripos( $icstzid, 'GMT'); /* do we have a brackedt GMT ? */
if (!empty($gmt)) unset ($icstzcities[0]); /* don't want the GMT - potentially misleading */
}
else { /* Maybe we have a list of cities and maybe we do not */
$icstzcities = array();
$temp = explode (',',$icstzid); /* could be commas, could be slashes */
foreach ($temp as $temp2) {
$temp3 = explode ('/',$temp2);
$icstzcities = array_merge($icstzcities, $temp3);
}
}
foreach ($icstzcities as $i=>$icscity) {
$icstzcities[$i] = trim($icscity,' ');
}
//if (isset ($_REQUEST['tzdebug'])) { echo '<br />Do we have a City? <br />';print_r($icstzcities);}
$globalcontcity = explode ('/',$globaltzstring);
if (isset ($globalcontcity[1]) )
$globalcity = $globalcontcity[1];
else
$globalcity = $globalcontcity[0];
// if (isset ($_REQUEST['tzdebug'])) { echo '<hr> text = '.$text.'<br/>icstzid = '.$icstzid.' and wp tz = '.$globalcity.' <br >'; print_r($icstzcities); }
if (in_array($globalcity, $icstzcities)) { /* if one of the cities in the tzid matches ours, again we can use the matched one */
$tzname = $globaltzstring;
}
else {
$alltzcities = amr_get_timezone_cities ();
if (isset($alltzcities[$icstzid])) { /* then it is a normal php timezone we know about, so we can proceed */
$tzname = $icstzid;
}
else {
foreach ($icstzcities as $i=>$c) {
if (isset ($alltzcities[$c] )) { /* try each of the cities if we have mutiple */
$tzname = $alltzcities[$c];
break;
}
}
if (isset ($_REQUEST['tzdebug'])) {echo '<br/>No match to known cities'; }
}
}
/* */
if (!isset ($tzname)) { /* see if we do it with GMT after all ? */
if (isset($icstzcities[0])) {
$tryoffset = str_replace('GMT','',$icstzcities[0]);
if (empty($tryoffset) or (is_int($tryoffset))) {
$tzname = amr_getTimeZone($tryoffset);
if (isset ($_REQUEST['tzdebug']))
{echo '<br/>Try see if offset:'.$tryoffset. ' gave '.$tzname; }
}
else {
$tzname = amr_unknown_timezone($icstzid, $globaltzstring);
}
}
else {
$tzname = amr_unknown_timezone($icstzid, $globaltzstring);
}
}
if (isset ($_REQUEST['tzdebug'])) echo '<br /><b>Timezone must be: </b> '.$tzname.'<br />';
$tz = amr_try_timezone ($tzname);
if (!$tz) $tz = $amr_globaltz;
return ($tz);
}
/* ------------------------------------------------------------------ */
function amr_tz_error_handler () { //cannot have anonymous function in php < 5.3
}
/* ------------------------------------------------------------------ */
function amr_parseTZID($text) { //also used when editing an event
global $amr_globaltz, // the main timezone object
$globaltzstring, // the string for the timezone object
$icsfile_tzname, // the timezone name string in the ics file
$icsfile_tz; // the ics timezone object that we last parsed and ended up with (often it's all the same
/* take a string that may have a olson tz object and try to return a tz object */
/* accept long and short TZ's, --- assume website tz if not valid eg Zimbra's: GMT+01.00/+02.00 */
$icstzid = trim($text,'"=' ); /* check for enclosing quotes like zimbra issues */
//-----------------------------------
//----------------------------------- is it same as wordpress ?
if (empty($globaltzstring))
$globaltzstring = timezone_name_get($amr_globaltz);
if ($globaltzstring == $icstzid ) {/* if the timezone matches the wordpress or shortcode time zone, then we are cool ! */
$icsfile_tzname = $icstzid; // set the global
$icsfile_tz = $amr_globaltz;
if (isset ($_REQUEST['tzdebug'])) echo '<br />'.$icstzid.' Matches wordpress tz.';
return ($amr_globaltz);
}
//----------------------------------- is it same as previously parsed ? if yes, go with that
if (!empty($icsfile_tzname) and ($icsfile_tzname == $icstzid )) {
if (isset ($_REQUEST['tzdebug'])) echo '<br />'.$icstzid.' Matches previously parsed tz.' ;
return ($icsfile_tz);
}
//----------------------------------- is it a valid php timezone ?
$timezone_identifiers = (DateTimeZone::listIdentifiers());
//foreach ($timezone_identifiers as $i=> $z) {echo '<br />'.$i; var_dump($z);}
if (in_array($icstzid,$timezone_identifiers, false)) { // we now have a valid php timezone
if (isset ($_REQUEST['tzdebug'])) {echo '<br/>Php should like:'.$icstzid; }
$tz = amr_try_timezone ($icstzid); // this should work else simething weird going on
if (!empty($tz)) return($tz);
}
//----------------------------------- if not already a valid php timezone, can we make it a valid zone ?
$tzid = apply_filters('amr-timezoneid-filter',$icstzid); //apply filters like for windows zones etc
// let us see if php likes it
try { set_error_handler( 'amr_tz_error_handler'); /* ignore errors , just giveit a go*/
$tz = timezone_open($tzid);
restore_error_handler();
if ($tz) {
$tzname = $tzid;
if (isset ($_REQUEST['tzdebug'])) {echo '<br/>Php liked:'.$tzid; }
}
else $tz = amr_deduceTZID($tzid) ;
}
catch(Exception $e) {/* we tried the filter but php did't like, so lets try fix it */
/* else try figure the timezone out */
$tz = amr_deduceTZID($tzid) ;
}
if (empty($tz)) {
if (isset ($_REQUEST['tzdebug'])) {echo '<br/>Giving up on timezone: '.$icstzid; }
return false; // we couldn't figure it out
}
if (empty($icsfile_tzname)) { // if this is the first time we parsed, lets save the hard work
$icsfile_tzname = $icstzid; // what was actually in the file
$icsfile_tz = $tz; // this is the php tz we ended up with
}
return ( $tz);
}
/* ------------------------------------------------------------------ */
function amr_try_timezone ($tzname) {
try {
$tz = timezone_open($tzname);
}
catch(Exception $e) {
$text = 'Unable to create Time zone object., Using wp default.<br />'.$e->getMessage();
amr_tell_admin_the_error ($text);
//echo '<br />Unable to create Time zone object., Using wp default.<br />'.$e->getMessage();
return ($amr_globaltz);
}
return ($tz);
}
/* ------------------------------------------------------------------ */
function amr_unknown_timezone ($text, $tzname) {
$emessage = 'Unable to deal with timezone like this: '.$text;
// echo '<!-- '.$emessage.' -->';
// if (isset ($_REQUEST['tzdebug']) or ICAL_EVENTS_DEBUG) {
if (is_super_admin()) {
echo '<br />Message to logged-in admin only: <b>'.$emessage.'</b>';
echo '- Making an assumption! Using '.$tzname.'<br />';
}
return ($tzname);
}
/* ------------------------------------------------------------------ */
function amr_parseSingleDate($VALUE='DATE-TIME', $text, $tzobj) {
/* used for those properties that should only have one value - since many other dates can have multiple date specs, the parsing function returns an array
Reduce the array to a single value */
$arr = amr_parseVALUE($VALUE, $text, $tzobj);
if (is_array($arr)) {
if (count($arr) > 1) {
error_log ( '<br>Unexpected multiple date values'.$text);
}
else return ($arr[0]);
}
return ($arr);
}
function amr_parseVALUE($VALUE, $text, $tzobj) {
/* amr parsing a value like
VALUE=PERIOD:19960403T020000Z/19960403T040000Z, 19960404T010000Z/PT3H
VALUE=DATE:19970101,19970120,19970217,19970421,.. 19970526,19970704,19970901,19971014,19971128,19971129,19971225
VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Berlin:20061223 */
//if (ICAL_EVENTS_DEBUG) {echo '<br />For value: '.$VALUE.' '.$text;}
if (empty($text)) {
if (ICAL_EVENTS_DEBUG) {echo '<br />For value: '.$VALUE.' text is blank';}
return (false);
}
switch ($VALUE) {
case 'DATE-TIME': {
if (!($d = amr_parseDateTime($text, $tzobj)))
return (false);
else
return ($d);
}
case 'DATE': {if (!($d = amr_parseDate($text, $tzobj))) return (false);
else return ($d); }
case 'PERIOD': {if (!($d = amr_parsePeriod($text, $tzobj))) return (false);
else return ($d); }
default: { /* something like DATE;TZID=/mozilla.org/20070129_1/Europe/Berlin */
$p = explode (';',$VALUE);
if (!($p[0] === 'DATE')) {
if (ICAL_EVENTS_DEBUG) {echo 'Error: Unexpected data in file '; print_r($p);}
return (false);
}
else {
if (substr ($p[1], 0, 4) === 'TZID') {/* then we have a weird TZ */
$tzobj = amr_deal_with_tzpath_in_date (substr($p[1],5)); /* pass the rest of the string over for tz extraction */
if (!($d = amr_parseDate($text, $tzobj))) {
amr_tell_admin_the_error ('Fail parse tz date'.$p[1]);
return (false);
}
else
return ($d);
}
else {
if (ICAL_EVENTS_DEBUG) {echo 'Error: Unexpected data in file '; print_r($p[1]);}
return (false);
};
}
}
return (false);
}
}
/** * Parse a Duration Value field.*/
function amr_parseDuration($text) {
/*
A duration of 15 days, 5 hours and 20 seconds would be: P15DT5H0M20S
A duration of 7 weeks would be: P7W, can be days or weeks, but not both
we want to convert so can use like this +1 week 2 days 4 hours 2 seconds ether for calc with modify or output. Could be neg (eg: for trigger)
*/
if (isset($_GET['debugdur'])) {echo '<br />Entering Pregmatch.. '.$text;}
if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/',
trim($text), $durvalue)) {
/* 0 is the full string, 1 is the sign, 2 is the , 3 is the week , 6 is th T*/
if (isset($_GET['debugdur'])) {echo '<br />Pregmatch gives '; var_dump($durvalue);}
if ($durvalue[1] == "-") { // Sign.
$dur['sign'] = '-';
}
// Weeks
if (!empty($durvalue[3])) $dur['weeks'] = rtrim($durvalue[3],'W');
if (count($durvalue) > 4) { // Days.
if (!empty($durvalue[4])) $dur['days'] = rtrim($durvalue[4],"D");
}
if (count($durvalue) > 5) { // Hours.
if (!empty($durvalue[7])) $dur['hours'] = rtrim($durvalue[7],"H");
if (isset($durvalue[8])) { // Mins.
$dur['mins'] = rtrim($durvalue[8],"M");
}
if (isset($durvalue[9])) { // Secs.
$dur['secs'] = rtrim($durvalue[9],"S");
}
}
if (!empty ($dur)) return $dur;
else { // possibly error in input data
if (isset($_GET['debugdur'])) {echo '<br />Possibly error in input data that pregmatch did not deal with .. '.$text;}
return false;
}
return $dur;
} else {
return false;
}
}
function amr_parse_CATEGORIES ($text ) {
$cats = explode(',',$text); // will return an array, but since we can have multiple CATEGORIES lines, will get an array of arrays .. confusing
return($cats);
}
function amr_parseRDATE ($string, $tzobj ) {
/*
RDATE:19970714T123000Z
RDATE:19970714T083000
RDATE;TZID=US-EASTERN:19970714T083000
RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H - not supported yet
RDATE;VALUE=DATE:19970101,19970120,19970217,19970421,19970526,19970704,19970901,19971014,19971128,19971129,19971225
could be multiple dates after : */
if (empty($string)) return false;
if (is_object($string)) {/* already parsed */ return($string); }
//echo '*** Here'; var_dump($string);
if (is_array($string) ) {
$rdatearray = array();
foreach ($string as $i => $rdatestring) {
// $r = $string[0];
if (is_object($rdatestring)) {/* already parsed and is an array of dates */ return($string); }
else {
if (isset($_GET['debugexc'])) {echo '<br />***Doing next r or exdate '.$i.' <pre>'.print_r($rdatestring,true).'</pre>'; }
$rdate = amr_parseRDATE ($rdatestring, $tzobj );
}
if (is_array($rdate)) $rdatearray = array_merge ($rdatearray, $rdate);
}
//if (isset($_GET['debugexc'])) {
//echo '<br />*** Array of r or exdate '; var_dump($rdatearray);// }
return ($rdatearray);
}
$rdatestring = explode(':',$string); /* $VALUE=DATE: or VALUE=DATE-TIME: and a series of dates (no time) */
// if (isset($_GET['rdebug'])) {echo '<br />Ok now really parse it '; var_dump($rdatestring); echo '<br />'; }
if (isset($rdatestring[0])) {
if (($rdatestring[0] == 'VALUE=DATE') and (isset($rdatestring[1])) ) {
$rdate = explode(',',$rdatestring[1]); /* that' sall we are doing for now */
// if (isset($_GET['rdebug'])) {echo '<br />Parsing value=date...<br/> '; var_dump($rdate);}
foreach ($rdate as $i => $r) {
$temp = amr_parseValue ('DATE', $r, $tzobj); //amr 20150622 only variables by reference
$dates[$i] = array_shift($temp);
/*returns array, but there should only be 1 value */
}
return($dates);
}
else if (($rdatestring[0] == 'VALUE=PERIOD') and (isset($rdatestring[1]))) {
echo "<br />HELP cannot yet deal with RDATE with VALUE=PERIOD<br />"; return (false);
}
else {
if (($rdatestring[0] == 'VALUE=DATE-TIME') and (isset($rdatestring[1]))) {
$rdate = explode(',',$rdatestring[1]);
}
else {
$rdate = explode(',',$rdatestring[0]);
}
foreach ($rdate as $i => $r) {
if (empty($r)) { return false; }
$dates[$i] = amr_parseDateTime ( $r, $tzobj);
if (isset($_GET['debugexc'])) {echo '<br />*** Parsed as: '.$dates[$i]->format('c');}
}
if (empty($dates)) return (false);
else return ($dates);
}
}
else return (false);
}
function amr_parseAttach ($parts) {
/*
This property can be specified multiple times in a
"VEVENT", "VTODO", "VJOURNAL", or "VALARM" calendar component with
the exception of AUDIO alarm that only allows this property to
occur once.
Default is a URL ATTACH:http://example.com/public/quarterly-report.doc
But could also have :
ATTACH:CID:jsmith.part3.960817T083000.xyzMail@example.com
ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/
sounds/bell-01.aud
ATTACH;FMTTYPE=application/msword:http://example.com/
templates/agenda.doc
ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY:VGhlIH
F1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4
*/
if (ICAL_EVENTS_DEBUG) {echo '<hr><br/>Attach to parse <br />'; var_dump($parts); echo '<hr>';}
if (!empty($parts[0])) {
if ($parts[0] === 'ATTACH') { // then we have a simple URL or CID
if (!empty ($parts[1])) {
if (substr($parts[1],0,3) === 'CID' ) {
$newattach['type'] = 'CID';
$newattach['CID'] = esc_attr(substr($parts[1],4));
}
else { // must be htpp or ftp
$newattach['type'] = 'url';
$newattach['url'] = esc_url_raw (rawurldecode($parts[1]));
}
}
else return (null);
}
else { // we have an FMTTYPE
$newattach['type'] = str_replace('ATTACH;FMTTYPE=','', $parts[0]); //should be something like application/msword, FMTTYPE=audio/basic
if (!stristr($newattach['type'], 'VALUE=BINARY')) { // not binary
$newattach['url'] = esc_url_raw($parts[1]);
}
else {
$newattach['binary'] = amr_remove_folding ($parts[1]);
}
}
}
else return (null);
return($newattach);
}
function amr_parseDefault($prop, $parts) {
$func = 'amr_parse'.str_replace('-','_',$prop);
//if (isset($_REQUEST['debugcustom'])) {echo '<br />Parsing..'.$prop. 'Look for '.$func; }
if (function_exists($func)) {
$result = call_user_func ($func, $parts);
if (isset($_REQUEST['debugcustom'])) {echo '<br />Used custom function..'.$func; var_dump($result);}
return ($result);
// return (amr_parseCustomModifiers($p));
}
else {
//if (isset($_REQUEST['debugcustom'])) {echo '<br>No function..'.$func.' for '; var_dump($parts);}
if (isset ($parts[1])) { // its a straight value
return (str_replace ('\,', ',', $parts[1]));
}
/* replace any slashes added by ical generator */
else { //nothing to see here folks... move along
return '';
}
return;
}
}
function amr_parse_property ($parts) {
/* would receive something like array ('DTSTART; VALUE=DATE', '20060315')) */
/* NOTE: parts[0] has the long tag eg: RDATE;TZID=US-EASTERN
or could be DTEND;TZID=America/New_York;VALUE=DATE-TIME: and the date 20110724T100000 in parts 1 (see forum note from dusty - he is generating the DATETIME
parts[1] the bit after the : 19960403T020000Z/19960403T040000Z, 19960404T010000Z/PT3H
IF 'Z' then must be in UTC
If no Z
*/
global $amr_globaltz;
if (empty($parts[1])) return false; // we got crap
$tzobj = $amr_globaltz; /* Because if there is no timezone specified in any way for the date time then it must a floating value, and so should be created in the global timezone.*/
// if (ICAL_EVENTS_DEBUG or isset($_REQUEST['tzdebug'])) {echo '<br /> Property : '.$parts[0];}
$p0 = explode (';', $parts[0]); /* Looking for ; VALUE = something...; or TZID=... or both, or more than one anyway ???*/
// the first bit will be the property like PRODID, or X_WR_TIMEZONE
// the next will be the modifiers
$prop = array_shift($p0);
if (!empty($p0)) { // if we have some modifiers
foreach ($p0 as $p) { // handle special modifiers , could be VALUE=DATE, TZID=
if (stristr($p, 'TZID')) {
/* Normal TZ, not the one with the path eg: DTSTART;TZID=US-Eastern:19980119T020000 or zimbras TZID="GMT+01.00/+02.00 */
// $TZID = substr($p0[1], 4 );
$TZID = substr($p, 4 );
$tzobj = amr_parseTZID($TZID);
} /* should create datetime object with it's own TZ, datetime maths works correctly with TZ's */
else {/* might be just a value=date, in which case we use the global tz? no may still have TZid */
$tzobj = $amr_globaltz;
if (stristr($p, 'VALUE')) { // we have a possibly redundant modified
$VALUE = substr($p,6); // take everything after the '='
}
else {// not necessary to deal with modifier here or unknown maybe custom modifiers
// only urgent to get tZID's.. or maybe we could even do those later?
//check it out on major code revamp, meanwhile if it aint broke, dont fix it
}
}
}
}
// else $tzobj = $amr_globaltz; /* Because if there is no timezone specified in any way for the date time then it must a floating value, and so should be created in the global timezone.*/
// switch ($p0[0]) {
switch ($prop) {
case 'CREATED':
case 'COMPLETED':
case 'LAST-MODIFIED':
case 'DTSTART':
case 'DTEND':
case 'DTSTAMP':
case 'DUE':
if (isset($VALUE)) {
$date = amr_parseValue($VALUE, $parts[1], $tzobj); }
/* return (amr_parseSingleDate($VALUE, $parts[1], $tzobj)); } */
else {
$date = amr_parseSingleDate('DATE-TIME', $parts[1], $tzobj);
}
if (is_object($date) and
(($prop === 'LAST-MODIFIED') or ($prop === 'CREATED'))) {
amr_track_last_mod($date);
}
return ($date);
case 'ALARM':
case 'RECURRENCE-ID': /* could also have range ?*/
if (isset($VALUE)) {
return (amr_parseValue($VALUE, $parts[1], $tzobj)); }
elseif (isset($RANGE)){
return (amr_parseRange($RANGE, $parts[1], $tzobj));
}
else {
return (amr_parseSingleDate('DATE-TIME', $parts[1], $tzobj));
}
case 'EXRULE':
case 'RRULE': {
return (amr_parseRRULE($parts[1]));
}
case 'BDAY':
return (amr_parseDate ($parts[1]));
case 'EXDATE': {if (isset($_REQUEST['debugexc'])) {echo '<br> Parsing EXDATE ';}}
case 'RDATE':
return (amr_parseRDATE ($parts[1],$tzobj));
case 'TRIGGER': /* not supported yet, check for datetime and / or duration */
case 'DURATION':
$dur = amr_parseDuration ($parts[1]);
if (isset($_GET['debugdur'])) {echo '<br />Parts1 = '.$parts[1].'<br />Duration = '; var_dump($dur);}
return ($dur);
case 'FREEBUSY':
return ( amr_parsePeriod ($parts[1], $tzobj));
case 'TZID': /* ie TZID is a property, not part of a date spec */
return ($parts[1]);
case 'ORGANIZER': {
return(amr_parseOrganiser($parts));
}
case 'ATTENDEE': {
return(amr_parseAttendees($parts));
}
case 'ATTACH': {
$attach = amr_parseAttach($parts);
If (ICAL_EVENTS_DEBUG) echo '<br />ATTACH returned:<br />'.print_r($attach,true);
return($attach);
}
case 'CATEGORIES': {
$cats = amr_parse_CATEGORIES($parts[1]);
return($cats );
}
default:{
return (amr_parseDefault($prop, $parts));
}
}
}
function amr_parse_component($type) { /* so we know we have a vcalendar at lines[$n] - check for properties or components */
global $amr_lines;
global $amr_totallines;
global $amr_n;
global $amr_validrepeatablecomponents;
global $amr_validrepeatableproperties;
global $amr_globaltz;
while (($amr_n < $amr_totallines) ) {
$amr_n++;
$parts = amr_explodeByColon($amr_lines[$amr_n]); // 201507
if ((!$parts) or ($parts === $amr_lines[$amr_n])) { // ie no colon
//
}
else { // ok
if ($parts[0] === 'BEGIN') { /* the we are starting a new sub component - end of the properties, so drop down */
if (in_array ($parts[1], $amr_validrepeatablecomponents)) {
$subarray[$parts[1]][] = amr_parse_component($parts[1]);
}
else {
$subarray[$parts[1]] = amr_parse_component($parts[1]);
}
}
else {
if ($parts[0] === 'END') {
if (empty($subarray)) return (false);
return ($subarray );
}
/* now grab the value - just in case there may have been ";" in the value we will take all the rest of the string */
else {
if ($parts[0] === 'X-WR-TIMEZONE;VALUE=TEXT')
$parts[0] === 'X-WR-TIMEZONE';
$basepart = explode (';', $parts[0], 2); /* Looking for RRULE; something...*/
//if (isset($_GET['debugcustom'])) {echo '<br />checking basepart '; var_dump($basepart); echo ' against '; var_dump($amr_validrepeatableproperties); }
if (in_array ($basepart[0], $amr_validrepeatableproperties)) {
$temp = amr_parse_property ($parts);
// now this might return an array (eg categories), which can be multiple lines too
if ($basepart[0] == 'CATEGORIES') {
//if (is_array($temp)) {
//if (WP_DEBUG) {echo '<br />Got an array:'.$basepart[0].' '; var_dump($temp);}
if (!empty($subarray[$basepart[0]]) and is_array($subarray[$basepart[0]])) {
// ie we got an array already, must be multiple lines
$subarray[$basepart[0]] = array_merge($subarray[$basepart[0]], $temp);
}
else $subarray[$basepart[0]]= $temp;
}
else $subarray[$basepart[0]][] = $temp;
}
else {
$subarray [$basepart[0]] = amr_parse_property($parts);
if (($basepart[0] === 'DTSTART') and (isset($basepart[1]))) {
// we have a DTSTART
// save the timezone separately in case we want to show events in event timezone
if (is_array($subarray['DTSTART'])) {
$subarray['DTSTART'] = $subarray['DTSTART'][0];
}
if (is_object($subarray['DTSTART'])) {
$subarray['timezone'] = $subarray['DTSTART']->getTimezone();
if (isset($_REQUEST['debugtz'])) { echo '<br />check tz';
var_dump($subarray['DTSTART']);
var_dump($subarray['timezone']);
}
if (amr_is_untimed($basepart[1])) { /* ie has VALUE=DATE */
//$subarray ['Untimed'] = true; // removed v4.0.28
//$subarray ['allday'] = true; // v4.0.17
$subarray ['allday'] = 'allday'; // v4.0.28
}
}
//else return false;
}
else if (($basepart[0] === 'X-MOZ-GENERATION') and (!isset( $subarray ['SEQUENCE']))) {
$subarray ['SEQUENCE'] = $subarray ['X-MOZ-GENERATION'] ;
/* If we have an mozilla funny thing, convert it to the sequence if there is no sequence */
}
else {
if (isset($_GET['debugcustom']) and ($basepart[0] === 'X-TRUMBA-CUSTOMFIELD')) {
echo '<br />Base Part = '.$basepart[0];
//var_dump($subarray [$basepart[0]]);
}
}
}
}
}
}
}
//if (ICAL_EVENTS_DEBUG) var_dump($subarray);
if (empty($subarray)) return false;
return ($subarray); /* return the possibly nested component */
}
// Parse the ical file and return an array ('Properties' => array ( name & params, value), 'Items' => array(( name & params, value), )
function amr_parse_ical ( $cal_file ) {
/* we will try to continue as much as possible, ignore lines that are problems */
global $icsfile_tzname,
$amr_lines,
$amr_totallines,
$amr_n,
$amr_validrepeatablecomponents,
$amr_last_modified;
$icsfile_tzname = '';
$line = 0;
$event = '';
//If (ICAL_EVENTS_DEBUG) { echo '<br />Calfile = '; var_dump($cal_file);echo '<br />';}
$data = file_get_contents($cal_file);
// Now fix folding. According to RFC, lines can fold by having
// a CRLF and then a single white space character.
// We will allow it to be CRLF, CR or LF or any repeated sequence
// so long as there is a single white space character next.
/**** we may also need to cope with backslahed backslashes, commas, semicolons as per http://www.kanzaki.com/docs/ical/text.html*/
$data = amr_remove_folding ($data);
$data = str_replace ( "\;", ";", $data );
$data = str_replace ( "\,", ",", $data );
$amr_n = 0;
$amr_lines = explode ( "\n", $data );
$amr_totallines = count ($amr_lines) - 1; /* because we start from 0 */
If (ICAL_EVENTS_DEBUG) {
echo '<br><b>Lines in ics file: '.$amr_totallines.'</b>' ;
//echo '<br />first line: '; var_dump($amr_lines);
echo '<br />';
}
$parts = explode (':', $amr_lines[$amr_n],2 );
/* explode faster than the preg, just split first : */
if ($parts[0] === 'BEGIN') {
$ical = amr_parse_component('VCALENDAR');
if (!empty ($amr_last_modified))
$ical['LastModificationTime'] = $amr_last_modified;
//if (isset($_GET['debugcustom'])) {var_dump($ical);}
return($ical);
}
else {
If (ICAL_EVENTS_DEBUG) {
echo '<br>VCALENDAR not found in file:'.$cal_file;
echo '<br>Line has: '.$amr_lines[$amr_n] ;
}
return false;
}
}
function amr_deal_with_tzpath_in_date ( $tzstring ) {
/* Receive something like /mozilla.org/20070129_1/Europe/Berlin
and return a tz object */
$tz = explode ('/',$tzstring);
$l = count ($tz);
if ($l>1) {
$tzid= $tz[$l-2].'/'.$tz[$l-1];
}
else $tzid = $tz[0] ;
$tzobj = timezone_open ( $tzid );
If (ICAL_EVENTS_DEBUG or isset ($_REQUEST['tzdebug'])) {
echo '<br />Timezone Reduced to: '.$tzid.' Result of timezone object creation:';
print_r($tzobj);
}
return ($tzobj);
}
function amr_get_timezone_cities () { //
$timezone_identifiers = DateTimeZone::listIdentifiers();
/* Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
*/
foreach( $timezone_identifiers as $i=> $value ){
$c = explode("/",$value);//obtain continent,city
$tzcities[$value]['continent'] = $c[0];
if (isset($c[1]))
$tzcities[$c[1]] = $value;
else
$tzcities[$c[0]] = $value;
}
return ($tzcities);
}
function amr_remove_folding ($data) {
$data = preg_replace ( "/[\r\n]+ /", "", $data );
$data = preg_replace ( "/[\r\n]+\t/", "", $data ); // unfold any htab folding 20151125 corrected
$data = preg_replace ( "/[\r\n]+/", "\n", $data );
return($data);
}
// Replace RFC 2445 escape characters
function amr_format_ical_text($value) {
$output = str_replace(
array('\\\\', '\;', '\,', '\N', '\n'),
array('\\', ';', ',', "\n", "\n"),
$value
);
return $output;
}
function amr_is_untimed($text) {
/* checks for VALUE=DATE */
if (stristr ($text, 'VALUE=DATE') and (!stristr($text, 'VALUE=DATE-TIME'))) return (true);
else return (false);
}
function amr_track_last_mod($date) {
global $amr_last_modified; $amr_globaltz;
if (empty ($amr_last_modified))
$amr_last_modified = amr_newDateTime('0000-00-00 00:00:01');
if ($date->format('c') > $amr_last_modified->format('c')) {
$amr_last_modified = clone $date;
}
}