From 5e684bf41b95dcabadc34c9d8020094393819278 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Wed, 5 Jun 2024 03:34:33 +0300 Subject: [PATCH 01/20] Add improvements to iCalendar parser --- mailboxes/mailboxes-lib.pl | 95 +++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index c877472d5..825eccdf8 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1286,14 +1286,32 @@ if (!glob("\Q$rv\E.*")) { return $rv; } -# parse_calendar_file(calendar-file) +# parse_calendar_file(calendar-file|lines) # Parses an iCalendar file and returns a list of events sub parse_calendar_file { my ($calendar_file) = @_; -my (@events, %event); +my (@events, %event, $line); eval "use DateTime; use DateTime::TimeZone;"; return \@events if ($@); +# Timezone map +my %timezone_map = ( + 'Eastern Standard Time' => 'EST', + 'Central Standard Time' => 'CST', + 'Mountain Standard Time' => 'MST', + 'Pacific Standard Time' => 'PST', + 'Alaskan Standard Time' => 'AKST', + 'Hawaiian Standard Time' => 'HST', + 'Atlantic Standard Time' => 'AST', + 'Newfoundland Standard Time' => 'NST', + 'Greenwich Mean Time' => 'GMT', + 'British Summer Time' => 'BST', + 'Central European Time' => 'CET', + 'Eastern European Time' => 'EET', + 'Australian Eastern Standard Time' => 'AEST', + 'Australian Central Standard Time' => 'ACST', +); +# Make a date from a special timestamp my $adjust_time_with_timezone = sub { my ($time, $tzid) = @_; my $dt = DateTime->new( @@ -1310,55 +1328,80 @@ my $adjust_time_with_timezone = sub { timestamp => $local_dt->epoch }; }; -my $ics_file_lines = &read_file_lines($calendar_file, 1); -foreach (@$ics_file_lines) { - if (/^BEGIN:VEVENT/) { - # Start a new event +# Lines processor +my $process_line = sub { + my ($line) = @_; + # Start a new event + if ($line =~ /^BEGIN:VEVENT/) { %event = (); } - elsif (/^END:VEVENT/) { - # Convert times using the timezone - if ($event{'dtstart'} && $event{'tzid'}) { - my $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); - $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp'}; - $event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'}); - } - if ($event{'dtend'} && $event{'tzid'}) { - my $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); - $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp'}; - $event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'}); + # Convert times using the timezone + elsif ($line =~ /^END:VEVENT/) { + if ($event{'tzid'}) { + $event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'}; + if ($event{'dtstart'}) { + my $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); + $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp'}; + $event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'}); + } + if ($event{'dtend'}) { + my $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); + $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp'}; + $event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'}); + } } # Local timezone - $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name; + $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name(); # Add the event to the list push(@events, { %event }); } # Parse fields - elsif (/^SUMMARY:(.*)$/) { + elsif ($line =~ /^SUMMARY:(.*)$/) { $event{'summary'} = $1; } - elsif (/^DTSTART;TZID=(.*?):(.*)$/) { + elsif ($line =~ /^DTSTART;TZID=(.*?):(.*)$/) { $event{'tzid'} = $1; $event{'dtstart'} = $2; } - elsif (/^DTEND;TZID=(.*?):(.*)$/) { + elsif ($line =~ /^DTEND;TZID=(.*?):(.*)$/) { $event{'tzid'} = $1; $event{'dtend'} = $2; } - elsif (/^DESCRIPTION:(.*)$/) { + elsif ($line =~ /^DESCRIPTION:(.*)$/) { $event{'description'} = $1; } - elsif (/^LOCATION:(.*)$/) { + elsif ($line =~ /^LOCATION:(.*)$/) { $event{'location'} = $1; } - elsif (/^ATTENDEE.*:(.*)$/) { - push @{$event{'attendees'}}, $1; + elsif ($line =~ /^ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=(.*?):mailto:(.*)$/ || + $line =~ /^ATTENDEE;.*CN=(.*?);.*mailto:(.*)$/) { + push @{$event{'attendees'}}, { 'name' => $1, 'email' => $2 }; } - elsif (/^ORGANIZER;CN=(.*?):mailto:(.*)$/) { + elsif ($line =~ /^ORGANIZER;CN=(.*?):mailto:(.*)$/) { $event{'organizer_name'} = $1; $event{'organizer_email'} = $2; } +}; +# Read the ICS file lines or just use the lines +my $ics_file_lines = + -r $calendar_file ? + &read_file_lines($calendar_file, 1) : + [ split(/\r?\n/, $calendar_file) ]; +# Process each line of the ICS file +foreach my $ics_file_line (@$ics_file_lines) { + # Check if the line is a continuation of the previous line + if ($ics_file_line =~ /^[ \t](.*)$/) { + $line .= $1; # Concatenate with the previous line + } + else { + # Process the previous line + $process_line->($line) if ($line); + $line = $ics_file_line; # Start a new line + } } +# Process the last line +$process_line->($line) if ($line); +# Return the list of events return \@events; } From 596ba13b1ea92bfdbab16a872f1c49bf061ad01e Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Thu, 6 Jun 2024 01:59:58 +0300 Subject: [PATCH 02/20] Add logic to store iCalendars --- mailboxes/view_mail.cgi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi index 199a4df15..9eefa8c41 100755 --- a/mailboxes/view_mail.cgi +++ b/mailboxes/view_mail.cgi @@ -89,6 +89,16 @@ foreach $s (@sub) { @attach = grep { $_ ne $body && $_ ne $dstatus } @attach; @attach = grep { !$_->{'attach'} } @attach; +# Calendar attachments +my @calendars; +eval { +foreach my $i (grep { $_->{'data'} } + grep { $_->{'type'} =~ /^text\/calendar/ } @attach) { + my $calendars = &parse_calendar_file($i->{'data'}); + push(@calendars, @{$calendars}); + }}; + +# Mail buttons if ($config{'top_buttons'} == 2 && &editable_mail($mail)) { &show_mail_buttons(1, scalar(@sub)); print "

\n"; From e3b94dc45840de431aa602d36eb7e2fff5a565de Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 02:18:20 +0300 Subject: [PATCH 03/20] Fix summary match for strings like `SUMMARY;LANGUAGE=fr-CA` --- mailboxes/mailboxes-lib.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index 825eccdf8..b57ef016a 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1356,7 +1356,7 @@ my $process_line = sub { push(@events, { %event }); } # Parse fields - elsif ($line =~ /^SUMMARY:(.*)$/) { + elsif ($line =~ /^SUMMARY.*?:(.*)$/) { $event{'summary'} = $1; } elsif ($line =~ /^DTSTART;TZID=(.*?):(.*)$/) { From 3a151469c7f7ed55ab8cc01929710b1babd7ead7 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 16:47:08 +0300 Subject: [PATCH 04/20] Add proper date parsing and storing extensive details about event --- mailboxes/mailboxes-lib.pl | 104 ++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index b57ef016a..42705f583 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1324,8 +1324,10 @@ my $adjust_time_with_timezone = sub { time_zone => $tzid); my $local_dt = $dt->clone->set_time_zone('local'); return { - formatted => $local_dt->strftime('%Y-%m-%d %H:%M:%S'), - timestamp => $local_dt->epoch + formatted => $dt->strftime("%Y-%m-%d %H:%M:%S"), + timestamp => $dt->epoch, + formatted_local => $local_dt->strftime('%Y-%m-%d %H:%M:%S'), + timestamp_local => $local_dt->epoch, }; }; # Lines processor @@ -1337,21 +1339,107 @@ my $process_line = sub { } # Convert times using the timezone elsif ($line =~ /^END:VEVENT/) { + # Local timezone + $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name(); + # Adjust times with timezone if ($event{'tzid'}) { + my ($adjusted_start, $adjusted_end); $event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'}; + # Add single start/end time if ($event{'dtstart'}) { - my $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); - $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp'}; + $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); + $event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'}; + my $dtstart_date = &make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} }); + $event{'dtstart_date'} = "$dtstart_date->{'short'} $dtstart_date->{'timeshort'}"; + $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp_local'}; $event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'}); } if ($event{'dtend'}) { - my $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); - $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp'}; + $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); + $event{'dtend_timestamp'} = $adjusted_end->{'timestamp'}; + my $dtend_date = &make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} }); + $event{'dtend_date'} = "$dtend_date->{'short'} $dtend_date->{'timeshort'}"; + $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp_local'}; $event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'}); } } - # Local timezone - $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name(); + if ($event{'dtstart'} && $event{'dtend'}) { + # Try to add local 'when (period)' + my $dtstart_local_obj = + $event{'_obj_dtstart_local_time'} = + make_date($event{'dtstart_local_timestamp'}, { _ }); + my $dtend_local_obj = + $event{'_obj_dtend_local_time'} = + make_date($event{'dtend_local_timestamp'}, { _ }); + # Build when local, e.g.: + # Tue Jun 04, 2024 04:30 PM – 05:15 PM (Asia/Nicosia +0300) + # or + # Tue Jun 04, 2024 04:30 PM – Wed Jun 05, 2024 01:15 AM (Asia/Nicosia +0300) + $event{'dtwhen_local'} = + # Start local + $dtstart_local_obj->{'week'}. ' '. $dtstart_local_obj->{'month'}. ' '. + $dtstart_local_obj->{'day'}. ', '. $dtstart_local_obj->{'year'}. ' '. + $dtstart_local_obj->{'timeshort'}. ' – '; + # End local + if ($dtstart_local_obj->{'year'} eq $dtend_local_obj->{'year'} && + $dtstart_local_obj->{'month'} eq $dtend_local_obj->{'month'} && + $dtstart_local_obj->{'day'} eq $dtend_local_obj->{'day'}) { + $event{'dtwhen_local'} .= $dtend_local_obj->{'timeshort'}; + } + else { + $event{'dtwhen_local'} .= + $dtend_local_obj->{'week'}. ' '. $dtend_local_obj->{'month'}. ' '. + $dtend_local_obj->{'day'}. ', '. $dtend_local_obj->{'year'}. ' '. + $dtend_local_obj->{'timeshort'}; + } + # Timezone local + if ($event{'tzid_local'} || $dtstart_local_obj->{'tz'}) { + if ($event{'tzid_local'} && $dtstart_local_obj->{'tz'}) { + if ($event{'tzid_local'} eq $dtstart_local_obj->{'tz'}) { + $event{'dtwhen_local'} .= " ($event{'tzid_local'})"; + } + else { + $event{'dtwhen_local'} .= + " ($event{'tzid_local'} $dtstart_local_obj->{'tz'})"; + } + } + elsif ($event{'tzid_local'}) { + $event{'dtwhen_local'} .= " ($event{'tzid_local'})"; + } + else { + $event{'dtwhen_local'} .= " ($dtstart_local_obj->{'tz'})"; + } + } + # Try to add original 'when (period)' + my $dtstart_obj = + $event{'_obj_dtstart_time'} = + make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} }); + my $dtend_obj = + $event{'_obj_dtend_time'} = + make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} }); + # Build original when + $event{'dtwhen'} = + # Start original + $dtstart_obj->{'week'}. ' '. $dtstart_obj->{'month'}. ' '. + $dtstart_obj->{'day'}. ', '. $dtstart_obj->{'year'}. ' '. + $dtstart_obj->{'timeshort'}. ' – '; + # End original + if ($dtstart_obj->{'year'} eq $dtend_obj->{'year'} && + $dtstart_obj->{'month'} eq $dtend_obj->{'month'} && + $dtstart_obj->{'day'} eq $dtend_obj->{'day'}) { + $event{'dtwhen'} .= $dtend_obj->{'timeshort'}; + } + else { + $event{'dtwhen'} .= + $dtend_obj->{'week'}. ' '. $dtend_obj->{'month'}. ' '. + $dtend_obj->{'day'}. ', '. $dtend_obj->{'year'}. ' '. + $dtend_obj->{'timeshort'}; + } + # Timezone original + if ($dtstart_obj->{'tz'}) { + $event{'dtwhen'} .= " ($dtstart_obj->{'tz'})"; + } + } # Add the event to the list push(@events, { %event }); } From 00885b1f76496b4b1dd5f218ad7054482ac41b21 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 18:40:35 +0300 Subject: [PATCH 05/20] Fix location detection --- mailboxes/mailboxes-lib.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index 42705f583..4f62bc806 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1458,7 +1458,7 @@ my $process_line = sub { elsif ($line =~ /^DESCRIPTION:(.*)$/) { $event{'description'} = $1; } - elsif ($line =~ /^LOCATION:(.*)$/) { + elsif ($line =~ /^LOCATION.*:(.*)$/) { $event{'location'} = $1; } elsif ($line =~ /^ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=(.*?):mailto:(.*)$/ || From 45852664fe7b7a75e74fc4d2098c8c1a7cf14feb Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 23:19:46 +0300 Subject: [PATCH 06/20] Add further fixes and improvements to the processor --- mailboxes/mailboxes-lib.pl | 63 +++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index 4f62bc806..c38bda979 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1336,32 +1336,33 @@ my $process_line = sub { # Start a new event if ($line =~ /^BEGIN:VEVENT/) { %event = (); + $event{'description'} = [ ]; + $event{'attendees'} = [ ]; } # Convert times using the timezone elsif ($line =~ /^END:VEVENT/) { # Local timezone $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name(); + $event{'tzid'} = '+0000' if (!$event{'tzid'}); # Adjust times with timezone - if ($event{'tzid'}) { - my ($adjusted_start, $adjusted_end); - $event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'}; - # Add single start/end time - if ($event{'dtstart'}) { - $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); - $event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'}; - my $dtstart_date = &make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} }); - $event{'dtstart_date'} = "$dtstart_date->{'short'} $dtstart_date->{'timeshort'}"; - $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp_local'}; - $event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'}); - } - if ($event{'dtend'}) { - $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); - $event{'dtend_timestamp'} = $adjusted_end->{'timestamp'}; - my $dtend_date = &make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} }); - $event{'dtend_date'} = "$dtend_date->{'short'} $dtend_date->{'timeshort'}"; - $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp_local'}; - $event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'}); - } + my ($adjusted_start, $adjusted_end); + $event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'}; + # Add single start/end time + if ($event{'dtstart'}) { + $adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'}); + $event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'}; + my $dtstart_date = &make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} }); + $event{'dtstart_date'} = "$dtstart_date->{'short'} $dtstart_date->{'timeshort'}"; + $event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp_local'}; + $event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'}); + } + if ($event{'dtend'}) { + $adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); + $event{'dtend_timestamp'} = $adjusted_end->{'timestamp'}; + my $dtend_date = &make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} }); + $event{'dtend_date'} = "$dtend_date->{'short'} $dtend_date->{'timeshort'}"; + $event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp_local'}; + $event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'}); } if ($event{'dtstart'} && $event{'dtend'}) { # Try to add local 'when (period)' @@ -1447,25 +1448,37 @@ my $process_line = sub { elsif ($line =~ /^SUMMARY.*?:(.*)$/) { $event{'summary'} = $1; } + elsif ($line =~ /^DTSTART:(.*)$/) { + $event{'dtstart'} = $1; + } elsif ($line =~ /^DTSTART;TZID=(.*?):(.*)$/) { $event{'tzid'} = $1; $event{'dtstart'} = $2; } + elsif ($line =~ /^DTEND:(.*)$/) { + $event{'dtend'} = $1; + } elsif ($line =~ /^DTEND;TZID=(.*?):(.*)$/) { $event{'tzid'} = $1; $event{'dtend'} = $2; } elsif ($line =~ /^DESCRIPTION:(.*)$/) { - $event{'description'} = $1; + unshift(@{$event{'description'}}, $1); } - elsif ($line =~ /^LOCATION.*:(.*)$/) { + elsif ($line =~ /^DESCRIPTION;LANGUAGE=([a-z]{2}-[A-Z]{2}):(.*)$/) { + my $description = $2; + $description =~ s/\\n/
/g; + unshift(@{$event{'description'}}, $description); + } + elsif ($line =~ /^LOCATION.*?:(.*)$/) { $event{'location'} = $1; } elsif ($line =~ /^ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=(.*?):mailto:(.*)$/ || - $line =~ /^ATTENDEE;.*CN=(.*?);.*mailto:(.*)$/) { - push @{$event{'attendees'}}, { 'name' => $1, 'email' => $2 }; + $line =~ /^ATTENDEE;.*CN=(.*?);.*mailto:(.*)$/ || + $line =~ /^ATTENDEE:mailto:(.*)$/) { + push(@{$event{'attendees'}}, { 'name' => $1, 'email' => $2 }); } - elsif ($line =~ /^ORGANIZER;CN=(.*?):mailto:(.*)$/) { + elsif ($line =~ /^ORGANIZER;CN=(.*?):(?:mailto:)?(.*)$/) { $event{'organizer_name'} = $1; $event{'organizer_email'} = $2; } From 37cde80bbe2cd43e6a3bad5218d1901620c90756 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 23:36:59 +0300 Subject: [PATCH 07/20] Fix standard description to replace new lines to HTML break --- mailboxes/mailboxes-lib.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index c38bda979..cb57edc77 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1463,11 +1463,15 @@ my $process_line = sub { $event{'dtend'} = $2; } elsif ($line =~ /^DESCRIPTION:(.*)$/) { - unshift(@{$event{'description'}}, $1); + my $description = $1; + $description =~ s/\\n/
/g; + $description =~ s/\\//g; + unshift(@{$event{'description'}}, $description); } elsif ($line =~ /^DESCRIPTION;LANGUAGE=([a-z]{2}-[A-Z]{2}):(.*)$/) { my $description = $2; $description =~ s/\\n/
/g; + $description =~ s/\\//g; unshift(@{$event{'description'}}, $description); } elsif ($line =~ /^LOCATION.*?:(.*)$/) { From 95ee1e2f2d41725d0485d84298a31eacd4eeda25 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sat, 8 Jun 2024 23:52:59 +0300 Subject: [PATCH 08/20] Add support to embed iCalendar to email message --- mailboxes/lang/en | 9 ++ mailboxes/view_mail.cgi | 204 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 211 insertions(+), 2 deletions(-) diff --git a/mailboxes/lang/en b/mailboxes/lang/en index cd387a07d..c2ebb6f34 100644 --- a/mailboxes/lang/en +++ b/mailboxes/lang/en @@ -154,6 +154,15 @@ view_sub=Attached Email view_sub2=Attached email from $1 view_egone=This message no longer exists view_eugone=This user does not exist +view_ical=Event +view_ical_when=When +view_ical_where=Where +view_ical_who=Who +view_ical_orginizertime=Organizer time +view_ical_orginizername=Organizer name +view_ical_orginizeremail=Organizer email +view_ical_attendees=Attendees details +view_ical_desc=Event description view_gnupg=GnuPG signature verification view_gnupg_0=Signature by $1 is valid. diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi index 9eefa8c41..91bfb2c37 100755 --- a/mailboxes/view_mail.cgi +++ b/mailboxes/view_mail.cgi @@ -148,11 +148,209 @@ else { print &ui_table_end(); # Show body attachment, with properly linked URLs -@bodyright = ( ); +my $bodycontents; +my @bodyright = ( ); +my $calendars = { }; +if (@calendars) { + # CSS for HTML version + $calendars->{'html'} .= < +.calendar-table { + width: 100%; + border-collapse: collapse; + border: 1px solid #99999933; + } + .calendar-table td { + padding: 5px; + vertical-align: top; + } + .calendar-table .calendar-cell { + background-color: #99999916; + text-align: center; + vertical-align: top; + padding: 2px; + padding-top: 24px; + width: 100px; + font-weight: bold; + } + .calendar-month { + font-size: 21px; + color: #1d72ff; + text-align: center; + padding: 2px 8px; + } + .calendar-day { + font-size: 24px; + text-align: center; + padding: 4px 8px; + } + .calendar-week { + font-size: 16px; + border-top: 1px dotted #999999aa; + padding: 6px; + display: inline-block; + } + .calendar-details h2 { + margin: 0; + font-size: 18px; + } + .calendar-details p { + margin: 4px 0; + } + .calendar-details .title { + font-size: 20px; + } + .calendar-details .detail strong { + opacity: 0.66; + white-space: nowrap; + } + .calendar-details .detail + .attendees p:first-child { + margin-top: 0; + } + details.calendar-details { + font-size: 90%; + display: inline-block; + margin-left: 9px; + } + details.calendar-details summary { + cursor: help; + } + details.calendar-details tr:has(>.detail+td:empty), + .calendar-details tr:has(>.detail+td:empty) { + display: none; + } + +STYLE + foreach my $calendar (@calendars) { + my $title = $calendar->{'summary'} || $calendar->{'description'}; + my $orginizer = $calendar->{'organizer_name'}; + my @attendees; + foreach my $a (@{$calendar->{'attendees'}}) { + push(@attendees, { name => $a->{'name'}, + email => $a->{'email'} }); + } + my $who = join(", ", map { $_->{'name'} } @attendees); + if ($who && $orginizer) { + $who .= ", ${orginizer}*"; + } + elsif ($orginizer) { + $who = "${orginizer}*"; + } + # HTML version + $calendars->{'html'} .= < + + +
+
+ $calendar->{'_obj_dtstart_local_time'}->{'month'} +
+
+ $calendar->{'_obj_dtstart_local_time'}->{'day'} +
+
+ $calendar->{'_obj_dtstart_local_time'}->{'week'} +
+
+ + + + + + + + + + + + + + + + + + +
+ $title +
+ $text{'view_ical_when'} + $calendar->{'dtwhen_local'}
+ $text{'view_ical_where'} + $calendar->{'location'}
+ $text{'view_ical_who'} + $who
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ $text{'view_ical_orginizertime'} + $calendar->{'dtwhen'}
+ $text{'view_ical_orginizername'} + $calendar->{'organizer_name'}
+ $text{'view_ical_orginizeremail'} + $calendar->{'organizer_email'}
+ $text{'view_ical_attendees'} + @{[join('', map { + "

$_->{'name'}
$_->{'email'}

" + } @attendees)]}
+ $text{'view_ical_desc'} + @{[join('
', + @{$calendar->{'description'}})]}
+
+ + + +HTML + # Text version + my %textical = ( + 'view_ical' => $title, + 'view_ical_when' => $calendar->{'dtwhen_local'}, + 'view_ical_where' => $calendar->{'location'}, + 'view_ical_who' => $who + ); + my $max_label_length = 0; + foreach my $key (sort keys %textical) { + my $label_length = length($text{$key}); + if ($label_length > $max_label_length) { + $max_label_length = $label_length; + } + } + $calendars->{'text'} = "=" x 79 . "\n"; + foreach my $key (sort keys %textical) { + my $label = $text{$key}; + my $value = $textical{$key}; + my $spaces .= " " x ($max_label_length - length($label)); + $calendars->{'text'} .= "$label$spaces : $value\n"; + } + $calendars->{'text'} .= "=" x 79 . "\n"; + } + } if ($body && $body->{'data'} =~ /\S/) { if ($body eq $textbody) { # Show plain text $bodycontents = "
";
+		$bodycontents .= $calendars->{'text'}
+			if ($calendars->{'text'});
 		foreach $l (&wrap_lines(&eucconv($body->{'data'}),
 					$config{'wrap_width'})) {
 			$bodycontents .= &link_urls_and_escape($l,
@@ -166,7 +364,9 @@ if ($body && $body->{'data'} =~ /\S/) {
 		}
 	elsif ($body eq $htmlbody) {
 		# Attempt to show HTML
-		$bodycontents = $body->{'data'};
+		$bodycontents = $calendars->{'html'}
+			if ($calendars->{'html'});
+		$bodycontents .= $body->{'data'};
 		my @imageurls;
 		my $image_mode = int(defined($in{'images'}) ? $in{'images'} : $config{'view_images'});
 		$bodycontents = &disable_html_images($bodycontents, $image_mode, \@imageurls);

From e36e943251122d243a3fe3991973b9bbc6b0f545 Mon Sep 17 00:00:00 2001
From: Ilia Ross 
Date: Sun, 9 Jun 2024 00:31:39 +0300
Subject: [PATCH 09/20] Fix to keep calendar cell always in right size

---
 mailboxes/view_mail.cgi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi
index 91bfb2c37..a29e0f8e3 100755
--- a/mailboxes/view_mail.cgi
+++ b/mailboxes/view_mail.cgi
@@ -170,7 +170,9 @@ if (@calendars) {
     vertical-align: top;
     padding: 2px;
     padding-top: 24px;
+    padding-bottom: 24px;
     width: 100px;
+    min-width: 100px;
     font-weight: bold;
   }
   .calendar-month {

From 17a27dbe0072339b23acddb789ac4dd177434a5b Mon Sep 17 00:00:00 2001
From: Ilia Ross 
Date: Sun, 9 Jun 2024 00:48:36 +0300
Subject: [PATCH 10/20] Fix to drop showing organizer time unless TZ is
 explicitly given

---
 mailboxes/mailboxes-lib.pl | 46 ++++++++++++++++++++------------------
 1 file changed, 24 insertions(+), 22 deletions(-)

diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl
index cb57edc77..094864c2b 100755
--- a/mailboxes/mailboxes-lib.pl
+++ b/mailboxes/mailboxes-lib.pl
@@ -1343,7 +1343,7 @@ my $process_line = sub  {
 	elsif ($line =~ /^END:VEVENT/) {
 		# Local timezone
 		$event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name();
-		$event{'tzid'} = '+0000' if (!$event{'tzid'});
+		$event{'tzid'} = 'UTC', $event{'tzid_missing'} = 1 if (!$event{'tzid'});
 		# Adjust times with timezone
 		my ($adjusted_start, $adjusted_end);
 		$event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'};
@@ -1419,27 +1419,29 @@ my $process_line = sub  {
 				$event{'_obj_dtend_time'} =
 					make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} });
 			# Build original when
-			$event{'dtwhen'} =
-				# Start original
-				$dtstart_obj->{'week'}. ' '. $dtstart_obj->{'month'}. ' '.
-				$dtstart_obj->{'day'}. ', '. $dtstart_obj->{'year'}. ' '.
-				$dtstart_obj->{'timeshort'}. ' – ';
-				# End original
-				if ($dtstart_obj->{'year'} eq $dtend_obj->{'year'} &&
-					$dtstart_obj->{'month'} eq $dtend_obj->{'month'} &&
-					$dtstart_obj->{'day'} eq $dtend_obj->{'day'}) {
-					$event{'dtwhen'} .= $dtend_obj->{'timeshort'};
-					}
-				else {
-					$event{'dtwhen'} .=
-						$dtend_obj->{'week'}. ' '. $dtend_obj->{'month'}. ' '.
-						$dtend_obj->{'day'}. ', '. $dtend_obj->{'year'}. ' '.
-						$dtend_obj->{'timeshort'};
-					}
-				# Timezone original
-				if ($dtstart_obj->{'tz'}) {
-					$event{'dtwhen'} .= " ($dtstart_obj->{'tz'})";
-					}
+			if (!$event{'tzid_missing'}) {
+				$event{'dtwhen'} =
+					# Start original
+					$dtstart_obj->{'week'}. ' '. $dtstart_obj->{'month'}. ' '.
+					$dtstart_obj->{'day'}. ', '. $dtstart_obj->{'year'}. ' '.
+					$dtstart_obj->{'timeshort'}. ' – ';
+					# End original
+					if ($dtstart_obj->{'year'} eq $dtend_obj->{'year'} &&
+						$dtstart_obj->{'month'} eq $dtend_obj->{'month'} &&
+						$dtstart_obj->{'day'} eq $dtend_obj->{'day'}) {
+						$event{'dtwhen'} .= $dtend_obj->{'timeshort'};
+						}
+					else {
+						$event{'dtwhen'} .=
+							$dtend_obj->{'week'}. ' '. $dtend_obj->{'month'}. ' '.
+							$dtend_obj->{'day'}. ', '. $dtend_obj->{'year'}. ' '.
+							$dtend_obj->{'timeshort'};
+						}
+					# Timezone original
+					if ($dtstart_obj->{'tz'}) {
+						$event{'dtwhen'} .= " ($dtstart_obj->{'tz'})";
+						}
+				}
 			}
 		# Add the event to the list
 		push(@events, { %event });

From e1ebcf0506d26df4179de573d30d8c5600049a86 Mon Sep 17 00:00:00 2001
From: Ilia Ross 
Date: Sun, 9 Jun 2024 01:06:27 +0300
Subject: [PATCH 11/20] Fix code to fit within an 80-character width

---
 mailboxes/mailboxes-lib.pl | 143 ++++++++++++++++++++++++-------------
 1 file changed, 95 insertions(+), 48 deletions(-)

diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl
index 094864c2b..e84d66894 100755
--- a/mailboxes/mailboxes-lib.pl
+++ b/mailboxes/mailboxes-lib.pl
@@ -1342,104 +1342,151 @@ my $process_line = sub  {
 	# Convert times using the timezone
 	elsif ($line =~ /^END:VEVENT/) {
 		# Local timezone
-		$event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name();
-		$event{'tzid'} = 'UTC', $event{'tzid_missing'} = 1 if (!$event{'tzid'});
+		$event{'tzid_local'} =
+			DateTime::TimeZone->new(name => 'local')->name();
+		$event{'tzid'} = 'UTC', $event{'tzid_missing'} = 1
+			if (!$event{'tzid'});
 		# Adjust times with timezone
 		my ($adjusted_start, $adjusted_end);
 		$event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'};
 		# Add single start/end time
 		if ($event{'dtstart'}) {
-			$adjusted_start = $adjust_time_with_timezone->($event{'dtstart'}, $event{'tzid'});
+			$adjusted_start =
+				$adjust_time_with_timezone->($event{'dtstart'},
+							     $event{'tzid'});
 			$event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'};
-			my $dtstart_date = &make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} });
-			$event{'dtstart_date'} = "$dtstart_date->{'short'} $dtstart_date->{'timeshort'}"; 
-			$event{'dtstart_local_timestamp'} = $adjusted_start->{'timestamp_local'};
-			$event{'dtstart_local_date'} = &make_date($event{'dtstart_local_timestamp'});
+			my $dtstart_date =
+				&make_date($event{'dtstart_timestamp'},
+					{ tz => $event{'tzid'} });
+			$event{'dtstart_date'} =
+				"$dtstart_date->{'short'} ".
+					"$dtstart_date->{'timeshort'}"; 
+			$event{'dtstart_local_timestamp'} =
+				$adjusted_start->{'timestamp_local'};
+			$event{'dtstart_local_date'} =
+				&make_date($event{'dtstart_local_timestamp'});
 			}
 		if ($event{'dtend'}) {
-			$adjusted_end = $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'});
+			$adjusted_end =
+				$adjust_time_with_timezone->($event{'dtend'},
+							     $event{'tzid'});
 			$event{'dtend_timestamp'} = $adjusted_end->{'timestamp'};
-			my $dtend_date = &make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} });
-			$event{'dtend_date'} = "$dtend_date->{'short'} $dtend_date->{'timeshort'}";
-			$event{'dtend_local_timestamp'} = $adjusted_end->{'timestamp_local'};
-			$event{'dtend_local_date'} = &make_date($event{'dtend_local_timestamp'});
+			my $dtend_date = &make_date($event{'dtend_timestamp'},
+				{ tz => $event{'tzid'} });
+			$event{'dtend_date'} =
+				"$dtend_date->{'short'} ".
+					"$dtend_date->{'timeshort'}";
+			$event{'dtend_local_timestamp'} =
+				$adjusted_end->{'timestamp_local'};
+			$event{'dtend_local_date'} =
+				&make_date($event{'dtend_local_timestamp'});
 			}
 		if ($event{'dtstart'} && $event{'dtend'}) {
 			# Try to add local 'when (period)'
 			my $dtstart_local_obj =
-				$event{'_obj_dtstart_local_time'} =
-					make_date($event{'dtstart_local_timestamp'}, { _ });
+			    $event{'_obj_dtstart_local_time'} =
+			        make_date($event{'dtstart_local_timestamp'},
+					{ _ });
 			my $dtend_local_obj =
-				$event{'_obj_dtend_local_time'} =
-					make_date($event{'dtend_local_timestamp'}, { _ });
+			    $event{'_obj_dtend_local_time'} =
+			        make_date($event{'dtend_local_timestamp'},
+					{ _ });
 			# Build when local, e.g.:
-			# Tue Jun 04, 2024 04:30 PM – 05:15 PM (Asia/Nicosia +0300)
+			# Tue Jun 04, 2024 04:30 PM – 05:15
+			# PM (Asia/Nicosia +0300)
 			# or
-			# Tue Jun 04, 2024 04:30 PM – Wed Jun 05, 2024 01:15 AM (Asia/Nicosia +0300)
+			# Tue Jun 04, 2024 04:30 PM – Wed Jun 05, 2024 01:15
+			# AM (Asia/Nicosia +0300)
 			$event{'dtwhen_local'} =
 				# Start local
-				$dtstart_local_obj->{'week'}. ' '. $dtstart_local_obj->{'month'}. ' '.
-				$dtstart_local_obj->{'day'}. ', '. $dtstart_local_obj->{'year'}. ' '.
-				$dtstart_local_obj->{'timeshort'}. ' – ';
+				$dtstart_local_obj->{'week'}.' '.
+				$dtstart_local_obj->{'month'}.' '.
+				$dtstart_local_obj->{'day'}.', '.
+				$dtstart_local_obj->{'year'}.' '.
+				$dtstart_local_obj->{'timeshort'}.' – ';
 				# End local
-				if ($dtstart_local_obj->{'year'} eq $dtend_local_obj->{'year'} &&
-				    $dtstart_local_obj->{'month'} eq $dtend_local_obj->{'month'} &&
-				    $dtstart_local_obj->{'day'} eq $dtend_local_obj->{'day'}) {
-					$event{'dtwhen_local'} .= $dtend_local_obj->{'timeshort'};
+				if ($dtstart_local_obj->{'year'} eq
+					$dtend_local_obj->{'year'} &&
+				    $dtstart_local_obj->{'month'} eq
+				    	$dtend_local_obj->{'month'} &&
+				    $dtstart_local_obj->{'day'} eq
+				    	$dtend_local_obj->{'day'}) {
+					$event{'dtwhen_local'} .=
+						$dtend_local_obj->{'timeshort'};
 					}
 				else {
 					$event{'dtwhen_local'} .=
-						$dtend_local_obj->{'week'}. ' '. $dtend_local_obj->{'month'}. ' '.
-						$dtend_local_obj->{'day'}. ', '. $dtend_local_obj->{'year'}. ' '.
-						$dtend_local_obj->{'timeshort'};
+					    $dtend_local_obj->{'week'}.' '.
+					    $dtend_local_obj->{'month'}.' '.
+					    $dtend_local_obj->{'day'}.', '.
+					    $dtend_local_obj->{'year'}.' '.
+					    $dtend_local_obj->{'timeshort'};
 					}
 				# Timezone local
-				if ($event{'tzid_local'} || $dtstart_local_obj->{'tz'}) {
-					if ($event{'tzid_local'} && $dtstart_local_obj->{'tz'}) {
-						if ($event{'tzid_local'} eq $dtstart_local_obj->{'tz'}) {
-							$event{'dtwhen_local'} .= " ($event{'tzid_local'})";
+				if ($event{'tzid_local'} ||
+				    $dtstart_local_obj->{'tz'}) {
+					if ($event{'tzid_local'} &&
+					    $dtstart_local_obj->{'tz'}) {
+						if ($event{'tzid_local'} eq
+						      $dtstart_local_obj->{'tz'}) {
+							$event{'dtwhen_local'} .=
+								" ($event{'tzid_local'})";
 							}
 						else {
 							$event{'dtwhen_local'} .=
-								" ($event{'tzid_local'} $dtstart_local_obj->{'tz'})";
+							    " ($event{'tzid_local'} ".
+							    "$dtstart_local_obj->{'tz'})";
 							}
 						}
 					elsif ($event{'tzid_local'}) {
-						$event{'dtwhen_local'} .= " ($event{'tzid_local'})";
+						$event{'dtwhen_local'} .=
+						    " ($event{'tzid_local'})";
 						}
 					else {
-						$event{'dtwhen_local'} .= " ($dtstart_local_obj->{'tz'})";
+						$event{'dtwhen_local'} .=
+						    " ($dtstart_local_obj->{'tz'})";
 						}
 					}
 			# Try to add original 'when (period)'
 			my $dtstart_obj =
 				$event{'_obj_dtstart_time'} =
-					make_date($event{'dtstart_timestamp'}, { tz => $event{'tzid'} });
+					make_date($event{'dtstart_timestamp'},
+						{ tz => $event{'tzid'} });
 			my $dtend_obj =
 				$event{'_obj_dtend_time'} =
-					make_date($event{'dtend_timestamp'}, { tz => $event{'tzid'} });
+					make_date($event{'dtend_timestamp'},
+					{ tz => $event{'tzid'} });
 			# Build original when
 			if (!$event{'tzid_missing'}) {
 				$event{'dtwhen'} =
 					# Start original
-					$dtstart_obj->{'week'}. ' '. $dtstart_obj->{'month'}. ' '.
-					$dtstart_obj->{'day'}. ', '. $dtstart_obj->{'year'}. ' '.
-					$dtstart_obj->{'timeshort'}. ' – ';
+					$dtstart_obj->{'week'}.' '.
+					$dtstart_obj->{'month'}.' '.
+					$dtstart_obj->{'day'}.', '.
+					$dtstart_obj->{'year'}.' '.
+					$dtstart_obj->{'timeshort'}.' – ';
 					# End original
-					if ($dtstart_obj->{'year'} eq $dtend_obj->{'year'} &&
-						$dtstart_obj->{'month'} eq $dtend_obj->{'month'} &&
-						$dtstart_obj->{'day'} eq $dtend_obj->{'day'}) {
-						$event{'dtwhen'} .= $dtend_obj->{'timeshort'};
+					if ($dtstart_obj->{'year'} eq
+					      $dtend_obj->{'year'} &&
+						$dtstart_obj->{'month'} eq
+						  $dtend_obj->{'month'} &&
+						$dtstart_obj->{'day'} eq
+						  $dtend_obj->{'day'}) {
+						$event{'dtwhen'} .=
+						    $dtend_obj->{'timeshort'};
 						}
 					else {
 						$event{'dtwhen'} .=
-							$dtend_obj->{'week'}. ' '. $dtend_obj->{'month'}. ' '.
-							$dtend_obj->{'day'}. ', '. $dtend_obj->{'year'}. ' '.
-							$dtend_obj->{'timeshort'};
+						    $dtend_obj->{'week'}.' '.
+						    $dtend_obj->{'month'}.' '.
+						    $dtend_obj->{'day'}.', '.
+						    $dtend_obj->{'year'}.' '.
+						    $dtend_obj->{'timeshort'};
 						}
 					# Timezone original
 					if ($dtstart_obj->{'tz'}) {
-						$event{'dtwhen'} .= " ($dtstart_obj->{'tz'})";
+						$event{'dtwhen'} .=
+						    " ($dtstart_obj->{'tz'})";
 						}
 				}
 			}

From 4014293760fda67b09652be34aa2f0ca475787a2 Mon Sep 17 00:00:00 2001
From: Ilia Ross 
Date: Sun, 9 Jun 2024 01:55:56 +0300
Subject: [PATCH 12/20] Fix to resize embedding iframe for content to fit on
 view details

---
 mailboxes/view_mail.cgi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi
index a29e0f8e3..9dfda1cf2 100755
--- a/mailboxes/view_mail.cgi
+++ b/mailboxes/view_mail.cgi
@@ -282,7 +282,7 @@ STYLE
         
       
       
- + + + + +
From a780103e2fd8048adc4b391f5db0e105e679adbe Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sun, 9 Jun 2024 02:45:51 +0300 Subject: [PATCH 13/20] Fix to improve calendar styles --- mailboxes/view_mail.cgi | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi index 9dfda1cf2..4fec123a6 100755 --- a/mailboxes/view_mail.cgi +++ b/mailboxes/view_mail.cgi @@ -157,12 +157,19 @@ if (@calendars) { +STYLE + foreach my $calendar (@calendars) { + my $title = $calendar->{'summary'} || $calendar->{'description'}; + my $orginizer = $calendar->{'organizer_name'}; + my @attendees; + foreach my $a (@{$calendar->{'attendees'}}) { + push(@attendees, { name => $a->{'name'}, + email => $a->{'email'} }); + } + my $who = join(", ", map { $_->{'name'} } @attendees); + if ($who && $orginizer) { + $who .= ", ${orginizer}*"; + } + elsif ($orginizer) { + $who = "${orginizer}*"; + } + # HTML version + $calendars->{'html'} .= < +
+
+
+ $calendar->{'_obj_dtstart_local_time'}->{'month'} +
+
+ $calendar->{'_obj_dtstart_local_time'}->{'day'} +
+
+ $calendar->{'_obj_dtstart_local_time'}->{'week'} +
+
+
+ + + + + + + + + + + + + + + + +
+ $title +
+ $text{'view_ical_when'} + $calendar->{'dtwhen_local'}
+ $text{'view_ical_where'} + $calendar->{'location'}
+ $text{'view_ical_who'} + $who
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ $text{'view_ical_orginizertime'} + $calendar->{'dtwhen'}
+ $text{'view_ical_orginizername'} + $calendar->{'organizer_name'}
+ $text{'view_ical_orginizeremail'} + $calendar->{'organizer_email'}
+ $text{'view_ical_attendees'} + @{[join('', map { + "

$_->{'name'}
$_->{'email'}

" + } @attendees)]}
+ $text{'view_ical_desc'} + @{[join('
', + @{$calendar->{'description'}})]}
+
+
+HTML + # Text version + my %textical = ( + 'view_ical' => $title, + 'view_ical_when' => $calendar->{'dtwhen_local'}, + 'view_ical_where' => $calendar->{'location'}, + 'view_ical_who' => $who + ); + my $max_label_length = 0; + foreach my $key (sort keys %textical) { + my $label_length = length($text{$key}); + if ($label_length > $max_label_length) { + $max_label_length = $label_length; + } + } + $calendars->{'text'} = "=" x 79 . "\n"; + foreach my $key (sort keys %textical) { + my $label = $text{$key}; + my $value = $textical{$key}; + my $spaces .= " " x ($max_label_length - length($label)); + $calendars->{'text'} .= "$label$spaces : $value\n"; + } + $calendars->{'text'} .= "=" x 79 . "\n"; + } + } +return $calendars; +} + 1; diff --git a/mailboxes/view_mail.cgi b/mailboxes/view_mail.cgi index 5741e4768..6197226c7 100755 --- a/mailboxes/view_mail.cgi +++ b/mailboxes/view_mail.cgi @@ -150,210 +150,7 @@ print &ui_table_end(); # Show body attachment, with properly linked URLs my $bodycontents; my @bodyright = ( ); -my $calendars = { }; -if (@calendars) { - # CSS for HTML version - $calendars->{'html'} .= < -.calendar-table { - width: 100%; - table-layout: fixed; - border-collapse: collapse; - border: 1px solid #99999933; - margin-bottom: 4px; - } - .calendar-table-inner { - table-layout: fixed; - border-collapse: collapse; - } - .calendar-table td { - padding: 5px; - vertical-align: top; - overflow-wrap: anywhere; - } - .calendar-table .calendar-cell { - background-color: #99999916; - text-align: center; - vertical-align: top; - padding: 2px; - padding-top: 24px; - padding-bottom: 24px; - width: 100px; - min-width: 100px; - font-weight: bold; - } - .calendar-month { - font-size: 21px; - color: #1d72ff; - text-align: center; - padding: 2px 8px; - } - .calendar-day { - font-size: 24px; - text-align: center; - padding: 4px 8px; - } - .calendar-week { - font-size: 16px; - border-top: 1px dotted #999999aa; - padding: 6px; - display: inline-block; - } - .calendar-details h2 { - margin: 0; - font-size: 18px; - } - .calendar-details p { - margin: 4px 0; - } - .calendar-details .title { - font-size: 20px; - } - .calendar-details .detail strong { - opacity: 0.66; - white-space: nowrap; - } - .calendar-details .detail + .desc p:first-child { - margin-top: 0; - } - details.calendar-details { - font-size: 90%; - display: inline-block; - margin-left: 9px; - } - details.calendar-details summary { - cursor: help; - } - details.calendar-details tr:has(>.detail+td:empty), - .calendar-details tr:has(>.detail+td:empty) { - display: none; - } - -STYLE - foreach my $calendar (@calendars) { - my $title = $calendar->{'summary'} || $calendar->{'description'}; - my $orginizer = $calendar->{'organizer_name'}; - my @attendees; - foreach my $a (@{$calendar->{'attendees'}}) { - push(@attendees, { name => $a->{'name'}, - email => $a->{'email'} }); - } - my $who = join(", ", map { $_->{'name'} } @attendees); - if ($who && $orginizer) { - $who .= ", ${orginizer}*"; - } - elsif ($orginizer) { - $who = "${orginizer}*"; - } - # HTML version - $calendars->{'html'} .= < - - -
-
- $calendar->{'_obj_dtstart_local_time'}->{'month'} -
-
- $calendar->{'_obj_dtstart_local_time'}->{'day'} -
-
- $calendar->{'_obj_dtstart_local_time'}->{'week'} -
-
- - - - - - - - - - - - - - - - - - -
- $title -
- $text{'view_ical_when'} - $calendar->{'dtwhen_local'}
- $text{'view_ical_where'} - $calendar->{'location'}
- $text{'view_ical_who'} - $who
-
- - - - - - - - - - - - - - - - - - - - - - -
- $text{'view_ical_orginizertime'} - $calendar->{'dtwhen'}
- $text{'view_ical_orginizername'} - $calendar->{'organizer_name'}
- $text{'view_ical_orginizeremail'} - $calendar->{'organizer_email'}
- $text{'view_ical_attendees'} - @{[join('', map { - "

$_->{'name'}
$_->{'email'}

" - } @attendees)]}
- $text{'view_ical_desc'} - @{[join('
', - @{$calendar->{'description'}})]}
-
- - - -HTML - # Text version - my %textical = ( - 'view_ical' => $title, - 'view_ical_when' => $calendar->{'dtwhen_local'}, - 'view_ical_where' => $calendar->{'location'}, - 'view_ical_who' => $who - ); - my $max_label_length = 0; - foreach my $key (sort keys %textical) { - my $label_length = length($text{$key}); - if ($label_length > $max_label_length) { - $max_label_length = $label_length; - } - } - $calendars->{'text'} = "=" x 79 . "\n"; - foreach my $key (sort keys %textical) { - my $label = $text{$key}; - my $value = $textical{$key}; - my $spaces .= " " x ($max_label_length - length($label)); - $calendars->{'text'} .= "$label$spaces : $value\n"; - } - $calendars->{'text'} .= "=" x 79 . "\n"; - } - } +my $calendars = &get_calendar_data(\@calendars); if ($body && $body->{'data'} =~ /\S/) { if ($body eq $textbody) { # Show plain text From 0221a092b95fa5951d83dbf5c32cfdf9efa3aa5f Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Sun, 9 Jun 2024 01:53:36 +0300 Subject: [PATCH 20/20] Drop duplicate code https://github.com/webmin/webmin/pull/2193#discussion_r1632362334 --- mailboxes/mailboxes-lib.pl | 332 ------------------------------------- 1 file changed, 332 deletions(-) diff --git a/mailboxes/mailboxes-lib.pl b/mailboxes/mailboxes-lib.pl index 9638ec9d3..00f560ec4 100755 --- a/mailboxes/mailboxes-lib.pl +++ b/mailboxes/mailboxes-lib.pl @@ -1286,336 +1286,4 @@ if (!glob("\Q$rv\E.*")) { return $rv; } -# parse_calendar_file(calendar-file|lines) -# Parses an iCalendar file and returns a list of events -sub parse_calendar_file -{ -my ($calendar_file) = @_; -my (@events, %event, $line); -eval "use DateTime; use DateTime::TimeZone;"; -return \@events if ($@); -# Timezone map -my %timezone_map = ( - 'Afghanistan Time' => 'AFT', - 'Alaskan Daylight Time' => 'AKDT', - 'Alaskan Standard Time' => 'AKST', - 'Anadyr Time' => 'ANAT', - 'Arabian Standard Time' => 'AST', - 'Argentina Time' => 'ART', - 'Atlantic Daylight Time' => 'ADT', - 'Atlantic Standard Time' => 'AST', - 'Australian Central Daylight Time' => 'ACDT', - 'Australian Central Standard Time' => 'ACST', - 'Australian Eastern Daylight Time' => 'AEDT', - 'Australian Eastern Standard Time' => 'AEST', - 'Bangladesh Standard Time' => 'BST', - 'Brasília Time' => 'BRT', - 'British Summer Time' => 'BST', - 'Central Africa Time' => 'CAT', - 'Central Asia Time' => 'ALMT', - 'Central Daylight Time' => 'CDT', - 'Central Daylight Time (US)' => 'CDT', - 'Central European Summer Time' => 'CEST', - 'Central European Time' => 'CET', - 'Central Indonesia Time' => 'WITA', - 'Central Standard Time (Australia)' => 'CST', - 'Central Standard Time (US)' => 'CST', - 'Central Standard Time' => 'CST', - 'Chamorro Daylight Time' => 'CHDT', - 'Chamorro Standard Time' => 'CHST', - 'China Standard Time' => 'CST', - 'Coordinated Universal Time' => 'UTC', - 'East Africa Time' => 'EAT', - 'Eastern Africa Time' => 'EAT', - 'Eastern Daylight Time' => 'EDT', - 'Eastern Daylight Time (US)' => 'EDT', - 'Eastern European Summer Time' => 'EEST', - 'Eastern European Time' => 'EET', - 'Eastern Indonesia Time' => 'WIT', - 'Eastern Standard Time (Australia)' => 'EST', - 'Eastern Standard Time (US)' => 'EST', - 'Eastern Standard Time' => 'EST', - 'Fiji Time' => 'FJT', - 'Greenwich Mean Time' => 'GMT', - 'Hawaii-Aleutian Daylight Time' => 'HADT', - 'Hawaii-Aleutian Standard Time' => 'HAST', - 'Hawaiian Standard Time' => 'HST', - 'Hong Kong Time' => 'HKT', - 'Indian Standard Time' => 'IST', - 'Iran Standard Time' => 'IRST', - 'Irish Standard Time' => 'IST', - 'Israel Standard Time' => 'IST', - 'Japan Standard Time' => 'JST', - 'Korea Standard Time' => 'KST', - 'Magadan Time' => 'MAGT', - 'Malaysia Time' => 'MYT', - 'Moscow Standard Time' => 'MSK', - 'Mountain Daylight Time' => 'MDT', - 'Mountain Standard Time' => 'MST', - 'Myanmar Standard Time' => 'MMT', - 'Nepal Time' => 'NPT', - 'New Caledonia Time' => 'NCT', - 'New Zealand Daylight Time' => 'NZDT', - 'New Zealand Standard Time' => 'NZST', - 'Newfoundland Daylight Time' => 'NDT', - 'Newfoundland Standard Time' => 'NST', - 'Pacific Daylight Time' => 'PDT', - 'Pacific Standard Time' => 'PST', - 'Pakistan Standard Time' => 'PKT', - 'Philippine Time' => 'PHT', - 'Sakhalin Time' => 'SAKT', - 'Samoa Standard Time' => 'SST', - 'Singapore Standard Time' => 'SGT', - 'South Africa Standard Time' => 'SAST', - 'Tahiti Time' => 'TAHT', - 'Venezuelan Standard Time' => 'VET', - 'West Africa Time' => 'WAT', - 'Western European Summer Time' => 'WEST', - 'Western European Time' => 'WET', - 'Western Indonesia Time' => 'WIB', - 'Western Standard Time (Australia)' => 'WST', -); -# Make a date from a special timestamp -my $adjust_time_with_timezone = sub { - my ($time, $tzid) = @_; - my $dt = DateTime->new( - year => substr($time, 0, 4), - month => substr($time, 4, 2), - day => substr($time, 6, 2), - hour => substr($time, 9, 2), - minute => substr($time, 11, 2), - second => substr($time, 13, 2), - time_zone => $tzid); - my $local_dt = $dt->clone->set_time_zone('local'); - return { - formatted => $dt->strftime("%Y-%m-%d %H:%M:%S"), - timestamp => $dt->epoch, - formatted_local => $local_dt->strftime('%Y-%m-%d %H:%M:%S'), - timestamp_local => $local_dt->epoch, - }; -}; -# Lines processor -my $process_line = sub -{ -my ($line) = @_; -# Start a new event -if ($line =~ /^BEGIN:VEVENT/) { - %event = (); - $event{'description'} = [ ]; - $event{'attendees'} = [ ]; - } -# Convert times using the timezone -elsif ($line =~ /^END:VEVENT/) { - # Local timezone - $event{'tzid_local'} = DateTime::TimeZone->new(name => 'local')->name(); - $event{'tzid'} = 'UTC', $event{'tzid_missing'} = 1 if (!$event{'tzid'}); - # Adjust times with timezone - my ($adjusted_start, $adjusted_end); - $event{'tzid'} = $timezone_map{$event{'tzid'}} || $event{'tzid'}; - # Add single start/end time - if ($event{'dtstart'}) { - $adjusted_start = - $adjust_time_with_timezone->($event{'dtstart'}, - $event{'tzid'}); - $event{'dtstart_timestamp'} = $adjusted_start->{'timestamp'}; - my $dtstart_date = - &make_date($event{'dtstart_timestamp'}, - { tz => $event{'tzid'} }); - $event{'dtstart_date'} = - "$dtstart_date->{'short'} $dtstart_date->{'timeshort'}"; - $event{'dtstart_local_timestamp'} = - $adjusted_start->{'timestamp_local'}; - $event{'dtstart_local_date'} = - &make_date($event{'dtstart_local_timestamp'}); - } - if ($event{'dtend'}) { - $adjusted_end = - $adjust_time_with_timezone->($event{'dtend'}, $event{'tzid'}); - $event{'dtend_timestamp'} = $adjusted_end->{'timestamp'}; - my $dtend_date = &make_date($event{'dtend_timestamp'}, - { tz => $event{'tzid'} }); - $event{'dtend_date'} = - "$dtend_date->{'short'} $dtend_date->{'timeshort'}"; - $event{'dtend_local_timestamp'} = - $adjusted_end->{'timestamp_local'}; - $event{'dtend_local_date'} = - &make_date($event{'dtend_local_timestamp'}); - } - if ($event{'dtstart'} && $event{'dtend'}) { - # Try to add local 'when (period)' - my $dtstart_local_obj = - $event{'_obj_dtstart_local_time'} = - make_date($event{'dtstart_local_timestamp'}, { _ }); - my $dtend_local_obj = - $event{'_obj_dtend_local_time'} = - make_date($event{'dtend_local_timestamp'}, { _ }); - # Build when local, e.g.: - # Tue Jun 04, 2024 04:30 PM – 05:15 - # PM (Asia/Nicosia +0300) - # or - # Tue Jun 04, 2024 04:30 PM – Wed Jun 05, 2024 01:15 - # AM (Asia/Nicosia +0300) - $event{'dtwhen_local'} = - # Start local - $dtstart_local_obj->{'week'}.' '. - $dtstart_local_obj->{'month'}.' '. - $dtstart_local_obj->{'day'}.', '. - $dtstart_local_obj->{'year'}.' '. - $dtstart_local_obj->{'timeshort'}.' – '; - # End local - if ($dtstart_local_obj->{'year'} eq - $dtend_local_obj->{'year'} && - $dtstart_local_obj->{'month'} eq - $dtend_local_obj->{'month'} && - $dtstart_local_obj->{'day'} eq - $dtend_local_obj->{'day'}) { - $event{'dtwhen_local'} .= - $dtend_local_obj->{'timeshort'}; - } - else { - $event{'dtwhen_local'} .= - $dtend_local_obj->{'week'}.' '. - $dtend_local_obj->{'month'}.' '. - $dtend_local_obj->{'day'}.', '. - $dtend_local_obj->{'year'}.' '. - $dtend_local_obj->{'timeshort'}; - } - # Timezone local - if ($event{'tzid_local'} || - $dtstart_local_obj->{'tz'}) { - if ($event{'tzid_local'} && - $dtstart_local_obj->{'tz'}) { - if ($event{'tzid_local'} eq - $dtstart_local_obj->{'tz'}) { - $event{'dtwhen_local'} .= - " ($event{'tzid_local'})"; - } - else { - $event{'dtwhen_local'} .= - " ($event{'tzid_local'} ". - "$dtstart_local_obj->{'tz'})"; - } - } - elsif ($event{'tzid_local'}) { - $event{'dtwhen_local'} .= - " ($event{'tzid_local'})"; - } - else { - $event{'dtwhen_local'} .= - " ($dtstart_local_obj->{'tz'})"; - } - } - # Try to add original 'when (period)' - my $dtstart_obj = - $event{'_obj_dtstart_time'} = - make_date($event{'dtstart_timestamp'}, - { tz => $event{'tzid'} }); - my $dtend_obj = - $event{'_obj_dtend_time'} = - make_date($event{'dtend_timestamp'}, - { tz => $event{'tzid'} }); - # Build original when - if (!$event{'tzid_missing'}) { - $event{'dtwhen'} = - # Start original - $dtstart_obj->{'week'}.' '. - $dtstart_obj->{'month'}.' '. - $dtstart_obj->{'day'}.', '. - $dtstart_obj->{'year'}.' '. - $dtstart_obj->{'timeshort'}.' – '; - # End original - if ($dtstart_obj->{'year'} eq - $dtend_obj->{'year'} && - $dtstart_obj->{'month'} eq - $dtend_obj->{'month'} && - $dtstart_obj->{'day'} eq - $dtend_obj->{'day'}) { - $event{'dtwhen'} .= - $dtend_obj->{'timeshort'}; - } - else { - $event{'dtwhen'} .= - $dtend_obj->{'week'}.' '. - $dtend_obj->{'month'}.' '. - $dtend_obj->{'day'}.', '. - $dtend_obj->{'year'}.' '. - $dtend_obj->{'timeshort'}; - } - # Timezone original - if ($dtstart_obj->{'tz'}) { - $event{'dtwhen'} .= - " ($dtstart_obj->{'tz'})"; - } - } - } - # Add the event to the list - push(@events, { %event }); - } -# Parse fields -elsif ($line =~ /^SUMMARY.*?:(.*)$/) { - $event{'summary'} = $1; - } -elsif ($line =~ /^DTSTART:(.*)$/) { - $event{'dtstart'} = $1; - } -elsif ($line =~ /^DTSTART;TZID=(.*?):(.*)$/) { - $event{'tzid'} = $1; - $event{'dtstart'} = $2; - } -elsif ($line =~ /^DTEND:(.*)$/) { - $event{'dtend'} = $1; - } -elsif ($line =~ /^DTEND;TZID=(.*?):(.*)$/) { - $event{'tzid'} = $1; - $event{'dtend'} = $2; - } -elsif ($line =~ /^DESCRIPTION:(.*)$/) { - my $description = $1; - $description =~ s/\\n/
/g; - $description =~ s/\\//g; - unshift(@{$event{'description'}}, $description); - } -elsif ($line =~ /^DESCRIPTION;LANGUAGE=([a-z]{2}-[A-Z]{2}):(.*)$/) { - my $description = $2; - $description =~ s/\\n/
/g; - $description =~ s/\\//g; - unshift(@{$event{'description'}}, $description); - } -elsif ($line =~ /^LOCATION.*?:(.*)$/) { - $event{'location'} = $1; - } -elsif ($line =~ /^ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=(.*?):mailto:(.*)$/ || - $line =~ /^ATTENDEE;.*CN=(.*?);.*mailto:(.*)$/ || - $line =~ /^ATTENDEE:mailto:(.*)$/) { - push(@{$event{'attendees'}}, { 'name' => $1, 'email' => $2 }); - } -elsif ($line =~ /^ORGANIZER;CN=(.*?):(?:mailto:)?(.*)$/) { - $event{'organizer_name'} = $1; - $event{'organizer_email'} = $2; - } -}; -# Read the ICS file lines or just use the lines -my $ics_file_lines = - -r $calendar_file ? - &read_file_lines($calendar_file, 1) : - [ split(/\r?\n/, $calendar_file) ]; -# Process each line of the ICS file -foreach my $ics_file_line (@$ics_file_lines) { - # Check if the line is a continuation of the previous line - if ($ics_file_line =~ /^[ \t](.*)$/) { - $line .= $1; # Concatenate with the previous line - } - else { - # Process the previous line - $process_line->($line) if ($line); - $line = $ics_file_line; # Start a new line - } - } -# Process the last line -$process_line->($line) if ($line); -# Return the list of events -return \@events; -} - 1; -