From ab9be1d2bfedf205eab7a9d399ef22ee14117f53 Mon Sep 17 00:00:00 2001
From: ivan
Date: Wed, 19 May 2010 02:32:01 +0000
Subject: [PATCH] add RTx::Calendar 0.07
---
rt/etc/RT_Config.pm | 4 +-
rt/etc/RT_Config.pm.in | 4 +-
rt/lib/RTx/Calendar.pm | 230 +++++++++++++++++++++
.../Callbacks/RTx-Calendar/Elements/Header/Head | 2 +
.../RTx-Calendar/Ticket/Elements/Tabs/Default | 19 ++
.../RTx-Calendar/User/Elements/Tabs/Default | 9 +
rt/share/html/Elements/CalendarEvent | 129 ++++++++++++
rt/share/html/Elements/MyCalendar | 78 +++++++
rt/share/html/NoAuth/Calendar/dhandler | 159 ++++++++++++++
rt/share/html/NoAuth/css/calendar.css | 40 ++++
rt/share/html/NoAuth/images/created.png | Bin 0 -> 994 bytes
rt/share/html/NoAuth/images/created_due.png | Bin 0 -> 997 bytes
rt/share/html/NoAuth/images/due.png | Bin 0 -> 936 bytes
rt/share/html/NoAuth/images/reminder.png | Bin 0 -> 921 bytes
rt/share/html/NoAuth/images/resolved.png | Bin 0 -> 229 bytes
rt/share/html/NoAuth/images/started.png | Bin 0 -> 934 bytes
rt/share/html/NoAuth/images/starts.png | Bin 0 -> 935 bytes
rt/share/html/NoAuth/images/starts_due.png | Bin 0 -> 173 bytes
rt/share/html/NoAuth/images/updated.png | Bin 0 -> 191 bytes
rt/share/html/Prefs/Calendar.html | 123 +++++++++++
rt/share/html/Prefs/Elements/CalendarFeed | 68 ++++++
rt/share/html/Search/Calendar.html | 185 +++++++++++++++++
22 files changed, 1046 insertions(+), 4 deletions(-)
create mode 100644 rt/lib/RTx/Calendar.pm
create mode 100644 rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head
create mode 100644 rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
create mode 100644 rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
create mode 100644 rt/share/html/Elements/CalendarEvent
create mode 100644 rt/share/html/Elements/MyCalendar
create mode 100644 rt/share/html/NoAuth/Calendar/dhandler
create mode 100644 rt/share/html/NoAuth/css/calendar.css
create mode 100644 rt/share/html/NoAuth/images/created.png
create mode 100644 rt/share/html/NoAuth/images/created_due.png
create mode 100644 rt/share/html/NoAuth/images/due.png
create mode 100644 rt/share/html/NoAuth/images/reminder.png
create mode 100644 rt/share/html/NoAuth/images/resolved.png
create mode 100644 rt/share/html/NoAuth/images/started.png
create mode 100644 rt/share/html/NoAuth/images/starts.png
create mode 100644 rt/share/html/NoAuth/images/starts_due.png
create mode 100644 rt/share/html/NoAuth/images/updated.png
create mode 100644 rt/share/html/Prefs/Calendar.html
create mode 100644 rt/share/html/Prefs/Elements/CalendarFeed
create mode 100644 rt/share/html/Search/Calendar.html
diff --git a/rt/etc/RT_Config.pm b/rt/etc/RT_Config.pm
index b36a60759..a976fb366 100644
--- a/rt/etc/RT_Config.pm
+++ b/rt/etc/RT_Config.pm
@@ -1346,7 +1346,7 @@ customized homepage ("RT at a glance").
=cut
-Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
=item C<@MasonParameters>
@@ -1778,7 +1778,7 @@ C
=cut
-Set(@Plugins, ());
+Set(@Plugins, (qw(RTx::Calendar)));
=back
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index 03ee5d9f7..564682b62 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -1346,7 +1346,7 @@ customized homepage ("RT at a glance").
=cut
-Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
=item C<@MasonParameters>
@@ -1778,7 +1778,7 @@ C
=cut
-Set(@Plugins, ());
+Set(@Plugins, (qw(RTx::Calendar)));
=back
diff --git a/rt/lib/RTx/Calendar.pm b/rt/lib/RTx/Calendar.pm
new file mode 100644
index 000000000..515bd4810
--- /dev/null
+++ b/rt/lib/RTx/Calendar.pm
@@ -0,0 +1,230 @@
+package RTx::Calendar;
+
+use strict;
+use DateTime;
+use DateTime::Set;
+
+our $VERSION = "0.07";
+
+sub FirstMonday {
+ my ($year, $month) = (shift, shift);
+ my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->subtract( days => 1 ) }
+ );
+
+ my $day = DateTime->new( year => $year, month => $month );
+
+ $day = $set->next($day) while $day->day_of_week != 1;
+ $day;
+
+}
+
+sub LastSunday {
+ my ($year, $month) = (shift, shift);
+ my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
+ );
+
+ my $day = DateTime->last_day_of_month( year => $year, month => $month );
+
+ $day = $set->next($day) while $day->day_of_week != 7;
+ $day;
+}
+
+# we can't use RT::Date::Date because it uses gmtime
+# and we need localtime
+sub LocalDate {
+ my $ts = shift;
+ my ($d,$m,$y) = (localtime($ts))[3..5];
+ sprintf "%4d-%02d-%02d", ($y + 1900), ++$m, $d;
+}
+
+sub DatesClauses {
+ my ($Dates, $begin, $end) = @_;
+
+ my $clauses = "";
+
+ my @DateClauses = map {
+ "($_ >= '" . $begin . "' AND $_ <= '" . $end . "')"
+ } @$Dates;
+ $clauses .= " AND " . " ( " . join(" OR ", @DateClauses) . " ) "
+ if @DateClauses;
+
+ return $clauses
+}
+
+sub FindTickets {
+ my ($CurrentUser, $Query, $Dates, $begin, $end) = @_;
+
+ $Query .= DatesClauses($Dates, $begin, $end)
+ if $begin and $end;
+
+ my $Tickets = RT::Tickets->new($CurrentUser);
+ $Tickets->FromSQL($Query);
+
+ my %Tickets;
+ my %AlreadySeen;
+
+ while ( my $Ticket = $Tickets->Next()) {
+
+ # How to find the LastContacted date ?
+ for my $Date (@$Dates) {
+ my $DateObj = $Date . "Obj";
+ push @{ $Tickets{ LocalDate($Ticket->$DateObj->Unix) } }, $Ticket
+ # if reminder, check it's refering to a ticket
+ unless ($Ticket->Type eq 'reminder' and not $Ticket->RefersTo->First)
+ or $AlreadySeen{ LocalDate($Ticket->$DateObj->Unix) }{ $Ticket }++;
+ }
+ }
+ return %Tickets;
+}
+
+#
+# Take a user object and return the search with Description "calendar" if it exists
+#
+sub SearchDefaultCalendar {
+ my $CurrentUser = shift;
+ my $Description = "calendar";
+
+ # I'm quite sure the loop isn't usefull but...
+ my @Objects = $CurrentUser->UserObj;
+ for my $object (@Objects) {
+ next unless ref($object) eq 'RT::User' && $object->id == $CurrentUser->Id;
+ my @searches = $object->Attributes->Named('SavedSearch');
+ for my $search (@searches) {
+ next if ($search->SubValue('SearchType')
+ && $search->SubValue('SearchType') ne 'Ticket');
+
+ return $search
+ if "calendar" eq $search->Description;
+ }
+ }
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+RTx::Calendar - Calendar for RT due tasks
+
+=head1 VERSION
+
+This document describes version 0.07 of RTx::Calendar
+
+=head1 DESCRIPTION
+
+This RT extension provides a calendar view for your tickets and your
+reminders so you see when is your next due ticket. You can find it in
+the menu Search->Calendar.
+
+There's a portlet to put on your home page (see Prefs/MyRT.html)
+
+You can also enable ics (ICal) feeds for your default calendar and all
+your private searches in Prefs/Calendar.html. Authentication is magic
+number based so that you can give those feeds to other people.
+
+You can find screenshots on
+http://gaspard.mine.nu/dotclear/index.php?tag/rtx-calendar
+
+=head1 INSTALLATION
+
+If you upgrade from 0.02, see next part before.
+
+You need to install those three modules :
+
+ * Date::ICal
+ * Data::ICal
+ * DateTime::Set
+
+Install it like a standard perl module
+
+ perl Makefile.PL
+ make
+ make install
+
+If your RT is not in the default path (/opt/rt3) you must set RTHOME
+before doing the Makefile.PL
+
+=head1 CONFIGURATION
+
+=head2 Base configuration
+
+In RT 3.8 and later, to enable calendar plugin, you must add something
+like that in your etc/RT_SiteConfig.pm :
+
+ Set(@Plugins,(qw(RTx::Calendar)));
+
+To use MyCalendar portlet you must add MyCalendar to
+$HomepageComponents in etc/RT_SiteConfig.pm like that :
+
+ Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+ MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
+
+To enable private searches ICal feeds, you need to give
+CreateSavedSearch and LoadSavedSearch rights to your users.
+
+=head2 Display configuration
+
+You can show the owner in each day box by adding this line to your
+etc/RT_SiteConfig.pm :
+
+ Set($CalendarDisplayOwner, 1);
+
+You can change which fields show up in the popup display when you
+mouse over a date in etc/RT_SiteConfig.pm :
+
+ @CalendarPopupFields = ('Status', 'OwnerObj->Name', 'DueObj->ISO');
+
+=head2 ICAL feed configuration
+
+By default, tickets are todo and reminders event. You can change this
+by setting $RT::ICalTicketType and $RT::ICalReminderType in etc/RT_SiteConfig.pm :
+
+ Set($ICalTicketType, "Data::ICal::Entry::Event");
+ Set($ICalReminderType ,"Data::ICal::Entry::Todo");
+
+=head1 USAGE
+
+A small help section is available in /Prefs/Calendar.html
+
+=head1 UPGRADE FROM 0.02
+
+As I've change directory structure, if you upgrade from 0.02 you need
+to delete old files manually. Go in RTHOME/share/html (by default
+/opt/rt3/share/html) and delete those files :
+
+ rm -rf Callbacks/RTx-Calendar
+ rm Tools/Calendar.html
+
+RTx-Calendar may work without this but it's not very clean.
+
+=head1 BUGS
+
+=over
+
+=item *
+compatible only with RT 3.6 for the moment. If someone need
+compatibility with 3.4 I can work on this. And I will work on 3.7
+compatibility later.
+
+=back
+
+=head1 AUTHORS
+
+Nicolas Chuche Enchuche@barna.beE
+
+Idea borrowed from redmine's calendar (Thanks Jean-Philippe).
+
+=head1 COPYRIGHT
+
+Copyright 2007 by Nicolas Chuche Enchuche@barna.beE
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L
+
+=cut
diff --git a/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head
new file mode 100644
index 000000000..c1f24c2b4
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head
@@ -0,0 +1,2 @@
+
+
diff --git a/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
new file mode 100644
index 000000000..cb46fdaf6
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
@@ -0,0 +1,19 @@
+<%init>
+my $args;
+$args= "?" . $m->comp(
+ '/Elements/QueryString',
+ Query => $ARGS{'Query'} || $session{'CurrentSearchHash'}->{'Query'},
+ Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
+ OrderBy => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'},
+ Order => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'},
+ Page => $ARGS{'Page'} || $session{'CurrentSearchHash'}->{'Page'},
+ Rows => $ARGS{'Rows'},
+ ) if ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'});
+$args ||= '';
+
+$tabs->{'zz'} = { title =>loc("Calendar"),
+ path => "Search/Calendar.html$args" };
+%init>
+<%args>
+$tabs
+%args>
diff --git a/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
new file mode 100644
index 000000000..06413e278
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
@@ -0,0 +1,9 @@
+<%init>
+ $tabs->{'z'} = { title =>loc("Calendar"),
+ path => "Prefs/Calendar.html" };
+%init>
+<%args>
+$tabs
+$current_subtab => undef
+$Searches => undef
+%args>
diff --git a/rt/share/html/Elements/CalendarEvent b/rt/share/html/Elements/CalendarEvent
new file mode 100644
index 000000000..3a6b00bb8
--- /dev/null
+++ b/rt/share/html/Elements/CalendarEvent
@@ -0,0 +1,129 @@
+<%args>
+$Date => undef
+$Object => undef
+$DateTypes => undef
+%args>
+
+
+
+% if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{Resolved}
+% and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
+% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today ) {
+
+
+% } elsif ($DateTypes->{Due} and $DateTypes->{Created}
+% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today ) {
+
+
+% } elsif ($DateTypes->{Starts}
+% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{Due}
+% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{Created}
+% and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{Started}
+% and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $today) {
+
+
+% } elsif ($DateTypes->{LastUpdated}
+% and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) {
+
+
+% }
+
+
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+ <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
+ <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %>
+
+
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+
+ : <% $subject%>
+
+
+%# logic taken from Ticket/Search/Results.tsv
+% foreach my $attr (@display_fields) {
+% my $value;
+%
+% if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
+% $value = '-';
+% } else {
+% my $method = '$Object->'.$attr.'()';
+% $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
+% $value = eval $method;
+% if ($@) {die "Check your CalendarPopupFields config in etc/RT_SiteConfig.pm.
+% for my $t (@{ $Tickets{$date->strftime("%F")} }) {
+<& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+% }
+
+% $date = $set->next($date);
+% }
+
+
+
+
+ &>
+
+<%INIT>
+
+use RTx::Calendar;
+
+my $title = loc("Calendar");
+
+my $rtdate = RT::Date->new($session{'CurrentUser'});
+
+my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
+
+my $today = DateTime->today;
+
+# this line is used to debug MyCalendar
+# $today = DateTime->new(year => 2007, month => 4, day => 11);
+
+my $begin = $today->clone->subtract( days => 3);
+my $end = $today->clone->add( days => 3);
+
+# use this to loop over days until $end
+my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
+);
+
+my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
+ AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' )
+ AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
+my $Format = "__Starts__ __Due__";
+
+if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) {
+ $Format = $Search->SubValue('Format');
+ $Query = $Search->SubValue('Query');
+}
+
+# we search all date types in Format string
+my @Dates = grep { $Format =~ m/__${_}(Relative)?__/ } @DateTypes;
+
+# used to display or not a date in Element/CalendarEvent
+my %DateTypes = map { $_ => 1 } @Dates;
+
+$Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->strftime("%F"));
+
+# print STDERR $Query, "\n";
+
+my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+
+%INIT>
diff --git a/rt/share/html/NoAuth/Calendar/dhandler b/rt/share/html/NoAuth/Calendar/dhandler
new file mode 100644
index 000000000..4b4aa631e
--- /dev/null
+++ b/rt/share/html/NoAuth/Calendar/dhandler
@@ -0,0 +1,159 @@
+<%init>
+
+use Data::ICal;
+use Data::ICal::Entry::Todo;
+use Data::ICal::Entry::Event;
+use Date::ICal;
+
+$RT::ICalTicketType ||= "Data::ICal::Entry::Todo";
+$RT::ICalReminderType ||= "Data::ICal::Entry::Event";
+
+my ($UserId, $SearchId, $MagicNumber);
+my $arg = $m->dhandler_arg;
+
+if ($arg =~ m{^(\d+)@(\d+)/(.*)$}) {
+ $UserId = $1;
+ $SearchId = $2;
+ $MagicNumber = $3;
+} elsif ($arg =~ m{^(\d+)/(.*)}) {
+ $UserId = $1;
+ $MagicNumber = $2;
+} else {
+ Abort("Corrupted URL.");
+}
+
+my $CurrentUser = new RT::CurrentUser();
+$CurrentUser->LoadById($UserId);
+my $user = $CurrentUser->Name;
+
+# if no user, abort
+unless ($CurrentUser->Id) {
+ $RT::Logger->error("No such user id $UserId from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+# verify user has LoadSavedSearch right
+if ($SearchId and not $CurrentUser->HasRight( Right => 'LoadSavedSearch',
+ Object=> $RT::System )) {
+ $RT::Logger->error("not enough rights for user $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+
+# if MagicNumber doesn't match the one stored in database, abort
+my $Search;
+my $ICalAttribute;
+if ($SearchId) {
+ $Search = $CurrentUser->Attributes->WithId($SearchId);
+ $ICalAttribute = $Search->FirstAttribute('ICalURL');
+} else {
+ $ICalAttribute = $CurrentUser->UserObj->FirstAttribute('ICalURL');
+}
+
+unless ($ICalAttribute) {
+ $RT::Logger->error("No such ICal feed for $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+
+if ($MagicNumber ne $ICalAttribute->Content) {
+ $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+my $Tickets = RT::Tickets->new($CurrentUser);
+
+my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
+ AND ( Owner = '" . $CurrentUser->Id ."' OR Owner = 'Nobody' )
+ AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
+
+$Query = $Search->SubValue('Query')
+ if $Search;
+
+$Query .= " AND ( Due > '1970-01-01' OR Starts > '1970-01-01' )";
+
+$Tickets->FromSQL($Query);
+
+$Tickets->OrderBy(FIELD => 'Due', ORDER => 'ASC');
+
+my $calendar = Data::ICal->new();
+
+my ($uid) = $RT::WebURL =~ m{https?://([^:]+)};
+
+while (my $Ticket = $Tickets->Next ) {
+
+ my $event;
+ if ($Ticket->Type eq 'ticket') {
+ $event = add_todo($Ticket, $uid);
+ } else {
+ $event = add_event($Ticket, $uid);
+ }
+ next unless $event;
+ $calendar->add_entry($event);
+}
+
+my $cal = $calendar->as_string;
+
+$r->content_type('text/calendar;charset=utf-8');
+$m->clear_buffer();
+$m->out($cal);
+$m->abort;
+
+sub add_event {
+ my ($Reminder, $uid) = @_;
+
+ return unless defined $Reminder->RefersTo->First;
+ my $Ticket = $Reminder->RefersTo->First->TargetObj;
+
+ my %event = (
+ summary => $Reminder->Subject ? $Reminder->Subject : '',
+ url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
+ uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Reminder->Id . "@" . $uid,
+ categories => $Ticket->QueueObj->Name,
+ dtstart => Date::ICal->new( epoch => $Reminder->DueObj->Unix )->ical,
+ );
+
+ my $event = $RT::ICalReminderType->new();
+ $event->add_properties(%event);
+
+ return $event;
+}
+
+sub add_todo {
+ my ($Ticket, $uid) = @_;
+
+ my %vtodo = (
+ summary => $Ticket->Subject ? $Ticket->Subject : '',
+ dtstart => Date::ICal->new( epoch => $Ticket->CreatedObj->Unix )->ical,
+ url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
+ uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Ticket->Id . "@" . $uid,
+ categories => $Ticket->QueueObj->Name,
+ );
+
+ $vtodo{due} = Date::ICal->new( epoch => $Ticket->DueObj->Unix )->ical,
+ if $Ticket->DueObj;
+
+ if ($Ticket->OwnerObj->Id != $RT::Nobody->Id and $Ticket->OwnerObj->EmailAddress) {
+ $vtodo{organizer} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
+ $vtodo{attendee} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
+ } elsif ($Ticket->QueueObj->CommentAddress) {
+ $vtodo{organizer} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
+ $vtodo{attendee} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
+ }
+
+ $vtodo{priority} = $Ticket->Priority
+ if $Ticket->Priority;
+
+ my $vtodo = $RT::ICalTicketType->new();
+ $vtodo->add_properties(%vtodo);
+
+ return $vtodo;
+}
+
+
+
+%init>
diff --git a/rt/share/html/NoAuth/css/calendar.css b/rt/share/html/NoAuth/css/calendar.css
new file mode 100644
index 000000000..e313dff99
--- /dev/null
+++ b/rt/share/html/NoAuth/css/calendar.css
@@ -0,0 +1,40 @@
+.tooltip{position:relative;z-index:1;}
+.tooltip:hover{z-index:5;color:#000;}
+.tooltip span.tip{display: none; text-align:left;}
+
+div.tooltip:hover span.tip{
+display:block;
+position:absolute;
+top:12px; left:24px; width:350px;
+border:1px solid #555;
+background-color:#fff;
+padding: 4px;
+font-size: 0.8em;
+color:#505050;
+}
+
+.date {
+text-align: right;
+}
+
+table.rtxcalendar {
+ width:100%;
+ border-collapse: collapse;
+ border: 1px dotted #d0d0d0;
+ margin-bottom: 6px;
+}
+
+table.rtxcalendar td {
+ border: 1px solid #d7d7d7;
+ vertical-align: top;
+}
+
+table.rtxcalendar th {
+ border: 1px solid #d7d7d7;
+ background: #eee;
+}
+table.rtxcalendar tbody th {
+ border: 1px solid #d7d7d7;
+ background: #eee;
+ font-weight: normal;
+}
diff --git a/rt/share/html/NoAuth/images/created.png b/rt/share/html/NoAuth/images/created.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d5eeb9ea6903e0cbc23134adb69a62623007a1e
GIT binary patch
literal 994
zcmeAS@N?(olHy`uVBq!ia0vp^AT~P(GmvB}(g_4ox+Sg=CC){ui6xo&c?uz!xv31f
ziA8z}<_dZFWqJxm1_ovdhL%=_23AG}3fcyS1_rZ|iY9_gDe?4mWxvMFE-I&*E_2Ni
zD8yOd5m^kh;{ymYI;}C80Tg6Ocl32+VA$Bt{U?!?fq|JJz$e6&V=Kr1Q7{?;BQ*s2
zBU@sCd4s7W$S?Rm!_(~sUO=9lr;B4q#jWIo1QtetlMJaw3Wkyr2RYa#xL&-+XY0De?4mWxvMFE-EMKQM_s&
zP>8d@BeEE1#|IE*bXsFF11QLn?~tz_78O`%fY(0|PTdfKQ0)e+GvCqhK@yMrsHw
zZb`ce%o|K4L4Lvi8J=!8@B;D_JzX3_DsCkwB&4LUB)YM&8BAvEJjtLaaeyIKBvkhX
Z3j-S)gL;U!lvNA9*GX(gA
zxc&zM1_ls03PwX<V@(X5gcy=QV$l)yTh%5$XpbsF-=(NUU22hZ_
z#M9T6{W>d`xSHvUZPFKkLUNuijv*Y^lM@)2+$;_rIMHz=g~d>EAw#H0tS(Rsi!*~v
T)7DjjK$Q%hu6{1-oD!MVEP;26XNH=O
z_UkNs{FZW>=?fHqLZY57jv*Y^lM@)2+}zkC6i&D>NpLYR88Yyi_&r|+RKVcr>gTe~
HDWM4f1g9EC
literal 0
HcmV?d00001
diff --git a/rt/share/html/NoAuth/images/resolved.png b/rt/share/html/NoAuth/images/resolved.png
new file mode 100644
index 0000000000000000000000000000000000000000..09db36d5a4230d90674b24974b319d2cc8db8d94
GIT binary patch
literal 229
zcmeAS@N?(olHy`uVBq!ia0vp^>_E)M!3HGx`C7_=6id3JuOkD)#(wTUiL5|AXMsm#
zF#`kF2M}g-T4OQ;D9B#o>FdgVos~nn=yIS?iKmNW2*-8ZV~RqKff5ZDi|_Y*
zWOut((woHlUeq{DVH4|OjxvYqIS;(1Da;VeIdsCD>s|P{=}O1N)N=}sO2?lGdg8Go
zIW9ISaMvdf0ZX-t#D?UTr}(7)yS=zBA8tBvaXYu2MY61Al*h!!4YpxB9(F1&v;U?e
VVRq>9i2|Vg44$rjF6*2Ung9y`ONsyh
literal 0
HcmV?d00001
diff --git a/rt/share/html/NoAuth/images/started.png b/rt/share/html/NoAuth/images/started.png
new file mode 100644
index 0000000000000000000000000000000000000000..e177addea3772d9c395cc98f9574280e3a2f7efa
GIT binary patch
literal 934
zcmeAS@N?(olHy`uVBq!ia0vp^AT~P(GmvB}(g_4oEa{HEjtmSN`?>!lvNA9*GX(gA
zxc>kDpJOWrkQ@c0Aus|%VAjh2Z-IG(u_VYZn8D%MjWi&Kv%n*=7?^=RfH0%e8j~47
zLG}_)Usv|)tX$%<3OjEKFad>RJY5_^IIbrrFfhBZStO*mBpEqr8aRl$hI1cbVAf?2
UE#%le8K{oI)78&qol`;+0Q$Hf(EtDd
literal 0
HcmV?d00001
diff --git a/rt/share/html/NoAuth/images/starts.png b/rt/share/html/NoAuth/images/starts.png
new file mode 100644
index 0000000000000000000000000000000000000000..88064ba50bcfccd7909fe6ef51a2cf890fe2b555
GIT binary patch
literal 935
zcmeAS@N?(olHy`uVBq!ia0vp^AT~P(GmvB}(g_4oEa{HEjtmSN`?>!lvNA9*GX(gA
zxc&zM1_ls03PwX<V@(X5gcy=QV$l)yTh%5$XpbsF-=(NUU22hZ_
z#M9T6{W>d`xVqrkb?1N?i9y!W#W95AdU8Sv1Cs%xn&d+tH;c)FTmh~VuR3uuu#_>#
U+zy$c4phkC>FVdQ&MBb@091Y
literal 0
HcmV?d00001
diff --git a/rt/share/html/NoAuth/images/starts_due.png b/rt/share/html/NoAuth/images/starts_due.png
new file mode 100644
index 0000000000000000000000000000000000000000..16a4de46f2e5d79c4c8bb7848d966cf95299fb1d
GIT binary patch
literal 173
zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngk!3HF+w~DI+DVB6cUq=Rpjs4tz5?O(K&H|6f
zVg?4T4!3HGPo$&JjQY`6?zK#qG8~eHcB(eheoCO|{
z#S9EwA3&JVX^qJYpdfpRr>`sfbyhBM9`%xxWgCG)ex5FlAsp9rPj2LG3Xotsa5>@6
zwoQwwrWP`^6-YWiXA1eFnDfA6{p7an_m8HWV%E;9_gU;G^zP`
jZ29(o{;7xNochb~$3v*kODUckXflJRtDnm{r-UW|c(Fiq
literal 0
HcmV?d00001
diff --git a/rt/share/html/Prefs/Calendar.html b/rt/share/html/Prefs/Calendar.html
new file mode 100644
index 000000000..5fbdd2717
--- /dev/null
+++ b/rt/share/html/Prefs/Calendar.html
@@ -0,0 +1,123 @@
+<%args>
+$ChangeURL => undef
+$ResetURL => undef
+$SearchType => 'Ticket'
+$HiddenField => undef
+%args>
+
+<& /Elements/Header, Title => $title &>
+<& /User/Elements/Tabs,
+ current_tab => 'Prefs/Calendar.html',
+ Title => $title
+&>
+
+<&| /Widgets/TitleBox, title => loc('ICal Feeds (ics)') &>
+
+<&| /Widgets/TitleBox, title => 'Help' &>
+
+
displaying reminders :
+
If you want to have reminders in a search you need to go in the /Search/Edit.html"><%loc("Edit Query")%> tab
+of the <%loc("query builder")%> and add something like that :
+
+
+ AND ( Type = 'ticket' OR Type = 'reminder' )
+
+
+
+
displaying other kind of dates :
+
By default RTx::Calendar display Due and Starts dates. You can
+select other kind of events you want with the <%loc("Display
+Columns")%> section in the /Search/Build.html"><%loc("Query
+Builder")%>. The following one will display the two latter and
+LastUpdated dates :
+
+