This commit was generated by cvs2svn to compensate for changes in r8690,
[freeside.git] / rt / lib / RT / Report / Tickets.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28
29
30 # CONTRIBUTION SUBMISSION POLICY:
31
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::Report::Tickets;
50
51 use base qw/RT::Tickets/;
52 use RT::Report::Tickets::Entry;
53
54 use strict;
55 use warnings;
56
57 sub Groupings {
58     my $self = shift;
59     my %args = (@_);
60     my @fields = map {$_, $_} qw(
61         Status
62         Queue
63     );
64
65     foreach my $type ( qw(Owner Creator LastUpdatedBy Requestor Cc AdminCc Watcher) ) {
66         push @fields, $type.' '.$_, $type.'.'.$_ foreach qw(
67             Name EmailAddress RealName NickName Organization Lang City Country Timezone
68         );
69     }
70
71     push @fields, map {$_, $_} qw(
72         DueDaily DueMonthly DueAnnually
73         ResolvedDaily ResolvedMonthly ResolvedAnnually
74         CreatedDaily CreatedMonthly CreatedAnnually
75         LastUpdatedDaily LastUpdatedMonthly LastUpdatedAnnually
76         StartedDaily StartedMonthly StartedAnnually
77         StartsDaily StartsMonthly StartsAnnually
78     );
79
80     my $queues = $args{'Queues'};
81     if ( !$queues && $args{'Query'} ) {
82         require RT::Interface::Web::QueryBuilder::Tree;
83         my $tree = RT::Interface::Web::QueryBuilder::Tree->new('AND');
84         $tree->ParseSQL( Query => $args{'Query'}, CurrentUser => $self->CurrentUser );
85         $queues = $tree->GetReferencedQueues;
86     }
87
88     if ( $queues ) {
89         my $CustomFields = RT::CustomFields->new( $self->CurrentUser );
90         foreach my $id (keys %$queues) {
91             my $queue = RT::Queue->new( $self->CurrentUser );
92             $queue->Load($id);
93             unless ($queue->id) {
94                 # XXX TODO: This ancient code dates from a former developer
95                 # we have no idea what it means or why cfqueues are so encoded.
96                 $id =~ s/^.'*(.*).'*$/$1/;
97                 $queue->Load($id);
98             }
99             $CustomFields->LimitToQueue($queue->Id);
100         }
101         $CustomFields->LimitToGlobal;
102         while ( my $CustomField = $CustomFields->Next ) {
103             push @fields, "Custom field '". $CustomField->Name ."'", "CF.{". $CustomField->id ."}";
104         }
105     }
106     return @fields;
107 }
108
109 sub Label {
110     my $self = shift;
111     my $field = shift;
112     if ( $field =~ /^(?:CF|CustomField)\.{(.*)}$/ ) {
113         my $cf = $1;
114         return $self->CurrentUser->loc( "Custom field '[_1]'", $cf ) if $cf =~ /\D/;
115         my $obj = RT::CustomField->new( $self->CurrentUser );
116         $obj->Load( $cf );
117         return $self->CurrentUser->loc( "Custom field '[_1]'", $obj->Name );
118     }
119     return $self->CurrentUser->loc($field);
120 }
121
122 sub SetupGroupings {
123     my $self = shift;
124     my %args = (Query => undef, GroupBy => undef, @_);
125
126     $self->FromSQL( $args{'Query'} );
127     my @group_by = ref( $args{'GroupBy'} )? @{ $args{'GroupBy'} } : ($args{'GroupBy'});
128     $self->GroupBy( map { {FIELD => $_} } @group_by );
129
130     # UseSQLForACLChecks may add late joins
131     my $joined = ($self->_isJoined || RT->Config->Get('UseSQLForACLChecks')) ? 1 : 0;
132
133     my @res;
134     push @res, $self->Column( FUNCTION => ($joined? 'DISTINCT COUNT' : 'COUNT'), FIELD => 'id' );
135     push @res, map $self->Column( FIELD => $_ ), @group_by;
136     return @res;
137 }
138
139 sub GroupBy {
140     my $self = shift;
141     my @args = ref $_[0]? @_ : { @_ };
142
143     @{ $self->{'_group_by_field'} ||= [] } = map $_->{'FIELD'}, @args;
144     $_ = { $self->_FieldToFunction( %$_ ) } foreach @args;
145
146     $self->SUPER::GroupBy( @args );
147 }
148
149 sub Column {
150     my $self = shift;
151     my %args = (@_);
152
153     if ( $args{'FIELD'} && !$args{'FUNCTION'} ) {
154         %args = $self->_FieldToFunction( %args );
155     }
156
157     return $self->SUPER::Column( %args );
158 }
159
160 =head2 _DoSearch
161
162 Subclass _DoSearch from our parent so we can go through and add in empty 
163 columns if it makes sense 
164
165 =cut
166
167 sub _DoSearch {
168     my $self = shift;
169     $self->SUPER::_DoSearch( @_ );
170     $self->AddEmptyRows;
171 }
172
173 =head2 _FieldToFunction FIELD
174
175 Returns a tuple of the field or a database function to allow grouping on that 
176 field.
177
178 =cut
179
180 sub _FieldToFunction {
181     my $self = shift;
182     my %args = (@_);
183
184     my $field = $args{'FIELD'};
185
186     if ($field =~ /^(.*)(Daily|Monthly|Annually)$/) {
187         my ($field, $grouping) = ($1, $2);
188         my $alias = $args{'ALIAS'} || 'main';
189         # Pg 8.3 requires explicit casting
190         $field .= '::text' if RT->Config->Get('DatabaseType') eq 'Pg';
191         if ( $grouping =~ /Daily/ ) {
192             $args{'FUNCTION'} = "SUBSTR($alias.$field,1,10)";
193         }
194         elsif ( $grouping =~ /Monthly/ ) {
195             $args{'FUNCTION'} = "SUBSTR($alias.$field,1,7)";
196         }
197         elsif ( $grouping =~ /Annually/ ) {
198             $args{'FUNCTION'} = "SUBSTR($alias.$field,1,4)";
199         }
200     } elsif ( $field =~ /^(?:CF|CustomField)\.{(.*)}$/ ) { #XXX: use CFDecipher method
201         my $cf_name = $1;
202         my $cf = RT::CustomField->new( $self->CurrentUser );
203         $cf->Load($cf_name);
204         unless ( $cf->id ) {
205             $RT::Logger->error("Couldn't load CustomField #$cf_name");
206         } else {
207             my ($ticket_cf_alias, $cf_alias) = $self->_CustomFieldJoin($cf->id, $cf->id, $cf_name);
208             @args{qw(ALIAS FIELD)} = ($ticket_cf_alias, 'Content');
209         }
210     } elsif ( $field =~ /^(?:(Owner|Creator|LastUpdatedBy))(?:\.(.*))?$/ ) {
211         my $type = $1 || '';
212         my $column = $2 || 'Name';
213         my $u_alias = $self->{"_sql_report_${type}_users_${column}"}
214             ||= $self->Join(
215                 TYPE   => 'LEFT',
216                 ALIAS1 => 'main',
217                 FIELD1 => $type,
218                 TABLE2 => 'Users',
219                 FIELD2 => 'id',
220             );
221         @args{qw(ALIAS FIELD)} = ($u_alias, $column);
222     } elsif ( $field =~ /^(?:Watcher|(Requestor|Cc|AdminCc))(?:\.(.*))?$/ ) {
223         my $type = $1 || '';
224         my $column = $2 || 'Name';
225         my $u_alias = $self->{"_sql_report_watcher_users_alias_$type"};
226         unless ( $u_alias ) {
227             my ($g_alias, $gm_alias);
228             ($g_alias, $gm_alias, $u_alias) = $self->_WatcherJoin( $type );
229             $self->{"_sql_report_watcher_users_alias_$type"} = $u_alias;
230         }
231         @args{qw(ALIAS FIELD)} = ($u_alias, $column);
232     }
233     return %args;
234 }
235
236
237 # Override the AddRecord from DBI::SearchBuilder::Unique. id isn't id here
238 # wedon't want to disambiguate all the items with a count of 1.
239 sub AddRecord {
240     my $self = shift;
241     my $record = shift;
242     push @{$self->{'items'}}, $record;
243     $self->{'rows'}++;
244 }
245
246 1;
247
248
249
250 # Gotta skip over RT::Tickets->Next, since it does all sorts of crazy magic we 
251 # don't want.
252 sub Next {
253     my $self = shift;
254     $self->RT::SearchBuilder::Next(@_);
255
256 }
257
258 sub NewItem {
259     my $self = shift;
260     return RT::Report::Tickets::Entry->new($RT::SystemUser); # $self->CurrentUser);
261 }
262
263
264 =head2 AddEmptyRows
265
266 If we're grouping on a criterion we know how to add zero-value rows
267 for, do that.
268
269 =cut
270
271 sub AddEmptyRows {
272     my $self = shift;
273     if ( @{ $self->{'_group_by_field'} || [] } == 1 && $self->{'_group_by_field'}[0] eq 'Status' ) {
274         my %has = map { $_->__Value('Status') => 1 } @{ $self->ItemsArrayRef || [] };
275
276         foreach my $status ( grep !$has{$_}, RT::Queue->new($self->CurrentUser)->StatusArray ) {
277
278             my $record = $self->NewItem;
279             $record->LoadFromHash( {
280                 id     => 0,
281                 status => $status
282             } );
283             $self->AddRecord($record);
284         }
285     }
286 }
287
288 eval "require RT::Report::Tickets_Vendor";
289 if ($@ && $@ !~ qr{^Can't locate RT/Report/Tickets_Vendor.pm}) {
290     die $@;
291 };
292
293 eval "require RT::Report::Tickets_Local";
294 if ($@ && $@ !~ qr{^Can't locate RT/Report/Tickets_Local.pm}) {
295     die $@;
296 };
297
298 1;