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('$', '&#36;', $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('$', '&#36;', $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('$', '&#36;', $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?>