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