import rt 3.8.10
[freeside.git] / rt / lib / RT / Dashboard.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 #                                          <sales@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 =head1 NAME
50
51   RT::Dashboard - an API for saving and retrieving dashboards
52
53 =head1 SYNOPSIS
54
55   use RT::Dashboard
56
57 =head1 DESCRIPTION
58
59   Dashboard is an object that can belong to either an RT::User or an
60   RT::Group.  It consists of an ID, a name, and a number of
61   saved searches and portlets.
62
63 =head1 METHODS
64
65
66 =cut
67
68 package RT::Dashboard;
69
70 use RT::SavedSearch;
71
72 use strict;
73 use warnings;
74 use base qw/RT::SharedSetting/;
75
76 use RT::System;
77 RT::System::AddRights(
78     SubscribeDashboard => 'Subscribe to dashboards', #loc_pair
79
80     SeeDashboard       => 'View system dashboards', #loc_pair
81     CreateDashboard    => 'Create system dashboards', #loc_pair
82     ModifyDashboard    => 'Modify system dashboards', #loc_pair
83     DeleteDashboard    => 'Delete system dashboards', #loc_pair
84
85     SeeOwnDashboard    => 'View personal dashboards', #loc_pair
86     CreateOwnDashboard => 'Create personal dashboards', #loc_pair
87     ModifyOwnDashboard => 'Modify personal dashboards', #loc_pair
88     DeleteOwnDashboard => 'Delete personal dashboards', #loc_pair
89 );
90
91
92 =head2 ObjectName
93
94 An object of this class is called "dashboard"
95
96 =cut
97
98 sub ObjectName { "dashboard" }
99
100 sub SaveAttribute {
101     my $self   = shift;
102     my $object = shift;
103     my $args   = shift;
104
105     return $object->AddAttribute(
106         'Name'        => 'Dashboard',
107         'Description' => $args->{'Name'},
108         'Content'     => {Panes => $args->{'Panes'}},
109     );
110 }
111
112 sub UpdateAttribute {
113     my $self = shift;
114     my $args = shift;
115
116     my ($status, $msg) = (1, undef);
117     if (defined $args->{'Panes'}) {
118         ($status, $msg) = $self->{'Attribute'}->SetSubValues(
119             Panes => $args->{'Panes'},
120         );
121     }
122
123     if ($status && $args->{'Name'}) {
124         ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'})
125             unless $self->Name eq $args->{'Name'};
126     }
127
128     if ($status && $args->{'Privacy'}) {
129         my ($new_obj_type, $new_obj_id) = split /-/, $args->{'Privacy'};
130         my ($obj_type, $obj_id) = split /-/, $self->Privacy;
131
132         my $attr = $self->{'Attribute'};
133         if ($new_obj_type ne $obj_type) {
134             ($status, $msg) = $attr->SetObjectType($new_obj_type);
135         }
136         if ($status && $new_obj_id != $obj_id ) {
137             ($status, $msg) = $attr->SetObjectId($new_obj_id);
138         }
139         $self->{'Privacy'} = $args->{'Privacy'} if $status;
140     }
141
142     return ($status, $msg);
143 }
144
145 =head2 Panes
146
147 Returns a hashref of pane name to portlets
148
149 =cut
150
151 sub Panes {
152     my $self = shift;
153     return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
154     return $self->{'Attribute'}->SubValue('Panes') || {};
155 }
156
157 =head2 Portlets
158
159 Returns the list of this dashboard's portlets, each a hashref with key
160 C<portlet_type> being C<search> or C<component>.
161
162 =cut
163
164 sub Portlets {
165     my $self = shift;
166     return map { @$_ } values %{ $self->Panes };
167 }
168
169 =head2 Dashboards
170
171 Returns a list of loaded sub-dashboards
172
173 =cut
174
175 sub Dashboards {
176     my $self = shift;
177     return map {
178         my $search = RT::Dashboard->new($self->CurrentUser);
179         $search->LoadById($_->{id});
180         $search
181     } grep { $_->{portlet_type} eq 'dashboard' } $self->Portlets;
182 }
183
184 =head2 Searches
185
186 Returns a list of loaded saved searches
187
188 =cut
189
190 sub Searches {
191     my $self = shift;
192     return map {
193         my $search = RT::SavedSearch->new($self->CurrentUser);
194         $search->Load($_->{privacy}, $_->{id});
195         $search
196     } grep { $_->{portlet_type} eq 'search' } $self->Portlets;
197 }
198
199 =head2 ShowSearchName Portlet
200
201 Returns an array for one saved search, suitable for passing to
202 /Elements/ShowSearch.
203
204 =cut
205
206 sub ShowSearchName {
207     my $self = shift;
208     my $portlet = shift;
209
210     if ($portlet->{privacy} eq 'RT::System') {
211         return Name => $portlet->{description};
212     }
213
214     return SavedSearch => join('-', $portlet->{privacy}, 'SavedSearch', $portlet->{id});
215 }
216
217 =head2 PossibleHiddenSearches
218
219 This will return a list of saved searches that are potentially not visible by
220 all users for whom the dashboard is visible. You may pass in a privacy to
221 use instead of the dashboard's privacy.
222
223 =cut
224
225 sub PossibleHiddenSearches {
226     my $self = shift;
227     my $privacy = shift || $self->Privacy;
228
229     return grep { !$_->IsVisibleTo($privacy) } $self->Searches, $self->Dashboards;
230 }
231
232 # _PrivacyObjects: returns a list of objects that can be used to load
233 # dashboards from. If the Modify parameter is true, then check modify rights.
234 # If the Create parameter is true, then check create rights. Otherwise, check
235 # read rights.
236
237 sub _PrivacyObjects {
238     my $self = shift;
239     my %args = @_;
240
241     my $CurrentUser = $self->CurrentUser;
242     my @objects;
243
244     my $prefix = $args{Modify} ? "Modify"
245                : $args{Create} ? "Create"
246                                : "See";
247
248     push @objects, $CurrentUser->UserObj
249         if $self->CurrentUser->HasRight(
250             Right  => "${prefix}OwnDashboard",
251             Object => $RT::System,
252         );
253
254     my $groups = RT::Groups->new($CurrentUser);
255     $groups->LimitToUserDefinedGroups;
256     $groups->WithMember( PrincipalId => $CurrentUser->Id,
257                          Recursively => 1 );
258
259     push @objects, grep {
260         $self->CurrentUser->HasRight(
261             Right  => "${prefix}GroupDashboard",
262             Object => $_,
263         )
264     } @{ $groups->ItemsArrayRef };
265
266     push @objects, RT::System->new($CurrentUser)
267         if $CurrentUser->HasRight(
268             Right  => "${prefix}Dashboard",
269             Object => $RT::System,
270         );
271
272     return @objects;
273 }
274
275 # ACLs
276
277 sub _CurrentUserCan {
278     my $self    = shift;
279     my $privacy = shift || $self->Privacy;
280     my %args    = @_;
281
282     if (!defined($privacy)) {
283         $RT::Logger->debug("No privacy provided to $self->_CurrentUserCan");
284         return 0;
285     }
286
287     my $object = $self->_GetObject($privacy);
288     return 0 unless $object;
289
290     my $level;
291
292        if ($object->isa('RT::User'))   { $level = 'Own' }
293     elsif ($object->isa('RT::Group'))  { $level = 'Group' }
294     elsif ($object->isa('RT::System')) { $level = '' }
295     else {
296         $RT::Logger->error("Unknown object $object from privacy $privacy");
297         return 0;
298     }
299
300     # users are mildly special-cased, since we actually have to check that
301     # the user is operating on himself
302     if ($object->isa('RT::User')) {
303         return 0 unless $object->Id == $self->CurrentUser->Id;
304     }
305
306     my $right = $args{FullRight}
307              || join('', $args{Right}, $level, 'Dashboard');
308
309     # all rights, except group rights, are global
310     $object = $RT::System unless $object->isa('RT::Group');
311
312     return $self->CurrentUser->HasRight(
313         Right  => $right,
314         Object => $object,
315     );
316 }
317
318 sub CurrentUserCanSee {
319     my $self    = shift;
320     my $privacy = shift;
321
322     $self->_CurrentUserCan($privacy, Right => 'See');
323 }
324
325 sub CurrentUserCanCreate {
326     my $self    = shift;
327     my $privacy = shift;
328
329     $self->_CurrentUserCan($privacy, Right => 'Create');
330 }
331
332 sub CurrentUserCanModify {
333     my $self    = shift;
334     my $privacy = shift;
335
336     $self->_CurrentUserCan($privacy, Right => 'Modify');
337 }
338
339 sub CurrentUserCanDelete {
340     my $self    = shift;
341     my $privacy = shift;
342
343     $self->_CurrentUserCan($privacy, Right => 'Delete');
344 }
345
346 sub CurrentUserCanSubscribe {
347     my $self    = shift;
348     my $privacy = shift;
349
350     $self->_CurrentUserCan($privacy, FullRight => 'SubscribeDashboard');
351 }
352
353 RT::Base->_ImportOverlays();
354
355 1;