1<?php 2if (!defined('BASE')) define('BASE', './'); 3include_once(BASE.'functions/init.inc.php'); 4include_once(BASE.'functions/date_functions.php'); 5include_once(BASE.'functions/draw_functions.php'); 6include_once(BASE.'functions/parse/overlapping_events.php'); 7include_once(BASE.'functions/timezones.php'); 8include_once(BASE.'functions/parse/recur_functions.php'); 9 10// reading the file if it's allowed 11$realcal_mtime = time(); 12$parse_file = true; 13if ($phpiCal_config->save_parsed_cals == 'yes') { 14 sort($cal_filelist); 15 $filename = sha1(implode(',', $cal_filelist)); 16 $parsedcal = $phpiCal_config->tmp_dir . '/parsedcal-' . $filename . '-' . $this_year; 17 if (file_exists($parsedcal)) { 18 $fd = fopen($parsedcal, 'r'); 19 $contents = fread($fd, filesize($parsedcal)); 20 fclose($fd); 21 $master_array = unserialize($contents); 22 $y=0; 23 // Check the calendars' last-modified time to determine if any need to be re-parsed 24 if (count($master_array['-4']) == count($cal_filelist)) { 25 foreach ($master_array['-4'] as $temp_array) { 26 $mtime = $temp_array['mtime']; 27 $fname = $temp_array['filename']; 28 $wcalc = $temp_array['webcal']; 29 30 if ($wcalc == 'no') { 31 /* 32 * Getting local file mtime is "fairly cheap" 33 * (disk I/O is expensive, but *much* cheaper than going to the network for remote files) 34 */ 35 $realcal_mtime = filemtime($fname); 36 } 37 else if ((time() - $mtime) >= $phpiCal_config->webcal_hours * 60 * 60) { 38 /* 39 * We limit remote file mtime checks based on the magic webcal_hours config variable 40 * This allows highly volatile web calendars to be cached for a period of time before 41 * downloading them again 42 */ 43 $realcal_mtime = remote_filemtime($fname); 44 } 45 else { 46 // This is our fallback, for the case where webcal_hours is taking effect 47 $realcal_mtime = $mtime; 48 } 49 50 // If this calendar is up-to-date, the $y magic number will be incremented... 51 if ($mtime >= $realcal_mtime) { 52 $y++; 53 } 54 } 55 56 foreach ($master_array['-3'] as $temp_array) { 57 if (isset($temp_array) && $temp_array !='') $caldisplaynames[] = $temp_array; 58 } 59 60 // And the $y magic number is used here to determine if all calendars are up-to-date 61 if ($y == count($cal_filelist)) { 62 if ($master_array['-1'] == 'valid cal file') { 63 // At this point, all calendars are up-to-date, so we can simply used the pre-parsed data 64 $parse_file = false; 65 $calendar_name = $master_array['calendar_name']; 66 if (isset($master_array['calendar_tz'])) 67 $calendar_tz = $master_array['calendar_tz']; 68 } 69 } 70 } 71 } 72 if ($parse_file == true) { 73 // We need to re-parse at least one calendar, so unset master_array 74 unset($master_array); 75 } 76} 77 78if ($parse_file) { 79 $overlap_array = array (); 80 $uid_counter = 0; 81} 82 83$calnumber = 1; 84foreach ($cal_filelist as $cal_key=>$filename) { 85 86 // Find the real name of the calendar. 87 $actual_calname = getCalendarName($filename); 88 89 if ($parse_file) { 90 91 // Let's see if we're doing a webcal 92 if (substr($filename, 0, 7) == 'http://' || substr($filename, 0, 8) == 'https://' || substr($filename, 0, 9) == 'webcal://') { 93 $cal_webcalPrefix = str_replace(array('http://', 'https://'), 'webcal://', $filename); 94 $cal_httpPrefix = str_replace(array('webcal://', 'https://'), 'http://', $filename); 95 $cal_httpsPrefix = str_replace(array('http://', 'webcal://'), 'https://', $filename); 96 97 $master_array['-4'][$calnumber]['webcal'] = 'yes'; 98 $actual_mtime = remote_filemtime($cal_httpPrefix); 99 100 /* 101 * We want to copy the remote calendar to a local temporary file, 102 * because the current parser will read it twice: 103 * - Once to get the timezone information 104 * - And again to collect the remaining data 105 * See: http://phpicalendar.net/forums/viewtopic.php?f=45&t=4140#p14451 106 */ 107 $filename = tempnam(sys_get_temp_dir(), 'ICS'); 108 if (copy($cal_httpPrefix, $filename) === FALSE) { 109 exit(error($lang['l_copy_error'], $cal_httpPrefix)); 110 } 111 } else { 112 $actual_mtime = filemtime($filename); 113 } 114 115 include(BASE.'functions/parse/parse_tzs.php'); 116 117 $ifile = @fopen($filename, 'r'); 118 if ($ifile == FALSE) exit(error($lang['l_error_cantopen'], $filename)); 119 $nextline = fgets($ifile, 1024); 120 #if (trim($nextline) != 'BEGIN:VCALENDAR') exit(error($lang['l_error_invalidcal'], $filename)); 121 122 // Set a value so we can check to make sure $master_array contains valid data 123 $master_array['-1'] = 'valid cal file'; 124 125 // Set default calendar name - can be overridden by X-WR-CALNAME 126 $calendar_name = $cal_filename; 127 $master_array['calendar_name'] = $calendar_name; 128 129 // read file in line by line 130 // XXX end line is skipped because of the 1-line readahead 131 while (!feof($ifile)) { 132 $line = $nextline; 133 $nextline = fgets($ifile, 1024); 134 $nextline = ereg_replace("[\r\n]", '', $nextline); 135 #handle continuation lines that start with either a space or a tab (MS Outlook) 136 while (isset($nextline{0}) && ($nextline{0} == ' ' || $nextline{0} == "\t")) { 137 $line = $line . substr($nextline, 1); 138 $nextline = fgets($ifile, 1024); 139 $nextline = ereg_replace("[\r\n]", '', $nextline); 140 } 141 $line = str_replace('\n', "\n", $line); 142 $line = str_replace('\t', "\t", $line); 143 $line = trim(stripslashes($line)); 144 switch ($line) { 145 // Begin VFREEBUSY/VEVENT Parsing 146 // 147 case 'BEGIN:VFREEBUSY': 148 case 'BEGIN:VEVENT': 149 // each of these vars were being set to an empty string 150 unset ( 151 $start_time, $end_time, $start_date, $end_date, 152 $allday_start, $allday_end, $start, $end, $the_duration, 153 $beginning, $start_of_vevent, 154 $valarm_description, $start_unixtime, $end_unixtime, $display_end_tmp, $end_time_tmp1, 155 $recurrence_id, $recurrence_d, $recurrence_, $uid, $rrule, $until_check, 156 $until, $byweek, $byweekno, 157 $byminute, $byhour, $bysecond 158 ); 159 160 $interval = 1; 161 $sequence = 0; 162 $summary = ''; 163 $description = ''; 164 $status = ''; 165 $class = ''; 166 $location = ''; 167 $url = ''; 168 $geo = ''; 169 $type = ''; 170 $other = ''; 171 $wkst = 'MO'; 172 $vtodo_categories = ''; 173 174 $except_dates = array(); 175 $except_times = array(); 176 $rrule_array = array(); 177 $byday = array(); 178 $bymonth = array(); 179 $bymonthday = array(); 180 $byyearday = array(); 181 $bysetpos = array(); 182 $first_duration = TRUE; 183 $count = 1000000; 184 $valarm_set = FALSE; 185 $attendee = array(); 186 $organizer = array(); 187 188 break; 189 case 'END:VFREEBUSY': 190 case 'END:VEVENT': 191 include BASE.'functions/parse/end_vevent.php'; 192 break; 193 194 // Begin VTODO Parsing 195 // 196 case 'END:VTODO': 197 if (($vtodo_priority == '') && ($status == 'COMPLETED')) { 198 $vtodo_sort = 11; 199 } elseif ($vtodo_priority == '') { 200 $vtodo_sort = 10; 201 } else { 202 $vtodo_sort = $vtodo_priority; 203 } 204 205 // CLASS support 206 if (isset($class)) { 207 if ($class == 'PRIVATE') { 208 $summary = '**PRIVATE**'; 209 $description = '**PRIVATE**'; 210 } elseif ($class == 'CONFIDENTIAL') { 211 $summary = '**CONFIDENTIAL**'; 212 $description = '**CONFIDENTIAL**'; 213 } 214 } 215 216 $master_array['-2']["$vtodo_sort"]["$uid"] = array ( 217 'start_date' => $start_date, 218 'start_time' => $start_time, 219 'vtodo_text' => $summary, 220 'due_date'=> $due_date, 221 'due_time'=> $due_time, 222 'completed_date' => $completed_date, 223 'completed_time' => $completed_time, 224 'priority' => $vtodo_priority, 225 'status' => $status, 226 'class' => $class, 227 'categories' => $vtodo_categories, 228 'description' => $description, 229 'calname' => $actual_calname, 230 'geo' => $geo, 231 'url' => $url 232 ); 233 unset ($start_date, $start_time, $due_date, $due_time, $completed_date, $completed_time, $vtodo_priority, $status, $class, $vtodo_categories, $summary, $description); 234 $vtodo_set = FALSE; 235 break; 236 237 case 'BEGIN:VTODO': 238 $vtodo_set = TRUE; 239 $start_date = ''; 240 $start_time = ''; 241 $summary = ''; 242 $due_date = ''; 243 $due_time = ''; 244 $completed_date = ''; 245 $completed_time = ''; 246 $vtodo_priority = ''; 247 $vtodo_categories = ''; 248 $status = ''; 249 $class = ''; 250 $description = ''; 251 break; 252 253 // Begin VALARM Parsing 254 // 255 case 'BEGIN:VALARM': 256 $valarm_set = TRUE; 257 break; 258 case 'END:VALARM': 259 $valarm_set = FALSE; 260 break; 261 262 default: 263 unset ($field, $data, $prop_pos, $property); 264 if (ereg ("([^:]+):(.*)", $line, $line)){ 265 $field = $line[1]; 266 $data = $line[2]; 267 $property = strtoupper($field); 268 $prop_pos = strpos($property,';'); 269 if ($prop_pos !== false) $property = substr($property,0,$prop_pos); 270 271 switch ($property) { 272 // Start VTODO Parsing 273 // 274 case 'DUE': 275 $datetime = extractDateTime($data, $property, $field); 276 $due_date = $datetime[1]; 277 $due_time = $datetime[2]; 278 break; 279 280 case 'COMPLETED': 281 $datetime = extractDateTime($data, $property, $field); 282 $completed_date = $datetime[1]; 283 $completed_time = $datetime[2]; 284 break; 285 286 case 'PRIORITY': 287 $vtodo_priority = "$data"; 288 break; 289 290 case 'STATUS': 291 $status = "$data"; 292 break; 293 294 case 'GEO': 295 $geo = "$data"; 296 break; 297 298 case 'CLASS': 299 $class = "$data"; 300 break; 301 302 case 'CATEGORIES': 303 $vtodo_categories = "$data"; 304 break; 305 // 306 // End VTODO Parsing 307 308 case 'DTSTART': 309 $datetime = extractDateTime($data, $property, $field); 310 $start_unixtime = $datetime[0]; 311 $start_date = $datetime[1]; 312 $start_time = $datetime[2]; 313 $allday_start = $datetime[3]; 314 $start_tz = $datetime[4]; 315 preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs); 316 $vevent_start_date = $regs[1] . $regs[2] . $regs[3]; 317 $day_offset = dayCompare($start_date, $vevent_start_date); 318 #echo date("Ymd Hi", $start_unixtime)." $start_date $start_time $vevent_start_date $day_offset<br>"; 319 break; 320 321 case 'DTEND': 322 $datetime = extractDateTime($data, $property, $field); 323 $end_unixtime = $datetime[0]; 324 $end_date = $datetime[1]; 325 $end_time = $datetime[2]; 326 $allday_end = $datetime[3]; 327 break; 328 329 case 'EXDATE': 330 $data = split(',', $data); 331 foreach ($data as $exdata) { 332 $exdata = str_replace('T', '', $exdata); 333 $exdata = str_replace('Z', '', $exdata); 334 preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $exdata, $regs); 335 $except_dates[] = $regs[1] . $regs[2] . $regs[3]; 336 // Added for Evolution, since they dont think they need to tell me which time to exclude. 337 if ($regs[4] == '' && isset($start_time) && $start_time != '') { 338 $except_times[] = $start_time; 339 } else { 340 $except_times[] = $regs[4] . $regs[5]; 341 } 342 } 343 break; 344 345 case 'SUMMARY': 346 $data = str_replace('$', '$', $data); 347 $data = stripslashes($data); 348 $data = htmlentities(urlencode($data)); 349 if ($valarm_set == FALSE) { 350 $summary = $data; 351 } else { 352 $valarm_summary = $data; 353 } 354 break; 355 356 case 'DESCRIPTION': 357 $data = str_replace('$', '$', $data); 358 $data = stripslashes($data); 359 $data = htmlentities(urlencode($data)); 360 if ($valarm_set == FALSE) { 361 $description = $data; 362 } else { 363 $valarm_description = $data; 364 } 365 break; 366 367 case 'RECURRENCE-ID': 368 $parts = explode(';', $field); 369 foreach($parts as $part) { 370 $eachval = split('=',$part); 371 if ($eachval[0] == 'RECURRENCE-ID') { 372 // do nothing 373 } elseif ($eachval[0] == 'TZID') { 374 $recurrence_id['tzid'] = $eachval[1]; 375 } elseif ($eachval[0] == 'RANGE') { 376 $recurrence_id['range'] = $eachval[1]; 377 } elseif ($eachval[0] == 'VALUE') { 378 $recurrence_id['value'] = $eachval[1]; 379 } else { 380 $recurrence_id[] = $eachval[1]; 381 } 382 } 383 unset($parts, $part, $eachval); 384 385 $data = str_replace('T', '', $data); 386 $data = str_replace('Z', '', $data); 387 ereg ('([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})', $data, $regs); 388 $recurrence_id['date'] = $regs[1] . $regs[2] . $regs[3]; 389 $recurrence_id['time'] = $regs[4] . $regs[5]; 390 391 $recur_unixtime = mktime($regs[4], $regs[5], 0, $regs[2], $regs[3], $regs[1]); 392 393 if (isset($recurrence_id['tzid'])) { 394 $offset_tmp = chooseOffset($recur_unixtime, $recurrence_id['tzid']); 395 } elseif (isset($calendar_tz)) { 396 $offset_tmp = chooseOffset($recur_unixtime, $calendar_tz); 397 } else { 398 $offset_tmp = chooseOffset($recur_unixtime); 399 } 400 $recur_unixtime = calcTime($offset_tmp, @$server_offset_tmp, $recur_unixtime); 401 $recurrence_id['date'] = date('Ymd', $recur_unixtime); 402 $recurrence_id['time'] = date('Hi', $recur_unixtime); 403 $recurrence_d = date('Ymd', $recur_unixtime); 404 $recurrence_t = date('Hi', $recur_unixtime); 405 unset($server_offset_tmp); 406 break; 407 408 case 'SEQUENCE': 409 $sequence = $data; 410 break; 411 case 'UID': 412 $uid = $data; 413 break; 414 case 'X-WR-CALNAME': 415 $actual_calname = $data; 416 $master_array['calendar_name'] = $actual_calname; 417 $cal_displaynames[$cal_key] = $actual_calname; #correct the default calname based on filename 418 break; 419 case 'X-WR-TIMEZONE': 420 $calendar_tz = $data; 421 $master_array['calendar_tz'] = $calendar_tz; 422 break; 423 case 'DURATION': 424 if (($first_duration == TRUE) && (!stristr($field, '=DURATION'))) { 425 ereg ('^P([0-9]{1,2}[W])?([0-9]{1,3}[D])?([T]{0,1})?([0-9]{1,2}[H])?([0-9]{1,2}[M])?([0-9]{1,2}[S])?', $data, $duration); 426 $weeks = str_replace('W', '', $duration[1]); 427 $days = str_replace('D', '', $duration[2]); 428 $hours = str_replace('H', '', $duration[4]); 429 $minutes = str_replace('M', '', $duration[5]); 430 $seconds = str_replace('S', '', $duration[6]); 431 $the_duration = ($weeks * 60 * 60 * 24 * 7) + ($days * 60 * 60 * 24) + ($hours * 60 * 60) + ($minutes * 60) + ($seconds); 432 $first_duration = FALSE; 433 } 434 break; 435 case 'RRULE': 436 $data = str_replace ('RRULE:', '', $data); 437 $rrule = split (';', $data); 438 foreach ($rrule as $recur) { 439 ereg ('(.*)=(.*)', $recur, $regs); 440 $rrule_array[$regs[1]] = $regs[2]; 441 } 442 break; 443 case 'ATTENDEE': 444 $email = preg_match('/mailto:(.*)/i', $data, $matches1); 445 $name = preg_match('/CN=([^;]*)/i', $field, $matches2); 446 $rsvp = preg_match('/RSVP=([^;]*)/i', $field, $matches3); 447 $partstat = preg_match('/PARTSTAT=([^;]*)/i', $field, $matches4); 448 $role = preg_match('/ROLE=([^;]*)/i', $field, $matches5); 449 450 $email = ($email ? $matches1[1] : ''); 451 $name = ($name ? $matches2[1] : $email); 452 $rsvp = ($rsvp ? $matches3[1] : ''); 453 $partstat = ($partstat ? $matches4[1] : ''); 454 $role = ($role ? $matches5[1] : ''); 455 456 // Emergency fallback 457 if (empty($name) && empty($email)) $name = $data; 458 459 $attendee[] = array ('name' => stripslashes($name), 460 'email' => stripslashes($email), 461 'RSVP' => stripslashes($rsvp), 462 'PARTSTAT' => stripslashes($partstat), 463 'ROLE' => stripslashes($role) 464 ); 465 break; 466 case 'ORGANIZER': 467 $email = preg_match('/mailto:(.*)/i', $data, $matches1); 468 $name = preg_match('/CN=([^;]*)/i', $field, $matches2); 469 470 $email = ($email ? $matches1[1] : ''); 471 $name = ($name ? $matches2[1] : $email); 472 473 // Emergency fallback 474 if (empty($name) && empty($email)) $name = $data; 475 476 $organizer[] = array ('name' => stripslashes($name), 'email' => stripslashes($email)); 477 break; 478 case 'LOCATION': 479 $data = str_replace('$', '$', $data); 480 $data = stripslashes($data); 481 $data = htmlentities(urlencode($data)); 482 $location = $data; 483 break; 484 case 'URL': 485 $url = $data; 486 break; 487 default: 488 if(strpos(':',$data) > 1) $other .= $data; 489 } 490 } 491 } 492 } 493 fclose($ifile); 494 495 // Delete the temporary file created for webcals 496 if (@$master_array['-4'][$calnumber]['webcal'] == 'yes') { 497 unlink($filename); 498 } 499 } 500 if (!isset($master_array['-3'][$calnumber])) $master_array['-3'][$calnumber] = $actual_calname; 501 if (!isset($master_array['-4'][$calnumber]['mtime'])) $master_array['-4'][$calnumber]['mtime'] = $actual_mtime; 502 if (!isset($master_array['-4'][$calnumber]['filename'])) $master_array['-4'][$calnumber]['filename'] = $filename; 503 if (!isset($master_array['-4'][$calnumber]['webcal'])) $master_array['-4'][$calnumber]['webcal'] = 'no'; 504 $calnumber = $calnumber + 1; 505} 506 507if ($parse_file) { 508 // Sort the array by absolute date. 509 if (isset($master_array) && is_array($master_array)) { 510 ksort($master_array); 511 reset($master_array); 512 513 // sort the sub (day) arrays so the times are in order 514 foreach (array_keys($master_array) as $k) { 515 if (isset($master_array[$k]) && is_array($master_array[$k])) { 516 ksort($master_array[$k]); 517 reset($master_array[$k]); 518 } 519 } 520 } 521 522 // write the new master array to the file 523 if (isset($master_array) && is_array($master_array) && $phpiCal_config->save_parsed_cals == 'yes') { 524 $write_me = serialize($master_array); 525 $fd = @fopen($parsedcal, 'w'); 526 if ($fd == FALSE) exit(error($lang['l_error_cache'], $filename)); 527 @fwrite($fd, $write_me); 528 @fclose($fd); 529 @chmod($parsedcal, 0600); // 0640 530 } 531} 532 533 534// Set a calender name for all calenders combined 535if ($cal == $phpiCal_config->ALL_CALENDARS_COMBINED) { 536 $calendar_name = $all_cal_comb_lang; 537} 538/* example of how to customize the display name sort order 539if(in_array('US Holidays',$cal_displaynames)){ 540 unset($cal_displaynames[array_search('US Holidays',$cal_displaynames)]); 541 array_unshift($cal_displaynames, 'US Holidays'); 542} 543*/ 544$cal_displayname = urldecode(implode(', ', $cal_displaynames)); #reset this with the correct names 545$template_started = getmicrotime(); 546 547 548 549//If you want to see the values in the arrays, uncomment below. 550#print '<pre>'; 551#var_dump($phpiCal_config); 552#print_r($master_array); 553#var_dump($overlap_array['20081211']); 554//print_r($day_array); 555//print_r($rrule_array); 556//print_r($byday_arr); 557//print_r($recurrence_delete); 558//print_r($cal_displaynames); 559//print_r($cal_filelist); 560//print_r($tz_array); 561#print '</pre>'; 562?>