
sub check_data()
{
  if ($data_storage_mode == 0 )  # flat text files
  {
    if (!(-e "$calendars_file"))
      {$fatal_error=1;$error_info .= "Calendars file $calendars_file not found!\n";}
    if (!(-e $new_calendars_file))
      {$fatal_error=1;$error_info .= "New calendars file $new_calendars_file not found!\n";}
    if (!(-e $events_file))
      {$fatal_error=1;$error_info .= "Events file $events_file not found!\n";}
    
    if ($fatal_error == 0)
    {  
      if (!(-w $calendars_file))
        {$fatal_error=1;$error_info .= "Calendars file $calendars_file is not writable!\n";}
      if (!(-w $new_calendars_file))
        {$fatal_error=1;$error_info .= "New calendars file $new_calendars_file is not writable!\n";}
      if (!(-w $events_file))
        {$fatal_error=1;$error_info .= "Events file $events_file is not writable!\n";}
    } 
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $calendars_table_exists=1;
    my $new_calendars_table_exists=1;
    my $events_table_exists=1;
      
      
    # if successful, check whether the calendars table exists  
    my $query_string="select * from $calendars_table limit 0";
    my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $calendars_table_exists=0;
      $error_info .= $dbh->errstr."\n";;
    }
    $sth->finish();

    # check whether the new_calendars table exists  
    my $query_string="select * from $new_calendars_table limit 0";
    my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $new_calendars_table_exists=0;
      $error_info .= $dbh->errstr."\n";;
    }
    $sth->finish();

    # check whether the events table exists  
    my $query_string="select * from $events_table limit 0";
    my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $events_table_exists=0;
      $error_info .= $dbh->errstr."\n";;
    }
    $sth->finish();

    if ($events_table_exists + $new_calendars_table_exists + $calendars_table_exists == 3)
    {
      # everything's ok
    }
    elsif ($events_table_exists + $new_calendars_table_exists + $calendars_table_exists > 0)
    {
      $fatal_error = 1;
      $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";
    }
    elsif ($events_table_exists + $new_calendars_table_exists + $calendars_table_exists == 0)
    {
      if ($q->param('create_tables') ne "1")
      {
        $fatal_error = 1;
        if ((-e "$calendars_file") && (-e $new_calendars_file) && (-e $events_file))
        {    
          $error_info .= <<p1;
\nIt looks like the required tables don't exist. 
\nShall Plans create them for you?
\n<a href="$script_url/$name?create_tables=1">Yes, please create them (but don't import anything)</a>
\n<a href="$script_url/$name?create_tables=1&import_data=1">Yes, please create them, and import all all existing data from <b>$calendars_file</b>, <b>$new_calendars_file</b>, and <b>$events_file</b>.</a>
p1
        }
        else
        {
          $error_info .= <<p1;
\nIt looks like the required tables don't exist.  
\nShall Plans create them for you?
\n<a href="$script_url/$name?create_tables=1">Yes, please create them</a>
p1
        }
      }
      else  # create the tables!
      {
        $error_info .= "\nCreating calendar and event tables...\n";
        # create the calendars table
        my $query_string="create table $calendars_table(id int(5),xml_data text,update_timestamp int(15));";
        my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
        my $rv = $sth->execute();
        if ($dbh->errstr ne "")
        {
          $fatal_error = 1;
          $error_info .= "error creating table \"$calendars_table\"!\n".$dbh->errstr."\n";
        }
        $sth->finish();
        
        # create the new calendars table
        my $query_string="create table $new_calendars_table(id int(5),xml_data text,update_timestamp int(15));";
        my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
        my $rv = $sth->execute();
        if ($dbh->errstr ne "")
        {
          $fatal_error = 1;
          $error_info .= "error creating table \"$new_calendars_table\"!\n".$dbh->errstr."\n";
        }
        $sth->finish();
      
        # create the events table
        my $query_string="create table $events_table(id int(5),cal_id int(5),start int(15),end int(15),xml_data text,update_timestamp int(15));";
        my $sth = $dbh->prepare($query_string) || ($error_info .= "Can't prepare $statement: $dbh->errstr\n");
        my $rv = $sth->execute();
        if ($dbh->errstr ne "")
        {
          $fatal_error = 1;
          $error_info .= "error creating table \"$events_table\"!\n".$dbh->errstr."\n";
        }
        $sth->finish();
        
        # either import existing text data, or create a record for the primary calendar
        if ($q->param('import_data') ne "1"  && $fatal_error != 1)  # create primary calendar
        {
          $error_info .= "\nAdding primary calendar...\n";

          $fatal_error = 0;
          # data for the primary calendar   
          my %primary_cal = %default_cal;
          $primary_cal{id}=0;
          $primary_cal{title}="Main Calendar";
          $primary_cal{password}=crypt("12345", substr("12345",0,2)); 
          $primary_cal{details}=<<p1;
This is the primary calendar.  You can't delete it (you can only rename it).  
The password for this calendar is "12345", which you should change right away.
This calendar's password is the "master password", and can be used to override 
the password of any other calendar. 
p1
          $primary_cal{update_timestamp}=time();
          
          
          my $cal_xml = &calendar2xml(\%primary_cal);
          
          # add the primary calendar to the table
          my $query_string="insert into $calendars_table (id, xml_data, update_timestamp) values ($primary_cal{id}, '$cal_xml', $primary_cal{update_timestamp});";
          my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
          my $rv = $sth->execute();
          if ($dbh->errstr ne "")
          {
            $fatal_error = 1;
            $error_info .= "Error adding primary calendar!\n".$dbh->errstr."\n";
            $error_info .= "$query_string\n";
          }
          else
          {
            $fatal_error = 1;
            $error_info = <<p1;
Tables created!<br/>
(you shouldn't ever see this message again.  To prove it, refresh the page or <a href="$script_url/$name">click here</a>.)
p1
          }
          $sth->finish();
        }
        else  # import data
        {
          $error_info .= "\nImporting data from flat files...\n";
          my $temp=$data_storage_mode;
          
          $data_storage_mode = 0;
          &load_calendars();
          &load_new_calendars();
          &load_events("all");
          $data_storage_mode = $temp;
          
          my @temp_cal_ids = keys %calendars;
          &add_calendars(\@temp_cal_ids);
          
          my @temp_new_cal_ids = keys %new_calendars;
          &add_new_calendars(\@temp_new_cal_ids);
          
          my @temp_event_ids = keys %events;
          &add_events(\@temp_event_ids);
          
          if ($dbh->errstr ne "")
          {
            $fatal_error = 1;
            $error_info .= "Error adding primary calendar!\n".$dbh->errstr."\n";
            $error_info .= "$query_string\n";
          }
          else
          {
            $fatal_error = 1;
            $error_info = <<p1;
Tables created, data imported!<br/>
(you shouldn't ever see this message again.  To prove it, refresh the page or <a href="$script_url/$name">click here</a>.)
p1
          }
        }
      }
    }
    #$dbh->disconnect;
  }
}

sub load_calendars
{
  my $max_update_timestamp=0;
  my $latest_cal_id=0;
  if ($data_storage_mode == 0 )  # flat text files
  {
    open (FH, "$calendars_file") || {$debug_info.= "unable to open file $calendars_file\n"};
    flock FH,2;
    my @calendar_lines=<FH>;
    close FH;

    # For the calendars, we do "complete" xml parsing (no validation or DTD though)
    foreach $line (@calendar_lines)
    {
      if ($line !~ /\S/) {next;}          # ignore blank lines
      #if ($line =~ /<\/?xml>/) {next;}    # ignore <xml> and </xml lines>
      
      $line =~ s/<\/?calendar>//g;      # remove <calendar> and </calendar>
      
      my ($cal_id) = &xml_quick_extract($line, "id");
      
      my ($cal_title) = &xml_quick_extract($line, "title");
      $cal_title = &decode($cal_title);
      
      my ($cal_details) = &xml_quick_extract($line, "details");
      $cal_details = &decode($cal_details);
      
      my ($cal_link) = &xml_quick_extract($line, "link");
      $cal_link = &decode($cal_link);
      
      my ($cal_password) = &xml_quick_extract($line, "admin_password");
      $cal_password = &decode($cal_password);
      
      my $update_timestamp=0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");
      
      # extract background calendars
      my @temp = &xml_quick_extract($line, "background_calendar");
      my %local_background_calendars;
      my $num_background_calendars = scalar @temp;
      foreach $background_calendar (@temp)
      {
        my ($type) = &xml_quick_extract($background_calendar, "type");
        $type = &decode($type);
        
        if ($type eq "plans_local")
        {
          my ($id) = &xml_quick_extract($background_calendar, "id");
          $local_background_calendars{$id} = 1;
        }
      }
      
      # extract selectable calendars
      my %selectable_calendars;
      @temp = &xml_quick_extract($line, "selectable_calendar");
      foreach $selectable_calendar (@temp)
      {
        $selectable_calendars{$selectable_calendar} = 1;
      }
      
      my ($list_background_calendars_together) = &xml_quick_extract($line, "list_background_calendars_together");
      $list_background_calendars_together = "no" if ($list_background_calendars_together eq "");
      
      my ($background_events_display_style) = &xml_quick_extract($line, "background_events_display_style");
      $background_events_display_style = "normal" if ($background_events_display_style eq "");
      
      my ($background_events_fade_factor) = &xml_quick_extract($line, "background_events_fade_factor");
      $background_events_fade_factor = 1 if ($background_events_fade_factor eq "" || $background_events_fade_factor < 1);
      
      my ($background_events_color) = &xml_quick_extract($line, "background_events_color");
      $background_events_color = &decode($background_events_color);
      $background_events_color = "#ffffff" if ($background_events_color eq "");

      my ($default_number_of_months) = &xml_quick_extract($line, "default_number_of_months");
      $default_number_of_months = 1 if ($default_number_of_months eq "");
       
      my ($max_number_of_months) = &xml_quick_extract($line, "max_number_of_months");
      $max_number_of_months = 24 if ($max_number_of_months eq "");
      
      my ($gmtime_diff) = &xml_quick_extract($line, "gmtime_diff");
      $gmtime_diff = 0 if ($gmtime_diff eq "");
      
      my ($date_format) = &xml_quick_extract($line, "date_format");
      $date_format = &decode($date_format);
      $date_format = "mm/dd/yy" if ($date_format eq "");
      
      my ($week_start_day) = &xml_quick_extract($line, "week_start_day");
      $week_start_day = "0" if ($week_start_day eq "");
            
      my ($info_window_size) = &xml_quick_extract($line, "info_window_size");
      $info_window_size = "400x400" if ($info_window_size eq "");
      
      my ($custom_template) = &xml_quick_extract($line, "custom_template");
      $custom_template = &decode($custom_template);
      
      my ($custom_stylesheet) = &xml_quick_extract($line, "custom_stylesheet");
      $custom_stylesheet = &decode($custom_stylesheet);
      
      if ($cal_id > $max_cal_id)
      {
        $max_cal_id = $cal_id;
      }
    
      $calendars{$cal_id} = {id => $cal_id, 
                            title => $cal_title, 
                            details => $cal_details,
                            link => $cal_link,
                            local_background_calendars => \%local_background_calendars,
                            selectable_calendars => \%selectable_calendars,
                            list_background_calendars_together => $list_background_calendars_together,
                            background_events_display_style => $background_events_display_style,
                            background_events_fade_factor => $background_events_fade_factor,
                            background_events_color => $background_events_color,
                            default_number_of_months => $default_number_of_months,
                            max_number_of_months => $max_number_of_months,
                            gmtime_diff => $gmtime_diff,
                            date_format => $date_format,
                            week_start_day => $week_start_day,
                            info_window_size => $info_window_size,
                            custom_template => $custom_template,
                            custom_stylesheet => $custom_stylesheet,
                            password => $cal_password,
                            update_timestamp => $update_timestamp};

      #the calendar with id 0 is assumed to be the master calendar.
      #its password can be used to approve/edit/delete any event
      #for any calendar
      if ($cal_id eq "0")
        {$master_password = $cal_password;}
      if ($cal_id eq "$in{cal_id}") 
        {$current_cal_title =$cal_title;}
        
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_cal_id = $cal_id;
      }
    }
    
    %latest_cal = %{$calendars{$latest_cal_id}};
  }
  elsif ($data_storage_mode == 1 ) # SQL database
  {
  
    my $query_string="select * from  $calendars_table;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "Error loading calendars!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }

    while(@row = $sth->fetchrow_array) 
    {
      my $cal_id = $row[0];
      my $line = $row[1];
      
      #$debug_info .= "cal id: $cal_id\n";
    
      my ($cal_title) = &xml_quick_extract($line, "title");
      $cal_title = &decode($cal_title);
      
      my ($cal_details) = &xml_quick_extract($line, "details");
      $cal_details = &decode($cal_details);
      
      my ($cal_link) = &xml_quick_extract($line, "link");
      $cal_link = &decode($cal_link);
      
      my ($cal_password) = &xml_quick_extract($line, "admin_password");
      $cal_password = &decode($cal_password);
      
      my $update_timestamp=0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");
      
      # extract background calendars
      my @temp = &xml_quick_extract($line, "background_calendar");
      my %local_background_calendars;
      my $num_background_calendars = scalar @temp;
      foreach $background_calendar (@temp)
      {
        my ($type) = &xml_quick_extract($background_calendar, "type");
        $type = &decode($type);
        
        if ($type eq "plans_local")
        {
          my ($id) = &xml_quick_extract($background_calendar, "id");
          $local_background_calendars{$id} = 1;
        }
      }
      
      # extract selectable calendars
      my %selectable_calendars;
      @temp = &xml_quick_extract($line, "selectable_calendar");
      foreach $selectable_calendar (@temp)
      {
        $selectable_calendars{$selectable_calendar} = 1;
      }
      
      my ($list_background_calendars_together) = &xml_quick_extract($line, "list_background_calendars_together");
      $list_background_calendars_together = "no" if ($list_background_calendars_together eq "");
      
      my ($background_events_display_style) = &xml_quick_extract($line, "background_events_display_style");
      $background_events_display_style = "normal" if ($background_events_display_style eq "");
      
      my ($background_events_fade_factor) = &xml_quick_extract($line, "background_events_fade_factor");
      $background_events_fade_factor = 1 if ($background_events_fade_factor eq "" || $background_events_fade_factor < 1);
      
      my ($background_events_color) = &xml_quick_extract($line, "background_events_color");
      $background_events_color = &decode($background_events_color);
      $background_events_color = "#ffffff" if ($background_events_color eq "");

      my ($default_number_of_months) = &xml_quick_extract($line, "default_number_of_months");
      $default_number_of_months = 1 if ($default_number_of_months eq "");
       
      my ($max_number_of_months) = &xml_quick_extract($line, "max_number_of_months");
      $max_number_of_months = 24 if ($max_number_of_months eq "");
      
      my ($gmtime_diff) = &xml_quick_extract($line, "gmtime_diff");
      $gmtime_diff = 0 if ($gmtime_diff eq "");
      
      my ($date_format) = &xml_quick_extract($line, "date_format");
      $date_format = &decode($date_format);
      $date_format = "mm/dd/yy" if ($date_format eq "");
      
      my ($week_start_day) = &xml_quick_extract($line, "week_start_day");
      $week_start_day = "0" if ($week_start_day eq "");
            
      my ($info_window_size) = &xml_quick_extract($line, "info_window_size");
      $info_window_size = "400x400" if ($info_window_size eq "");
      
      my ($custom_template) = &xml_quick_extract($line, "custom_template");
      $custom_template = &decode($custom_template);
      
      my ($custom_stylesheet) = &xml_quick_extract($line, "custom_stylesheet");
      $custom_stylesheet = &decode($custom_stylesheet);
      
      if ($cal_id > $max_cal_id)
      {
        $max_cal_id = $cal_id;
      }
    
      $calendars{$cal_id} = {id => $cal_id, 
                            title => $cal_title, 
                            details => $cal_details,
                            link => $cal_link,
                            local_background_calendars => \%local_background_calendars,
                            selectable_calendars => \%selectable_calendars,
                            list_background_calendars_together => $list_background_calendars_together,
                            background_events_display_style => $background_events_display_style,
                            background_events_fade_factor => $background_events_fade_factor,
                            background_events_color => $background_events_color,
                            default_number_of_months => $default_number_of_months,
                            max_number_of_months => $max_number_of_months,
                            gmtime_diff => $gmtime_diff,
                            date_format => $date_format,
                            week_start_day => $week_start_day,
                            info_window_size => $info_window_size,
                            custom_template => $custom_template,
                            custom_stylesheet => $custom_stylesheet,
                            password => $cal_password,
                            update_timestamp => $update_timestamp};

      #the calendar with id 0 is assumed to be the master calendar.
      #its password can be used to approve/edit/delete any event
      #for any calendar
      if ($cal_id eq "0")
        {$master_password = $cal_password;}
      if ($cal_id eq "$in{cal_id}") 
        {$current_cal_title =$cal_title;}
        
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_cal_id = $cal_id;
      }
    }
    
    %latest_cal = %{$calendars{$latest_cal_id}};
    $sth->finish();

  }
}

sub load_new_calendars()
{
  my $latest_new_cal_id=0;
  my $max_update_timestamp=0;
  if ($data_storage_mode == 0 )  # flat text files
  {
    open (FH, "$new_calendars_file") || {$debug_info.= "unable to open file $new_calendars_file\n"};
    flock FH,2;
    my @calendar_lines=<FH>;
    close FH;

    # For the calendars, we do "complete" xml parsing (no validation or DTD though)
    foreach $line (@calendar_lines)
    {
      if ($line !~ /\S/) {next;}          # ignore blank lines
      
      $line =~ s/<\/?calendar>//g;      # remove <calendar> and </calendar>
      
      # extract calendar data.
      my ($cal_id) = &xml_quick_extract($line, "id");

      my ($cal_title) = &xml_quick_extract($line, "title");
      $cal_title = &decode($cal_title);
      
      my ($cal_details) = &xml_quick_extract($line, "details");
      $cal_details = &decode($cal_details);
      
      my ($cal_link) = &xml_quick_extract($line, "link");
      $cal_link = &decode($cal_link);
      
      my ($cal_password) = &xml_quick_extract($line, "admin_password");
      $cal_password = &decode($cal_password);
      
      my ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");
      
      my @temp = &xml_quick_extract($line, "background_calendar");
      my %local_background_calendars;
      my $num_background_calendars = scalar @temp;
      foreach $background_calendar (@temp)
      {
        my ($type) = &xml_quick_extract($background_calendar, "type");
        $type = &decode($type);
        
        if ($type eq "plans_local")
        {
          my ($id) = &xml_quick_extract($background_calendar, "id");
          
          $local_background_calendars{$id} = 1;
        }
      }
      
      # extract selectable calendars
      my %selectable_calendars;

      @temp = &xml_quick_extract($line, "selectable_calendar");
      foreach $selectable_calendar (@temp)
        {$selectable_calendars{$selectable_calendar} = 1;}
      
      my ($list_background_calendars_together) = &xml_quick_extract($line, "list_background_calendars_together");
      $list_background_calendars_together = "no" if ($list_background_calendars_together eq "");
      
      my ($background_events_display_style) = &xml_quick_extract($line, "background_events_display_style");
      $background_events_display_style = "normal" if ($background_events_display_style eq "");
      
      my ($background_events_fade_factor) = &xml_quick_extract($line, "background_events_fade_factor");
      $background_events_fade_factor = 1 if ($background_events_fade_factor eq "" || $background_events_fade_factor < 1);
      
      my ($background_events_color) = &xml_quick_extract($line, "background_events_color");
      $background_events_color = $temp->{data};
      
      my ($default_number_of_months) = &xml_quick_extract($line, "default_number_of_months");
      $default_number_of_months = 1 if ($default_number_of_months eq "");
       
      my ($max_number_of_months) = &xml_quick_extract($line, "max_number_of_months");
      $max_number_of_months = 24 if ($max_number_of_months eq "");
      
      my ($gmtime_diff) = &xml_quick_extract($line, "gmtime_diff");
      $gmtime_diff = 0 if ($gmtime_diff eq "");
      
      my ($date_format) = &xml_quick_extract($line, "date_format");
      $date_format = &decode($date_format);
      $date_format = "mm/dd/yy" if ($date_format eq "");
      
      my ($week_start_day) = &xml_quick_extract($line, "week_start_day");
      $week_start_day = "0" if ($week_start_day eq "");
            
      my ($info_window_size) = &xml_quick_extract($line, "info_window_size");
      $info_window_size = "400x400" if ($info_window_size eq "");
      
      my ($custom_template) = &xml_quick_extract($line, "custom_template");
      $custom_template = &decode($custom_template);
      
      my ($custom_stylesheet) = &xml_quick_extract($line, "custom_stylesheet");
      $custom_stylesheet = &decode($custom_stylesheet);
      
      if ($cal_id > $max_new_cal_id)
      {
        $max_new_cal_id = $cal_id;
      }
    
      $new_calendars{$cal_id} = {id => $cal_id, 
                            title => $cal_title, 
                            details => $cal_details,
                            link => $cal_link,
                            local_background_calendars => \%local_background_calendars,
                            selectable_calendars => \%selectable_calendars,
                            list_background_calendars_together => $list_background_calendars_together,
                            background_events_display_style => $background_events_display_style,
                            background_events_fade_factor => $background_events_fade_factor,
                            background_events_color => $background_events_color,
                            default_number_of_months => $default_number_of_months,
                            max_number_of_months => $max_number_of_months,
                            gmtime_diff => $gmtime_diff,
                            date_format => $date_format,
                            week_start_day => $week_start_day,
                            info_window_size => $info_window_size,
                            custom_template => $custom_template,
                            custom_stylesheet => $custom_stylesheet,
                            password => $cal_password,
                            update_timestamp => $update_timestamp};
                            
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_new_cal_id = $cal_id;
      }
    }
    
    %latest_new_cal = %{$new_calendars{$latest_new_cal_id}};
  }
  elsif ($data_storage_mode == 1 ) # SQL database
  {
  
    my $query_string="select * from  $new_calendars_table;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "Error loading new calendars!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }

    while(@row = $sth->fetchrow_array) 
    {
      my $cal_id = $row[0];
      my $line = $row[1];
      
      #$debug_info .= "cal id: $cal_id\n";
    
      my ($cal_title) = &xml_quick_extract($line, "title");
      $cal_title = &decode($cal_title);
      
      my ($cal_details) = &xml_quick_extract($line, "details");
      $cal_details = &decode($cal_details);
      
      my ($cal_link) = &xml_quick_extract($line, "link");
      $cal_link = &decode($cal_link);
      
      my ($cal_password) = &xml_quick_extract($line, "admin_password");
      $cal_password = &decode($cal_password);
      
      my $update_timestamp=0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");
      
      # extract background calendars
      my @temp = &xml_quick_extract($line, "background_calendar");
      my %local_background_calendars;
      my $num_background_calendars = scalar @temp;
      foreach $background_calendar (@temp)
      {
        my ($type) = &xml_quick_extract($background_calendar, "type");
        $type = &decode($type);
        
        if ($type eq "plans_local")
        {
          my ($id) = &xml_quick_extract($background_calendar, "id");
          $local_background_calendars{$id} = 1;
        }
      }
      
      # extract selectable calendars
      my %selectable_calendars;
      @temp = &xml_quick_extract($line, "selectable_calendar");
      foreach $selectable_calendar (@temp)
      {
        $selectable_calendars{$selectable_calendar} = 1;
      }
      
      my ($list_background_calendars_together) = &xml_quick_extract($line, "list_background_calendars_together");
      $list_background_calendars_together = "no" if ($list_background_calendars_together eq "");
      
      my ($background_events_display_style) = &xml_quick_extract($line, "background_events_display_style");
      $background_events_display_style = "normal" if ($background_events_display_style eq "");
      
      my ($background_events_fade_factor) = &xml_quick_extract($line, "background_events_fade_factor");
      $background_events_fade_factor = 1 if ($background_events_fade_factor eq "" || $background_events_fade_factor < 1);
      
      my ($background_events_color) = &xml_quick_extract($line, "background_events_color");
      $background_events_color = &decode($background_events_color);
      $background_events_color = "#ffffff" if ($background_events_color eq "");

      my ($default_number_of_months) = &xml_quick_extract($line, "default_number_of_months");
      $default_number_of_months = 1 if ($default_number_of_months eq "");
       
      my ($max_number_of_months) = &xml_quick_extract($line, "max_number_of_months");
      $max_number_of_months = 24 if ($max_number_of_months eq "");
      
      my ($gmtime_diff) = &xml_quick_extract($line, "gmtime_diff");
      $gmtime_diff = 0 if ($gmtime_diff eq "");
      
      my ($date_format) = &xml_quick_extract($line, "date_format");
      $date_format = &decode($date_format);
      $date_format = "mm/dd/yy" if ($date_format eq "");
      
      my ($week_start_day) = &xml_quick_extract($line, "week_start_day");
      $week_start_day = "0" if ($week_start_day eq "");
            
      my ($info_window_size) = &xml_quick_extract($line, "info_window_size");
      $info_window_size = "400x400" if ($info_window_size eq "");
      
      my ($custom_template) = &xml_quick_extract($line, "custom_template");
      $custom_template = &decode($custom_template);
      
      my ($custom_stylesheet) = &xml_quick_extract($line, "custom_stylesheet");
      $custom_stylesheet = &decode($custom_stylesheet);
      
      if ($cal_id > $max_cal_id)
      {
        $max_new_cal_id = $cal_id;
      }
    
      $new_calendars{$cal_id} = {id => $cal_id, 
                            title => $cal_title, 
                            details => $cal_details,
                            link => $cal_link,
                            local_background_calendars => \%local_background_calendars,
                            selectable_calendars => \%selectable_calendars,
                            list_background_calendars_together => $list_background_calendars_together,
                            background_events_display_style => $background_events_display_style,
                            background_events_fade_factor => $background_events_fade_factor,
                            background_events_color => $background_events_color,
                            default_number_of_months => $default_number_of_months,
                            max_number_of_months => $max_number_of_months,
                            gmtime_diff => $gmtime_diff,
                            date_format => $date_format,
                            week_start_day => $week_start_day,
                            info_window_size => $info_window_size,
                            custom_template => $custom_template,
                            custom_stylesheet => $custom_stylesheet,
                            password => $cal_password,
                            update_timestamp => $update_timestamp};
        
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_new_cal_id = $cal_id;
      }
    }
    
    $sth->finish();
  }
  %latest_new_cal = %{$new_calendars{$latest_new_cal_id}};
}




sub load_events()
{
  # load events for a given number of calendars, within a given time range.
  my ($start, $end, $temp) = @_;
  my @calendar_ids = @{$temp};

  #$debug_info .="start: $start\n";
  #$debug_info .="end: $end\n";

  if ($data_storage_mode == 0 )  # flat text files
  {
    open (FH, "$events_file") || {$debug_info.= "unable to open file $events_file\n"};
    flock FH,2;
    my @event_lines=<FH>;
    close FH;
    
    my $max_update_timestamp = 0;
    my $latest_event_id = 0;
    
    my $event_loaded_count = 0;
    foreach $line (@event_lines)
    {
      my $temp_line = substr($line,0,120);     # grab first 180 characters
      $temp_line =~ s/<title.+//;                # remove everything afer evt_label
      $temp_line =~ s/<event>//;                     # remove <event>
      
      $temp_line =~ /<id>(\d+)/;
      my $evt_id = $1;
      $temp_line =~ /<cal_id>(\d+)/;
      my $temp_cal_id = $1;
      $temp_line =~ /<start>(\d+)/;
      my $temp_start_timestamp = $1;
      $temp_line =~ /<end>(\d+)/;
      my $temp_end_timestamp = $1;
        
      my $cal_valid=0;
      foreach $cal_id (@calendar_ids)
      {
        if ($temp_cal_id == $cal_id)
          {$cal_valid=1;}
      }
      if ($cal_valid == 0 && $start ne "all") {next;}   # event on some other calendar that we don't care about
      
      if ($temp_end_timestamp < $start && $start ne "all") {next;}  # in the past
      if ($temp_start_timestamp > $end && $start ne "all") {next;}  # in the future
      
      $event_loaded_count++;     
      
      $line =~ s/<\/?event>//g;      # remove <event> and </event>

      #my ($evt_id) = &xml_quick_extract($line, "id");
      
      my ($cal_id) = &xml_quick_extract($line, "cal_id");
      $cal_id = &decode($cal_id);
      
      my ($evt_start) = &xml_quick_extract($line, "start");
      #$evt_start = &decode($evt_start);
      
      my ($evt_end) = &xml_quick_extract($line, "end");

      my ($evt_title) = &xml_quick_extract($line, "title");
      $evt_title = &decode($evt_title);
      
      my ($evt_details) = &xml_quick_extract($line, "details");
      $evt_details = &decode($evt_details);
      
      my ($evt_icon) = &xml_quick_extract($line, "icon");
      $evt_icon = &decode($evt_icon);
      
      my ($evt_bgcolor) = &xml_quick_extract($line, "bgcolor");
      $evt_bgcolor = &decode($evt_bgcolor);
      
      my ($evt_unit_number) = &xml_quick_extract($line, "unit_number");
      $evt_unit_number = &decode($evt_unit_number);
      
      my $update_timestamp = 0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");

      my $evt_days = int(($evt_end - $evt_start)/86400)+1;

      $events{$evt_id} = {id => $evt_id, 
                          cal_id => $cal_id, 
                          start => $evt_start, 
                          end => $evt_end, 
                          days => $evt_days, 
                          title => $evt_title, 
                          details => $evt_details,
                          icon => $evt_icon,
                          bgcolor => $evt_bgcolor,
                          unit_number => $evt_unit_number,
                          update_timestamp => $update_timestamp};
    
      if ($evt_id > $max_event_id)
        {$max_event_id = $evt_id;}
      
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_event_id = $evt_id;
      }
    }
    #$debug_info .= "$event_loaded_count events total.\n";
    #$debug_info .= "loaded event $evt_id\n";
          
   %latest_event = %{$events{$latest_event_id}};

    
  }
  elsif ($data_storage_mode == 1 ) # SQL database
  {
    my $query_string;
    if ($start eq "all") 
    {
      $query_string="select * from $events_table;";
    }
    else
    {
      $query_string="select * from $events_table where (start > $start and end < $end )";
    
      if ($calendar_ids[0] ne "" && $calendar_ids[0] !~ /\D/)
      {
        $query_string .= " and ( cal_id=$calendar_ids[0]";
        for ($l1=1;$l1<scalar @calendar_ids;$l1++)
        {
          if ($calendar_ids[$l1] ne "" && $calendar_ids[$l1] !~ /\D/)
            {$query_string .= " or cal_id=$calendar_ids[$l1]";}
        }
        $query_string .= ")";
      }
      
      $query_string .= ";";
    }
    
    
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "Error loading events!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }

    while(@row = $sth->fetchrow_array) 
    {
      my $evt_id = $row[0];
      my $temp_cal_id = $row[1];
      #my $temp_start_timestamp = $row[2];
      #$temp_line =~ /<end>(\d+)/;
      #my $temp_end_timestamp = $row[3];
        
      my $cal_valid=0;
      foreach $cal_id (@calendar_ids)
      {
        if ($temp_cal_id == $cal_id)
          {$cal_valid=1;}
      }
      
      my $line = $row[4];
      $line  =~ s/<\/?event>//g;      # remove <event> and </event>

      #my ($evt_id) = &xml_quick_extract($line, "id");
      
      my ($cal_id) = &xml_quick_extract($line, "cal_id");
      $cal_id = &decode($cal_id);
      
      my ($evt_start) = &xml_quick_extract($line, "start");
      #$evt_start = &decode($evt_start);
      
      my ($evt_end) = &xml_quick_extract($line, "end");

      my ($evt_title) = &xml_quick_extract($line, "title");
      $evt_title = &decode($evt_title);
      
      my ($evt_details) = &xml_quick_extract($line, "details");
      $evt_details = &decode($evt_details);
      
      my ($evt_icon) = &xml_quick_extract($line, "icon");
      $evt_icon = &decode($evt_icon);
      
      my ($evt_bgcolor) = &xml_quick_extract($line, "bgcolor");
      $evt_bgcolor = &decode($evt_bgcolor);
      
      my ($evt_unit_number) = &xml_quick_extract($line, "unit_number");
      $evt_unit_number = &decode($evt_unit_number);
      
      my $update_timestamp = 0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");

      my $evt_days = int(($evt_end - $evt_start)/86400)+1;

      $events{$evt_id} = {id => $evt_id, 
                          cal_id => $cal_id, 
                          start => $evt_start, 
                          end => $evt_end, 
                          days => $evt_days, 
                          title => $evt_title, 
                          details => $evt_details,
                          icon => $evt_icon,
                          bgcolor => $evt_bgcolor,
                          unit_number => $evt_unit_number,
                          update_timestamp => $update_timestamp};
    }  
    # get max event id
    my $query_string="select max(id) from $events_table;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "$dbh->errstr\n";
      $debug_info .= "query string:\n$query_string\n";
    }
    $max_event_id = $sth->fetchrow_array;
    
    # get latest event
    my $query_string="select * from $events_table order by update_timestamp desc limit 0, 1;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "$dbh->errstr\n";
      $debug_info .= "query string:\n$query_string\n";
    }
    while(@row = $sth->fetchrow_array) 
    {
      my $evt_id = $row[0];
      my $temp_cal_id = $row[1];
      
      my $line = $row[4];
      $line  =~ s/<\/?event>//g;      # remove <event> and </event>

      my ($cal_id) = &xml_quick_extract($line, "cal_id");
      $cal_id = &decode($cal_id);
      
      my ($evt_start) = &xml_quick_extract($line, "start");
      
      my ($evt_end) = &xml_quick_extract($line, "end");

      my ($evt_title) = &xml_quick_extract($line, "title");
      $evt_title = &decode($evt_title);
      
      my ($evt_details) = &xml_quick_extract($line, "details");
      $evt_details = &decode($evt_details);
      
      my ($evt_icon) = &xml_quick_extract($line, "icon");
      $evt_icon = &decode($evt_icon);
      
      my ($evt_bgcolor) = &xml_quick_extract($line, "bgcolor");
      $evt_bgcolor = &decode($evt_bgcolor);
      
      my ($evt_unit_number) = &xml_quick_extract($line, "unit_number");
      $evt_unit_number = &decode($evt_unit_number);
      
      my $update_timestamp = 0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");

      my $evt_days = int(($evt_end - $evt_start)/86400)+1;

      $latest_event_id = $evt_id;
      %latest_event = (id => $evt_id, 
                       cal_id => $cal_id, 
                       start => $evt_start, 
                       end => $evt_end, 
                       days => $evt_days, 
                       title => $evt_title, 
                       details => $evt_details,
                       icon => $evt_icon,
                       bgcolor => $evt_bgcolor,
                       unit_number => $evt_unit_number,
                       update_timestamp => $update_timestamp);
    }
  }
}

sub load_event()
{
  # load events for a given number of calendars, within a given time range.
  my ($event_id) = @_;

  if ($data_storage_mode == 0 )  # flat text files
  {
    open (FH, "$events_file") || {$debug_info.= "unable to open file $events_file\n"};
    flock FH,2;
    my @event_lines=<FH>;
    close FH;
    
    my $max_update_timestamp = 0;
    my $latest_event_id = 0;
    
    my $event_loaded_count = 0;
    foreach $line (@event_lines)
    {
      my $temp_line = substr($line,0,120);     # grab first 180 characters
      $temp_line =~ s/<title.+//;              # remove everything afer evt_label
      $temp_line =~ s/<event>//;               # remove <event>
      
      $temp_line =~ /<id>(\d+)/;
      my $evt_id = $1;
      
      if ($evt_id != $event_id) {next;}   # event on some other calendar that we don't care about
      
      $line =~ s/<\/?event>//g;      # remove <event> and </event>

      #my ($evt_id) = &xml_quick_extract($line, "id");
      
      my ($cal_id) = &xml_quick_extract($line, "cal_id");
      $cal_id = &decode($cal_id);
      
      my ($evt_start) = &xml_quick_extract($line, "start");
      #$evt_start = &decode($evt_start);
      
      my ($evt_end) = &xml_quick_extract($line, "end");

      my ($evt_title) = &xml_quick_extract($line, "title");
      $evt_title = &decode($evt_title);
      
      my ($evt_details) = &xml_quick_extract($line, "details");
      $evt_details = &decode($evt_details);
      
      my ($evt_icon) = &xml_quick_extract($line, "icon");
      $evt_icon = &decode($evt_icon);
      
      my ($evt_bgcolor) = &xml_quick_extract($line, "bgcolor");
      $evt_bgcolor = &decode($evt_bgcolor);
      
      my ($evt_unit_number) = &xml_quick_extract($line, "unit_number");
      $evt_unit_number = &decode($evt_unit_number);
      
      my $update_timestamp = 0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");

      my $evt_days = int(($evt_end - $evt_start)/86400)+1;

      $events{$evt_id} = {id => $evt_id, 
                          cal_id => $cal_id, 
                          start => $evt_start, 
                          end => $evt_end, 
                          days => $evt_days, 
                          title => $evt_title, 
                          details => $evt_details,
                          icon => $evt_icon,
                          bgcolor => $evt_bgcolor,
                          unit_number => $evt_unit_number,
                          update_timestamp => $update_timestamp};
    
    
      if ($evt_id > $max_event_id)
        {$max_event_id = $evt_id;}
      
      if ($update_timestamp > $max_update_timestamp)
      {
        $max_update_timestamp = $update_timestamp;
        $latest_event_id = $evt_id;
      }
    }
  }
  elsif ($data_storage_mode == 1 ) # SQL database
  {
    my $query_string;
    $query_string="select * from $events_table where (id = $event_id);";
    
    
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "Error loading events!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }

    while(@row = $sth->fetchrow_array) 
    {
      my $evt_id = $row[0];
      my $temp_cal_id = $row[1];
      
      my $line = $row[4];
      $line  =~ s/<\/?event>//g;      # remove <event> and </event>
      
      my ($cal_id) = &xml_quick_extract($line, "cal_id");
      $cal_id = &decode($cal_id);
      
      my ($evt_start) = &xml_quick_extract($line, "start");
      
      my ($evt_end) = &xml_quick_extract($line, "end");

      my ($evt_title) = &xml_quick_extract($line, "title");
      $evt_title = &decode($evt_title);
      
      my ($evt_details) = &xml_quick_extract($line, "details");
      $evt_details = &decode($evt_details);
      
      my ($evt_icon) = &xml_quick_extract($line, "icon");
      $evt_icon = &decode($evt_icon);
      
      my ($evt_bgcolor) = &xml_quick_extract($line, "bgcolor");
      $evt_bgcolor = &decode($evt_bgcolor);
      
      my ($evt_unit_number) = &xml_quick_extract($line, "unit_number");
      $evt_unit_number = &decode($evt_unit_number);
      
      my $update_timestamp = 0;
      ($update_timestamp) = &xml_quick_extract($line, "update_timestamp");
      $update_timestamp = 0 if ($update_timestamp eq "");

      my $evt_days = int(($evt_end - $evt_start)/86400)+1;

      $events{$evt_id} = {id => $evt_id, 
                          cal_id => $cal_id, 
                          start => $evt_start, 
                          end => $evt_end, 
                          days => $evt_days, 
                          title => $evt_title, 
                          details => $evt_details,
                          icon => $evt_icon,
                          bgcolor => $evt_bgcolor,
                          unit_number => $evt_unit_number,
                          update_timestamp => $update_timestamp};
    }  
  }
}


# add an event to the data file
sub add_event()
{
  my ($event_id) = @_;
  # temporary copy of the event in question
  my %temp_event = %{$events{$event_id}};

  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    my $event_xml .= &event2xml($events{$event_id})."\n";
    $event_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;
    
    
    open (FH, ">>$events_file") || {$debug_info .= "unable to open file $events_file for writing!\n"};
    flock FH,2;
    print FH $event_xml;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $event_xml = &event2xml(\%temp_event);
  
    my $query_string="insert into $events_table (id, cal_id, start, end, xml_data, update_timestamp) values ($temp_event{id}, $temp_event{cal_id}, $temp_event{start}, $temp_event{end}, '$event_xml', $temp_event{update_timestamp});";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $fatal_error = 1;
      $debug_info .= "Error adding event!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }
    $sth->finish();
  }
}

# add multiple events to the data file
sub add_events()
{
  my ($event_ids_ref) = @_;
  
  my @event_ids = @{$event_ids_ref};
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    foreach $id (sort {$a <=> $b} @event_ids)
    {
      $out_text .= &event2xml($events{$id})."\n";
    }
    
    open (FH, ">>$events_file") || {$debug_info .= "unable to open file $events_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    foreach $id (@event_ids)
    {
      my %temp_event = %{$events{$id}};
      my $event_xml = &event2xml(\%temp_event);
  
      my $query_string="insert into $events_table (id, cal_id, start, end, xml_data, update_timestamp) values ($temp_event{id}, $temp_event{cal_id}, $temp_event{start}, $temp_event{end}, '$event_xml', $temp_event{update_timestamp});";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $fatal_error = 1;
        $debug_info .= "Error adding event!\n".$dbh->errstr."\n";
        $debug_info .= "query string:\n$query_string\n";
      }
      $sth->finish();
    }
  }
}

# update an event (already present in the data file)
sub update_event()
{
  my ($event_id) = @_;
  
  # temporary copy of the event in question
  my %temp_event = %{$events{$event_id}};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    foreach $id (sort {$a <=> $b} keys %events)
    {
      my $event_xml = &event2xml($events{$id})."\n";
      $out_text .= $event_xml;
      #if ($id eq $event_id)
      #  {$event_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
    }
    open (FH, ">$events_file") || {$debug_info .= "unable to open file $events_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $event_xml = &event2xml(\%temp_event);
  
    my $query_string="update $events_table set cal_id=$temp_event{cal_id}, start=$temp_event{start}, end=$temp_event{end}, xml_data='$event_xml', update_timestamp=$temp_event{update_timestamp} where id=$temp_event{id};";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $fatal_error = 1;
      $debug_info .= "Error updating event!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }
    $sth->finish();
  }
}


# delete an event
sub delete_event()
{
  my ($event_id) = @_;
  if ($data_storage_mode == 0 )  # flat text files
  {
    delete $events{$event_id};
    my $out_text="";
    foreach $id (sort {$a <=> $b} keys%events)
      {$out_text .= &event2xml($events{$id})."\n";}
    open (FH, ">$events_file") || {$debug_info .= "unable to open file $events_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $query_string="delete from $events_table where id=$event_id;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $fatal_error = 1;
      $debug_info .= "Error delteing event!\n".$dbh->errstr."\n";
      $debug_info .= "query string:\n$query_string\n";
    }
    $sth->finish();
  }
}

# delete multiple events
sub delete_events()
{
  my ($event_ids_ref) = @_;
  my @event_ids = @{$event_ids_ref};
  
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    foreach $event_id (@event_ids)
      {delete $events{$event_id};}

    delete $events{$event_id};
    my $out_text="";
    foreach $id (sort {$a <=> $b} keys%events)
      {$out_text .= &event2xml($events{$id})."\n";}
    open (FH, ">$events_file") || {$debug_info .= "unable to open file $events_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    foreach $event_id (@event_ids)
    {   
      my $query_string="delete from $events_table where id=$event_id;";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $fatal_error = 1;
        $debug_info .= "Error deleting event!\n".$dbh->errstr."\n";
        $debug_info .= "query string:\n$query_string\n";
      }
      $sth->finish();
    }
  }
}

sub add_new_calendar()
{
  my ($cal_id) = @_;
  #$debug_info .= "adding new calendar $cal_id\n";
  if ($data_storage_mode == 0 )  # flat text files
  {
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $id (sort {$a <=> $b} keys %new_calendars)
    {
      #$debug_info .= "calendar2xml $id\n";
      my $cal_xml = &calendar2xml($new_calendars{$id})."\n";
      #if ($id eq $cal_id)
      #  {$cal_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
      $out_text .= $cal_xml;
    }

    open (FH, ">$new_calendars_file") || {$debug_info.= "unable to open file $new_calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;

  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $cal_xml = &calendar2xml($new_calendars{$cal_id})."\n";
    
    # add the primary calendar to the table
    my $query_string="insert into $new_calendars_table (id, xml_data, update_timestamp) values ($cal_id, '$cal_xml', $new_calendars{$cal_id}{update_timestamp});";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $fatal_error = 1;
      $error_info .= "Error adding new calendar!\n".$dbh->errstr."\n";
      $error_info .= "$query_string\n";
    }
  }
}

sub add_new_calendars()  # add multiple calendars (this is used for data conversion)
{
  my ($add_new_cal_ids_ref) = @_;
  my @add_new_cal_ids = @{$add_new_cal_ids_ref};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    foreach $new_cal_id (@add_new_cal_ids)
    {
      my $new_cal_xml = &calendar2xml($new_calendars{$new_cal_id});
      my $query_string="insert into $calendars_table (id, xml_data, update_timestamp) values ($new_cal_id, '$new_cal_xml', $new_calendars{$new_cal_id}{update_timestamp});";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $debug_info .= "Error adding new calendar!\n".$dbh->errstr."\n";
        $debug_info .= "$query_string\n";
      }
    }
  }
}

sub update_new_calendars()  # this is called after a record is trandferred from new_calendars to calendars
{
  my ($temp1, $temp2) = @_;
  
  my @pending_calendars_to_delete = @{$temp1};
  my @calendars_to_add = @{$temp2};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %new_calendars)
    {
      $out_text .= &calendar2xml($new_calendars{$calendar_id})."\n";
    }
    
    open (FH, ">$new_calendars_file") || {$html_output.= "unable to open file $new_calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
    
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %calendars)
    {
      $out_text .= &calendar2xml($calendars{$calendar_id})."\n";
    }
    
    open (FH, ">$calendars_file") || {$html_output.= "unable to open file $calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
  
    foreach $cal_id (@calendars_to_add)
    {
      my $cal_xml = &calendar2xml($calendars{$cal_id});
      
      my $query_string="insert into $calendars_table (id, xml_data, update_timestamp) values ($cal_id, '$cal_xml', $calendars{$cal_id}{update_timestamp});";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $debug_info .= "Error approving new calendar!\n".$dbh->errstr."\n";
        $debug_info .= "$query_string\n";
      }
    }
    foreach $cal_id (@pending_calendars_to_delete)
    {
      my $query_string="delete from $new_calendars_table where id=$cal_id;";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $debug_info .= "Error deleting pending calendar after approval!\n".$dbh->errstr."\n";
        $debug_info .= "$query_string\n";
      }
    }
  }
}

sub add_calendars()  # add multiple calendars (this is used for data conversion)
{
  my ($add_cal_ids_ref) = @_;
  my @add_cal_ids = @{$add_cal_ids_ref};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %calendars)
    {
      my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
      $out_text .= $cal_xml;
    }
    
    open (FH, ">$calendars_file") || {$debug_info.= "unable to open file $calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    foreach $cal_id (@add_cal_ids)
    {
      my $cal_xml = &calendar2xml($calendars{$cal_id});
      my $query_string="insert into $calendars_table (id, xml_data, update_timestamp) values ($cal_id, '$cal_xml', $calendars{$cal_id}{update_timestamp});";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $debug_info .= "Error adding calendar!\n".$dbh->errstr."\n";
        $debug_info .= "$query_string\n";
      }
    }
  }
}




sub update_calendar()
{
  my ($cal_id) = @_;
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %calendars)
    {
      my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
      $out_text .= $cal_xml;
    }
    
    open (FH, ">$calendars_file") || {$debug_info.= "unable to open file $calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    my $cal_xml = &calendar2xml($calendars{$cal_id});
 
    # add the primary calendar to the table
    my $query_string="update $calendars_table set xml_data='$cal_xml', update_timestamp=$calendars{$cal_id}{update_timestamp} where id=$cal_id;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $fatal_error = 1;
      $error_info .= "Error updating calendar!\n".$dbh->errstr."\n";
      $error_info .= "$query_string\n";
    }
  }
}


sub update_calendars()  # update multiple calendars
{
  my ($update_cal_ids_ref) = @_;
  my @update_cal_ids = @{$update_cal_ids_ref};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text="";
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %calendars)
    {
      my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
      #foreach $update_cal_id (@update_cal_ids)
      #{
      #  if ($update_cal_id eq $calendar_id)
      #    {$cal_xml =~ s/(<update_timestamp>)\d*(<\/update_timestamp>)/$1$rightnow$2/;}
      #}
      
      $out_text .= $cal_xml;
    }
    
    open (FH, ">$calendars_file") || {$debug_info.= "unable to open file $calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {
    foreach $cal_id (@update_cal_ids)
    {
      my $cal_xml = &calendar2xml($calendars{$cal_id});
      my $query_string="update $calendars_table set xml_data='$cal_xml', update_timestamp=$calendars{$cal_id}{update_timestamp} where id=$cal_id;";
      my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
      my $rv = $sth->execute();
      if ($dbh->errstr ne "")
      {
        $debug_info .= "Error updating calendar!\n".$dbh->errstr."\n";
        $debug_info .= "$query_string\n";
      }
    }
  }
}


sub delete_calendar()
{
  my ($cal_id) = @_;
  
  delete $calendars{$cal_id};
  
  if ($data_storage_mode == 0 )  # flat text files
  {
    my $out_text ="";
    # write out the entire file!  Grossly inefficient, but that's how it goes if you don't use a DB.
    foreach $calendar_id (sort {$a <=> $b} keys %calendars)
    {
      my $cal_xml = &calendar2xml($calendars{$calendar_id})."\n";
      $out_text .= $cal_xml;
    }
    
    open (FH, ">$calendars_file") || {$debug_info.= "unable to open file $calendars_file for writing!\n"};
    flock FH,2;
    print FH $out_text;
    close FH;
  }
  elsif ($data_storage_mode == 1 )  # DBI
  {      
    my $query_string="delete from $calendars_table where id=$cal_id;";
    my $sth = $dbh->prepare($query_string) or ($error_info .= "Can't prepare $query_string:\n");
    my $rv = $sth->execute();
    if ($dbh->errstr ne "")
    {
      $debug_info .= "Error deleting calendar!\n".$dbh->errstr."\n";
      $debug_info .= "$query_string\n";
    }

  }
}



sub calendar2xml()
{
   my ($calendar_ref) = @_;
   
   my %calendar = %{$calendar_ref};
   
   #$error_info .= "Calendar title: $calendar{title}\n";
   
   my $xml_data = "<calendar>";
   $xml_data .= &xml_store($calendar{id}, "id");
   $xml_data .= &xml_store($calendar{title}, "title");
   $xml_data .= &xml_store($calendar{details}, "details");
   $xml_data .= &xml_store($calendar{link}, "link");
   $xml_data .= &xml_store($calendar{password}, "admin_password");
   
  # add local background calendars            
  foreach $local_background_calendar (sort {$a <=> $b} keys %{$calendar{local_background_calendars}})
    {$xml_data .= "<background_calendar><type>plans_local</type><id>$local_background_calendar</id></background_calendar>";}
    
  # add selectable calendars            
  foreach $selectable_calendar (sort {$a <=> $b} keys %{$calendar{selectable_calendars}})
    {$xml_data .= "<selectable_calendar>$selectable_calendar</selectable_calendar>";}
    
  # add other fields            
  $xml_data .= &xml_store($calendar{list_background_calendars_together}, "list_background_calendars_together");
  $xml_data .= &xml_store($calendar{background_events_display_style}, "background_events_display_style");
  $xml_data .= &xml_store($calendar{background_events_fade_factor}, "background_events_fade_factor");
  $xml_data .= &xml_store($calendar{background_events_color}, "background_events_color");
   
  $xml_data .= &xml_store($calendar{default_number_of_months}, "default_number_of_months");
  $xml_data .= &xml_store($calendar{max_number_of_months}, "max_number_of_months");
  $xml_data .= &xml_store($calendar{gmtime_diff}, "gmtime_diff");
  $xml_data .= &xml_store($calendar{date_format}, "date_format");
  $xml_data .= &xml_store($calendar{week_start_day}, "week_start_day");
  $xml_data .= &xml_store($calendar{info_window_size}, "info_window_size");
  $xml_data .= &xml_store($calendar{custom_template}, "custom_template");
  $xml_data .= &xml_store($calendar{custom_stylesheet}, "custom_stylesheet");
  $xml_data .= &xml_store($calendar{update_timestamp}, "update_timestamp");
  
  $xml_data .= "</calendar>";

  return $xml_data;
}


sub event2xml()
{
  my ($event_ref) = @_;
  my %event = %{$event_ref};

  my $xml_data = "<event>";
  $xml_data .= &xml_store($event{id}, "id");
  $xml_data .= &xml_store($event{cal_id}, "cal_id");
  $xml_data .= &xml_store($event{start}, "start");
  $xml_data .= &xml_store($event{end}, "end");
  $xml_data .= &xml_store($event{title}, "title");
  $xml_data .= &xml_store($event{details}, "details");
  $xml_data .= &xml_store($event{icon}, "icon");
  $xml_data .= &xml_store($event{bgcolor}, "bgcolor");
  $xml_data .= &xml_store($event{unit_number}, "unit_number");
  $xml_data .= &xml_store($event{update_timestamp}, "update_timestamp");
  $xml_data .= "</event>";
  return $xml_data;
}

sub find_end_of_month
{
  my ($month, $year) = @_;

  my $next_month = $month+1;
  if ($next_month > 11) 
  {
    $next_month=0;
    $year++;
  }
  my $month_end_timestamp = timegm(0,0,0,1,$next_month,$year);
  
  return $month_end_timestamp;

}

sub xml_store
{
  my ($data_string, $tag_name) = @_;
  my $result_string = "";
  $data_string = &encode($data_string);
  my $result_string = "<$tag_name>$data_string</$tag_name>";
  return $result_string;
}

sub xml_quick_extract   # it doesn't get any dumber than this.  ignores attributes, element order, fooled by duplicate tag names at different depths.
{
  my ($data, $tag_name) = @_;
  my @results_array = ();
                           
  while  ($data =~ /<$tag_name>(.+?)<\/$tag_name>/g)
  {
    push @results_array, $1;
  }
  return @results_array;
}

sub xml_extract    # Slow, but can handle attributes, element order, same tag names at different depths. Can't handle encodings, DTDs.
{
  my ($data, $tag_name, $debug) = @_;
  my @results_array = ();
  my $results = "";
  my $final_results = "";
  
  my $depth_count=0;
  my $start_index=0;
  my $end_index=0;
  my $match_index=0;

  my $attributes=();
  my $position=0;          # position is the position of the element we're looking for, with respect to all other elements
                           # under the parent element
                           
  while  ($data =~ /(<.*?>|<\/.*?>)/g)
  {
    my $match=$1;
    my $temp_index = $+[1];
    if ($match =~ /<$tag_name\b.*?>/ && $depth_count==0)  # the opening tag we're looking for
    {
      $start_index = $temp_index;
      $depth_count++;
      if ($debug) {$debug_info .= "active opening tag, $match \ndepth count $depth_count\n";}
      if ($debug) {$debug_info .= "start index $start_index\n";}
      
      my $attribute_text = $match;
      $attribute_text =~ s/\s*=\s*/=/g;                # compress whitespace on either side of = sign
      $attribute_text =~ s/=([^"])(.+?\b)/="$1$2"/g;   # properly format attributes with quote marks
     
      #if ($debug) {$debug_info .= "rejiggered attribute text: $attribute_text\n";}
      
      # extract attributes
      while ($attribute_text =~ /\w+?=".+?[^\\]"/g)
      {
        my $a_match = $&;
        my ($name, $value)= split('=',$a_match);

        $value =~ s/\\"/"/g;
        # remove first and last characters (the quotes) from value
        $value = substr $value, 1,-1;
        
        if ($debug) {$debug_info .= "attribute: $a_match\n";}
        if ($debug) {$debug_info .= " name: $name\n";}
        if ($debug) {$debug_info .= " value: $value\n";}
        
        $attributes->{$name} = $value;
      }
      #%attributes=();
      #$debug_info .= "end position, $+[0]\n\n";
    }
    elsif ($match =~ /<[^\/].*?>/)  # some other opening tag
    {
      
      $depth_count++;
      if ($debug) {$debug_info .= "other opening tag, $match \ndepth count $depth_count\n";}
    }
    elsif ($match eq "<\/$tag_name>" && $depth_count == 1)  # the closing tag we're looking for
    {
      $depth_count--;
      if ($debug) {$debug_info .= "active closing tag, $match \ndepth count $depth_count\n";}
      if ($depth_count==0) # done!  return results
      {
        $end_index = $-[0];
        $results = substr $data, $start_index,($end_index-$start_index);
        
        my $results_hash=();
        $results_hash -> {data} = $results;
        $results_hash -> {attributes} = $attributes;
        $results_hash -> {position} = $position;
        push @results_array, $results_hash;
        
        if ($debug) {$debug_info .= "  pushing results: \"$results\" onto array\n";}
        if ($debug) {$debug_info .= "  attributes: \"$attributes\" \n";}
        if ($debug) {$debug_info .= "  position: \"$position\" \n";}
        if ($debug) {$debug_info .= "  start: $start_index end $end_index\n\n";}
        #if ($debug) {$debug_info .= " $results\n\n";}
        $start_index=0;
        $end_index=0;
        $attributes=();
      }
      #$debug_info .= "closing tag, $1 \ndepth count $depth_count\n";
      #$debug_info .= "start position, $-[0]\n\n";
      $position++;
    }
    else # other closing tag
    {
      $depth_count--;
      if ($depth_count==0)
      {$position++;}
      
      if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
    }
    $match_index++;
  }
  return @results_array;
}   #******************** end xml_extract **********************


sub xml_tags
{
  my ($data, $debug) = @_;
  my @results_array = ();
  my %tags_hash;
  
  my $depth_count=0;
                           
  while  ($data =~ /(<.*?>|<\/.*?>)/g)
  {
    my $match=$1;
    if ($match =~ /<[^\/].*?>/)  # any opening tag
    {
      if ($depth_count == 0)     # level 0 opening tag
      {
        $tag_name = $match;
        $tag_name =~ s/<//;
        $tag_name =~ s/\b(.+)\b.+/$1/;
        if ($debug) {$debug_info .= "level 0 opening tag, $tag_name \n\n";}
      }
      $depth_count++;

    }
    elsif ($depth_count == 1)  # level 1 closing tag
    {
      $tag_name = $match;
      $tag_name =~ s/<//;
      $tag_name =~ s/\/(.+)(\b|>).+/$1/;

      $depth_count--;
      if ($debug) {$debug_info .= "level 1 closing tag, $tag_name \n";}
      $tags_hash{$tag_name}=1;
        
      if ($debug) {$debug_info .= "  pushing tag name $tag_name onto array\n\n";}
    }
    else # other closing tag
    {
      $depth_count--;
      
      if ($debug) {$debug_info .= "other closing tag, $match \ndepth count $depth_count\n";}
    }
  }
  return keys %tags_hash;
  
}  #******************** end xml_tags **********************


sub xml2hash
{
  my ($xml_data, $debug) = @_;
  my $item;

  my @item_tags = &xml_tags($xml_data);
  
  if (scalar @item_tags == 0)
  {
    if ($debug) {$debug_info .= " plain text item data: ($xml_data) \n";}
    
    return $xml_data;
  }
  else
  {
    if ($debug) {$debug_info .= " xml data: ($xml_data) \n";}
    my %results_hash;
    foreach $tag (@item_tags)
    {
      my @tag_data = &xml_extract($xml_data,"$tag");
      
      if (scalar @tag_data == 1)
      {
        if ($debug) {$debug_info .= "  extracting xml for tag $tag (single data)\n";}
        $results_hash{$tag} = &xml2hash($tag_data[0]->{data},$debug);
      }
      else
      {
        if ($debug) {$debug_info .= "  extracting xml for tag $tag (array data)\n";}
        my @tag_array;
        foreach $thing (@tag_data)
        {
          push @tag_array, &xml2hash($thing->{data},$debug);
        }
        $results_hash{$tag}=\@tag_array;
      } 
    }
    if ($debug) {$debug_info .= "\n";}
    return \%results_hash;
  }
}  #******************** end xml2hash **********************




sub get_remote_file
{
  my ($url) = @_;
  $url =~ s/http:\/\///;
  
  my $hostname = $url;
  $hostname =~ s/\/.+//g;

  my $document = $url;
  $document =~ s/.+?\//\//;
  
  #$debug_info .= "url: $url<br>";
  #$debug_info .= "hostname: $hostname<br>";
  #$debug_info .= "document: $document<br>";
    
  if ($hostname eq "" | $document eq "") {return;}

  $remote = IO::Socket::INET->new( Proto     => "tcp",
                                   PeerAddr  => $hostname,
                                   PeerPort  => "http(80)"
                                 );
  unless ($remote) 
  {
    $debug_info .= "cannot connect to http daemon on $hostname <br>";
    return;
  }
  $remote->autoflush(1);
  print $remote "GET $document HTTP/1.0\n";
  print $remote "User-Agent: Mozilla 4.0 (compatible; I; Linux-2.0.35i586)\n";
  
  #without this line, virtual hosts won't work (multiple domain names on a single IP)
  print $remote "Host: $hostname\n";
  
  print $remote "\n\n";

  @textbuffer=<$remote>;
  my $textstring = join "", @textbuffer;
  
  $textstring =~ s/\r//gs;         #some servers sneak these in.
  $textstring =~ s/.+?\n\n//si;
  return $textstring;
}

sub time_overlap
{
  my ($start1, $end1, $start2, $end2) = @_;
  
  my $temp1 = $end2 - $start1;
  my $temp2 = $end1 - $start2;
  
  my $range_total = $end2 - $start2;
  
  #$debug_info .= "temp1:$temp1 temp2:$temp2 range_total:$range_total\n";
  
  
  # if the event falls in or overlaps this week (there are 3 cases), the third being an event
  # that *completely* overlaps the week.
  if ( ($temp1 <= $range_total && $temp1 > 0)  || ($temp2 <= $range_total && $temp2 > 0) || ($temp1 > 0 && $temp2 > 0))
    {return 1;}
  else 
    {return 0;}
}

sub make_email_link
{
  my ($string) = @_;
  my $new_string = "";
  #remove all newlines
  $string =~ s/\n//g;
  
  #insert newlines after > characters
  $string =~ s/</\n</g;
  
  my @lines = split ("\n", $string);
  
  foreach $line (@lines)
  {
    $line .= "\n";
    my $new_line = $line;
    $new_line =~ s/([^ >]+?\@[^ <>]+)/<a href=\"mailto:$1\">$1<\/a>/g;
    
    #ignore substitution if the email address was already a link.
    if ($1 =~ /(:|")/)
      {$new_string .= $line;}
    else
    {
      $new_line =~ s/\n//g;
      $new_string .= $new_line;
    }
  }
  return $new_string;  
}

sub formatted_time
{

  my ($input_time, $format_string) = @_;
  my @input_time_array = gmtime ($input_time+0);
  my $ampm="";
  
  if ($input_time_array[5]<1900) {$input_time_array[5]+=1900;}
  $month_name=$months[$input_time_array[4]];
  $input_time_array[4]++;

  if ($input_time_array[1]<10) {$input_time_array[1]="0".$input_time_array[1];}

  if ($input_time_array[2]>12)  #convert from 24-hour to am/pm
  {
     $input_time_array[2]=$input_time_array[2] - 12;
    $ampm="pm";
  }
  else
  {
    $ampm="am";
  }
  $format_string =~ s/ampm/$ampm[2]/g;  
  $format_string =~ s/hh/$input_time_array[2]/g;
  $format_string =~ s/mm/$input_time_array[1]/g;  
  $format_string =~ s/ss/$input_time_array[0]/g;
  $format_string =~ s/mo/$input_time_array[4]/g;
  $format_string =~ s/mn/$month_name/g;
  $format_string =~ s/md/$input_time_array[3]/g;
  $format_string =~ s/yy/$input_time_array[5]/g;
  return $format_string;
}

sub nice_date_range_format
{
  my ($timestamp1, $timestamp2, $separator_string) = @_;
  my $result_string = "";

  #make sure the timestamps are in the correct order
  if ($timestamp1 > $timestamp2)
  {
    $temp=$timestamp2;
    $timestamp2=$timestamp1;
    $timestamp1=$temp;
  }
  
  my @timestamp1_array = gmtime $timestamp1;
  my @timestamp2_array = gmtime $timestamp2;
  
  #format the year for humans
  $timestamp1_array[5] +=1900;
  $timestamp2_array[5] +=1900;
  
  if (lc $current_cal{date_format} eq "dd/mm/yy")
  {
    if ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5] && $timestamp1_array[3] == $timestamp2_array[3])
    { #same year, same month, same day
      $result_string = " $timestamp1_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]";
    }
    elsif ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5])
    { #same year, same month
      $result_string = "$timestamp1_array[3]$separator_string$timestamp2_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]";
    }
    elsif ($timestamp1_array[5] != $timestamp2_array[5])
    { #different year
      $result_string = "$timestamp1_array[3] $months[$timestamp1_array[4]], $timestamp1_array[5]$separator_string$timestamp2_array[3] $months[$timestamp2_array[4]], $timestamp2_array[5]";
    }
    else 
    { #same year, different months
      $result_string = "$timestamp1_array[3] $months[$timestamp1_array[4]]$separator_string$timestamp2_array[3] $months[$timestamp2_array[4]], $timestamp2_array[5]";
    }
  }
  #elsif (lc $current_cal{date_format} eq "mm/dd/yy")
  else
  {
    if ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5] && $timestamp1_array[3] == $timestamp2_array[3])
    { #same year, same month, same day
      $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3], $timestamp1_array[5]";
    }
    elsif ($timestamp1_array[4] == $timestamp2_array[4] && $timestamp1_array[5] == $timestamp2_array[5])
    { #same year, same month
      $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3]$separator_string$timestamp2_array[3], $timestamp1_array[5]";
    }
    elsif ($timestamp1_array[5] != $timestamp2_array[5])
    { #different year
      $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3], $timestamp1_array[5]$separator_string$months[$timestamp2_array[4]] $timestamp2_array[3], $timestamp2_array[5]";
    }
    else 
    { #same year, different months
      $result_string = "$months[$timestamp1_array[4]] $timestamp1_array[3]$separator_string$months[$timestamp2_array[4]] $timestamp2_array[3], $timestamp2_array[5]";
    }
  }
  
  return $result_string;
}

sub encode
{
  my ($input_string) = @_;
  return if ($input_string eq "");
  my $output_string=$input_string;

  $output_string =~ s/(\W)/"\%".sprintf("%02x", (ord $1))/ge;
  $output_string =~ s/\%20/+/g;
  return $output_string;
}

sub decode
{
  my ($input_string) = @_;
  return if ($input_string eq "");
  my $output_string = $input_string;
  
  $output_string =~ s/\+/ /g;
  $output_string =~ s/%([0-9A-Fa-f]{2})/pack("c",hex($1))/ge;
  return $output_string;
}

sub generate_event_details
{
  my ($event_ref) = @_;
  
  my %event = %{$event_ref};

  my $return_text = $event_details_template;
  my @event_start_timestamp_array = gmtime $event{start};
  
  my $event_cal_name = "$calendars{$event{cal_id}}{title}";
  if ($calendars{$event{cal_id}}{link} =~ /\S/)
  {
    $event_cal_name = "<a target= _blank href=\"http://$calendars{$event{cal_id}}{link}\">$calendars{$event{cal_id}}{title}</a>";
  }
  
  $return_text =~ s/###event calendar name###/$event_cal_name/g;
  
  my $date_string = "Date goes here";
  if ($event{start} ne "")
  {
    $date_string = &nice_date_range_format($event{start}, $event{end}, " - ");
  }
  $return_text =~ s/###event date###/$date_string/g;
  
  
  $return_text =~ s/###event title###/$event{title}/g;
  $return_text =~ s/###event id###/$event{id}/g;
  $return_text =~ s/###event calendar id###/$event{cal_id}/g;
  $return_text =~ s/###event background color###/$event{bgcolor}/g;

  my $event_details = $event{details};
  #replace \n characters with <br> tags
  $event_details =~ s/\n/\n<br>\n/g;
  
  # check the event details, and see if there are any non-htmlified
  # links.  If so, turn them into links.
  $event_details =~ s/[^"](htt.:\/\/.+?),*\.?(\s|\n|<|$)/ <a href=\"$1\">$1<\/a>$2/g;
  
  # convert email addresses to links.
  $event_details = &make_email_link($event_details);

  # make sure all links open up in a new window
  $event_details =~ s/<a/<a target = "blank"/g;
  
  $return_text =~ s/###event details###/$event_details/g;
  
  my $event_icon_text = "";
  if ($event{icon} ne "blank")
  {
    $event_icon_text = "<img style=\"border-width:0px;\" src = \"$icons_path/$event{icon}_50x50.gif\" hspace=2 vspace=1><br>";
  }
  $return_text =~ s/###event icon###/$event_icon_text/g;

  my $unit_number_text = $event{unit_number};
  $unit_number_text =~ s/(\d)/<img src="$graphics_path\/unit_number_patch_$1_40x25.gif" alt="" border="0" vspace=0 hspace=0>/g;
  $return_text =~ s/###unit number icon###/$unit_number_text/g;
  
  my $edit_event_link = "<a href=\"$script_url/$name?active_tab=1&add_edit_event=edit&amp;cal_id=$event{cal_id}&evt_id=$event{id}$consistent_parameter_string\">Edit this event</a>";
  $return_text =~ s/###edit event link###/$edit_event_link/g;

  my $edit_event_link = "<a href=\"javascript:return false;\" onMouseUp =\"opener.active_event_id = $event{id}; opener.delete_event()\">Delete this event</a>";
  $return_text =~ s/###delete event link###/$edit_event_link/g;
  
  my $temp = &export_event_link(\%event);
  $return_text =~ s/###export event link###/$temp/g;

  my $cal_detail_text .= <<p1;
$calendars{$event{cal_id}}{cal_details}
p1
  
  $return_text =~ s/###event calendar details###/$cal_detail_text/g;

  return $return_text;
}

sub export_event_link()
{
  my $results = "";
  my ($event_ref) = @_;
  my %event = %{$event_ref};
  
  $results .=<<p1;
<form name="export_event_form" id="export_event_form" target="_blank" action="$script_url/$name" method=GET>
<input type="submit" value="$lang{export}" style="padding:0px;font-size:x-small;"> $lang{this_event_to}
<input type="hidden" name="export_event" value=1>
<input type="hidden" name="evt_id" value="$event{id}">
<br/>

<select name="export_type" style="font-size:x-small;">
<option value="ascii_text">$lang{text_option}
<option value="vcalendar">$lang{vcalendar_option}
</select>
</form>
p1


}





sub fatal_error()
{

  $error_info =~ s/\n/<br>/g;

  $html_output .=<<p1;
Cache-control: no-cache,no-store,private
Content-Type: text/html; charset=iso-8859-1\n
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Plans alert!</title>
</head>
<body>

<b>Plans alert:</b><br>
$error_info
p1
  if ($debug_info ne "")
  {
    $debug_info =~ s/\n/<br>/g;
    $html_output .=<<p1;
<hr>
Debug info:<br>
$debug_info
p1

  }

  $html_output .=<<p1;
</body>
</html>
p1

  print $html_output;
  exit(0);
}



# default calendar data structure
#%default_cal;
%default_cal = (id => "", 
                title => "", 
                details => $new_calendar_default_details,
                link => "",
                local_background_calendars => {},
                selectable_calendars => {},
                list_background_calendars_together => "",
                background_events_display_style => "normal",
                background_events_fade_factor => "",
                background_events_color => "#ffffff",
                default_number_of_months => 1,
                max_number_of_months => 24,
                gmtime_diff => 0,
                date_format => "mm/dd/yy",
                week_start_day => 0,
                info_window_size => "400x400",
                custom_template => "",
                custom_stylesheet => "",
                password => "",
                update_timestamp => 0);



# If an included file contains only subroutines, perl will complain 
# that it "did not return a true value".  The "return 1;" at the end fixes this.
return 1;
