3 if ($options{data_storage_mode
} == 0 ) { # flat text files
4 if (!(-e
"$options{calendars_file}")) {
5 $fatal_error=1;$error_info .= "Calendars file $options{calendars_file} not found!\n";
7 if (!(-e
$options{pending_actions_file
})) {
8 $fatal_error=1;$error_info .= "New calendars file $options{pending_actions_file} not found!\n";
10 if (!(-e
$options{events_file
})) {
11 $fatal_error=1;$error_info .= "Events file $options{events_file} not found!\n";
14 if ($fatal_error == 0) {
15 # Remember which files are writable.
16 $writable{calendars_file
} = (-w
$options{calendars_file
});
17 $writable{pending_actions_file
} = (-w
$options{pending_actions_file
});
18 $writable{events_file
} = (-w
$options{events_file
});
19 $writable{email_reminders_datafile
} = (-w
$options{email_reminders_datafile
});
21 # If the events file is not writable then we shouldn't
22 # show the Add/Edit events tab on the main page.
23 delete($tab_text[1]) unless $writable{events_file
};
26 $writable{calendars_file
} = 1;
27 $writable{pending_actions_file
} = 1;
28 $writable{events_file
} = 1;
29 $writable{email_reminders_datafile
} = 1;
30 $writable{users_file
} = 1;
32 my $calendars_table_exists = 1;
33 my $pending_actions_table_exists = 1;
34 my $events_table_exists = 1;
35 my $users_table_exists = 1;
38 # if successful, check whether the calendars table exists
39 my $query_string="select * from $options{calendars_table}";
40 $query_string .= " limit 0" if ($options{data_storage_mode
} != 2);
42 my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
44 if ($dbh->errstr ne "") {
45 $calendars_table_exists=0;
46 $error_info .= $dbh->errstr."\n";
50 # check whether the pending_actions table exists
51 $query_string="select * from $options{pending_actions_table}";
52 $query_string .= " limit 0" if ($options{data_storage_mode
} != 2);
54 $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
56 if ($dbh->errstr ne "") {
57 $pending_actions_table_exists=0;
58 $error_info .= $dbh->errstr."\n";
62 # check whether the events table exists
63 $query_string="select * from $options{events_table}";
64 $query_string .= " limit 0" if ($options{data_storage_mode
} != 2);
66 $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
68 if ($dbh->errstr ne "") {
69 $events_table_exists=0;
70 $error_info .= $dbh->errstr."\n";
74 # check whether the users table exists
75 $query_string="select * from $options{users_table}";
76 $query_string .= " limit 0" if ($options{data_storage_mode
} != 2);
78 $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
80 if ($dbh->errstr ne "") {
81 $users_table_exists = 0;
82 $error_info .= $dbh->errstr."\n";
86 if ($users_table_exists + $events_table_exists + $pending_actions_table_exists + $calendars_table_exists == 4) {
88 } elsif ($users_table_exists + $events_table_exists + $pending_actions_table_exists + $calendars_table_exists > 0) {
90 $error_info .= "Ok, this is a serious problem. Some of the required tables exist, but not all.\n Plans can't fix this automatically.\n";
91 } elsif ($users_table_exists + $events_table_exists + $pending_actions_table_exists + $calendars_table_exists == 0) {
92 if ($q->param('create_tables') ne "1") {
94 if ((-e
"$options{users_file}") && (-e
"$options{calendars_file}") && (-e
$options{pending_actions_file
}) && (-e
$options{events_file
})) {
96 \nIt looks like the required tables don't exist.
97 \nShall Plans create them for you?
98 \n<a href="$script_url/$name?create_tables=1">Yes, please create them (but don't import anything)</a>
99 \n<a href="$script_url/$name?create_tables=1&import_data=1">Yes, please create them, and import all all existing data from<b>$options{users_file}</b> <b>$options{calendars_file}</b>, <b>$options{pending_actions_file}</b>, and <b>$options{events_file}</b>.</a>
103 \nIt looks like the required tables don
't exist.
104 \nShall Plans create them for you?
105 \n<a href="$script_url/$name?create_tables=1">Yes, please create them</a>
108 } else { # create the tables!
109 $error_info .= "\nCreating calendar and event tables...\n";
110 # create the calendars table
111 my $query_string="create table $options{calendars_table}(id int(5),xml_data text,update_timestamp int(15));";
112 $query_string="create table $options{calendars_table}(id int,xml_data text,update_timestamp int);" if ($options{data_storage_mode} == 2);
114 my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare
$statement: $dbh->errstr\n");
116 if ($dbh->errstr ne "") {
118 $error_info .= "error creating table
\"$options{calendars_table
}\"!\n".$dbh->errstr."\n";
122 # create the pending actions table
123 $query_string="create table
$options{pending_actions_table
}(id
int(5), xml_data text
, update_timestamp
int(15));";
124 $query_string="create table
$options{pending_actions_table
}(id
int, xml_data text
, update_timestamp
int);" if ($options{data_storage_mode} == 2);
126 $sth = $dbh->prepare($query_string) || ($error_info .= "Can
't prepare $statement: $dbh->errstr\n");
128 if ($dbh->errstr ne "") {
130 $error_info .= "error creating table \"$options{pending_actions_table}\"!\n".$dbh->errstr."\n";
134 # create the events table
135 $query_string="create table $options{events_table}(id int(5),cal_ids text,start int(15),end int(15),xml_data text,update_timestamp int(15));";
136 $query_string="create table $options{events_table}(id int,cal_ids text,start int,[end] int,xml_data text,update_timestamp int);" if ($options{data_storage_mode} == 2);
137 $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare
$statement: $dbh->errstr\n");
139 if ($dbh->errstr ne "") {
141 $error_info .= "error creating table
\"$options{events_table
}\"!\n".$dbh->errstr."\n";
145 # create the users table
146 $query_string="create table
$options{users_table
}(id
int(5), xml_data text
, update_timestamp
int(15));";
147 $query_string="create table
$options{users_table
}(id
int, xml_data text
, update_timestamp
int);" if ($options{data_storage_mode} == 2);
149 $sth = $dbh->prepare($query_string) || ($error_info .= "Can
't prepare $statement: $dbh->errstr\n");
151 if ($dbh->errstr ne "") {
153 $error_info .= "error creating table \"$options{users_table}\"!\n".$dbh->errstr."\n";
157 # either import existing text data, or create a record for the primary calendar
158 if ($q->param('import_data
') ne "1" && $fatal_error != 1) { # create primary calendar
159 $error_info .= "\nAdding primary calendar...\n";
162 # data for the primary calendar
163 my %primary_cal = %default_cal;
165 $primary_cal{title}="Main Calendar";
166 $primary_cal{password}=crypt("12345", $options{salt});
167 $primary_cal{details}=<<p1;
168 This is the primary calendar. You can't
delete it
(you can only
rename it
).
169 The password
for this calendar is
"12345", which you should change right away
.
170 This calendar
's password is the "master password", and can be used to override
171 the password of any other calendar.
173 $primary_cal{update_timestamp}=time();
176 my $cal_xml = &calendar2xml(\%primary_cal);
178 # add the primary calendar to the table
179 my $query_string="insert into $options{calendars_table} (id, xml_data, update_timestamp) values (?, ?, ?);";
180 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare
$query_string:\n");
181 $sth->execute($primary_cal{id}, $cal_xml, $primary_cal{update_timestamp});
182 if ($dbh->errstr ne "") {
184 $error_info .= "Error adding primary calendar
!\n".$dbh->errstr."\n";
185 $error_info .= "$query_string\n";
190 (you shouldn't ever see this message again. To prove it, refresh the page or <a href="$script_url/$name">click here</a>.)
194 } else { # import data
195 $error_info .= "\nImporting data from flat files...\n";
196 my $temp = $options{data_storage_mode
};
198 $options{data_storage_mode
} = 0;
203 $options{data_storage_mode
} = $temp;
205 my @temp_cal_ids = keys %calendars;
206 &add_calendars
(\
@temp_cal_ids);
208 my @temp_new_cal_ids = keys %new_calendars;
209 &add_new_calendars
(\
@temp_new_cal_ids);
211 my @temp_event_ids = keys %events;
212 &add_events
(\
@temp_event_ids);
214 my @temp_user_ids = keys %users;
215 foreach $temp_user_id (@temp_user_ids) {
216 &add_user
($temp_user_id);
219 foreach $new_cal_id (keys %new_calendars) {
220 $debug_info .= "adding new calendar $new_cal_id\n";
221 &add_action
($new_cal_id, "event");
224 foreach $new_event_id (keys %new_events) {
225 $debug_info .= "adding new event $new_cal_id\n";
226 &add_action
($new_event_id, "event");
229 if ($dbh->errstr ne "") {
231 $error_info .= "Error importing data!\n".$dbh->errstr."\n";
232 $error_info .= "$query_string\n";
236 Tables created, data imported!<br/>
237 (you shouldn't ever see this message again. To prove it, refresh the page or <a href="$script_url/$name">click here</a>.)
250 my $max_update_timestamp = 0;
251 my $latest_cal_id = 0;
252 if ($options{data_storage_mode} == 0 ) { # flat text files
253 open (FH, "$options{calendars_file}") || {$debug_info.= "unable to open file $options{calendars_file}\n"};
255 my @calendar_lines=<FH>;
258 # For the calendars, we do "complete" xml parsing (no validation or DTD though)
259 foreach $line (@calendar_lines) {
260 if ($line !~ /\S/) {next;} # ignore blank lines
261 #if ($line =~ /<\/?xml>/) {next;} # ignore <xml> and </xml lines>
262 my %calendar = %{&xml2calendar($line)};
263 next if ($calendar{id} eq ""); # don't propagate corrupt data.
264 $calendars{"$calendar{id}"} = \%calendar;
266 #the calendar with id 0 is assumed to be the master calendar.
267 #its password can be used to approve/edit/delete any event
269 if ($calendar{id} eq "0") {$master_password = $calendar{password};}
271 if ($calendar{update_timestamp} > $max_update_timestamp) {
272 $max_update_timestamp = $calendar{update_timestamp};
273 $latest_cal_id = $calendar{id};
276 } else { # SQL database
277 my $query_string="select * from $options{calendars_table};";
278 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
280 if ($dbh->errstr ne "") {
281 $debug_info .= "Error loading calendars!\n".$dbh->errstr."\n";
282 $debug_info .= "query string:\n$query_string\n";
285 while(@row = $sth->fetchrow_array) {
286 my $cal_id = $row[0];
289 my %calendar = %{&xml2calendar($line)};
290 $calendars{$calendar{id}} = \%calendar;
292 #the calendar with id 0 is assumed to be the master calendar.
293 #its password can be used to approve/edit/delete any event
295 if ($calendar{id} eq "0") {$master_password = $calendar{password};}
297 if ($calendar{update_timestamp} > $max_update_timestamp) {
298 $max_update_timestamp = $calendar{update_timestamp};
299 $latest_cal_id = $calendar{id};
305 # force all calendars to the same timezone?
306 if ($options{force_single_timezone} eq "1") {
307 foreach $cal_id (keys %calendars) {
308 $calendars{$cal_id}{gmtime_diff} = $calendars{0}{gmtime_diff};
315 my $max_new_cal_timestamp = 0;
316 my $max_new_event_timestamp = 0;
319 if ($options{data_storage_mode} == 0 ) { # flat text files
320 open (FH, "$options{pending_actions_file}") || {$debug_info.= "unable to open file $options{pending_actions_file}\n"};
324 } else { # SQL database
325 my $query_string="select * from $options{pending_actions_table};";
326 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
328 if ($dbh->errstr ne "") {
329 $debug_info .= "Error loading actions!\n".$dbh->errstr."\n";
330 $debug_info .= "query string:\n$query_string\n";
333 while(@row = $sth->fetchrow_array) {
334 my $action_id = $row[0];
341 # For the calendars, we do "complete" xml parsing (no validation or DTD though)
342 foreach $line (@lines) {
343 if ($line !~ /\S/) {next;} # ignore blank lines
345 my ($id) = &xml_quick_extract($line, "id");
348 if ($id > $max_action_id) {
349 $max_action_id = $id;
352 my ($type) = &xml_quick_extract($line, "action_type");
353 $type = &decode($type);
355 if ($type eq "new_calendar") {
356 my ($data) = &xml_quick_extract($line, "action_data");
357 $data = &decode($data);
359 my %calendar = %{&xml2calendar($data)};
360 $new_calendars{$id} = \%calendar;
362 $max_new_cal_id = ($calendar{id} > $max_new_cal_id) ? $calendar{id} : $max_new_cal_id;
364 if ($calendar{update_timestamp} > $max_new_cal_timestamp) {
365 $max_new_cal_timestamp = $calendar{update_timestamp};
366 $latest_new_cal_id = $calendar{id};
368 } elsif ($type eq "new_event") {
369 my ($data) = &xml_quick_extract($line, "action_data");
370 $data = &decode($data);
372 my %event = %{&xml2event($data)};
373 $new_events{$id} = \%event;
375 # if this is a recurring event, grab recurrence parms, slap them onto the event data structure
376 # this the best thing about perl - the ease of extending data structures at runtime.
377 my ($recurring) = &xml_quick_extract($data, "recurring");
379 if ($recurring eq "1") {
380 #$debug_info .= "(load_actions) new_event $id recurring=1\n";
381 my %recurrence_parms;
383 my ($duration) = &xml_quick_extract($data, "duration");
384 $recurrence_parms{'duration'} = &decode($duration);
386 my ($recurrence_type) = &xml_quick_extract($data, "recurrence_type");
387 $recurrence_parms{'recurrence_type'} = &decode($recurrence_type);
389 my ($custom_months_string) = &xml_quick_extract($data, "custom_months");
390 $custom_months_string = &decode($custom_months_string);
391 my @custom_months = split(/,/, $custom_months_string);
392 $recurrence_parms{'custom_months'} = \@custom_months;
394 my ($weekday_of_month_type) = &xml_quick_extract($data, "weekday_of_month_type");
395 $recurrence_parms{'weekday_of_month_type'} = &decode($weekday_of_month_type);
397 my ($every_x_days) = &xml_quick_extract($data, "every_x_days");
398 $recurrence_parms{'every_x_days'} = &decode($every_x_days);
400 my ($every_x_weeks) = &xml_quick_extract($data, "every_x_weeks");
401 $recurrence_parms{'every_x_weeks'} = &decode($every_x_weeks);
403 my ($year_fit_type) = &xml_quick_extract($data, "year_fit_type");
404 $recurrence_parms{'year_fit_type'} = &decode($year_fit_type);
406 my ($recur_end_timestamp) = &xml_quick_extract($data, "recur_end_timestamp");
407 $recurrence_parms{'recur_end_timestamp'} = &decode($recur_end_timestamp);
409 $new_events{$id}{recurring} = \%recurrence_parms;
410 $new_events{$id}{recurrence_parms} = \%recurrence_parms;
413 $max_new_event_id = ($event{id} > $max_new_event_id) ? $event{id} : $max_new_event_id;
415 if ($event{update_timestamp} > $max_new_event_timestamp) {
416 $max_new_event_timestamp = $event{update_timestamp};
417 $latest_new_event_id = $event{id};
422 %latest_new_calendar = %{$new_calendars{$latest_new_cal_id}};
424 #$debug_info .= (scalar keys %new_calendars)." new calendars\n";
425 #$debug_info .= (scalar keys %new_events)." new events\n";
429 if ($options{data_storage_mode} == 0 ) { # flat text files
430 open (FH, "$options{users_file}") || {$debug_info.= "unable to open file $options{users_file}\n"};
435 foreach $user_line (@user_lines) {
436 my %user = %{&xml2user($user_line)};
437 $users{$user{id}} = \%user;
439 if ($user{id} > $max_user_id) {$max_user_id = $user{id};}
442 #$debug_info .= "calendar $cal_id, ".(scalar @calendar_users)." users\n";
444 } else { # SQL database
445 my $query_string="select * from $options{users_table};";
446 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
448 if ($dbh->errstr ne "") {
449 $debug_info .= "Error loading users!\n".$dbh->errstr."\n";
450 $debug_info .= "query string:\n$query_string\n";
453 while(@row = $sth->fetchrow_array) {
455 my $xml_data = $row[1];
456 my $update_timestamp = $row[2];
458 my %user = %{&xml2user($xml_data)};
459 $users{$id} = \%user;
461 if ($id > $max_user_id) {$max_user_id = $user{id};}
470 $normalized_timezone = 0;
471 # load events for a given number of calendars, within a given time range.
472 my ($start, $end, $temp) = @_;
473 my @calendar_ids = @{$temp};
475 #$debug_info .="(load_events)\n";
476 #$debug_info .="start: $start\n";
477 #$debug_info .="end: $end\n";
479 if ($options{data_storage_mode} == 0 ) { # flat text files
480 open (FH, "$options{events_file}") || {$debug_info.= "unable to open file $options{events_file}\n"};
482 my @event_lines=<FH>;
485 my $max_update_timestamp = 0;
486 my $latest_event_id = 0;
488 my $event_loaded_count = 0;
489 foreach $line (@event_lines) {
490 my $temp_line = substr($line,0,120); # grab first 180 characters
491 $temp_line =~ s/<title.+//; # remove everything afer evt_label
492 $temp_line =~ s/<event>//; # remove <event>
494 $temp_line =~ /<id>(\d+)/;
496 next if ($evt_id eq "");
499 $temp_line =~ /<start>(.+?)</;
500 my $temp_start_timestamp = &decode($1);
501 $temp_line =~ /<end>(.+?)</;
502 my $temp_end_timestamp = &decode($1);
504 if ($temp_end_timestamp < $start && $start ne "all") {next;} # in the past
505 if ($temp_start_timestamp > $end && $start ne "all") {next;} # in the future
508 if ($temp_line =~ /<cal_ids>(.+?)</) {
509 @temp_cal_ids = split(',', $1);
512 my $cal_valid = &intersects(\@temp_cal_ids, \@calendar_ids);
513 if ($cal_valid == 0 && $start ne "all") {next;} # event on some other calendar that we don't care about
515 $event_loaded_count++;
517 # exclude event from merged calendars
518 my %event = %{&xml2event($line)};
519 my @current_cal_id_array = ($current_calendar{id});
520 if ($event{block_merge} eq "1" && ! &intersects(\@temp_cal_ids, \@current_cal_id_array)) {
524 $events{$event{id}} = \%event;
526 $max_event_id = ($event{id} > $max_event_id) ? $event{id} : $max_event_id;
527 $max_series_id = ($event{series_id} > $max_series_id) ? $event{series_id} : $max_series_id;
529 if ($event{update_timestamp} > $max_update_timestamp) {
530 $max_update_timestamp = $event{update_timestamp};
531 $latest_event_id = $event{id};
534 #$debug_info .= "$event_loaded_count events total.\n";
535 #$debug_info .= "loaded event $evt_id\n";
537 } elsif ($options{data_storage_mode} > 0 ) { # SQL database
539 if ($start eq "all") {
540 $query_string="select * from $options{events_table};";
541 $loaded_all_events = 1;
543 $query_string = "select * from $options{events_table} where (start > $start and end < $end )";
544 $query_string = "select * from $options{events_table} where (start > $start and [end] < $end )" if ($options{data_storage_mode} == 2);
546 if ($calendar_ids[0] ne "" && $calendar_ids[0] !~ /\D/) {
548 if ($options{data_storage_mode} == 2) {
549 $query_string .= " and ( cal_ids like '$calendar_ids[0]' or cal_ids like '$calendar_ids[0],%' or cal_ids like '%,$calendar_ids[0]' or cal_ids like '%,$calendar_ids[0],%'";
551 $query_string .= " and ( cal_ids='$calendar_ids[0]' or cal_ids like '$calendar_ids[0],%' or cal_ids like '%,$calendar_ids[0]' or cal_ids like '%,$calendar_ids[0],%'";
554 for ($l1=1;$l1<scalar @calendar_ids;$l1++) {
555 if ($options{data_storage_mode} == 2) {
556 if ($calendar_ids[$l1] ne "" && $calendar_ids[$l1] !~ /\D/) {$query_string .= " or cal_ids like '$calendar_ids[$l1]' or cal_ids like '$calendar_ids[$l1],%' or cal_ids like '%,$calendar_ids[$l1]' or cal_ids like '%,$calendar_ids[$l1],%'";}
558 if ($calendar_ids[$l1] ne "" && $calendar_ids[$l1] !~ /\D/) {$query_string .= " or cal_ids='$calendar_ids[$l1]' or cal_ids like '$calendar_ids[$l1],%' or cal_ids like '%,$calendar_ids[$l1]' or cal_ids like '%,$calendar_ids[$l1],%'";}
561 $query_string .= ")";
564 $query_string .= ";";
568 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
570 if ($dbh->errstr ne "") {
571 $debug_info .= "Error loading events!\n".$dbh->errstr."\n";
572 $debug_info .= "query string:\n$query_string\n";
575 while(@row = $sth->fetchrow_array) {
576 my $evt_id = $row[0];
577 my $temp_cal_id = $row[1];
578 next if ($evt_id eq "");
580 #my $temp_start_timestamp = $row[2];
581 #$temp_line =~ /<end>(\d+)/;
582 #my $temp_end_timestamp = $row[3];
585 foreach $cal_id (@calendar_ids) {
586 if ($temp_cal_id == $cal_id) {$cal_valid=1;}
588 if ($cal_valid == 0 && $start ne "all") {next;} # event on some other calendar that we don't care about
591 my %event = %{&xml2event($line)};
592 # exclude event from merged calendars
593 my %event = %{&xml2event($line)};
594 my @current_cal_id_array = ($current_calendar{id});
595 if ($event{block_merge} eq "1" && ! &intersects(\@temp_cal_ids, \@current_cal_id_array)) {
599 $events{$event{id}} = \%event;
601 $max_series_id = ($event{series_id} > $max_series_id) ? $event{series_id} : $max_series_id;
606 my $query_string="select max(id) from $options{events_table};";
607 $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
609 if ($dbh->errstr ne "") {
610 $debug_info .= "$dbh->errstr\n";
611 $debug_info .= "query string:\n$query_string\n";
613 $max_event_id = $sth->fetchrow_array;
616 $query_string="select * from $options{events_table} order by update_timestamp desc limit 0, 1;";
617 $query_string="select * from $options{events_table} order by update_timestamp desc, 1;" if ($options{data_storage_mode} == 2);
619 $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
621 if ($dbh->errstr ne "") {
622 $debug_info .= "$dbh->errstr\n";
623 $debug_info .= "query string:\n$query_string\n";
626 while(@row = $sth->fetchrow_array) {
627 my $evt_id = $row[0];
628 my $temp_cal_id = $row[1];
631 $line =~ s/<\/?event>//g; # remove <event> and </event>
633 $latest_event_id = $latest_event{id};
639 # load a single event.
642 if ($options{data_storage_mode} == 0 ) { # flat text files
643 open (FH, "$options{events_file}") || {$debug_info.= "unable to open file $options{events_file}\n"};
645 my @event_lines=<FH>;
648 my $max_update_timestamp = 0;
649 my $latest_event_id = 0;
651 my $event_loaded_count = 0;
652 foreach $line (@event_lines) {
653 my $temp_line = substr($line,0,120); # grab first 180 characters
654 $temp_line =~ s/<title.+//; # remove everything afer evt_label
655 $temp_line =~ s/<event>//; # remove <event>
657 $temp_line =~ /<id>(\d+)/;
660 if ($evt_id != $event_id) {next;} # some other event that we don't care about
662 my %event = %{&xml2event($line)};
663 $events{$event{id}} = \%event;
665 if ($event{id} > $max_event_id) {$max_event_id = $event{id};}
667 if ($event{update_timestamp} > $max_update_timestamp) {
668 $max_update_timestamp = $event{update_timestamp};
669 $latest_event_id = $event{id};
673 } else { # SQL database
675 $query_string="select * from $options{events_table} where (id = ?);";
678 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
679 $sth->execute($event_id);
680 if ($dbh->errstr ne "") {
681 $debug_info .= "Error loading events!\n".$dbh->errstr."\n";
682 $debug_info .= "query string:\n$query_string\n";
685 while(@row = $sth->fetchrow_array) {
686 #$debug_info .= "(load_event) loaded event $row[0]\n";
687 my $evt_id = $row[0];
688 my $temp_cal_ids = $row[1];
691 my %event = %{&xml2event($line)};
692 $events{$event{id}} = \%event;
697 sub load_remote_events() {
698 my ($remote_events_xml, $remote_calendar_id, $remote_cal_gmtime_diff) = @_;
700 my %remote_calendar_link = %{$current_calendar{remote_background_calendars}{$remote_calendar_id}};
701 my @remote_calendars = &xml_quick_extract($remote_events_xml, "calendar");
703 foreach $temp (@remote_calendars) {
704 my %remote_calendar = %{&xml2calendar($temp)};
705 #$debug_info .= "successfully fetched remote calendar: $remote_calendar{title}\n";
706 #$debug_info .= "$remote_calendar{title} $remote_calendar{title}: $remote_calendar{gmtime_diff}\n";
709 my @remote_events = &xml_quick_extract($remote_events_xml, "event");
710 foreach $temp (@remote_events) {
711 my %remote_event = %{&xml2event($temp)};
712 #$debug_info .= "successfully fetched remote event: $remote_event{title}\n";
714 my $new_remote_event_id = "r".($max_remote_event_id);
715 $remote_event{remote_event_id} = $remote_event{id};
716 $remote_event{id} = $new_remote_event_id;
717 $remote_event{remote_calendar} = \%remote_calendar_link;
718 $remote_event{remote_gmtime_diff} = $remote_cal_gmtime_diff;
720 #$debug_info .= "\n\$remote_calendars{$remote_event{cal_ids}[0]}{gmtime_diff}: $remote_calendars{$remote_event{cal_ids}[0]}{gmtime_diff}\n";
721 #$debug_info .= "\$remote_calendars{$remote_event{cal_ids}[0]}{title}: $remote_calendars{$remote_event{cal_ids}[0]}{title}\n";
722 #$debug_info .= "\$remote_cal_gmtime_diff: $remote_cal_gmtime_diff\n";
725 #$debug_info .= "before offset: remote event $remote_event{id} start $remote_event{start}, end $remote_event{end}\n";
727 #$remote_event{start} += $remote_cal_gmtime_diff *3600;
728 #$remote_event{end} += $remote_cal_gmtime_diff *3600;
730 #$debug_info .= "after offset: remote event $remote_event{id} start $remote_event{start}, end $remote_event{end}\n";
734 #$debug_info .= "new remote id: $new_remote_event_id\n";
735 #$debug_info .= "remote url: $remote_event{remote_calendar}{url}\n";
737 $events{$new_remote_event_id} = \%remote_event;
739 $max_remote_event_id++;
741 #$debug_info .= "event r2: $events{r2}{title} ($events{r2}{start})\n";
742 #$debug_info .= "\n\n";
747 sub get_events_in_series() {
748 my ($series_id) = @_;
751 &load_events("all") unless $loaded_all_events;
752 foreach $event_id (keys %events) {
753 if ($events{$event_id}{series_id} eq $series_id) {
754 push @series_ids, $event_id
760 # add an event to the data file
763 # temporary copy of the event in question
764 my %temp_event = %{$events{$event_id}};
766 if ($options{data_storage_mode} == 0 ) { # flat text files
768 my $event_xml .= &event2xml($events{$event_id})."\n";
769 $event_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;
770 open (FH, ">>$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
775 my $event_xml = &event2xml(\%temp_event);
777 my $cal_ids_string = "";
778 foreach $cal_id (@{$temp_event{cal_ids}}) {
779 $cal_ids_string .= "$cal_id";
780 if ($cal_id ne @{$temp_event{cal_ids}}[-1]) {
781 $cal_ids_string .= ",";
784 $cal_ids_string =~ s/,$//;
786 my $query_string="insert into $options{events_table} (id, cal_ids, start, end, xml_data, update_timestamp) values (?, ?, ?, ?, ?, ?);";
787 $query_string="insert into $options{events_table} (id, cal_ids, start, [end], xml_data, update_timestamp) values (?, ?, ?, ?, ?, ?);" if ($options{data_storage_mode} == 2);
789 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
790 $sth->execute($temp_event{id}, $cal_ids_string, $temp_event{start}, $temp_event{end}, $event_xml, $temp_event{update_timestamp});
791 if ($dbh->errstr ne "") {
793 $debug_info .= "Error adding event!\n".$dbh->errstr."\n";
794 $debug_info .= "query string:\n$query_string\n";
799 foreach $cal_id (@{$temp_event{cal_ids}}) {
800 &export_ical($calendars{$cal_id});
804 # add multiple events to the data file
806 my ($event_ids_ref) = @_;
808 my @event_ids = @{$event_ids_ref};
809 if ($options{data_storage_mode} == 0 ) { # flat text files
811 foreach $id (sort {$a <=> $b} @event_ids) {
812 $out_text .= &event2xml($events{$id})."\n";
814 open (FH, ">>$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
819 foreach $id (@event_ids) {
821 my %temp_event = %{$events{$id}};
822 my $event_xml = &event2xml(\%temp_event);
824 my $cal_ids_string = "";
825 foreach $cal_id (@{$temp_event{cal_ids}}) {
826 $cal_ids_string .= "$cal_id";
827 if ($cal_id ne @{$temp_event{cal_ids}}[-1]) {
828 $cal_ids_string .= ",";
831 $cal_ids_string =~ s/,$//;
833 #$debug_info .= "(add events) event $id cal_ids_string: $cal_ids_string\n";
835 if ($options{data_storage_mode} == 2) {
836 $query_string = "insert into $options{events_table} (id, cal_ids, start, [end], xml_data, update_timestamp) values ($temp_event{id}, '$cal_ids_string', $temp_event{start}, $temp_event{end}, '$event_xml', $temp_event{update_timestamp});";
837 $query_string = "insert into $options{events_table} (id, cal_ids, start, [end], xml_data, update_timestamp) values (?,?,?,?,?,?);";
839 $query_string = "insert into $options{events_table} (id, cal_ids, start, end, xml_data, update_timestamp) values ($temp_event{id}, '$cal_ids_string', $temp_event{start}, $temp_event{end}, '$event_xml', $temp_event{update_timestamp});";
840 $query_string = "insert into $options{events_table} (id, cal_ids, start, end, xml_data, update_timestamp) values (?,?,?,?,?,?);";
844 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
845 $sth->execute($temp_event{id}, $cal_ids_string, $temp_event{start}, $temp_event{end}, $event_xml, $temp_event{update_timestamp});
846 if ($dbh->errstr ne "") {
848 $debug_info .= "Error adding event!\n($query_string)\n".$dbh->errstr."\n";
849 $debug_info .= "query string:\n$query_string\n";
855 my %cal_ids_to_export = {};
857 foreach $id (@event_ids) {
859 my %temp_event = %{$events{$id}};
860 foreach $cal_id (@{$temp_event{cal_ids}}) {
861 $cal_ids_to_export{$cal_id} = 1;
865 foreach $cal_id (keys %cal_ids_to_export) {
866 &export_ical($calendars{$cal_id});
871 # update an event (already present in the data file)
875 # temporary copy of the event in question
876 my %temp_event = %{$events{$event_id}};
878 if ($options{data_storage_mode} == 0 ) { # flat text files
880 foreach $id (sort {$a <=> $b} keys %events) {
881 if ($id =~ /\D/) {next};
882 my $event_xml = &event2xml($events{$id})."\n";
883 $out_text .= $event_xml;
884 #if ($id eq $event_id)
885 # {$event_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
887 open (FH, ">$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
892 my $event_xml = &event2xml(\%temp_event);
894 my $cal_ids_string = "";
895 foreach $cal_id (@{$temp_event{cal_ids}}) {
896 $cal_ids_string .= "$cal_id";
897 if ($cal_id ne @{$temp_event{cal_ids}}[-1]) {
898 $cal_ids_string .= ",";
901 $cal_ids_string =~ s/,$//;
903 my $query_string = "update $options{events_table} set cal_ids=?, start=?, end=?, xml_data=?, update_timestamp=? where id=?;";
904 $query_string = "update $options{events_table} set cal_ids=?, start=?, [end]=?, xml_data=?, update_timestamp=? where id=?;" if ($options{data_storage_mode} == 2);
906 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
907 $sth->execute($cal_ids_string, $temp_event{start}, $temp_event{end}, $event_xml, $temp_event{update_timestamp}, $temp_event{id});
908 if ($dbh->errstr ne "") {
910 $debug_info .= "Error updating event!\n".$dbh->errstr."\n";
911 $debug_info .= "query string:\n$query_string\n";
916 foreach $cal_id (@{$temp_event{cal_ids}}) {
917 &export_ical($calendars{$cal_id});
922 # update multiple events
923 sub update_events() {
924 my ($event_ids_ref) = @_;
925 my @event_ids = @{$event_ids_ref};
927 if ($options{data_storage_mode} == 0 ) { # flat text files
929 foreach $id (sort {$a <=> $b} keys %events) {
930 if ($id =~ /\D/) {next};
931 my $event_xml = &event2xml($events{$id})."\n";
932 $out_text .= $event_xml;
934 open (FH, ">$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
939 # temporary copy of the event in question
940 foreach $event_id (@event_ids) {
941 my %temp_event = %{$events{$event_id}};
942 my $event_xml = &event2xml(\%temp_event);
944 my $cal_ids_string = "";
945 foreach $cal_id (@{$temp_event{cal_ids}}) {
946 $cal_ids_string .= "$cal_id";
947 if ($cal_id ne @{$temp_event{cal_ids}}[-1]) {
948 $cal_ids_string .= ",";
951 $cal_ids_string =~ s/,$//;
953 my $query_string="update $options{events_table} set cal_ids=?, start=?, end=?, xml_data=?, update_timestamp=? where id=?;";
954 $query_string="update $options{events_table} set cal_ids=?, start=?, [end]=?, xml_data=?, update_timestamp=? where id=?;" if ($options{data_storage_mode} == 2);
956 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
957 $sth->execute($cal_ids_string, $temp_event{start}, $temp_event{end}, $event_xml, $temp_event{update_timestamp}, $temp_event{id});
958 if ($dbh->errstr ne "") {
960 $debug_info .= "Error updating event!\n".$dbh->errstr."\n";
961 $debug_info .= "query string:\n$query_string\n";
968 my %cal_ids_to_export = {};
970 foreach $id (@event_ids) {
972 my %temp_event = %{$events{$id}};
973 foreach $cal_id (@{$temp_event{cal_ids}}) {
974 $cal_ids_to_export{$cal_id} = 1;
978 foreach $cal_id (keys %cal_ids_to_export) {
979 &export_ical($calendars{$cal_id});
988 delete $events{$event_id};
990 if ($options{data_storage_mode} == 0 ) { # flat text files
992 foreach $id (sort {$a <=> $b} keys%events) {
993 if ($id =~ /\D/) {next};
994 $out_text .= &event2xml($events{$id})."\n";
996 open (FH, ">$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
1001 my $query_string="delete from $options{events_table} where id=?;";
1002 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1003 $sth->execute($event_id);
1004 if ($dbh->errstr ne "") {
1006 $debug_info .= "Error deleting event!\n".$dbh->errstr."\n";
1007 $debug_info .= "query string:\n$query_string\n";
1013 # delete multiple events
1014 sub delete_events() {
1015 my ($event_ids_ref) = @_;
1016 my @event_ids = @{$event_ids_ref};
1018 if ($options{data_storage_mode} == 0 ) { # flat text files
1019 foreach $event_id (@event_ids) {delete $events{$event_id};}
1022 foreach $id (sort {$a <=> $b} keys%events) {
1023 if ($id =~ /\D/) {next};
1024 $out_text .= &event2xml($events{$id})."\n";
1026 open (FH, ">$options{events_file}") || {$debug_info .= "unable to open file $options{events_file} for writing!\n"};
1031 foreach $event_id (@event_ids) {
1032 my $query_string="delete from $options{events_table} where id=$event_id;";
1033 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1035 if ($dbh->errstr ne "") {
1037 $debug_info .= "Error deleting event!\n".$dbh->errstr."\n";
1038 $debug_info .= "query string:\n$query_string\n";
1046 my ($action_id, $action_type) = @_;
1049 #$debug_info .= "adding new calendar $cal_id\n";
1050 if ($options{data_storage_mode} == 0 ) { # flat text files
1051 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1052 foreach $id (sort {$a <=> $b} keys %new_calendars) {
1053 #$debug_info .= "calendar2xml $id\n";
1054 my $xml = &calendar2xml($new_calendars{$id});
1055 #if ($id eq $cal_id)
1056 # {$cal_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
1057 $out_text .= "<id>$id</id><action_type>new_calendar</action_type><action_data>".&encode($xml)."</action_data>\n";
1060 foreach $id (sort {$a <=> $b} keys %new_events) {
1061 #$debug_info .= "calendar2xml $id\n";
1062 my $xml = &event2xml($new_events{$id});
1064 if ($new_events{$id}{recurring} ne "") { # add extra fields that will be needed if this event is approved.
1065 $xml .= &xml_store(1, "recurring");
1066 $xml .= "<recurrence_parms>";
1067 foreach $recurrence_parm (keys %{$new_events{$id}{recurrence_parms}}) {
1068 #$debug_info .= "recurrence parm $recurrence_parm, ref: ".(ref $new_events{$id}{recurrence_parms}{$recurrence_parm})."\n";
1069 $xml .= &xml_store($new_events{$id}{recurrence_parms}{$recurrence_parm}, $recurrence_parm);
1071 $xml .= "</recurrence_parms>";
1073 $out_text .= "<id>$id</id><action_type>new_event</action_type><action_data>".&encode($xml)."</action_data>\n";
1076 open (FH, ">$options{pending_actions_file}") || {$debug_info.= "unable to open file $options{pending_actions_file} for writing!\n"};
1083 if ($action_type eq "new_calendar") {
1084 my $xml = &calendar2xml($new_calendars{$action_id});
1085 $out_text .= "<id>$action_id</id><action_type>new_calendar</action_type><action_data>".&encode($xml)."</action_data>\n";
1086 } elsif ($action_type eq "new_event") {
1087 my $xml = &event2xml($new_events{$action_id});
1089 if ($new_events{$action_id}{recurring} ne "") { # add extra fields that will be needed if this event is approved.
1091 $xml .= &xml_store(1, "recurring");
1092 $xml .= "<recurrence_parms>";
1093 foreach $recurrence_parm (keys %{$new_events{$action_id}{recurrence_parms}}) {
1094 #$debug_info .= "recurrence parm $recurrence_parm, ref: ".(ref $new_events{$id}{recurrence_parms}{$recurrence_parm})."\n";
1095 $xml .= &xml_store($new_events{$action_id}{recurrence_parms}{$recurrence_parm}, $recurrence_parm);
1097 $xml .= "</recurrence_parms>";
1099 $out_text .= "<id>$action_id</id><action_type>new_event</action_type><action_data>".&encode($xml)."</action_data>\n";
1102 # insert the action to the table
1103 my $query_string = "insert into $options{pending_actions_table} (id, xml_data, update_timestamp) values (?,?,?);";
1104 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1105 $sth->execute($action_id, $out_text, $rightnow);
1106 if ($dbh->errstr ne "") {
1108 $error_info .= "Error adding new calendar!\n".$dbh->errstr."\n";
1109 $error_info .= "$query_string\n";
1114 sub add_new_calendars() { # add multiple calendars (this is used for data conversion)
1115 my ($add_new_cal_ids_ref) = @_;
1116 my @add_new_cal_ids = @{$add_new_cal_ids_ref};
1118 if ($options{data_storage_mode} == 0 ) { # flat text files
1120 foreach $new_cal_id (@add_new_cal_ids) {
1121 my $new_cal_xml = &calendar2xml($new_calendars{$new_cal_id});
1122 my $query_string="insert into $options{calendars_table} (id, xml_data, update_timestamp) values (?, ?, ?);";
1123 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1124 $sth->execute($new_cal_id, $new_cal_xml, $new_calendars{$new_cal_id}{update_timestamp});
1125 if ($dbh->errstr ne "") {
1126 $debug_info .= "Error adding new calendar!\n".$dbh->errstr."\n";
1127 $debug_info .= "$query_string\n";
1133 sub delete_pending_actions() { # this is called after a record is transferred from new_calendars to calendars
1136 my @pending_actions_to_delete = @{$temp1};
1138 if ($options{data_storage_mode} == 0 ) { # flat text files
1139 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1140 foreach $id (sort {$a <=> $b} keys %new_calendars) {
1141 if (&contains(\@pending_actions_to_delete, $id)) {next;}
1142 #$debug_info .= "calendar2xml $id\n";
1143 my $xml = &calendar2xml($new_calendars{$id});
1144 #if ($id eq $cal_id)
1145 # {$cal_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
1146 $out_text .= "<id>$id</id><action_type>new_calendar</action_type><action_data>".&encode($xml)."</action_data>\n";
1149 foreach $id (sort {$a <=> $b} keys %new_events) {
1150 if (&contains(\@pending_actions_to_delete, $id)) {next;}
1152 #$debug_info .= "calendar2xml $id\n";
1153 my $xml = &event2xml($new_events{$id});
1155 if ($new_events{$id}{recurring} ne "") # add extra fields that will be needed if this event is approved.
1157 $xml .= &xml_store(1, "recurring_event");
1158 $xml .= "<recurrence_parms>";
1159 foreach $recurrence_parm (keys %{$new_events{$id}{recurrence_parms}}) {
1160 $xml .= &xml_store($new_events{$id}{recurrence_parms}{$recurrence_parm}, $recurrence_parm);
1162 $xml .= "</recurrence_parms>";
1165 #if ($id eq $cal_id)
1166 # {$cal_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
1167 $out_text .= "<id>$id</id><action_type>new_event</action_type><action_data>".&encode($xml)."</action_data>\n";
1170 open (FH, ">$options{pending_actions_file}") || {$debug_info.= "unable to open file $options{pending_actions_file} for writing!\n"};
1175 foreach $action_id (@pending_actions_to_delete) {
1176 my $query_string="delete from $options{pending_actions_table} where id=?;";
1177 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1178 $sth->execute($action_id);
1179 if ($dbh->errstr ne "") {
1180 $debug_info .= "Error deleting pending calendar after approval!\n".$dbh->errstr."\n";
1181 $debug_info .= "$query_string\n";
1187 sub add_calendars() { # add multiple calendars (this is used for data conversion)
1188 my ($add_cal_ids_ref) = @_;
1189 my @add_cal_ids = @{$add_cal_ids_ref};
1191 if ($options{data_storage_mode} == 0 ) { # flat text files
1193 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1194 foreach $calendar_id (sort {$a <=> $b} keys %calendars) {
1195 my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
1196 $out_text .= $cal_xml;
1199 open (FH, ">$options{calendars_file}") || {$debug_info.= "unable to open file $options{calendars_file} for writing!\n"};
1204 foreach $cal_id (@add_cal_ids) {
1205 if ($cal_id eq "") {next};
1207 my $cal_xml = &calendar2xml($calendars{$cal_id});
1208 my $query_string="insert into $options{calendars_table} (id, xml_data, update_timestamp) values (?, ?, ?);";
1209 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1210 $sth->execute($cal_id, $cal_xml, $calendars{$cal_id}{update_timestamp});
1211 if ($dbh->errstr ne "") {
1212 $debug_info .= "Error adding calendar!\n($query_string)\n".$dbh->errstr."\n";
1213 $debug_info .= "$query_string\n";
1222 sub update_calendar(){
1225 if ($options{data_storage_mode} == 0 ) { # flat text files
1227 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1228 foreach $calendar_id (sort {$a <=> $b} keys %calendars) {
1229 my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
1230 $out_text .= $cal_xml;
1233 open (FH, ">$options{calendars_file}") || {$debug_info.= "unable to open file $options{calendars_file} for writing!\n"};
1238 my $cal_xml = &calendar2xml($calendars{$cal_id});
1240 # add the primary calendar to the table
1241 my $query_string="update $options{calendars_table} set xml_data=?, update_timestamp=? where id=?;";
1242 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1243 $sth->execute($cal_xml, $calendars{$cal_id}{update_timestamp}, $cal_id);
1244 if ($dbh->errstr ne "") {
1246 $error_info .= "Error updating calendar!\n".$dbh->errstr."\n";
1247 $error_info .= "$query_string\n";
1250 $calendars{$cal_id}{'update_timestamp'} = time();
1251 &export_ical($calendars{$cal_id});
1255 sub update_calendars() { # update multiple calendars
1256 my ($update_cal_ids_ref) = @_;
1257 my @update_cal_ids = @{$update_cal_ids_ref};
1259 if ($options{data_storage_mode} == 0 ) { # flat text files
1261 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1262 foreach $calendar_id (sort {$a <=> $b} keys %calendars) {
1263 my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
1264 $out_text .= $cal_xml;
1267 open (FH, ">$options{calendars_file}") || {$debug_info.= "unable to open file $options{calendars_file} for writing!\n"};
1272 foreach $cal_id (@update_cal_ids) {
1273 my $cal_xml = &calendar2xml($calendars{$cal_id});
1274 my $query_string="update $options{calendars_table} set xml_data=?, update_timestamp=? where id=?;";
1275 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1276 $sth->execute($cal_xml, $calendars{$cal_id}{update_timestamp}, $cal_id);
1277 if ($dbh->errstr ne "") {
1278 $debug_info .= "Error updating calendar!\n".$dbh->errstr."\n";
1279 $debug_info .= "$query_string\n";
1284 foreach $cal_id (@update_cal_ids) {
1285 $calendars{$cal_id}{'update_timestamp'} = time();
1286 &export_ical($calendars{$cal_id});
1291 sub delete_calendar() {
1293 delete $calendars{$cal_id};
1295 if ($options{data_storage_mode} == 0 ) { # flat text files
1297 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1298 foreach $calendar_id (sort {$a <=> $b} keys %calendars) {
1299 next if ($calendar_id eq "");
1300 my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
1301 $out_text .= $cal_xml;
1304 open (FH, ">$options{calendars_file}") || {$debug_info.= "unable to open file $options{calendars_file} for writing!\n"};
1309 my $query_string="delete from $options{calendars_table} where id=?;";
1310 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1311 $sth->execute($cal_id);
1312 if ($dbh->errstr ne "") {
1313 $debug_info .= "Error deleting calendar!\n".$dbh->errstr."\n";
1314 $debug_info .= "$query_string\n";
1322 # temporary copy of the event in question
1323 my %user = %{$users{$user_id}};
1325 if ($options{data_storage_mode} == 0 ) { # flat text files
1327 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1328 foreach $user_id (sort {$a <=> $b} keys %users) {
1329 my $xml = &user2xml($users{$user_id})."\n";
1330 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1333 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1338 my $xml = &user2xml($users{$user_id})."\n";
1339 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1342 my $query_string="insert into $options{users_table} (id, xml_data, update_timestamp) values (?, ?, ?);";
1343 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1344 $sth->execute($user_id, $xml, $rightnow);
1345 if ($dbh->errstr ne "") {
1346 $debug_info .= "Error adding user!\n".$dbh->errstr."\n";
1347 $debug_info .= "$query_string\n";
1356 if ($options{data_storage_mode} == 0 ) { # flat text files
1358 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1359 foreach $user_id (sort {$a <=> $b} keys %users) {
1360 next if ($user_id eq "");
1361 my $xml = &user2xml($users{$user_id})."\n";
1362 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1365 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1370 my $xml = &user2xml($users{$user_id})."\n";
1371 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1374 my $query_string="update $options{users_table} set xml_data=?, update_timestamp=? where id=?;";
1375 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1376 $sth->execute($xml, $rightnow, $user_id);
1377 if ($dbh->errstr ne "") {
1379 $debug_info .= "Error updating user!\n".$dbh->errstr."\n";
1380 $debug_info .= "query string:\n$query_string\n";
1389 delete $users{$user_id};
1391 if ($options{data_storage_mode} == 0 ) { # flat text files
1393 # write out the entire file! Grossly inefficient, but that's how it goes if you don't use a DB.
1394 foreach $user_id (sort {$a <=> $b} keys %users) {
1395 next if ($user_id eq "");
1396 my $xml = &user2xml($users{$user_id})."\n";
1397 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1401 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1407 my $query_string="delete from $options{users_table} where id=?;";
1408 my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
1409 $sth->execute($user_id);
1410 if ($dbh->errstr ne "") {
1411 $debug_info .= "Error deleting user!\n".$dbh->errstr."\n";
1412 $debug_info .= "$query_string\n";
1419 sub calendar2xml() {
1420 my ($calendar_ref) = @_;
1422 my %calendar = %{$calendar_ref};
1424 #$error_info .= "Calendar title: $calendar{title}\n";
1426 my $xml_data = "<calendar>";
1427 $xml_data .= &xml_store($calendar{id}, "id");
1428 $xml_data .= &xml_store($calendar{type}, "type");
1429 $xml_data .= &xml_store($calendar{url}, "url");
1430 $xml_data .= &xml_store($calendar{title}, "title");
1431 $xml_data .= &xml_store($calendar{details}, "details");
1432 $xml_data .= &xml_store($calendar{link}, "link");
1433 $xml_data .= &xml_store($calendar{password}, "admin_password");
1435 # add local background calendars
1436 foreach $local_background_calendar_id (sort {$a <=> $b} keys %{$calendar{local_background_calendars}}) {$xml_data .= "<background_calendar><id>$local_background_calendar_id</id></background_calendar>";}
1438 # add remote background calendars
1439 #$debug_info .= "adding remote background calendars\n";
1440 foreach $remote_background_calendar_id (sort {$a <=> $b} keys %{$calendar{remote_background_calendars}}) {
1441 #$debug_info .= "  id $remote_background_calendar_id\n";
1442 my %c = %{$calendar{remote_background_calendars}{$remote_background_calendar_id}};
1443 if (lc $c{type} eq "plans") {
1444 #$debug_info .= " type: $c{type}\n";
1445 $xml_data .= "<remote_background_calendar><id>$remote_background_calendar_id</id><type>$c{type}</type><version>$c{version}</version><remote_id>$c{remote_id}</remote_id><url>$c{url}</url><password>$c{password}</password></remote_background_calendar>";
1449 # add selectable calendars
1450 foreach $selectable_calendar (sort {$a <=> $b} keys %{$calendar{selectable_calendars}}) {$xml_data .= "<selectable_calendar>$selectable_calendar</selectable_calendar>";}
1452 # make sure the calendar can select itself.
1453 if ($calendar{selectable_calendars}{$calendar{id}} ne "1") {$calendar{selectable_calendars}{$calendar{id}} = 1;}
1457 $xml_data .= &xml_store($calendar{new_calendars_automatically_selectable}, "new_calendars_automatically_selectable");
1458 $xml_data .= &xml_store($calendar{list_background_calendars_together}, "list_background_calendars_together");
1459 $xml_data .= &xml_store($calendar{calendar_events_color}, "calendar_events_color");
1460 $xml_data .= &xml_store($calendar{background_events_display_style}, "background_events_display_style");
1461 $xml_data .= &xml_store($calendar{background_events_fade_factor}, "background_events_fade_factor");
1462 $xml_data .= &xml_store($calendar{background_events_color}, "background_events_color");
1463 $xml_data .= &xml_store($calendar{default_number_of_months}, "default_number_of_months");
1464 $xml_data .= &xml_store($calendar{max_number_of_months}, "max_number_of_months");
1465 $xml_data .= &xml_store($calendar{gmtime_diff}, "gmtime_diff");
1466 $xml_data .= &xml_store($calendar{date_format}, "date_format");
1467 $xml_data .= &xml_store($calendar{week_start_day}, "week_start_day");
1468 $xml_data .= &xml_store($calendar{event_change_email}, "event_change_email");
1469 $xml_data .= &xml_store($calendar{info_window_size}, "info_window_size");
1470 $xml_data .= &xml_store($calendar{custom_template}, "custom_template");
1471 $xml_data .= &xml_store($calendar{custom_stylesheet}, "custom_stylesheet");
1472 $xml_data .= &xml_store($calendar{update_timestamp}, "update_timestamp");
1473 $xml_data .= &xml_store($calendar{allow_remote_calendar_requests}, "allow_remote_calendar_requests");
1474 $xml_data .= &xml_store($calendar{remote_calendar_requests_require_password}, "remote_calendar_requests_require_password");
1475 $xml_data .= &xml_store($calendar{remote_calendar_requests_password}, "remote_calendar_requests_password");
1477 $xml_data .= "</calendar>";
1482 sub calculate_event_days() {
1483 my ($start, $end, $id) = @_;
1485 my $duration = $end - $start;
1486 #$debug_info .= "id $id duration=$duration\n" if ($id eq "3258");
1487 return 1 if ($duration < 0) ;
1490 if (($duration+1) % 86400 == 0) # all-day event
1491 {$days = int(($duration)/86400)+1;}
1492 else { # partial-day event
1495 for (my $i=$start;$i<$end;$i+=3600) {
1496 #$debug_info .= "id $id i=$i\n" if ($id eq "3258");
1497 if ((gmtime $i)[3] != $mday) {
1499 $mday = (gmtime $i)[3];
1500 #$debug_info .= "id $id mday=$mday days=$days\n" if ($id eq "3258");
1511 $xml =~ s/<\/?event>//g; # remove <event> and </event>
1513 my ($id) = &xml_quick_extract($xml, "id");
1516 my ($cal_id) = &xml_quick_extract($xml, "cal_id");
1517 $cal_id = &decode($cal_id);
1519 my ($cal_ids) = &xml_quick_extract($xml, "cal_ids");
1520 $cal_ids = &decode($cal_ids);
1522 my ($evt_start) = &xml_quick_extract($xml, "start");
1523 $evt_start = &decode($evt_start);
1525 my ($evt_end) = &xml_quick_extract($xml, "end");
1526 $evt_end = &decode($evt_end);
1528 my $evt_gmtime_start = $evt_start;
1529 my $evt_gmtime_end = $evt_end;
1531 my ($series_id) = &xml_quick_extract($xml, "series_id");
1532 $series_id = &decode($series_id);
1534 my ($evt_title) = &xml_quick_extract($xml, "title");
1535 $evt_title = &decode($evt_title);
1537 my ($evt_details) = &xml_quick_extract($xml, "details");
1538 $evt_details = &decode($evt_details);
1540 my ($block_merge) = &xml_quick_extract($xml, "block_merge");
1541 $block_merge = &decode($block_merge);
1543 my $details_url = "";
1544 if ($evt_details =~ /^http.*:\/\/.+\s*$/) {$details_url=1;}
1547 my ($evt_icon) = &xml_quick_extract($xml, "icon");
1548 $evt_icon = &decode($evt_icon);
1550 my ($evt_bgcolor) = &xml_quick_extract($xml, "bgcolor");
1551 $evt_bgcolor = &decode($evt_bgcolor);
1553 my ($evt_unit_number) = &xml_quick_extract($xml, "unit_number");
1554 $evt_unit_number = &decode($evt_unit_number);
1556 my $update_timestamp = 0;
1557 ($update_timestamp) = &xml_quick_extract($xml, "update_timestamp");
1559 my $event_duration = $evt_end - $evt_start;
1561 my $evt_days = 1; # 1 by default - recalculated later
1563 #$debug_info .= "(xml2event) id $id $evt_start - $evt_end\n" if ($id eq "159");
1565 # create cal_ids hash
1568 if ($cal_id ne "") {
1569 push @cal_ids_array, $cal_id;
1571 @cal_ids_array = split(',', $cal_ids);
1575 my $all_day_event = "";
1576 my $no_end_time = "";
1577 if (($event_duration+1) % 86400 == 0) {
1579 $evt_days = &calculate_event_days($evt_start, $evt_end, $id);
1580 #$debug_info .= "event $id ($evt_title) is an all day event\n";
1582 $no_end_time = 1 if ($event_duration == 1);
1583 #$debug_info .= "(xml2event) event $id ($evt_title) has no end time\n" if ($no_end_time eq "1");
1584 # offset start and end by calendar offset.
1585 #my $timezone_offset = $calendars{$cal_ids_array[0]}{gmtime_diff} - $current_calendar{gmtime_diff};
1588 if ($current_cal_id eq "") {
1589 %this_calendar = %{$calendars{$cal_ids_array[0]}};
1591 %this_calendar = %{$calendars{$current_cal_id}};
1594 my $timezone_offset = $this_calendar{gmtime_diff}*3600;
1595 #$debug_info .= "timezone_offset = $timezone_offset\n" if ($id eq "1698");
1597 # calculate_event_days is dependent on the timezone offset of the current calendar
1598 $evt_days = &calculate_event_days($evt_start+$timezone_offset, $evt_end+$timezone_offset, $id);
1599 #$debug_info .= "event $id days: ($evt_days)\n" if ($id eq "1698");
1602 $event = {id => $id,
1603 cal_ids => \@cal_ids_array,
1604 start => $evt_start,
1606 gmtime_start => $evt_gmtime_start,
1607 gmtime_end => $evt_gmtime_end,
1609 series_id => $series_id,
1610 all_day_event => $all_day_event,
1611 no_end_time => $no_end_time,
1612 details_url => $details_url,
1613 title => $evt_title,
1614 details => $evt_details,
1615 block_merge => $block_merge,
1617 bgcolor => $evt_bgcolor,
1618 unit_number => $evt_unit_number,
1619 update_timestamp => $update_timestamp};
1629 my ($id) = &xml_quick_extract($xml, "id");
1632 my ($name) = &xml_quick_extract($xml, "name");
1633 $name = &decode($name);
1635 my ($notes) = &xml_quick_extract($xml, "notes");
1636 $notes = &decode($notes);
1638 my ($password) = &xml_quick_extract($xml, "password");
1639 $password = &decode($password);
1641 my ($timestamp) = &xml_quick_extract($xml, "timestamp");
1642 $timestamp = &decode($timestamp);
1645 my @calendars = &xml_quick_extract($xml, "calendar");
1646 foreach $calendar_xml (@calendars) {
1647 my ($cal_id) = &xml_quick_extract($calendar_xml, "cal_id");
1648 $cal_id = &decode($cal_id);
1650 my ($edit_calendar) = &xml_quick_extract($calendar_xml, "edit_calendar");
1651 $edit_calendar = &decode($edit_calendar);
1653 my ($edit_events) = &xml_quick_extract($calendar_xml, "edit_events");
1654 $edit_events = &decode($edit_events);
1656 $cal_refs{$cal_id}{edit_calendar} = $edit_calendar;
1657 $cal_refs{$cal_id}{edit_events} = $edit_events;
1663 password => $password,
1664 timestamp => $timestamp,
1665 calendars => \%cal_refs
1672 my ($user_ref) = @_;
1673 my %user = %{$user_ref};
1675 my $xml_data = "<user>";
1677 $xml_data .= &xml_store($user{id}, "id");
1678 $xml_data .= &xml_store($user{name}, "name");
1679 $xml_data .= &xml_store($user{notes}, "notes");
1680 $xml_data .= &xml_store($user{password}, "password");
1683 foreach $calendar_id (keys %{$user{calendars}}) {
1684 $xml_data .= "<calendar><cal_id>$calendar_id</cal_id>";
1685 #$xml_data .= "<edit_calendar>$user{calendars}{$calendar_id}{edit_calendar}</edit_calendar>";
1686 $xml_data .= "<edit_events>$user{calendars}{$calendar_id}{edit_events}</edit_events>";
1687 $xml_data .= "</calendar>";
1689 $xml_data .= &xml_store($user{timestamp}, "timestamp");
1691 $xml_data .= "</user>";
1699 sub xml2calendar() {
1705 $xml =~ s/<\/?calendar>//g; # remove <calendar> and </calendar>
1707 my ($cal_id) = &xml_quick_extract($xml, "id");
1709 my ($cal_type) = &xml_quick_extract($xml, "type");
1710 $cal_type = &decode($cal_type);
1711 $cal_type = "plans" if ($cal_type eq "");
1714 my ($cal_url) = &xml_quick_extract($xml, "url");
1715 $cal_url = &decode($cal_url);
1717 my ($cal_title) = &xml_quick_extract($xml, "title");
1718 $cal_title = &decode($cal_title);
1720 my ($cal_details) = &xml_quick_extract($xml, "details");
1721 $cal_details = &decode($cal_details);
1723 my ($cal_link) = &xml_quick_extract($xml, "link");
1724 $cal_link = &decode($cal_link);
1726 my ($cal_password) = &xml_quick_extract($xml, "admin_password");
1727 $cal_password = &decode($cal_password);
1730 my $update_timestamp=0;
1731 ($update_timestamp) = &xml_quick_extract($xml, "update_timestamp");
1732 $update_timestamp = 0 if ($update_timestamp eq "");
1734 # extract local background calendars
1735 my @temp = &xml_quick_extract($xml, "background_calendar");
1736 my %local_background_calendars;
1737 my $num_background_calendars = scalar @temp;
1738 foreach $background_calendar (@temp) {
1739 my ($id) = &xml_quick_extract($background_calendar, "id");
1740 $local_background_calendars{$id} = 1;
1743 # extract remote background calendars
1744 my @temp = &xml_quick_extract($xml, "remote_background_calendar");
1745 my %remote_background_calendars;
1746 my $num_remote_background_calendars = scalar @temp;
1747 foreach $remote_background_calendar (@temp) {
1748 my ($id) = &xml_quick_extract($remote_background_calendar, "id");
1749 my ($type) = &xml_quick_extract($remote_background_calendar, "type");
1750 my ($version) = &xml_quick_extract($remote_background_calendar, "version");
1751 my ($remote_id) = &xml_quick_extract($remote_background_calendar, "remote_id");
1752 my ($url) = &xml_quick_extract($remote_background_calendar, "url");
1753 my ($password) = &xml_quick_extract($remote_background_calendar, "password");
1755 $remote_background_calendars{$id} = {id => $id,
1757 version => $version,
1758 remote_id => $remote_id,
1760 password => $password}
1763 # extract selectable calendars
1764 my %selectable_calendars;
1765 @temp = &xml_quick_extract($xml, "selectable_calendar");
1766 foreach $selectable_calendar (@temp) {
1767 $selectable_calendars{$selectable_calendar} = 1;
1770 my ($new_calendars_automatically_selectable) = &xml_quick_extract($xml, "new_calendars_automatically_selectable");
1771 $new_calendars_automatically_selectable = "no" if ($new_calendars_automatically_selectable eq "");
1773 my ($list_background_calendars_together) = &xml_quick_extract($xml, "list_background_calendars_together");
1774 $list_background_calendars_together = "no" if ($list_background_calendars_together eq "");
1776 my ($calendar_events_color) = &xml_quick_extract($xml, "calendar_events_color");
1777 $calendar_events_color = &decode($calendar_events_color);
1779 my ($background_events_display_style) = &xml_quick_extract($xml, "background_events_display_style");
1780 $background_events_display_style = "normal" if ($background_events_display_style eq "");
1782 my ($background_events_fade_factor) = &xml_quick_extract($xml, "background_events_fade_factor");
1783 $background_events_fade_factor = 1 if ($background_events_fade_factor eq "" || $background_events_fade_factor < 1);
1785 my ($background_events_color) = &xml_quick_extract($xml, "background_events_color");
1786 $background_events_color = &decode($background_events_color);
1787 $background_events_color = "#ffffff" if ($background_events_color eq "");
1789 my ($default_number_of_months) = &xml_quick_extract($xml, "default_number_of_months");
1790 $default_number_of_months = 1 if ($default_number_of_months eq "");
1792 my ($max_number_of_months) = &xml_quick_extract($xml, "max_number_of_months");
1793 $max_number_of_months = 24 if ($max_number_of_months eq "");
1795 my ($gmtime_diff) = &xml_quick_extract($xml, "gmtime_diff");
1796 $gmtime_diff = &decode($gmtime_diff);
1797 $gmtime_diff = 0 if ($gmtime_diff eq "");
1799 my ($date_format) = &xml_quick_extract($xml, "date_format");
1800 $date_format = &decode($date_format);
1801 $date_format = "mm/dd/yy" if ($date_format eq "");
1803 my ($week_start_day) = &xml_quick_extract($xml, "week_start_day");
1804 $week_start_day = "0" if ($week_start_day eq "");
1806 my ($event_change_email) = &xml_quick_extract($xml, "event_change_email");
1807 $event_change_email = &decode($event_change_email);
1809 my @emails = split(/\s+/, $event_change_email);
1814 foreach my $email (@emails) {
1815 next if ($email !~ /\S/); # ignore blanks
1817 push @add_emails, $1 if ($email =~ /add:(.+)/);
1818 push @update_emails, $1 if ($email =~ /update:(.+)/ || $email =~ /change:(.+)/);
1819 push @delete_emails, $1 if ($email =~ /delete:(.+)/ || $email =~ /del:(.+)/);
1821 if ($email !~ /(add:|update:|change:|delete:|del:)/) { # if just a plain email address, add to all 3 categories
1822 push @add_emails, $email;
1823 push @update_emails, $email;
1824 push @delete_emails, $email;
1828 my ($info_window_size) = &xml_quick_extract($xml, "info_window_size");
1829 $info_window_size = "400x400" if ($info_window_size eq "");
1831 my ($custom_template) = &xml_quick_extract($xml, "custom_template");
1832 $custom_template = &decode($custom_template);
1834 my ($custom_stylesheet) = &xml_quick_extract($xml, "custom_stylesheet");
1835 $custom_stylesheet = &decode($custom_stylesheet);
1838 my ($allow_remote_calendar_requests) = &xml_quick_extract($xml, "allow_remote_calendar_requests");
1839 $allow_remote_calendar_requests = &decode($allow_remote_calendar_requests);
1841 my ($remote_calendar_requests_require_password) = &xml_quick_extract($xml, "remote_calendar_requests_require_password");
1842 $remote_calendar_requests_require_password = &decode($remote_calendar_requests_require_password);
1844 my ($remote_calendar_requests_password) = &xml_quick_extract($xml, "remote_calendar_requests_password");
1845 $remote_calendar_requests_password = &decode($remote_calendar_requests_password);
1847 if ($cal_id > $max_cal_id) {
1848 $max_cal_id = $cal_id;
1851 $calendar = {id => $cal_id,
1854 title => $cal_title,
1855 details => $cal_details,
1857 #users => \@calendar_users,
1858 local_background_calendars => \%local_background_calendars,
1859 remote_background_calendars => \%remote_background_calendars,
1860 selectable_calendars => \%selectable_calendars,
1861 new_calendars_automatically_selectable => $options{new_calendars_automatically_selectable},
1862 calendar_events_color => $calendar_events_color,
1863 list_background_calendars_together => $list_background_calendars_together,
1864 background_events_display_style => $background_events_display_style,
1865 background_events_fade_factor => $background_events_fade_factor,
1866 background_events_color => $background_events_color,
1867 allow_remote_calendar_requests => $allow_remote_calendar_requests,
1868 remote_calendar_requests_require_password => $remote_calendar_requests_require_password,
1869 remote_calendar_requests_password => $remote_calendar_requests_password,
1870 default_number_of_months => $default_number_of_months,
1871 max_number_of_months => $max_number_of_months,
1872 gmtime_diff => $gmtime_diff,
1873 date_format => $date_format,
1874 week_start_day => $week_start_day,
1875 info_window_size => $info_window_size,
1876 custom_template => $custom_template,
1877 custom_stylesheet => $custom_stylesheet,
1878 password => $cal_password,
1879 event_change_email => $event_change_email,
1880 add_emails => \@add_emails,
1881 update_emails => \@update_emails,
1882 delete_emails => \@delete_emails,
1883 update_timestamp => $update_timestamp};
1885 if ($cal_type eq "ical") {
1886 my $url_results = &get_remote_file($cal_url);
1888 # check for 404, other errors
1890 return (&parse_ical($url_results, $calendar));
1899 return if ( $options{'ical_export'} ne "1" );
1900 my ($calendar_ref) = @_;
1901 my %calendar = %{$calendar_ref};
1903 my $ical_dir = "$options{default_theme_path}/ical";
1905 # make ical directory, if not present
1906 if (!(-d $ical_dir)) {
1907 mkdir($ical_dir, 0777);
1910 $ical_file = $ical_dir."/plans_calendar_".$calendar{'id'}.".ics";
1912 $ical_file_date = 0;
1913 if ( -e $ical_file) {
1914 $ical_file_date = (stat($ical_file))[9];
1919 # check calendar's update
1920 $export_needed = 1 if ( $calendar{'update_timestamp'} > $ical_file_date );
1922 if ( !$export_needed ) {
1923 foreach $event_id (keys %events) {
1924 if (&contains($events{$event_id}{'cal_ids'}, $calendar{id})) {
1925 if ( $events{$event_id}{'update_timestamp'} > $ical_file_date ) {
1933 if ( !$export_needed ) {
1937 $ical_contents = &icalendar_export_cal(0,1970,11,2030,$cal_id);
1939 open (FH, ">$ical_file") || {$debug_info.= "unable to open file $ical_file for writing!\n"};
1941 print FH $ical_contents;
1946 sub icalendar_export_cal {
1947 ($start_month, $start_year, $end_month, $end_year, $cal_id) = @_;
1950 %export_calendar = %current_calendar;
1952 %export_calendar = %{$calendars{$cal_id}};
1955 #calculate where to start and end the list
1957 #format for timegm: timegm($sec,$min,$hour,$mday,$mon,$year);
1958 my $list_start_timestamp = timegm(0,0,0,1,$start_month,$start_year);
1959 my $list_end_timestamp = &find_end_of_month($end_month, $end_year);
1960 # loop through all the events.
1962 #Create an array of events which fall
1963 # within the supplied dates
1964 my @selected_cal_events;
1966 #and a funky data structure for the background calendars
1967 # each element of this hash will be an array.
1968 my $shared_cal_events={}; #empty hash
1970 my @background_cal_ids = keys %{$export_calendar{local_background_calendars}};
1971 foreach $event_id (keys %events) {
1972 if (&time_overlap($events{$event_id}{start},$events{$event_id}{end},$list_start_timestamp,$list_end_timestamp)) {
1973 my $event_in_export_calendar = 0;
1975 foreach $temp_cal_id (@{$events{$event_id}{cal_ids}}) {
1976 if ($temp_cal_id eq $export_calendar{'id'}) {
1977 push @selected_cal_events, $event_id;
1979 foreach $background_cal_id( @background_cal_ids ) {
1980 if ($temp_cal_id eq $background_cal_id) {
1981 push @{$shared_cal_events{$background_cal_id}}, $event_id;
1996 #initialize loop variables
1997 #$current_timestamp = $list_start_timestamp;
2000 #display events for selected calendar
2001 foreach $event_id (sort {$events{$a}{start
} <=> $events{$b}{start
}} @selected_cal_events) {
2002 my %event = %{$events{$event_id}};
2003 $results .= &event2ical
(\
%event)."\n";
2006 foreach $background_cal_id (keys %{$export_calendar{local_background_calendars
}}) {
2007 #list events for that calendar
2008 foreach $event_id (sort {$events{$a}{start
} <=> $events{$b}{start
}} @
{$shared_cal_events{$background_cal_id}}) {
2009 my %event = %{$events{$event_id}};
2010 $results .= &event2ical
(\
%event)."\n";
2018 $results .= $debug_info;
2027 my ($url_results, $cal_ref) = @_;
2029 my %calendar = %{$cal_ref};
2031 # start parsing ical data
2032 # there's a perl module for this(iCal::Parser), but it's not used
2033 # because A) not everyone has it
2034 # and B) it doesn't do some things the plans way (for instance, multi-day events are split into multiple events)
2036 # the first BEGIN:VCALENDAR describes the calendar
2037 # subsequent BEGIN:VCALENDARs are ignored.
2038 $url_results =~ s/\r//gs; #some servers sneak these in.
2039 $url_results =~ s/\f//gs; #some servers sneak these in.
2041 my $tzone_offset = 0;
2043 my $calendar_data = $url_results;
2044 $calendar_data =~ s/.+(BEGIN:VCALENDAR)(.+?)(END:VCALENDAR).+/$1$2$3/si;
2045 $calendar_data =~ s/(\s|\n)*(:|;)(\s|\n)*/$2/sg;
2046 $calendar_data =~ s/BEGIN:VEVENT.+?END:VEVENT//si;
2048 if ($calendar_data =~ /BEGIN:STANDARD/) {
2049 my $timezone_data = $calendar_data;
2050 ($tzone_offset) = &ical_get($timezone_data, "TZOFFSETTO");
2051 $tzone_offset =~ s/00//;
2052 $tzone_offset = $tzone_offset * 3600;
2053 #$debug_info .= "iCal timezone offset: $tzone_offset\n";
2055 #$debug_info .= "calendar_data: $calendar_data\n\n\n\n";
2057 my $temp_events_data = $url_results;
2058 $temp_events_data =~ s/(\s|\n)*(:|;)(\s|\n)*/$2/sg;
2060 while ($temp_events_data =~ /(BEGIN:VEVENT.+?END:VEVENT)/sgi) {
2061 my $event_text = $1;
2063 $event{cal_ids} = ($calendar{id});
2065 my ($uid) = &ical_get($event_text, "uid");
2067 my ($title) = &ical_get($event_text, "summary");
2068 $event{title} = $title;
2069 my ($timestamp) = &ical_get($event_text, "dtstamp");
2070 $event{timestamp} = $timestamp;
2072 my ($dtstart, $tzone) = &ical_get($event_text, "dtstart");
2073 $event{start} = &parse_ical_date($dtstart, $tzone);
2075 my ($dtend, $tzone) = &ical_get($event_text, "dtend");
2077 my ($duration) = &ical_get($event_text, "duration");
2079 my $dur_h = $1 if ($duration =~ /(\d*)H/) ? $1: 0;
2080 my $dur_m = $1 if ($duration =~ /(\d*)M/) ? $1: 0;
2081 my $dur_s = $1 if ($duration =~ /(\d*)S/) ? $1: 0;
2083 my $duration_seconds = $dur_s + (60*$dur_m) + (3600*$dur_h);
2084 $event{end} = $event{start} + $duration_seconds;
2085 } else { #DTEND found
2086 $event{end} = &parse_ical_date($dtend, $tzone);
2089 #$debug_info .= "event $id ($event{id}) $event{start} - $event{end}\n";
2090 $event{start} -= $tzone_offset;
2091 $event{end} -= $tzone_offset;
2092 #$debug_info .= "event $id ($event{id}) $event{start} - $event{end}\n";
2094 my $ical_details = "";
2096 my ($location) = &ical_get($event_text, "location");
2097 if ($location ne "") {
2098 $ical_details .= "Location: $location<br/>";
2100 my ($description) = &ical_get($event_text, "description");
2101 if ($description ne "") {
2102 $ical_details .= "$description<br/>";
2105 my $event_duration = $event{end} - $event{start};
2106 #$debug_info .= "event $id ($evt_title) duration: ($event_duration)\n";
2108 my $all_day_event = "";
2109 my $no_end_time = "";
2111 # make an educated guess about length of events that look like this: DTSTART;VALUE=DATE:20070202
2112 # Lord, I hate the ical/vcal standard. DTEND;VALUE=DATE:20070203
2116 my @temp1 = gmtime $event{start};
2117 my @temp2 = gmtime $event{end};
2118 if ($temp1[0] ==0 && $temp1[1] ==0 && $temp1[2] ==0 && $temp2[0] ==0 && $temp2[1] ==0 && $temp2[2] ==0) {
2119 $evt_days = int(($event{end} - $event{start})/86400);
2121 $evt_days = int(($event{end} - $event{start})/86400)+1;
2124 if ($event_duration > 1 && (($event_duration+1) % 86400 == 0 || ($event_duration) % 86400 == 0)){
2126 #$debug_info .= "event $id ($evt_title) is an all day event\n";
2128 if ($event_duration == 1) {
2132 #$debug_info .= "(xml2event) event $id, timezone offset: $timezone_offset\n";
2134 #$debug_info .= "(xml2event) event $id start: $evt_start\n";
2136 # experimental--may cause problems.
2137 # used to stretch an event that crosses midnight over 2 days.
2138 my @temp1 = gmtime $event{start};
2139 my @temp2 = gmtime $event{end};
2141 if ($temp1[3] != $temp2[3]) {
2145 #$debug_info .= "event $id ($event{id}) all_day_event: $all_day_event\n";
2147 $event{all_day_event} = $all_day_event;
2148 $event{no_end_time} = $no_end_time;
2149 $event{days} = $evt_days;
2150 $event{details} = $ical_details;
2151 $event{bgcolor} = "#ffffff";
2153 #$debug_info .= "Event uid: ($uid)\n";
2154 #$debug_info .= "ical event calendar id: $calendar{id}\n";
2155 #$debug_info .= "event start: $event{start}\n";
2157 $events_data .= "$event_text\n\n\n\n";
2159 $events{$uid} = \%event;
2162 #$debug_info .= "events_data: $events_data\n\n\n\n";
2169 sub parse_ical_date {
2170 my ($ical_date, $tzone) = @_;
2172 #my $date_portion = substr($ical_date, 0,8);
2173 my $time_portion = substr($ical_date, 8);
2174 #$debug_info .= "date_portion: $date_portion\n";
2175 #$debug_info .= "time_portion: $time_portion\n";
2177 my $year = substr($ical_date,0,4);
2178 my $month = substr($ical_date,4,2) - 1;
2179 my $mday = substr($ical_date,6,2);
2182 my $hour = substr($ical_date,9,2);
2183 my $minute = substr($ical_date,11,2);
2184 my $second = substr($ical_date,13,2);
2186 #$debug_info .= "date $ical_date: hour: $hour minute: $minute second $second\n";
2188 my $timestamp = timegm($second,$minute,$hour,$mday,$month,$year);
2195 my ($text, $field) = @_;
2199 if ($text =~ /$field(;.+?)?:(.+?)\n/si) {
2204 return ($value, $comment);
2208 sub normalize_timezone() {
2209 return if ($normalized_timezone == 1);
2211 $normalized_timezone = 1;
2212 #$debug_info .= "(normalize_timezone) current_cal_id: $current_cal_id timezone offset: $calendars{$current_cal_id}{gmtime_diff}\n";
2213 foreach $event_id (keys %events) {
2214 next if ($events{$event_id}{all_day_event} eq "1");
2215 my %event = %{$events{$event_id}};
2217 #$debug_info .= "(normalize_timezone) event $event_id $events{$event_id}{start} - $events{$event_id}{end}\n";
2218 #$debug_info .= "(normalize_timezone) event $event_id $events{$event_id}{start} - $events{$event_id}{end}\n" if ($event_id eq "117");
2219 $events{$event_id}{start} += $calendars{$current_cal_id}{gmtime_diff} * 3600;
2220 $events{$event_id}{end} += $calendars{$current_cal_id}{gmtime_diff} * 3600;
2221 #$debug_info .= "(normalize_timezone) event $event_id $events{$event_id}{start} - $events{$event_id}{end}\n";
2222 #$debug_info .= "(normalize_timezone) event $event_id $events{$event_id}{start} - $events{$event_id}{end}\n" if ($event_id eq "117");
2226 sub normalize_timezone_pending_events() {
2227 return if ($normalized_timezone_pending_events == 1);
2229 $normalized_timezone_pending_events = 1;
2230 #$debug_info .= "(normalize_timezone_pending_events) current_cal_id: $current_cal_id\n";
2232 foreach $new_event_id (keys %new_events) {
2233 next if ($new_events{$new_event_id}{all_day_event} eq "1");
2234 my %new_event = %{$new_events{$new_event_id}};
2235 #$debug_info .= "(normalize_timezone_pending_events) event $new_event_id start: $new_events{$new_event_id}{start} \n" if ($new_event_id eq "164");
2237 $new_events{$new_event_id}{start} += $calendars{$current_cal_id}{gmtime_diff} * 3600;
2238 $new_events{$new_event_id}{end} += $calendars{$current_cal_id}{gmtime_diff} * 3600;
2240 #$debug_info .= "(normalize_timezone_pending_events) event $new_event_id start: $new_events{$new_event_id}{start} \n" if ($new_event_id eq "164");
2246 sub event2javascript() {
2247 my ($event_ref) = @_;
2248 my %event = %{$event_ref};
2250 $results .= "'id':'".javascript_cleanup($event{id})."',";
2251 $results .= "'cal_ids':'".javascript_cleanup(join ",", @{$event{cal_ids}})."',";
2252 $results .= "'title':'".javascript_cleanup($event{title})."',";
2253 $results .= "'details':'".javascript_cleanup($event{details})."',";
2255 my $details_url = ($event{details_url} eq "1") ? "true":"false";
2256 $results .= "'details_url':$details_url,";
2257 $results .= "'icon':'".javascript_cleanup($event{icon})."',";
2258 $results .= "'bgcolor':'".javascript_cleanup($event{bgcolor})."',";
2259 $results .= "'start':'".javascript_cleanup($event{start})."',";
2260 $results .= "'end':'".javascript_cleanup($event{end})."',";
2261 $results .= "'days':".javascript_cleanup($event{days}).",";
2263 if ($event{all_day_event} eq "1") {
2264 $results .= "'all_day_event':true,";
2266 $results .= "'all_day_event':false,";
2269 if ($event{no_end_time} eq "1") {
2270 $results .= "'no_end_time':true";
2272 $results .= "'no_end_time':false";
2278 sub calendar2javascript() {
2279 my ($calendar_ref) = @_;
2280 my %calendar = %{$calendar_ref};
2282 $results .= "'id':'".javascript_cleanup($calendar{id})."',";
2283 $results .= "'title':'".javascript_cleanup($calendar{title})."',";
2284 $results .= "'local_background_calendars':'".javascript_cleanup(join ",", keys %{$calendar{local_background_calendars}})."'";
2292 sub javascript_cleanup() {
2294 $text =~ s/\n/\\n/g;
2297 $text =~ s/\//\\\//g;
2304 my ($event_ref, $cal_id) = @_;
2305 my %event = %{$event_ref};
2307 my $xml_data = "<event>";
2308 $xml_data .= &xml_store($event{id}, "id");
2309 my $cal_ids_string = "";
2310 foreach $cal_id (@{$event{cal_ids}}) {
2311 $cal_ids_string .= "$cal_id";
2312 if ($cal_id ne @{$event{cal_ids}}[-1]) {
2313 $cal_ids_string .= ",";
2316 $cal_ids_string =~ s/,$//;
2318 my $event_start_timestamp = $event{start};
2319 my $event_end_timestamp = $event{end};
2321 #$debug_info .= "(event2xml) id $event{id} $event{start} - $event{end}\n" if ($event{id} eq "159");
2322 #$debug_info .= "(event2xml) current_cal_id: $current_cal_id\n";
2324 # denormalize event time
2325 if ($event{all_day_event} ne "1" && ($normalized_timezone == 1 || $normalized_timezone_pending_events == 1)) {
2326 #$debug_info .= "(event2xml) denormalizing timezone by cal $current_cal_id: $calendars{$current_cal_id}{gmtime_diff}\n" if ($event{id} eq "159");
2327 $event_start_timestamp -= $calendars{$current_cal_id}{gmtime_diff} * 3600;
2328 $event_end_timestamp -= $calendars{$current_cal_id}{gmtime_diff} * 3600;
2330 #$debug_info .= "(event2xml) after denormalize: $event_start_timestamp - $event_end_timestamp\n" if ($event{id} eq "159");
2332 #$debug_info .= "(event2xml) id $event{id} evt_start=$event_start_timestamp\n" if ($event{id} eq "266");
2334 $xml_data .= "<cal_ids>$cal_ids_string</cal_ids>";
2335 $xml_data .= &xml_store($event_start_timestamp, "start");
2336 $xml_data .= &xml_store($event_end_timestamp, "end");
2337 $xml_data .= &xml_store($event{series_id}, "series_id");
2338 $xml_data .= &xml_store($event{title}, "title");
2339 $xml_data .= &xml_store($event{details}, "details");
2340 $xml_data .= &xml_store($event{icon}, "icon");
2341 $xml_data .= &xml_store($event{block_merge}, "block_merge");
2342 $xml_data .= &xml_store($event{bgcolor}, "bgcolor");
2343 $xml_data .= &xml_store($event{unit_number}, "unit_number");
2344 $xml_data .= &xml_store($event{update_timestamp}, "update_timestamp");
2345 $xml_data .= "</event>";
2350 my ($event_ref) = @_;
2351 my %event = %{$event_ref};
2354 my $start_timestamp = $event{start} - $calendars{$event{cal_ids}[0]}{timezone_offset}*3600;
2355 my $end_timestamp = $event{end} - $calendars{$event{cal_ids}[0]}{timezone_offset}*3600;
2356 #my $start_timestamp = $event{start};
2357 #my $end_timestamp = $event{end};
2358 my $cal_title = $calendars{$event{cal_ids}[0]}{title}; # need to add titles for all other cal_ids
2360 my $dtstart_string = "";
2361 my $dtend_string = "";
2363 my $rightnow_date = &outlook_date_time($rightnow) . 'Z';
2365 if ($event{all_day_event} eq "1") {
2366 $dtstart_string = ";VALUE=DATE:".&outlook_date($event{start});
2367 $dtend_string = ";VALUE=DATE:".&outlook_date($event{end}+86400);
2369 $dtstart_string = ":".&outlook_date_time($event{start});
2370 $dtend_string = ":".&outlook_date_time($event{end});
2373 # replace newlines with carraige-returns (otherwise two newlines in a row
2374 # causes errors. Not sure whether this is an outlook error or an IE error.
2375 $event{details} =~ s/\n/\\n/g;
2376 $event{title} =~ s/\n/\\n/g;
2380 ORGANIZER
:$cal_title
2381 DTSTART
$dtstart_string
2385 UID
:$script_url/$name?view_event
=1&evt_id
=$event{id
}
2386 DTSTAMP
;VALUE
=DATE
:$rightnow_date
2387 DESCRIPTION
:$event{details
}
2388 SUMMARY
:$event{title
} ($cal_title)
2400 my ($event_ref) = @_;
2401 my %event = %{$event_ref};
2404 my $start_timestamp = $event{start
};
2405 my $end_timestamp = $event{end
};
2406 my $cal_name=$calendars{$event{cal_ids
}[0]}{title
};
2408 my $dtstart_string = &outlook_date_time
($event{start
});
2409 my $dtend_string = &outlook_date_time
($event{end
});
2411 $cal_title = $calendars{$event{cal_ids
}[0]}{title
};
2413 # replace newlines with carraige-returns (otherwise two newlines in a row
2414 # causes errors. Not sure whether this is an outlook error or an IE error.
2415 $event{details
} =~ s/\n/\r/g;
2416 $event{title
} =~ s/\n/\r/g;
2420 DTSTART:$dtstart_string
2424 DTSTAMP:20020322T043444Z
2425 DESCRIPTION:$event{details}
2426 SUMMARY:$event{title} ($cal_title)
2431 if ($event{days} > 1) {
2433 RRULE
:D
$event{days
} $dtend_string
2445 my ($timestamp) = @_;
2447 my @timestamp_array = gmtime($timestamp);
2448 my $year_string = 1900 + $timestamp_array[5];
2450 my $month_string = $timestamp_array[4]+1;
2451 if ($month_string < 10) {$month_string="0".$month_string;}
2453 my $mday_string = $timestamp_array[3];
2454 if ($mday_string < 10) {$mday_string="0".$mday_string;}
2456 my $dt_string="$year_string$month_string$mday_string";
2460 sub outlook_date_time {
2461 my ($timestamp) = @_;
2463 my @timestamp_array = gmtime($timestamp);
2464 my $year_string = 1900 + $timestamp_array[5];
2466 my $month_string = $timestamp_array[4]+1;
2467 if ($month_string < 10) {$month_string="0".$month_string;}
2469 my $mday_string = $timestamp_array[3];
2470 if ($mday_string < 10) {$mday_string="0".$mday_string;}
2472 my $hour_string = $timestamp_array[2];
2473 if ($hour_string < 10) {$hour_string="0".$hour_string;}
2475 $hour_string="$timestamp_array[2]";
2476 $hour_string = "0$hour_string" if (length $hour_string == 1);
2477 $minute_string="$timestamp_array[1]";
2478 $minute_string = "0$minute_string" if (length $minute_string == 1);
2479 $second_string="$timestamp_array[0]";
2480 $second_string = "0$second_string" if (length $second_string == 1);
2482 my $dt_string="$year_string$month_string$mday_string";
2483 $dt_string .= "T".$hour_string.$minute_string.$second_string;
2487 sub event2palmcsv() {
2488 my ($event_ref) = @_;
2489 my %event = %{$event_ref};
2493 my $palm_begin = &formatted_time($event{start}, "yy mo md hh:mm");
2494 my $palm_end = &formatted_time($event{end}, "yy mo md hh:mm");
2496 if ($event{days} == 1) {
2497 $results .= "\"\""; # category
2498 $results .= ",\"0\""; # private
2499 $results .= ",\"$event{title}\""; # description
2500 $results .= ",\"$event{details}\""; # note
2501 $results .= ",\"1\""; # event
2502 $results .= ",\"$palm_begin\""; # begin time
2503 $results .= ",\"$palm_end\""; # end time
2504 $results .= ",\"\""; # alarm
2505 $results .= ",\"\""; # advance
2506 $results .= ",\"\""; # advance units
2507 $results .= ",\"0\""; # repeat type
2508 $results .= ",\"\""; # repeat forever
2509 $results .= ",\"\""; # repeat end
2510 $results .= ",\"\""; # repeat freq.
2511 $results .= ",\"\""; # repeat day.
2512 $results .= ",\"\""; # repeat days.
2513 $results .= ",\"\""; # week start.
2514 $results .= ",\"\""; # number of exceptions.
2515 $results .= ",\"\""; # exceptions
2516 } else { # multi-day event.
2517 $results .= "\"\""; # category
2518 $results .= ",\"0\""; # private
2519 $results .= ",\"$event{title}\""; # description
2520 $results .= ",\"$event{details}\""; # note
2521 $results .= ",\"1\""; # event
2522 $results .= ",\"$palm_begin\""; # begin time
2523 $results .= ",\"$palm_begin\""; # end time
2524 $results .= ",\"\""; # alarm
2525 $results .= ",\"\""; # advance
2526 $results .= ",\"\""; # advance units
2527 $results .= ",\"1\""; # repeat type
2528 $results .= ",\"\""; # repeat forever
2529 $results .= ",\"$palm_end\""; # repeat end
2530 $results .= ",\"1\""; # repeat freq.
2531 $results .= ",\"\""; # repeat day.
2532 $results .= ",\"\""; # repeat days.
2533 $results .= ",\"\""; # week start.
2534 $results .= ",\"\""; # number of exceptions.
2535 $results .= ",\"\""; # exceptions
2538 $results =~ s/\n/ /g;
2546 sub find_end_of_month {
2547 my ($month, $year) = @_;
2549 my $next_month = $month+1;
2550 if ($next_month > 11) {
2554 return timegm(0,0,0,1,$next_month,$year);
2558 my ($data_ref, $tag_name) = @_;
2561 if (ref $data_ref eq "ARRAY") {
2563 my $max = scalar @{$data_ref} - 1;
2564 #$debug_info .= "(xml_store) $tag_name (array) max: $max\n";
2565 foreach $val (@{$data_ref}) {
2566 $data_string .= $val;
2567 if ($i != $max) {$data_string .= ',';}
2570 #$debug_info .= "(xml_store) $tag_name (array): $data_string\n";
2571 } else {$data_string = $data_ref;}
2573 $data_string = &encode($data_string);
2574 return "<$tag_name>$data_string</$tag_name>";
2577 sub xml_quick_extract { # it doesn't get any dumber than this. ignores attributes, element order, fooled by duplicate tag names at different depths.
2578 my ($data, $tag_name) = @_;
2579 my @results_array = ();
2581 while ($data =~ /<$tag_name>(.+?)<\/$tag_name>/gs) {
2582 push @results_array, $1;
2584 return @results_array;
2587 sub xml_extract { # Slow, but can handle attributes, element order, same tag names at different depths. Can't handle encodings, DTDs.
2588 my ($data, $tag_name, $debug) = @_;
2589 my @results_array = ();
2591 my $final_results = "";
2599 my $position=0; # position is the position of the element we're looking for, with respect to all other elements
2600 # under the parent element
2602 while ($data =~ /(<.*?>|<\/.*?>)/g) {
2604 my $temp_index = $+[1];
2605 if ($match =~ /<$tag_name\b.*?>/ && $depth_count==0) { # the opening tag we're looking for
2606 $start_index = $temp_index;
2608 if ($debug) {$debug_info .= "active opening tag, $match \ndepth count $depth_count\n";}
2609 if ($debug) {$debug_info .= "start index $start_index\n";}
2611 my $attribute_text = $match;
2612 $attribute_text =~ s/\s*=\s*/=/g; # compress whitespace on either side of = sign
2613 $attribute_text =~ s/=([^"])(.+?\b)/="$1$2"/g; # properly format attributes with quote marks
2615 #if ($debug) {$debug_info .= "rejiggered attribute text: $attribute_text\n";}
2617 # extract attributes
2618 while ($attribute_text =~ /\w+?=".+?[^\\]"/g) {
2620 my ($name, $value)= split('=',$a_match);
2622 $value =~ s/\\"/"/g;
2623 # remove first and last characters (the quotes) from value
2624 $value = substr $value, 1,-1;
2626 if ($debug) {$debug_info .= "attribute: $a_match\n";}
2627 if ($debug) {$debug_info .= " name: $name\n";}
2628 if ($debug) {$debug_info .= " value: $value\n";}
2630 $attributes->{$name} = $value;
2633 #$debug_info .= "end position, $+[0]\n\n";
2634 } elsif ($match =~ /<[^\/].*?>/) { # some other opening tag
2636 if ($debug) {$debug_info .= "other opening tag, $match \ndepth count $depth_count\n";}
2637 } elsif ($match eq "<\/$tag_name>" && $depth_count == 1) { # the closing tag we're looking for
2639 if ($debug) {$debug_info .= "active closing tag, $match \ndepth count $depth_count\n";}
2640 if ($depth_count==0) { # done! return results
2642 $results = substr $data, $start_index,($end_index-$start_index);
2644 my $results_hash=();
2645 $results_hash -> {data} = "".$results;
2646 $results_hash -> {attributes} = $attributes;
2647 $results_hash -> {position} = $position;
2648 push @results_array, $results_hash;
2650 if ($debug) {$debug_info .= " pushing results: \"$results\" onto array\n";}
2651 if ($debug) {$debug_info .= " attributes: \"$attributes\" \n";}
2652 if ($debug) {$debug_info .= " position: \"$position\" \n";}
2653 if ($debug) {$debug_info .= " start: $start_index end $end_index\n\n";}
2654 #if ($debug) {$debug_info .= " $results\n\n";}
2659 #$debug_info .= "closing tag, $1 \ndepth count $depth_count\n";
2660 #$debug_info .= "start position, $-[0]\n\n";
2662 } else { # other closing tag
2664 if ($depth_count==0) {$position++;}
2666 if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
2670 return @results_array;
2671 } #******************** end xml_extract **********************
2675 my ($data, $debug) = @_;
2676 my @results_array = ();
2681 while ($data =~ /(<.*?>|<\/.*?>)/g) {
2683 if ($match =~ /<[^\/].*?>/) { # any opening tag
2684 if ($depth_count == 0) { # level 0 opening tag
2687 $tag_name =~ s/\b(.+)\b.+/$1/;
2688 if ($debug) {$debug_info .= "level 0 opening tag, $tag_name \n\n";}
2691 } elsif ($depth_count == 1) { # level 1 closing tag
2694 $tag_name =~ s/\/(.+)(\b|>).+/$1/;
2697 if ($debug) {$debug_info .= "level 1 closing tag, $tag_name \n";}
2698 $tags_hash{$tag_name}=1;
2700 if ($debug) {$debug_info .= " pushing tag name $tag_name onto array\n\n";}
2701 } else { # other closing tag
2703 if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
2706 return keys %tags_hash;
2708 } #******************** end xml_tags **********************
2712 my ($xml_data, $debug) = @_;
2715 my @item_tags = &xml_tags($xml_data);
2717 if (scalar @item_tags == 0) {
2718 if ($debug) {$debug_info .= " plain text item data: ($xml_data) \n";}
2722 if ($debug) {$debug_info .= " xml data: ($xml_data) \n";}
2724 foreach $tag (@item_tags) {
2725 my @tag_data = &xml_extract($xml_data,"$tag");
2727 if (scalar @tag_data == 1) {
2728 if ($debug) {$debug_info .= " extracting xml for tag $tag (single data)\n";}
2729 $results_hash{$tag} = &xml2hash($tag_data[0]->{data},$debug);
2731 if ($debug) {$debug_info .= " extracting xml for tag $tag (array data)\n";}
2733 foreach $thing (@tag_data) {
2734 push @tag_array, &xml2hash($thing->{data},$debug);
2736 $results_hash{$tag}=\@tag_array;
2739 if ($debug) {$debug_info .= "\n";}
2740 return \%results_hash;
2742 } #******************** end xml2hash **********************
2745 my ($temp, $parent_tag, $order_hashref) = @_;
2748 my %order_hash = %{$order_hashref};
2750 #$debug_info .= "\nhash2xml: $temp, $parent_tag\n";
2752 if (ref $temp eq "ARRAY") { # array
2753 my @temp_array = @{$temp};
2754 foreach $element (@temp_array) {
2755 $results .= "<$parent_tag>";
2756 $results .= $element;
2757 $results .= "</$parent_tag>";
2759 } elsif (ref $temp eq "HASH") { # hash
2760 $results .= "<$parent_tag>";
2761 my %temp_hash = %{$temp};
2762 foreach $key (sort {$order_hash{$a} <=> $order_hash{$b}} keys %temp_hash) {
2763 $results .= &hash2xml($temp_hash{$key}, $key, $order_hashref);
2765 $results .= "</$parent_tag>";
2767 #$debug_info .= "hash2xml: data\n";
2768 $results .= "<$parent_tag>".&encode($temp)."</$parent_tag>";
2772 } #******************** end hash2xml **********************
2777 my ($cgi, $session) = @_; # receive two args
2779 if ( $session->param("~logged-in") ) {
2780 #$debug_info .= "already logged in!\n";
2781 return 1; # if logged in, don't bother going further
2784 #$debug_info .= "name:".$lg_name."\n";
2786 return if ($lg_name eq "");
2787 return if ($lg_password eq "");
2789 #$debug_info .= "both name and password submitted\n";
2791 # if we came this far, user did submit login data
2792 # so let's try to load his/her profile if name/psswds match
2793 if ( my $profile = _load_profile($lg_name, $lg_password) ) {
2794 $session->param("~profile", $profile);
2795 $session->param("~logged-in", 1);
2796 $session->clear(["~login-trials"]);
2800 # if we came this far, the login/psswds do not match
2801 # the entries in the database
2802 my $trials = $session->param("~login-trials") || 0;
2804 return $session->param("~login-trials", ++$trials);
2808 my ($name, $password) = @_;
2810 $password_crypt = crypt($password, $options{salt});
2812 my $password_match = 0;
2813 my %calendar_permissions;
2815 # first check calendar passwords
2816 foreach $calendar_id (keys %calendars) {
2817 my %calendar = %{$calendars{$calendar_id}};
2818 if ($calendar{id} eq $name && $calendar{password} eq $password_crypt && $password_crypt ne "") {
2819 $password_match = 1;
2820 $calendar_permissions{$calendar{id}}{admin} = 1;
2821 #return {cal_id => $calendar{id}, user_id=>"admin"};
2825 # then check user passwords
2826 foreach $user_id (keys %users) {
2827 my %user = %{$users{$user_id}};
2828 #$debug_info .= "checking password for user $user{id}\n";
2830 if ($user{password} eq $password_crypt && $password_crypt ne "") {
2831 foreach $calendar_id (keys %{$user{calendars}}) {
2832 #$debug_info .= "checking user permissions for calendar $calendar_id for user $user{id}\n";
2833 if ($user{calendars}{$calendar_id}{edit_events} eq "1") {
2834 #$debug_info .= "setting user permissions for calendar $calendar_id for user $user{id}\n";
2835 $password_match = 1;
2836 $calendar_permissions{$calendar_id}{user} = $user{id};
2839 #return {cal_id => "", user_id=>$user_id};
2844 return {calendar_permissions => \%calendar_permissions} if ($password_match);
2849 sub delete_old_sessions {
2852 opendir (DIR, "$options{sessions_directory}/");
2853 @FILES = grep(/cgisess_/,readdir(DIR));
2856 ## DELETE THE .TXT FILES THAT ARE OLDER THAN 1 DAY
2857 foreach $FILE (@FILES) {
2858 if (-M "$options{sessions_directory}/$FILE" > $days) {
2859 unlink("$options{sessions_directory}/$FILE");
2868 sub get_remote_file {
2870 if (!$options{proxy_server}) {
2871 $url =~ s/http:\/\///;
2874 my $hostname = $url;
2875 $hostname =~ s/\/.+//g;
2877 my $document = $url;
2878 if (!$options{proxy_server}) {
2879 $document =~ s/.+?\//\//;
2882 if ($hostname eq "" | $document eq "") {return;}
2884 if ($options{proxy_server}) {
2885 $remote = IO::Socket::INET->new( Proto => "tcp",
2886 PeerAddr => $options{proxy_server},
2887 PeerPort => "$options{proxy_port}"
2890 $remote = IO::Socket::INET->new( Proto => "tcp",
2891 PeerAddr => $hostname,
2892 PeerPort => "http(80)"
2896 $debug_info .= "cannot connect to http daemon on $hostname <br>";
2899 $remote->autoflush(1);
2900 print $remote "GET $document HTTP/1.0\r\n";
2901 print $remote "User-Agent: Mozilla 4.0 (compatible; I; Linux-2.0.35i586)\r\n";
2902 print $remote "Host: $hostname\r\n"; #without this line, virtual hosts won't work (multiple domain names on a single IP)
2904 print $remote "\r\n\r\n";
2906 @textbuffer=<$remote>;
2907 my $textstring = join "", @textbuffer;
2909 $textstring =~ s/\r//gs; #some servers sneak these in.
2911 my $header = $textstring;
2912 $header =~ s/\n\n.+//si;
2913 my $firstline = $header;
2914 $firstline =~ s/\n.+//si;
2916 if ($firstline =~ /404/) {return "404 not found!";}
2918 $textstring =~ s/.+?\n\n//si;
2923 my ($start1, $end1, $start2, $end2) = @_;
2925 my $temp1 = $end2 - $start1;
2926 my $temp2 = $end1 - $start2;
2928 my $range_total = $end2 - $start2;
2930 #$debug_info .= "temp1:$temp1 temp2:$temp2 range_total:$range_total\n";
2933 # if the event falls in or overlaps this week (there are 3 cases), the third being an event
2934 # that *completely* overlaps the week.
2935 if ( ($temp1 <= $range_total && $temp1 > 0) || ($temp2 <= $range_total && $temp2 > 0) || ($temp1 > 0 && $temp2 > 0)) {return 1;}
2940 sub make_email_link {
2942 my $new_string = "";
2943 #remove all newlines
2946 #insert newlines after > characters
2947 $string =~ s/</\n</g;
2949 my @lines = split ("\n", $string);
2951 foreach $line (@lines) {
2953 my $new_line = $line;
2954 $new_line =~ s/([^ >]+?\@[^ <>]+)/<a href=\"mailto:$1\">$1<\/a>/g;
2956 #ignore substitution if the email address was already a link.
2957 if ($1 =~ /(:|")/) {$new_string .= $line;}
2959 $new_line =~ s/\n//g;
2960 $new_string .= $new_line;
2966 sub formatted_time {
2968 my ($input_time, $format_string) = @_;
2969 my @input_time_array = gmtime ($input_time+0);
2970 my $ampm = $lang{pm};
2972 if ($input_time_array[5]<1900) {$input_time_array[5]+=1900;}
2973 $month_name=$months[$input_time_array[4]];
2974 $input_time_array[4]++;
2976 if ($input_time_array[1]<10) {$input_time_array[1]="0".$input_time_array[1];}
2978 if ($input_time_array[2] < 12) {
2979 #$debug_info .= "$input_time -> $input_time_array[2] (am)\n";
2983 if (!$options{twentyfour_hour_format}) {
2984 if ($input_time_array[2] > 12) { #convert from 24-hour to am/pm
2985 $input_time_array[2] = $input_time_array[2] - 12;
2988 if ($input_time_array[2] == 0) { #convert from 24-hour to am/pm
2989 $input_time_array[2] = 12;
2993 $format_string =~ s/ampm//g;
2996 my $day_name = @day_names[$input_time_array[6]];
2997 my $day_name_abv = @day_names_abv[$input_time_array[6]];
2999 $format_string =~ s/ampm/$ampm/g;
3000 $format_string =~ s/wd/$day_name/g;
3001 $format_string =~ s/wda/$day_name_abv/g;
3002 $format_string =~ s/hh/$input_time_array[2]/g;
3003 $format_string =~ s/mm/$input_time_array[1]/g;
3004 $format_string =~ s/ss/$input_time_array[0]/g;
3005 $format_string =~ s/mo/$input_time_array[4]/g;
3006 $format_string =~ s/mn/$month_name/g;
3007 $format_string =~ s/md/$input_time_array[3]/g;
3008 $format_string =~ s/yy/$input_time_array[5]/g;
3009 return $format_string;
3012 sub nice_date_range_format {
3013 my ($timestamp1, $timestamp2, $separator_string) = @_;
3014 my $result_string = "";
3016 #make sure the timestamps are in the correct order
3017 if ($timestamp1 > $timestamp2) {
3019 $timestamp2=$timestamp1;
3023 my @timestamp1_array = gmtime $timestamp1;
3024 my @timestamp2_array = gmtime $timestamp2;
3026 #format the year for humans
3027 $timestamp1_array[5] +=1900;
3028 $timestamp2_array[5] +=1900;
3030 if (lc $current_calendar{date_format} eq "dd/mm/yy") {
3031 if ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5] && $timestamp1_array[3] == $timestamp2_array[3]) {
3032 #same year, same month, same day
3033 $result_string = " $timestamp1_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]";
3035 elsif ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5]) {
3036 #same year, same month
3037 $result_string = "$timestamp1_array[3]$separator_string$timestamp2_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]";
3039 elsif ($timestamp1_array[5] != $timestamp2_array[5]) {
3041 $result_string = "$timestamp1_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]$separator_string$timestamp2_array[3] $months[$timestamp2_array[4]], $timestamp2_array[5]";
3044 { #same year, different months
3045 $result_string = "$timestamp1_array[3] $months[$timestamp1_array[4]]$separator_string$timestamp2_array[3] $months[$timestamp2_array[4]], $timestamp2_array[5]";
3048 if ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5] && $timestamp1_array[3] == $timestamp2_array[3]) {
3049 #same year, same month, same day
3050 $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3], $timestamp1_array[5]";
3052 elsif ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5]) {
3053 #same year, same month
3054 $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3]$separator_string$timestamp2_array[3], $timestamp1_array[5]";
3056 elsif ($timestamp1_array[5] != $timestamp2_array[5]) {
3058 $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3], $timestamp1_array[5]$separator_string$months[$timestamp2_array[4]] $timestamp2_array[3], $timestamp2_array[5]";
3061 #same year, different months
3062 $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3]$separator_string$months[$timestamp2_array[4]] $timestamp2_array[3], $timestamp2_array[5]";
3066 return $result_string;
3069 sub nice_time_range_format {
3070 my ($start, $end) = @_;
3072 $results = &formatted_time($start,"hh:mm ampm")." - ".&formatted_time($end,"hh:mm ampm");
3074 # if times are the same, remove the second one.
3075 if ($end - $start <=1) {
3076 $results =~ s/s*-.+//;
3080 # if both times are am or pm, remove the first one (it's redundant!)
3081 $results =~ s/(.*) $lang{am}(.*$lang{am}.*)/$1$2/;
3082 $results =~ s/(.*) $lang{pm}(.*$lang{pm}.*)/$1$2/;
3086 sub timestamp_from_datetime {
3087 my ($mday, $mon, $year, $days, $start_time, $end_time, $allday) = @_;
3089 my $sts = timegm(0,0,0,$mday,$mon,$year);
3092 #$debug_info .= "(timestamp_from_datetime) mday: $mday, mon: $mon, year: $year, days: $days, start_time: $start_time, end_time: $end_time, allday: $allday\n";
3094 if ($allday eq "1") { # easy case first
3095 $ets = $sts + ($days * 86400) - 1;
3096 #$debug_info .= "all day event! $sts $ets\n";
3098 my $start_time_offset = &time2seconds($start_time);
3099 if ($end_time ne "" && $start_time ne "") {
3101 $ets = $sts + 86400 * ($days-1) + &time2seconds($end_time);
3103 $sts+= $start_time_offset;
3104 } elsif ($start_time ne "") { # no end time
3105 #$ets = $sts + 86400 * $days - 1;
3106 $sts += $start_time_offset;
3110 #$debug_info .= "sts: $sts\n";
3111 #$debug_info .= "ets: $ets\n";
3113 return ($sts, $ets);
3120 my($hours, $minutes, $seconds);
3121 if($options{twentyfour_hour_format}) {
3122 $time =~ /(\d+):(\d+)/;
3125 $seconds = 3600*$hours + 60*$minutes;
3127 $time =~ /(\d+):(\d+)\s*($lang{am}|$lang{pm})/;
3132 $seconds = 3600*$hours + 60*$minutes;
3134 if ($ampm eq $lang{pm} && $hours < 12) {
3135 $seconds += 3600*12;
3138 if ($ampm eq $lang{am} && $hours == 12) {
3139 $seconds -= 3600*12;
3146 my ($input_string) = @_;
3147 my $output_string = $input_string;
3148 $output_string =~ s/"/"/g;
3149 return $output_string;
3153 my ($input_string) = @_;
3154 return if ($input_string eq "");
3155 my $output_string=$input_string;
3157 $output_string =~ s/(\W)/"\%".sprintf("%02x", (ord $1))/ge;
3158 $output_string =~ s/\%20/+/g;
3159 return $output_string;
3163 my ($input_string) = @_;
3164 return if ($input_string eq "");
3165 my $output_string = $input_string;
3167 $output_string =~ s/\+/ /g;
3168 $output_string =~ s/%([0-9A-Fa-f]{2})/pack("c",hex($1))/ge;
3169 return $output_string;
3172 sub min { @_ = sort {$a <=> $b} @_; shift; }
3173 sub max { @_ = sort {$a <=> $b} @_; pop; }
3175 sub generate_event_details {
3176 my ($event_ref, $preview) = @_;
3178 my %event = %{$event_ref};
3180 # force white color if the background is dark
3181 my $event_bgcolor = $event{bgcolor};
3182 my $textcolor_style = "";
3183 my $r = hex substr $event_bgcolor,1,2;
3184 my $g = hex substr $event_bgcolor,3,2;
3185 my $b = hex substr $event_bgcolor,5,2;
3186 my $bright = ($r*299+$g*587+$b*114)/1000;
3187 $textcolor_style = "color:#fff" if ($bright < 128);
3189 my %previous_current_calendar = %current_calendar;
3190 %current_calendar = %{$calendars{$event{cal_ids}[0]}};
3192 my $return_text = $event_details_template;
3193 my @event_start_timestamp_array = gmtime $event{start};
3195 my $event_cal_title_text = "";
3196 foreach $temp_cal_id (@{$event{cal_ids}}) {
3197 my $event_cal_name = "$calendars{$temp_cal_id}{title}";
3198 if ($calendars{$temp_cal_id}{link} =~ /\S/) {
3199 $event_cal_name = "<a target= _blank href=\"http://$calendars{$temp_cal_id}{link}\">$calendars{$temp_cal_id}{title}</a>";
3201 $event_cal_name = "<a href=\"javascript:toggle_visible('calendar_details')\">$calendars{$temp_cal_id}{title}</a>";
3203 $event_cal_title_text .= $event_cal_name.",";
3205 $event_cal_title_text =~ s/,$//; # remove trailing comma
3207 $return_text =~ s/###event calendar name###/$event_cal_title_text/g;
3209 my $date_string = $lang{event_details_date_goes_here};
3210 my $event_time = "";
3212 if ($event{start} ne "") {
3213 if ($event{all_day_event} eq "1") {
3214 $date_string = &nice_date_range_format($event{start}, $event{start}+86400*($event{days}-1), " - ");
3216 $date_string = &nice_date_range_format($event{start}, $event{end}, " - ");
3217 $event_time = &nice_time_range_format($event{start},$event{end});
3218 $event_time = "<span class=\"event_time\" style=\"$textcolor_style;\">$event_time</span>";
3221 $date_string = "<span style=\"$textcolor_style;\">$date_string</span>";
3223 if ($calendars{$event{cal_ids}[0]}{type} eq "ical") {
3224 $return_text =~ s/###event icon###//g; # no icon
3227 my $evt_title = "<span style=\"$textcolor_style;\">$event{title}</span>";
3229 $return_text =~ s/###event date###/$date_string/g;
3230 $return_text =~ s/###event time###/$event_time/g;
3233 $return_text =~ s/###event title###/$evt_title/g;
3234 $return_text =~ s/###event id###/$event{id}/g;
3235 $return_text =~ s/###event calendar id###/$event{cal_id}[0]/g;
3236 $return_text =~ s/###event background color###/$event{bgcolor}/g;
3238 my $event_details = $event{details};
3239 #replace \n characters with <br> tags
3240 $event_details =~ s/\n/\n<br>\n/g;
3242 # check the event details, and see if there are any non-htmlified
3243 # links. If so, turn them into links.
3244 $event_details =~ s/[^"](htt.:\/\/.+?),*\.?(\s|\n|<|$)/ <a href=\"$1\">$1<\/a>$2/g;
3246 # convert email addresses to links.
3247 $event_details = &make_email_link($event_details);
3249 # make sure all links open up in a new window
3250 $event_details =~ s/<a/<a target = "blank"/g;
3252 $event_details = "<span style=\"$textcolor_style;\">$event_details</span>";
3254 $return_text =~ s/###event details###/$event_details/g;
3256 my $event_icon_text = "";
3257 if ($event{icon} ne "blank") {
3258 $event_icon_text = "<img style=\"border-width:0px;\" src = \"$icons_url/$event{icon}_50x50.gif\" hspace=2 vspace=1><br>";
3260 $return_text =~ s/###event icon###/$event_icon_text/g;
3262 my $unit_number_text = $event{unit_number};
3263 $unit_number_text =~ s/(\d)/<img src="$graphics_url\/unit_number_patch_$1_40x25.gif" alt="" border="0" vspace=0 hspace=0>/g;
3264 $return_text =~ s/###unit number icon###/$unit_number_text/g;
3266 my $edit_event_link = "<a target = \"cal_mainwindow\" href=\"$script_url/$name?active_tab=1&add_edit_event=edit&&evt_id=$event{id}$consistent_parameter_string\">$lang{context_menu_edit_event}</a>";
3267 $edit_event_link = $lang{event_details_edit_disable} unless $writable{events_file};
3268 $return_text =~ s/###edit event link###/$edit_event_link/g;
3270 my $email_reminder_link = "";
3271 if ($options{email_mode} != 0) {
3272 $email_reminder_link = "<a href=\"$script_url/$name?email_reminder=1&evt_id=$event{id}\">$lang{email_reminder_link}</a>";
3273 $email_reminder_link = $lang{event_email_reminder_disable2} unless $writable{email_reminders_datafile};
3274 $return_text =~ s/###email reminder link###/$email_reminder_link/g;
3276 $email_reminder_link = $lang{event_email_reminder_disable2} unless $writable{email_reminders_datafile};
3277 $return_text =~ s/(<li.+>)?###email reminder link###/$email_reminder_link/g;
3280 my $temp = &export_event_link(\%event);
3281 $return_text =~ s/###export event link###/$temp/g;
3283 my $cal_detail_text .= <<p1;
3284 $calendars{$event{cal_ids
}[0]}{details
}
3287 $return_text =~ s/###event calendar details###/$cal_detail_text/g;
3289 %current_calendar = %previous_current_calendar;
3291 return $return_text;
3292 } # generate_event_details
3294 sub generate_events_details
{
3295 my ($event_ref, $preview) = @_;
3297 my %event = %{$event_ref};
3299 # force white color if the background is dark
3300 my $event_bgcolor = $event{bgcolor
};
3301 my $textcolor_style = "";
3302 my $r = hex substr $event_bgcolor,1,2;
3303 my $g = hex substr $event_bgcolor,3,2;
3304 my $b = hex substr $event_bgcolor,5,2;
3305 my $bright = ($r*299+$g*587+$b*114)/1000;
3306 $textcolor_style = "color:#fff" if ($bright < 128);
3308 my %previous_current_calendar = %current_calendar;
3309 %current_calendar = %{$calendars{$event{cal_ids
}[0]}};
3311 my $return_text = $events_details_template;
3312 my @event_start_timestamp_array = gmtime $event{start
};
3314 my $event_cal_title_text = "";
3315 foreach $temp_cal_id (@
{$event{cal_ids
}}) {
3316 my $event_cal_name = "$calendars{$temp_cal_id}{title}";
3317 if ($calendars{$temp_cal_id}{link} =~ /\S/) {
3318 $event_cal_name = "<a target= _blank href=\"http://$calendars{$temp_cal_id}{link}\">$calendars{$temp_cal_id}{title}</a>";
3320 $event_cal_name = "<a href=\"javascript:toggle_visible('calendar_details')\">$calendars{$temp_cal_id}{title}</a>";
3322 $event_cal_title_text .= $event_cal_name.",";
3324 $event_cal_title_text =~ s/,$//; # remove trailing comma
3326 $return_text =~ s/###event calendar name###/$event_cal_title_text/g;
3328 my $date_string = $lang{event_details_date_goes_here
};
3329 my $event_time = "";
3331 if ($event{start
} ne "") {
3332 if ($event{all_day_event
} eq "1") {
3333 $date_string = &nice_date_range_format
($event{start
}, $event{start
}+86400*($event{days
}-1), " - ");
3335 $date_string = &nice_date_range_format
($event{start
}, $event{end
}, " - ");
3336 $event_time = &nice_time_range_format
($event{start
},$event{end
});
3337 $event_time = "<span class=\"event_time\" style=\"$textcolor_style;\">$event_time</span>";
3340 $date_string = "<span style=\"$textcolor_style;\">$date_string</span>";
3342 if ($calendars{$event{cal_ids
}[0]}{type
} eq "ical") {
3343 $return_text =~ s/###event icon###//g; # no icon
3346 my $evt_title = "<span style=\"$textcolor_style;\">$event{title}</span>";
3348 $return_text =~ s/###event date###/$date_string/g;
3349 $return_text =~ s/###event time###/$event_time/g;
3352 $return_text =~ s/###event title###/$evt_title/g;
3353 $return_text =~ s/###event id###/$event{id}/g;
3354 $return_text =~ s/###event calendar id###/$event{cal_id}[0]/g;
3355 $return_text =~ s/###event background color###/$event{bgcolor}/g;
3357 my $event_details = $event{details
};
3358 #replace \n characters with <br> tags
3359 $event_details =~ s/\n/\n<br>\n/g;
3361 # check the event details, and see if there are any non-htmlified
3362 # links. If so, turn them into links.
3363 $event_details =~ s/[^"](htt.:\/\/.+?
),*\
.?
(\s
|\n|<|$)/ <a href=\"$1\">$1<\/a>$2/g
;
3365 # convert email addresses to links.
3366 $event_details = &make_email_link
($event_details);
3368 # make sure all links open up in a new window
3369 $event_details =~ s/<a/<a target = "blank"/g;
3371 $event_details = "<span style=\"$textcolor_style;\">$event_details</span>";
3373 $return_text =~ s/###event details###/$event_details/g;
3375 my $event_icon_text = "";
3376 if ($event{icon
} ne "blank") {
3377 $event_icon_text = "<img style=\"border-width:0px;\" src = \"$icons_url/$event{icon}_50x50.gif\" hspace=2 vspace=1><br>";
3379 $return_text =~ s/###event icon###/$event_icon_text/g;
3381 my $unit_number_text = $event{unit_number
};
3382 $unit_number_text =~ s/(\d)/<img src="$graphics_url\/unit_number_patch_
$1_40x25.gif
" alt="" border="0" vspace=0 hspace=0>/g;
3383 $return_text =~ s/###unit number icon###/$unit_number_text/g;
3385 my $edit_event_link = "<a target
= \"cal_mainwindow
\" href
=\"$script_url/$name?active_tab=1&add_edit_event=edit&&evt_id=$event{id}$consistent_parameter_string\">$lang{context_menu_edit_event}</a>";
3386 $edit_event_link = $lang{event_details_edit_disable} unless $writable{events_file};
3387 $return_text =~ s/###edit event link###/$edit_event_link/g;
3389 my $email_reminder_link = "";
3390 if ($options{email_mode} != 0) {
3391 $email_reminder_link = "<a href
=\"$script_url/$name?email_reminder=1&evt_id=$event{id}\">$lang{email_reminder_link}</a>";
3392 $email_reminder_link = $lang{event_email_reminder_disable2} unless $writable{email_reminders_datafile};
3393 $return_text =~ s/###email reminder link###/$email_reminder_link/g;
3395 $email_reminder_link = $lang{event_email_reminder_disable2} unless $writable{email_reminders_datafile};
3396 $return_text =~ s/(<li.+>)?###email reminder link###/$email_reminder_link/g;
3399 my $temp = &export_event_link(\%event);
3400 $return_text =~ s/###export event link###/$temp/g;
3402 my $cal_detail_text .= <<p1;
3403 $calendars{$event{cal_ids}[0]}{details}
3406 $return_text =~ s/###event calendar details###/$cal_detail_text/g;
3408 %current_calendar = %previous_current_calendar;
3410 return $return_text;
3411 } # generate_events_details
3413 sub export_event_link() {
3415 my ($event_ref) = @_;
3416 my %event = %{$event_ref};
3419 <form name="export_event_form
" id="export_event_form
" target="_blank
" action="$script_url/$name" method=GET>
3420 <a href="javascript
:document
.export_event_form
.submit
();">$lang{export}</a> $lang{this_event_to}
3421 <input type="hidden
" name="export_event
" value=1>
3422 <input type="hidden
" name="evt_id
" value="$event{id
}">
3425 <select name="export_type
" style="font
-size
:x
-small
;">
3426 <option value="icalendar
">$lang{icalendar_option}
3427 <option value="vcalendar
">$lang{vcalendar_option}
3428 <option value="ascii_text
">$lang{text_option}
3432 } #export_event_link
3434 sub validate_emails() {
3435 my ($email_string) = @_;
3437 # support multiple email addresses
3438 my @to_addresses = split (',', "$email_string");
3440 foreach $to_address (@to_addresses) {
3441 if (!($to_address =~ /^[\w\-\_\.]+\@([\w\-\_]+\.)+[a-zA-Z]{2,}$/)) {
3449 sub send_email_reminder() {
3450 my ($event_ref, $to_address, $email_text) = @_;
3451 my %event = %{$event_ref};
3453 if ($options{email_mode} == 0) {return $lang{send_email_reminder2};}
3455 $date_string = &nice_date_range_format($event{start}, $event{end}, " - ");
3457 $to_address =~ s/\s//g;
3460 my $email_valid = &validate_emails($to_address);
3461 if ($email_valid ne "") {
3462 return "$lang{send_email_reminder1
} ($email_valid).";
3464 my @to_addresses = split (',', "$to_address");
3466 foreach $temp (@to_addresses) {
3467 my $subject = $lang{send_email_reminder_subject};
3468 $subject =~ s/###title###/$event{title}/g;
3469 &send_email($temp, $options{from_address}, $options{reply_address}, $subject, $email_text);
3472 #return "$lang{send_email_reminder3
} ($options{email_mode
}).";
3474 } # send_email_reminder
3478 my ($to, $from, $reply_to, $subject, $body) = @_;
3480 my $content_type = "text
/plain
";
3482 $body =~ s/\n/\r\n/g;
3484 if ($options{html_email} eq "1") {
3485 $content_type = "text
/html
";
3488 <!DOCTYPE HTML PUBLIC "-//W3C
//DTD HTML
4.01 Transitional
//EN
">
3492 <title>$subject</title>
3501 if ($options{email_mode} == 1) {
3502 open(SENDMAIL, "| $options{sendmail_location
} -t
") || ($debug_info .= "Can
't open sendmail at $options{sendmail_location}!\n");
3504 print SENDMAIL <<p1;
3509 Content-type: $content_type\n
3514 } elsif ($options{email_mode} == 2) {
3515 $smtp->mail($reply_to);
3518 $smtp->datasend("To: $to\n");
3519 $smtp->datasend("From: $from\n");
3520 $smtp->datasend("Subject: $subject\n");
3521 $smtp->datasend("Content-type: $content_type\n\n");
3522 $smtp->datasend("$body\n");
3528 sub extract_cookie_parms() {
3530 my $cookie_text = $q->cookie('plans_view
');
3532 ($cd{'cal_start_month
'}) = &xml_quick_extract($cookie_text, "cal_start_month");
3533 ($cd{cal_start_year}) = &xml_quick_extract($cookie_text, "cal_start_year");
3534 ($cd{cal_num_months}) = &xml_quick_extract($cookie_text, "cal_num_months");
3535 ($cd{cal_id}) = &xml_quick_extract($cookie_text, "cal_id");
3536 ($cd{display_type}) = &xml_quick_extract($cookie_text, "display_type");
3537 ($cd{theme_url}) = &xml_quick_extract($cookie_text, "theme_url");
3546 if (not ref $this) {
3548 } elsif (ref $this eq "ARRAY") {
3549 [map deep_copy($_), @$this];
3550 } elsif (ref $this eq "HASH") {
3551 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
3552 } else { die "what type is $_?" }
3558 $xml =~ s/</</gs;
3559 $xml =~ s/>/>/gs;
3564 my ($arr_ref, $val) = @_;
3565 my @arr = @{$arr_ref};
3567 foreach $val2 (@arr) {
3568 return 1 if ($val2 eq $val);
3574 my ($arr1, $arr2) = @_;
3578 foreach $val1 (@ar1) {
3579 foreach $val2 (@ar2) {
3580 return 1 if ($val1 eq $val2);
3586 sub load_templates() {
3587 my $custom_template_file_found=1;
3589 if ($current_calendar{custom_template} ne "") { # custom template
3590 $template_html = &get_remote_file("$current_calendar{custom_template}");
3592 if ($template_html !~ /###/){
3593 $custom_template_file_found=0;
3594 $lang{custom_template_fail} =~ s/###template###/$current_calendar{custom_template}/;
3595 $debug_info .= "$lang{custom_template_fail}\n";
3599 if ($current_calendar{custom_template} eq "" || $custom_template_file_found ==0){
3600 if (!(-e "$options{default_template_path}")) {
3602 $lang{default_template_fail} =~ s/###template###/$options{default_template_path}/;
3603 $error_info .= "$lang{default_template_fail}\n";
3606 open (FH, "$options{default_template_path}") || ($debug_info .="<br/>Unable to open default template file $options{default_template_path} for reading<br/>");
3608 @template_lines=<FH>;
3610 $template_html = join "", @template_lines;
3611 $local_template_file = 1;
3619 sub split_templates {
3620 $event_details_template = $template_html;
3621 $events_details_template = $template_html;
3622 $list_item_template = $template_html;
3623 $calendar_item_template = $template_html;
3625 # strip other templates from main template
3626 $template_html =~ s/<\/html>.+/<\/html>/s;
3627 $template_html =~ s/<event_details>.+<\/event_details>//s;
3628 $template_html =~ s/<events_details>.+<\/events_details>//s;
3629 $template_html =~ s/<event_list_item>.+<\/event_list_item>//s;
3630 $template_html =~ s/<calendar_item>.+<\/calendar_item>//s;
3632 if ($event_details_template =~ /<event_details>/ && $event_details_template =~ /<\/event_details>/) {
3633 $event_details_template =~ s/.*<event_details>//s;
3634 $event_details_template =~ s/<\/event_details>.+//s;
3636 $debug_info .= "Warning! No event details template found. (The template file doesn't contain
<event_details
>...</event_details
>\n";
3637 $event_details_template = "";
3640 if ($events_details_template =~ /<events_details>/ && $events_details_template =~ /<\/events_details>/) {
3641 $events_details_template =~ s/.*<events_details>//s;
3642 $events_details_template =~ s/<\/events_details>.+//s;
3644 $debug_info .= "Warning
! No event details template found
. (The template file doesn
't contain <events_details>...</events_details>\n";
3645 $events_details_template = "";
3648 if ($list_item_template =~ /<event_list_item>/ && $list_item_template =~ /<\/event_list_item>/) {
3649 $list_item_template =~ s/.*<event_list_item>//s;
3650 $list_item_template =~ s/<\/event_list_item>.+//s;
3652 $debug_info .= "Warning! No event event list item template found. (The template file doesn't contain
<event_list_item
>...</event_list_item
>\n";
3653 $event_details_template = "";
3654 $events_details_template = "";
3657 if ($calendar_item_template =~ /<calendar_item>/ && $calendar_item_template =~ /<\/calendar_item>/) {
3658 $calendar_item_template =~ s/.*<calendar_item>//s;
3659 $calendar_item_template =~ s/<\/calendar_item>.+//s;
3661 $debug_info .= "Warning
! No calendar event list item template found
. (The template file doesn
't contain <calendar_list_item>...</calendar_list_item>\n";
3662 $calendar_item_template = "";
3668 # takes a format string (which can be "dd/mm/yy", "yy,mm,dd", etc.)
3669 # and a date in that format, and returns the month, day, and year.
3670 my ($date, $format) = @_;
3672 my @temp_date = split (/\W+/, $date);
3673 my @temp_format = split (/\W+/, $format);
3674 my %temp_format_map;
3676 for (my $l1=0;$l1<3;$l1++) {
3677 $temp_format_map{$temp_format[$l1]} = $temp_date[$l1];
3680 my $mon = $temp_format_map{"mm"};
3681 my $day = $temp_format_map{"dd"};
3682 my $year = $temp_format_map{"yy"};
3684 return ($mon, $day, $year);
3689 $string =~ s/\//\\\//g;
3690 $string =~ s/\n/\\n/g;
3691 $string =~ s/"/\\"/g;
3692 $string =~ s/'/\\'/g;
3699 # r,g,b values are from 0 to 255
3700 # h = [0..360], s = [0..100], v = [0..100]
3701 # if s == 0, then h = -1 (undefined)
3710 my ($min, $max, $delta);
3712 $min = &min ( $r, $g, $b );
3713 $max = &max ( $r, $g, $b );
3715 # value is just the brightest rgb value
3718 # account for shades of gray:
3719 $delta = $max - $min;
3721 $s = 0; # no hue, so it can't be saturated
!
3722 $h = -1; # hue is really undefined, but...
3723 return ($h, $s, $v*100);
3726 # saturation is intensity/blandness of color:
3727 $s = $delta / $max; # max > 0 or delta would be 0
3729 # hue depends on the relative strengths of the colors:
3731 $h = ( $g - $b ) / $delta; # between yellow & magenta
3732 } elsif( $g == $max ) {
3733 $h = 2 + (( $b - $r ) / $delta); # between cyan & yellow
3735 $h = 4 + (( $r - $g ) / $delta); # between magenta & cyan
3738 # it's also calculated as degrees on a color wheel
3740 $h += 360 if ($h < 0);
3742 # s and v are percentages
3745 return (int( $h ), int($s), int($v));
3749 my ($hue, $sat, $val) = @_;
3750 my @hsv_map = ('vkm', 'nvm', 'mvk', 'mnv', 'kmv', 'vmn');
3751 # HSV conversions from pages 401-403 "Procedural Elements for Computer
3752 # Graphics", 1985, ISBN 0-07-053534-5.
3756 return ( 255 * $val, 255 * $val, 255 * $val );
3758 $val >= 0 or $val = 0;
3759 $val <= 1 or $val = 1;
3760 $sat <= 1 or $sat = 1;
3761 $hue >= 360 and $hue %= 360;
3762 $hue < 0 and $hue += 360;
3767 my $m = $val * (1.0 - $sat);
3768 my $n = $val * (1.0 - $sat * $f);
3770 my $k = $val * (1.0 - $sat * (1 - $f));
3772 my %fields = ( 'm'=>$m, 'n'=>$n, 'v'=>$v, 'k'=>$k, );
3773 return @fields{split //, $hsv_map[$i]};
3778 sub compatible_textcolor
{
3782 my $r = hex substr $ebgc,1,2;
3783 my $g = hex substr $ebgc,3,2;
3784 my $b = hex substr $ebgc,5,2;
3786 my $bright = ($r*299+$g*587+$b*114)/1000;
3788 if ($bright < 128) {$compat = "#ffffff";}
3789 else {$compat = "#000000";}
3796 open (FH
, "$file") || (return "unable to open include file $file for reading");
3800 $text = join "", @lines;
3803 return "file $file does not exist";
3808 # default calendar data structure
3810 %default_cal = (id
=> "",
3812 details
=> $new_calendar_default_details,
3814 local_background_calendars
=> {},
3815 selectable_calendars
=> {},
3816 make_new_calendars_selectable
=> $options{new_calendars_automatically_selectable
},
3817 list_background_calendars_together
=> "",
3818 background_events_display_style
=> "normal",
3819 background_events_fade_factor
=> "",
3820 background_events_color
=> "#ffffff",
3821 default_number_of_months
=> 1,
3822 max_number_of_months
=> 24,
3824 date_format
=> "mm/dd/yy",
3825 week_start_day
=> 0,
3826 event_change_email
=> "",
3827 info_window_size
=> "400x400",
3828 custom_template
=> "",
3829 custom_stylesheet
=> "",
3831 update_timestamp
=> 0);
3835 # If an included file contains only subroutines, perl will complain
3836 # that it "did not return a true value". The "return 1;" at the end fixes this.