weekly view for RTx::Calendar, RT#16584
[freeside.git] / rt / lib / RTx / Calendar.pm
1 package RTx::Calendar;
2
3 use strict;
4 use base qw( Exporter );
5 use DateTime;
6 use DateTime::Set;
7
8 our $VERSION = "0.17";
9
10 RT->AddStyleSheets('calendar.css')
11     if RT->can('AddStyleSheets');
12
13 our @EXPORT_OK = qw( FirstDay LastDay LastDayOfWeek );
14
15 sub FirstDay {
16     my ($year, $month, $matchday) = @_;
17     my $set = DateTime::Set->from_recurrence(
18         next => sub { $_[0]->truncate( to => 'day' )->subtract( days => 1 ) }
19     );
20
21     my $day = DateTime->new( year => $year, month => $month );
22
23     $day = $set->next($day) while $day->day_of_week != $matchday;
24     $day;
25
26 }
27
28 sub LastDay {
29     my ($year, $month, $matchday) = @_;
30     my $set = DateTime::Set->from_recurrence(
31         next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
32     );
33
34     my $day = DateTime->last_day_of_month( year => $year, month => $month );
35
36     $day = $set->next($day) while $day->day_of_week != $matchday;
37     $day;
38 }
39
40 sub LastDayOfWeek {
41     my ($year, $month, $day, $matchday) = @_;
42     my $set = DateTime::Set->from_recurrence(
43         next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
44     );
45
46     my $day = DateTime->new( year => $year, month => $month, day => $day );
47
48     $day = $set->next($day) while $day->day_of_week != $matchday;
49     $day;
50
51 }
52
53 # we can't use RT::Date::Date because it uses gmtime
54 # and we need localtime
55 sub LocalDate {
56   my $ts = shift;
57   my ($d,$m,$y) = (localtime($ts))[3..5];
58   sprintf "%4d-%02d-%02d", ($y + 1900), ++$m, $d;
59 }
60
61 sub DatesClauses {
62     my ($Dates, $begin, $end) = @_;
63
64     my $clauses = "";
65
66     my @DateClauses = map {
67         "($_ >= '" . $begin . " 00:00:00' AND $_ <= '" . $end . " 23:59:59')"
68     } @$Dates;
69     $clauses  .= " AND " . " ( " . join(" OR ", @DateClauses) . " ) "
70         if @DateClauses;
71
72     return $clauses
73 }
74
75 sub FindTickets {
76     my ($CurrentUser, $Query, $Dates, $begin, $end) = @_;
77
78     $Query .= DatesClauses($Dates, $begin, $end)
79         if $begin and $end;
80
81     my $Tickets = RT::Tickets->new($CurrentUser);
82     $Tickets->FromSQL($Query);
83
84     my %Tickets;
85     my %AlreadySeen;
86
87     while ( my $Ticket = $Tickets->Next()) {
88
89         # How to find the LastContacted date ?
90         for my $Date (@$Dates) {
91             my $DateObj = $Date . "Obj";
92             push @{ $Tickets{ LocalDate($Ticket->$DateObj->Unix) } }, $Ticket
93                 # if reminder, check it's refering to a ticket
94                 unless ($Ticket->Type eq 'reminder' and not $Ticket->RefersTo->First)
95                     or $AlreadySeen{  LocalDate($Ticket->$DateObj->Unix) }{ $Ticket }++;
96         }
97     }
98     return %Tickets;
99 }
100
101 #
102 # Take a user object and return the search with Description "calendar" if it exists
103 #
104 sub SearchDefaultCalendar {
105     my $CurrentUser = shift;
106     my $Description = "calendar";
107
108     # I'm quite sure the loop isn't usefull but...
109     my @Objects = $CurrentUser->UserObj;
110     for my $object (@Objects) {
111         next unless ref($object) eq 'RT::User' && $object->id == $CurrentUser->Id;
112         my @searches = $object->Attributes->Named('SavedSearch');
113         for my $search (@searches) {
114             next if ($search->SubValue('SearchType')
115                          && $search->SubValue('SearchType') ne 'Ticket');
116
117             return $search
118                 if "calendar" eq $search->Description;
119         }
120     }
121 }
122
123 package RT::Interface::Web::Menu;
124
125 # we should get an add_after method in 4.0.6 (hopefully), but until then
126 # shim this in so I don't copy the code.
127 unless (RT::Interface::Web::Menu->can('add_after')) {
128         *RT::Interface::Web::Menu::add_after = sub {
129             my $self = shift;
130             my $parent = $self->parent;
131             my $sort_order;
132             for my $contemporary ($parent->children) {
133                 if ( $contemporary->key eq $self->key ) {
134                     $sort_order = $contemporary->sort_order + 1;
135                     next;
136                 }
137                 if ( $sort_order ) {
138                     $contemporary->sort_order( $contemporary->sort_order + 1 );
139                 }
140             }
141             $parent->child( @_, sort_order => $sort_order );
142         };
143 }
144
145
146 1;
147
148 __END__
149
150 =head1 NAME
151
152 RTx::Calendar - Calendar for RT due tasks
153
154 =head1 DESCRIPTION
155
156 This RT extension provides a calendar view for your tickets and your
157 reminders so you see when is your next due ticket. You can find it in
158 the menu Search->Calendar.
159
160 There's a portlet to put on your home page (see Prefs/MyRT.html)
161
162 You can also enable ics (ICal) feeds for your default calendar and all
163 your private searches in Prefs/Calendar.html. Authentication is magic
164 number based so that you can give those feeds to other people.
165
166 =head1 INSTALLATION
167
168 If you upgrade from 0.02, see next part before.
169
170 You need to install those two modules :
171
172   * Data::ICal
173   * DateTime::Set
174
175 Install it like a standard perl module
176
177  perl Makefile.PL
178  make
179  make install
180
181 If your RT is not in the default path (/opt/rt3) you must set RTHOME
182 before doing the Makefile.PL
183
184 =head1 CONFIGURATION
185
186 =head2 Base configuration
187
188 In RT 3.8 and later, to enable calendar plugin, you must add something
189 like that in your etc/RT_SiteConfig.pm :
190
191   Set(@Plugins,(qw(RTx::Calendar)));
192
193 To use MyCalendar portlet you must add MyCalendar to
194 $HomepageComponents in etc/RT_SiteConfig.pm like that :
195
196   Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
197      MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
198
199 To enable private searches ICal feeds, you need to give
200 CreateSavedSearch and LoadSavedSearch rights to your users.
201
202 =head2 Display configuration
203
204 You can show the owner in each day box by adding this line to your
205 etc/RT_SiteConfig.pm :
206
207     Set($CalendarDisplayOwner, 1);
208
209 You can change which fields show up in the popup display when you
210 mouse over a date in etc/RT_SiteConfig.pm :
211
212     @CalendarPopupFields = ('Status', 'OwnerObj->Name', 'DueObj->ISO');
213
214 =head2 ICAL feed configuration
215
216 By default, tickets are todo and reminders event. You can change this
217 by setting $RT::ICalTicketType and $RT::ICalReminderType in etc/RT_SiteConfig.pm :
218
219   Set($ICalTicketType,   "Data::ICal::Entry::Event");
220   Set($ICalReminderType ,"Data::ICal::Entry::Todo");
221
222 =head1 USAGE
223
224 A small help section is available in /Prefs/Calendar.html
225
226 =head1 UPGRADE FROM 0.02
227
228 As I've change directory structure, if you upgrade from 0.02 you need
229 to delete old files manually. Go in RTHOME/share/html (by default
230 /opt/rt3/share/html) and delete those files :
231
232   rm -rf Callbacks/RTx-Calendar
233   rm Tools/Calendar.html
234
235 RTx-Calendar may work without this but it's not very clean.
236
237 =head1 BUGS
238
239 All bugs should be reported via
240 L<http://rt.cpan.org/Public/Dist/Display.html?Name=RTx-Calendar>
241 or L<bug-RTx-Calendar@rt.cpan.org>.
242  
243 =head1 AUTHORS
244
245 Best Practical Solutions
246
247 Nicolas Chuche E<lt>nchuche@barna.beE<gt>
248
249 Idea borrowed from redmine's calendar (Thanks Jean-Philippe).
250
251 =head1 COPYRIGHT
252
253 Copyright 2007-2009 by Nicolas Chuche E<lt>nchuche@barna.beE<gt>
254
255 Copyright 2010-2012 by Best Practical Solutions.
256
257 This program is free software; you can redistribute it and/or
258 modify it under the same terms as Perl itself.
259
260 See L<http://www.perl.com/perl/misc/Artistic.html>
261
262 =cut