Fixing http%3A bug for after adding a new iCal Calendar
[plans.git] / plans_lib.pl
blob54cfe7ebff8c503e1983d96ae4619530974b0efe
2 sub check_data() {
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};
25 } else { # DBI
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");
43 $sth->execute();
44 if ($dbh->errstr ne "") {
45 $calendars_table_exists=0;
46 $error_info .= $dbh->errstr."\n";
48 $sth->finish();
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");
55 $sth->execute();
56 if ($dbh->errstr ne "") {
57 $pending_actions_table_exists=0;
58 $error_info .= $dbh->errstr."\n";
60 $sth->finish();
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");
67 $sth->execute();
68 if ($dbh->errstr ne "") {
69 $events_table_exists=0;
70 $error_info .= $dbh->errstr."\n";
72 $sth->finish();
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");
79 $sth->execute();
80 if ($dbh->errstr ne "") {
81 $users_table_exists = 0;
82 $error_info .= $dbh->errstr."\n";
84 $sth->finish();
86 if ($users_table_exists + $events_table_exists + $pending_actions_table_exists + $calendars_table_exists == 4) {
87 # everything's ok
88 } elsif ($users_table_exists + $events_table_exists + $pending_actions_table_exists + $calendars_table_exists > 0) {
89 $fatal_error = 1;
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") {
93 $fatal_error = 1;
94 if ((-e "$options{users_file}") && (-e "$options{calendars_file}") && (-e $options{pending_actions_file}) && (-e $options{events_file})) {
95 $error_info .= <<p1;
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>
101 } else {
102 $error_info .= <<p1;
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");
115 $sth->execute();
116 if ($dbh->errstr ne "") {
117 $fatal_error = 1;
118 $error_info .= "error creating table \"$options{calendars_table}\"!\n".$dbh->errstr."\n";
120 $sth->finish();
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");
127 $sth->execute();
128 if ($dbh->errstr ne "") {
129 $fatal_error = 1;
130 $error_info .= "error creating table \"$options{pending_actions_table}\"!\n".$dbh->errstr."\n";
132 $sth->finish();
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");
138 $sth->execute();
139 if ($dbh->errstr ne "") {
140 $fatal_error = 1;
141 $error_info .= "error creating table \"$options{events_table}\"!\n".$dbh->errstr."\n";
143 $sth->finish();
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");
150 $sth->execute();
151 if ($dbh->errstr ne "") {
152 $fatal_error = 1;
153 $error_info .= "error creating table \"$options{users_table}\"!\n".$dbh->errstr."\n";
155 $sth->finish();
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";
161 $fatal_error = 0;
162 # data for the primary calendar
163 my %primary_cal = %default_cal;
164 $primary_cal{id}=0;
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 "") {
183 $fatal_error = 1;
184 $error_info .= "Error adding primary calendar!\n".$dbh->errstr."\n";
185 $error_info .= "$query_string\n";
186 } else {
187 $fatal_error = 1;
188 $error_info = <<p1;
189 Tables created!<br/>
190 (you shouldn't ever see this message again. To prove it, refresh the page or <a href="$script_url/$name">click here</a>.)
193 $sth->finish();
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;
199 &load_calendars();
200 &load_actions();
201 &load_users();
202 &load_events("all");
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 "") {
230 $fatal_error = 1;
231 $error_info .= "Error importing data!\n".$dbh->errstr."\n";
232 $error_info .= "$query_string\n";
233 } else {
234 $fatal_error = 1;
235 $error_info = <<p1;
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>.)
243 #$dbh->disconnect;
249 sub load_calendars {
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"};
254 flock FH,2;
255 my @calendar_lines=<FH>;
256 close 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
268 #for any calendar
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");
279 $sth->execute();
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];
287 my $line = $row[1];
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
294 #for any calendar
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};
302 $sth->finish();
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};
314 sub load_actions() {
315 my $max_new_cal_timestamp = 0;
316 my $max_new_event_timestamp = 0;
317 my @lines;
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"};
321 flock FH,2;
322 @lines=<FH>;
323 close FH;
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");
327 $sth->execute();
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];
335 my $line = $row[1];
336 push @lines, $line;
338 $sth->finish();
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");
346 $id = &decode($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";
428 sub load_users() {
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"};
431 flock FH,2;
432 my @user_lines=<FH>;
433 close FH;
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");
447 $sth->execute();
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) {
454 my $id = $row[0];
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};}
464 $sth->finish();
469 sub load_events() {
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"};
481 flock FH,2;
482 my @event_lines=<FH>;
483 close 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+)/;
495 my $evt_id = $1;
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
507 my @temp_cal_ids;
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)) {
521 next;
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
538 my $query_string;
539 if ($start eq "all") {
540 $query_string="select * from $options{events_table};";
541 $loaded_all_events = 1;
542 } else {
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],%'";
550 } else {
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],%'";}
557 } else {
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");
569 $sth->execute();
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];
584 my $cal_valid=0;
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
590 my $line = $row[4];
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)) {
596 next;
599 $events{$event{id}} = \%event;
601 $max_series_id = ($event{series_id} > $max_series_id) ? $event{series_id} : $max_series_id;
605 # get max event 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");
608 $sth->execute();
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;
615 # get latest event
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");
620 $sth->execute();
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];
630 my $line = $row[4];
631 $line =~ s/<\/?event>//g; # remove <event> and </event>
633 $latest_event_id = $latest_event{id};
638 sub load_event() {
639 # load a single event.
640 my ($event_id) = @_;
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"};
644 flock FH,2;
645 my @event_lines=<FH>;
646 close 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+)/;
658 my $evt_id = $1;
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};
671 last;
673 } else { # SQL database
674 my $query_string;
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];
690 my $line = $row[4];
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) = @_;
749 my @series_ids=();
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
757 return @series_ids;
760 # add an event to the data file
761 sub add_event() {
762 my ($event_id) = @_;
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
767 my $out_text="";
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"};
771 flock FH,2;
772 print FH $event_xml;
773 close FH;
774 } else { # DBI
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 "") {
792 $fatal_error = 1;
793 $debug_info .= "Error adding event!\n".$dbh->errstr."\n";
794 $debug_info .= "query string:\n$query_string\n";
796 $sth->finish();
799 foreach $cal_id (@{$temp_event{cal_ids}}) {
800 &export_ical($calendars{$cal_id});
804 # add multiple events to the data file
805 sub add_events() {
806 my ($event_ids_ref) = @_;
808 my @event_ids = @{$event_ids_ref};
809 if ($options{data_storage_mode} == 0 ) { # flat text files
810 my $out_text="";
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"};
815 flock FH,2;
816 print FH $out_text;
817 close FH;
818 } else { # DBI
819 foreach $id (@event_ids) {
820 next if ($id eq "");
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";
834 my $query_string;
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 (?,?,?,?,?,?);";
838 } else {
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 "") {
847 $fatal_error = 1;
848 $debug_info .= "Error adding event!\n($query_string)\n".$dbh->errstr."\n";
849 $debug_info .= "query string:\n$query_string\n";
851 $sth->finish();
855 my %cal_ids_to_export = {};
857 foreach $id (@event_ids) {
858 next if ($id eq "");
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)
872 sub update_event() {
873 my ($event_id) = @_;
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
879 my $out_text="";
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"};
888 flock FH,2;
889 print FH $out_text;
890 close FH;
891 } else { # DBI
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 "") {
909 $fatal_error = 1;
910 $debug_info .= "Error updating event!\n".$dbh->errstr."\n";
911 $debug_info .= "query string:\n$query_string\n";
913 $sth->finish();
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
928 my $out_text="";
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"};
935 flock FH,2;
936 print FH $out_text;
937 close FH;
938 } else { # DBI
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 "") {
959 $fatal_error = 1;
960 $debug_info .= "Error updating event!\n".$dbh->errstr."\n";
961 $debug_info .= "query string:\n$query_string\n";
963 $sth->finish();
968 my %cal_ids_to_export = {};
970 foreach $id (@event_ids) {
971 next if ($id eq "");
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});
985 # delete an event
986 sub delete_event() {
987 my ($event_id) = @_;
988 delete $events{$event_id};
990 if ($options{data_storage_mode} == 0 ) { # flat text files
991 my $out_text="";
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"};
997 flock FH,2;
998 print FH $out_text;
999 close FH;
1000 } else { # DBI
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 "") {
1005 $fatal_error = 1;
1006 $debug_info .= "Error deleting event!\n".$dbh->errstr."\n";
1007 $debug_info .= "query string:\n$query_string\n";
1009 $sth->finish();
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};}
1021 my $out_text="";
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"};
1027 flock FH,2;
1028 print FH $out_text;
1029 close FH;
1030 } else { # DBI
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");
1034 $sth->execute();
1035 if ($dbh->errstr ne "") {
1036 $fatal_error = 1;
1037 $debug_info .= "Error deleting event!\n".$dbh->errstr."\n";
1038 $debug_info .= "query string:\n$query_string\n";
1040 $sth->finish();
1045 sub add_action() {
1046 my ($action_id, $action_type) = @_;
1047 my $out_text = "";
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"};
1077 flock FH,2;
1078 print FH $out_text;
1079 close FH;
1080 } else { # DBI
1082 my $out_text = "";
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 "") {
1107 $fatal_error = 1;
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
1119 } else { # DBI
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
1134 my ($temp1) = @_;
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"};
1171 flock FH,2;
1172 print FH $out_text;
1173 close FH;
1174 } else { # DBI
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
1192 my $out_text="";
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"};
1200 flock FH,2;
1201 print FH $out_text;
1202 close FH;
1203 } else { # DBI
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(){
1223 my ($cal_id) = @_;
1225 if ($options{data_storage_mode} == 0 ) { # flat text files
1226 my $out_text="";
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"};
1234 flock FH,2;
1235 print FH $out_text;
1236 close FH;
1237 } else { # DBI
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 "") {
1245 $fatal_error = 1;
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
1260 my $out_text="";
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"};
1268 flock FH,2;
1269 print FH $out_text;
1270 close FH;
1271 } else { # DBI
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() {
1292 my ($cal_id) = @_;
1293 delete $calendars{$cal_id};
1295 if ($options{data_storage_mode} == 0 ) { # flat text files
1296 my $out_text ="";
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"};
1305 flock FH,2;
1306 print FH $out_text;
1307 close FH;
1308 } else { # DBI
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";
1320 sub add_user() {
1321 my ($user_id) = @_;
1322 # temporary copy of the event in question
1323 my %user = %{$users{$user_id}};
1325 if ($options{data_storage_mode} == 0 ) { # flat text files
1326 my $out_text="";
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/;
1331 $out_text .= $xml;
1333 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1334 flock FH,2;
1335 print FH $out_text;
1336 close FH;
1337 } else { # DBI
1338 my $xml = &user2xml($users{$user_id})."\n";
1339 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1340 $out_text .= $xml;
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";
1353 sub update_user() {
1354 my ($user_id) = @_;
1356 if ($options{data_storage_mode} == 0 ) { # flat text files
1357 my $out_text="";
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/;
1363 $out_text .= $xml;
1365 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1366 flock FH,2;
1367 print FH $out_text;
1368 close FH;
1369 } else { # DBI
1370 my $xml = &user2xml($users{$user_id})."\n";
1371 $xml =~ s/(<timestamp>)\d*(<\/timestamp>)/$1$rightnow$2/;
1372 $out_text .= $xml;
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 "") {
1378 $fatal_error = 1;
1379 $debug_info .= "Error updating user!\n".$dbh->errstr."\n";
1380 $debug_info .= "query string:\n$query_string\n";
1382 $sth->finish();
1386 sub delete_user() {
1387 my ($user_id) = @_;
1389 delete $users{$user_id};
1391 if ($options{data_storage_mode} == 0 ) { # flat text files
1392 my $out_text="";
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/;
1398 $out_text .= $xml;
1401 open (FH, ">$options{users_file}") || {$debug_info.= "unable to open file $options{users_file} for writing!\n"};
1402 flock FH,2;
1403 print FH $out_text;
1404 close FH;
1406 } else { # DBI
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 .= "&nbsp 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;}
1456 # add other fields
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>";
1479 return $xml_data;
1482 sub calculate_event_days() {
1483 my ($start, $end, $id) = @_;
1484 my $days = 1;
1485 my $duration = $end - $start;
1486 #$debug_info .= "id $id duration=$duration\n" if ($id eq "3258");
1487 return 1 if ($duration < 0) ;
1489 $days = 0;
1490 if (($duration+1) % 86400 == 0) # all-day event
1491 {$days = int(($duration)/86400)+1;}
1492 else { # partial-day event
1493 # calculate days
1494 my $mday = 99;
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) {
1498 $days++;
1499 $mday = (gmtime $i)[3];
1500 #$debug_info .= "id $id mday=$mday days=$days\n" if ($id eq "3258");
1504 return $days;
1507 sub xml2event() {
1508 my ($xml) = @_;
1509 my $event;
1511 $xml =~ s/<\/?event>//g; # remove <event> and </event>
1513 my ($id) = &xml_quick_extract($xml, "id");
1514 $id = &decode($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
1566 my @cal_ids_array;
1568 if ($cal_id ne "") {
1569 push @cal_ids_array, $cal_id;
1570 } else {
1571 @cal_ids_array = split(',', $cal_ids);
1575 my $all_day_event = "";
1576 my $no_end_time = "";
1577 if (($event_duration+1) % 86400 == 0) {
1578 $all_day_event = 1;
1579 $evt_days = &calculate_event_days($evt_start, $evt_end, $id);
1580 #$debug_info .= "event $id ($evt_title) is an all day event\n";
1581 } else {
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};
1587 my %this_calendar;
1588 if ($current_cal_id eq "") {
1589 %this_calendar = %{$calendars{$cal_ids_array[0]}};
1590 } else {
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,
1605 end => $evt_end,
1606 gmtime_start => $evt_gmtime_start,
1607 gmtime_end => $evt_gmtime_end,
1608 days => $evt_days,
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,
1616 icon => $evt_icon,
1617 bgcolor => $evt_bgcolor,
1618 unit_number => $evt_unit_number,
1619 update_timestamp => $update_timestamp};
1621 return $event;
1625 sub xml2user() {
1626 my ($xml) = @_;
1627 my $user;
1629 my ($id) = &xml_quick_extract($xml, "id");
1630 $id = &decode($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);
1644 my %cal_refs;
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;
1660 $user = {id => $id,
1661 name => $name,
1662 notes => $notes,
1663 password => $password,
1664 timestamp => $timestamp,
1665 calendars => \%cal_refs
1668 return $user;
1671 sub user2xml() {
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>";
1693 return $xml_data;
1699 sub xml2calendar() {
1700 my ($xml) = @_;
1702 my $calendar;
1703 my @calendar_users;
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,
1756 type => $type,
1757 version => $version,
1758 remote_id => $remote_id,
1759 url => $url,
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);
1810 my @add_emails;
1811 my @update_emails;
1812 my @delete_emails;
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,
1852 type => $cal_type,
1853 url => $cal_url,
1854 title => $cal_title,
1855 details => $cal_details,
1856 link => $cal_link,
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));
1891 } else {
1892 return $calendar;
1898 sub export_ical() {
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];
1917 $export_needed = 0;
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 ) {
1926 $export_needed = 1;
1927 break;
1933 if ( !$export_needed ) {
1934 return;
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"};
1940 flock FH,2;
1941 print FH $ical_contents;
1942 close FH;
1946 sub icalendar_export_cal {
1947 ($start_month, $start_year, $end_month, $end_year, $cal_id) = @_;
1948 my $results = "";
1950 %export_calendar = %current_calendar;
1951 if ( $cal_id ) {
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;
1988 $results .=<<p1;
1989 BEGIN:VCALENDAR
1990 PRODID:-//Plans//EN
1991 VERSION:2.0
1992 METHOD:PUBLISH
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";
2013 $results .=<<p1;
2015 END:VCALENDAR
2018 $results .= $debug_info;
2019 return $results;
2026 sub parse_ical() {
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;
2062 my %event;
2063 $event{cal_ids} = ($calendar{id});
2065 my ($uid) = &ical_get($event_text, "uid");
2066 $event{id} = $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");
2076 if ($dtend eq "") {
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
2114 my $evt_days;
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);
2120 } else {
2121 $evt_days = int(($event{end} - $event{start})/86400)+1;
2124 if ($event_duration > 1 && (($event_duration+1) % 86400 == 0 || ($event_duration) % 86400 == 0)){
2125 $all_day_event = 1;
2126 #$debug_info .= "event $id ($evt_title) is an all day event\n";
2127 } else {
2128 if ($event_duration == 1) {
2129 $no_end_time = 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]) {
2142 $evt_days++;
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";
2164 return \%calendar
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);
2189 return $timestamp;
2194 sub ical_get {
2195 my ($text, $field) = @_;
2196 my $value = "";
2197 my $comment = "";
2199 if ($text =~ /$field(;.+?)?:(.+?)\n/si) {
2200 $value = $2;
2201 $comment = $1;
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};
2249 my $results = "";
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,";
2265 } else {
2266 $results .= "'all_day_event':false,";
2269 if ($event{no_end_time} eq "1") {
2270 $results .= "'no_end_time':true";
2271 } else {
2272 $results .= "'no_end_time':false";
2275 return $results;
2278 sub calendar2javascript() {
2279 my ($calendar_ref) = @_;
2280 my %calendar = %{$calendar_ref};
2281 my $results = "";
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}})."'";
2286 return $results;
2292 sub javascript_cleanup() {
2293 my ($text) = @_;
2294 $text =~ s/\n/\\n/g;
2295 $text =~ s/"/\\"/g;
2296 $text =~ s/'/\\'/g;
2297 $text =~ s/\//\\\//g;
2298 return $text;
2303 sub event2xml() {
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>";
2346 return $xml_data;
2349 sub event2ical() {
2350 my ($event_ref) = @_;
2351 my %event = %{$event_ref};
2352 my $results;
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);
2368 } else {
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;
2378 $results =<<p1;
2379 BEGIN:VEVENT
2380 ORGANIZER:$cal_title
2381 DTSTART$dtstart_string
2382 DTEND$dtend_string
2383 TRANSP:OPAQUE
2384 SEQUENCE:0
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)
2389 PRIORITY:5
2390 CLASS:PUBLIC
2391 END:VEVENT
2394 return $results;
2399 sub event2vcal() {
2400 my ($event_ref) = @_;
2401 my %event = %{$event_ref};
2402 my $results;
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;
2418 $results =<<p1;
2419 BEGIN:VEVENT
2420 DTSTART:$dtstart_string
2421 DTEND:$dtend_string
2422 TRANSP:0
2423 SEQUENCE:0
2424 DTSTAMP:20020322T043444Z
2425 DESCRIPTION:$event{details}
2426 SUMMARY:$event{title} ($cal_title)
2427 PRIORITY:5
2428 CLASS:PUBLIC
2431 if ($event{days} > 1) {
2432 $results .=<<p1;
2433 RRULE:D$event{days} $dtend_string
2436 $results .=<<p1;
2437 END:VEVENT
2440 return $results;
2444 sub outlook_date {
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";
2457 return $dt_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;
2484 return $dt_string;
2487 sub event2palmcsv() {
2488 my ($event_ref) = @_;
2489 my %event = %{$event_ref};
2490 my $results;
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;
2540 return $results;
2546 sub find_end_of_month {
2547 my ($month, $year) = @_;
2549 my $next_month = $month+1;
2550 if ($next_month > 11) {
2551 $next_month=0;
2552 $year++;
2554 return timegm(0,0,0,1,$next_month,$year);
2557 sub xml_store {
2558 my ($data_ref, $tag_name) = @_;
2559 my $data_string;
2561 if (ref $data_ref eq "ARRAY") {
2562 my $i=0;
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 .= ',';}
2568 $i++;
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 = ();
2590 my $results = "";
2591 my $final_results = "";
2593 my $depth_count=0;
2594 my $start_index=0;
2595 my $end_index=0;
2596 my $match_index=0;
2598 my $attributes=();
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) {
2603 my $match=$1;
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;
2607 $depth_count++;
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) {
2619 my $a_match = $&;
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;
2632 #%attributes=();
2633 #$debug_info .= "end position, $+[0]\n\n";
2634 } elsif ($match =~ /<[^\/].*?>/) { # some other opening tag
2635 $depth_count++;
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
2638 $depth_count--;
2639 if ($debug) {$debug_info .= "active closing tag, $match \ndepth count $depth_count\n";}
2640 if ($depth_count==0) { # done! return results
2641 $end_index = $-[0];
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";}
2655 $start_index=0;
2656 $end_index=0;
2657 $attributes=();
2659 #$debug_info .= "closing tag, $1 \ndepth count $depth_count\n";
2660 #$debug_info .= "start position, $-[0]\n\n";
2661 $position++;
2662 } else { # other closing tag
2663 $depth_count--;
2664 if ($depth_count==0) {$position++;}
2666 if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
2668 $match_index++;
2670 return @results_array;
2671 } #******************** end xml_extract **********************
2674 sub xml_tags {
2675 my ($data, $debug) = @_;
2676 my @results_array = ();
2677 my %tags_hash;
2679 my $depth_count=0;
2681 while ($data =~ /(<.*?>|<\/.*?>)/g) {
2682 my $match=$1;
2683 if ($match =~ /<[^\/].*?>/) { # any opening tag
2684 if ($depth_count == 0) { # level 0 opening tag
2685 $tag_name = $match;
2686 $tag_name =~ s/<//;
2687 $tag_name =~ s/\b(.+)\b.+/$1/;
2688 if ($debug) {$debug_info .= "level 0 opening tag, $tag_name \n\n";}
2690 $depth_count++;
2691 } elsif ($depth_count == 1) { # level 1 closing tag
2692 $tag_name = $match;
2693 $tag_name =~ s/<//;
2694 $tag_name =~ s/\/(.+)(\b|>).+/$1/;
2696 $depth_count--;
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
2702 $depth_count--;
2703 if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
2706 return keys %tags_hash;
2708 } #******************** end xml_tags **********************
2711 sub xml2hash {
2712 my ($xml_data, $debug) = @_;
2713 my $item;
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";}
2720 return $xml_data;
2721 } else {
2722 if ($debug) {$debug_info .= " xml data: ($xml_data) \n";}
2723 my %results_hash;
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);
2730 } else {
2731 if ($debug) {$debug_info .= " extracting xml for tag $tag (array data)\n";}
2732 my @tag_array;
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 **********************
2744 sub hash2xml {
2745 my ($temp, $parent_tag, $order_hashref) = @_;
2746 my $results="";
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>";
2766 } else { # data
2767 #$debug_info .= "hash2xml: data\n";
2768 $results .= "<$parent_tag>".&encode($temp)."</$parent_tag>";
2771 return $results;
2772 } #******************** end hash2xml **********************
2776 sub init_session {
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"]);
2797 return $session;
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);
2807 sub _load_profile {
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);
2845 return undef;
2849 sub delete_old_sessions {
2850 my ($days) = @_;
2852 opendir (DIR, "$options{sessions_directory}/");
2853 @FILES = grep(/cgisess_/,readdir(DIR));
2854 closedir (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 {
2869 my ($url) = @_;
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}"
2889 } else {
2890 $remote = IO::Socket::INET->new( Proto => "tcp",
2891 PeerAddr => $hostname,
2892 PeerPort => "http(80)"
2895 unless ($remote) {
2896 $debug_info .= "cannot connect to http daemon on $hostname <br>";
2897 return;
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;
2919 return $textstring;
2922 sub time_overlap {
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;}
2936 else
2937 {return 0;}
2940 sub make_email_link {
2941 my ($string) = @_;
2942 my $new_string = "";
2943 #remove all newlines
2944 $string =~ s/\n//g;
2946 #insert newlines after > characters
2947 $string =~ s/</\n</g;
2949 my @lines = split ("\n", $string);
2951 foreach $line (@lines) {
2952 $line .= "\n";
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;}
2958 else {
2959 $new_line =~ s/\n//g;
2960 $new_string .= $new_line;
2963 return $new_string;
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";
2980 $ampm = $lang{am};
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;
2992 else {
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) {
3018 $temp=$timestamp2;
3019 $timestamp2=$timestamp1;
3020 $timestamp1=$temp;
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]) {
3040 #different year
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]";
3043 else
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]";
3047 } else {
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]) {
3057 #different year
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]";
3060 else {
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) = @_;
3071 my $results = "";
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*-.+//;
3077 return $results;
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/;
3083 return $results;
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);
3090 my $ets = 0;
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";
3097 } else {
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;
3107 $ets = $sts+1;
3110 #$debug_info .= "sts: $sts\n";
3111 #$debug_info .= "ets: $ets\n";
3113 return ($sts, $ets);
3118 sub time2seconds {
3119 my ($time) = @_;
3120 my($hours, $minutes, $seconds);
3121 if($options{twentyfour_hour_format}) {
3122 $time =~ /(\d+):(\d+)/;
3123 $hours = $1;
3124 $minutes = $2;
3125 $seconds = 3600*$hours + 60*$minutes;
3126 } else {
3127 $time =~ /(\d+):(\d+)\s*($lang{am}|$lang{pm})/;
3128 $hours = $1;
3129 $minutes = $2;
3130 my $ampm = $3;
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;
3142 return $seconds;
3145 sub escapequotes {
3146 my ($input_string) = @_;
3147 my $output_string = $input_string;
3148 $output_string =~ s/"/&quot;/g;
3149 return $output_string;
3152 sub encode {
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;
3162 sub decode {
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>";
3200 } else {
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), " - ");
3215 } else {
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&amp;&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;
3275 } else {
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>";
3319 } else {
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), " - ");
3334 } else {
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&amp;&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;
3394 } else {
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() {
3414 my $results = "";
3415 my ($event_ref) = @_;
3416 my %event = %{$event_ref};
3418 $results .=<<p1;
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}">
3423 <br/>
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}
3429 </select>
3430 </form>
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,}$/)) {
3442 return $to_address;
3445 return "";
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;
3458 chomp $to_address;
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);
3471 return "1";
3472 #return "$lang{send_email_reminder3} ($options{email_mode}).";
3474 } # send_email_reminder
3477 sub send_email() {
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";
3487 $body = <<p1;
3488 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3490 <html>
3491 <head>
3492 <title>$subject</title>
3493 </head>
3495 $body
3496 </body></html>
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;
3505 Reply-to: $reply_to
3506 From: $from
3507 Subject: $subject
3508 To: $to
3509 Content-type: $content_type\n
3510 $body
3513 close(SENDMAIL);
3514 } elsif ($options{email_mode} == 2) {
3515 $smtp->mail($reply_to);
3516 $smtp->to($to);
3517 $smtp->data();
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");
3523 $smtp->dataend();
3524 $smtp->reset();
3528 sub extract_cookie_parms() {
3529 my %cd;
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");
3539 return \%cd;
3544 sub deep_copy {
3545 my $this = shift;
3546 if (not ref $this) {
3547 $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 $_?" }
3556 sub xml2html {
3557 my ($xml) = @_;
3558 $xml =~ s/</&lt;/gs;
3559 $xml =~ s/>/&gt;/gs;
3560 return $xml;
3563 sub contains {
3564 my ($arr_ref, $val) = @_;
3565 my @arr = @{$arr_ref};
3567 foreach $val2 (@arr) {
3568 return 1 if ($val2 eq $val);
3570 return 0;
3573 sub intersects {
3574 my ($arr1, $arr2) = @_;
3575 my @ar1 = @{$arr1};
3576 my @ar2 = @{$arr2};
3578 foreach $val1 (@ar1) {
3579 foreach $val2 (@ar2) {
3580 return 1 if ($val1 eq $val2);
3583 return 0;
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}")) {
3601 $fatal_error=1;
3602 $lang{default_template_fail} =~ s/###template###/$options{default_template_path}/;
3603 $error_info .= "$lang{default_template_fail}\n";
3604 &fatal_error();
3605 } else {
3606 open (FH, "$options{default_template_path}") || ($debug_info .="<br/>Unable to open default template file $options{default_template_path} for reading<br/>");
3607 flock FH,2;
3608 @template_lines=<FH>;
3609 close FH;
3610 $template_html = join "", @template_lines;
3611 $local_template_file = 1;
3614 &split_templates();
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;
3635 } else {
3636 $debug_info .= "Warning! No event details template found. (The template file doesn't contain &lt;event_details&gt;...&lt;/event_details&gt;\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;
3643 } else {
3644 $debug_info .= "Warning! No event details template found. (The template file doesn't contain &lt;events_details&gt;...&lt;/events_details&gt;\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;
3651 } else {
3652 $debug_info .= "Warning! No event event list item template found. (The template file doesn't contain &lt;event_list_item&gt;...&lt;/event_list_item&gt;\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;
3660 } else {
3661 $debug_info .= "Warning! No calendar event list item template found. (The template file doesn't contain &lt;calendar_list_item&gt;...&lt;/calendar_list_item&gt;\n";
3662 $calendar_item_template = "";
3667 sub format2mdy() {
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);
3687 sub js_string() {
3688 my ($string) = @_;
3689 $string =~ s/\//\\\//g;
3690 $string =~ s/\n/\\n/g;
3691 $string =~ s/"/\\"/g;
3692 $string =~ s/'/\\'/g;
3693 $string =~ s/\r//g;
3694 return $string;
3698 sub rgb2hsv {
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)
3703 my ($r,$g,$b) = @_;
3705 $r /= 255;
3706 $g /= 255;
3707 $b /= 255;
3709 my ($h, $s, $v);
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
3716 $v = $max;
3718 # account for shades of gray:
3719 $delta = $max - $min;
3720 if ($delta == 0 ) {
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:
3730 if( $r == $max ) {
3731 $h = ( $g - $b ) / $delta; # between yellow & magenta
3732 } elsif( $g == $max ) {
3733 $h = 2 + (( $b - $r ) / $delta); # between cyan & yellow
3734 } else {
3735 $h = 4 + (( $r - $g ) / $delta); # between magenta & cyan
3738 # it's also calculated as degrees on a color wheel
3739 $h *= 60; # degrees
3740 $h += 360 if ($h < 0);
3742 # s and v are percentages
3743 $s *= 100;
3744 $v *= 100;
3745 return (int( $h ), int($s), int($v));
3748 sub hsv2rgb {
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.
3754 my @result;
3755 if ($sat <= 0) {
3756 return ( 255 * $val, 255 * $val, 255 * $val );
3757 } else {
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;
3763 $hue /= 60.0;
3764 my $i = int($hue);
3765 my $f = $hue - $i;
3766 $val *= 255;
3767 my $m = $val * (1.0 - $sat);
3768 my $n = $val * (1.0 - $sat * $f);
3770 my $k = $val * (1.0 - $sat * (1 - $f));
3771 my $v = $val;
3772 my %fields = ( 'm'=>$m, 'n'=>$n, 'v'=>$v, 'k'=>$k, );
3773 return @fields{split //, $hsv_map[$i]};
3778 sub compatible_textcolor {
3779 my ($ebgc) = @_;
3780 my $compat;
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";}
3790 return $compat;
3793 sub load_file() {
3794 my ($file)=@_;
3795 if (-e $file) {
3796 open (FH, "$file") || (return "unable to open include file $file for reading");
3797 flock FH,2;
3798 my @lines=<FH>;
3799 close FH;
3800 $text = join "", @lines;
3801 return $text;
3802 } else {
3803 return "file $file does not exist";
3808 # default calendar data structure
3809 #%default_cal;
3810 %default_cal = (id => "",
3811 title => "",
3812 details => $new_calendar_default_details,
3813 link => "",
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,
3823 gmtime_diff => 0,
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 => "",
3830 password => "",
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.
3837 return 1;