Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Dashboard.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
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
68package RT::Dashboard;
69
84fb5b46
MKG
70use strict;
71use warnings;
72
73use base qw/RT::SharedSetting/;
74
af59614d
MKG
75use RT::SavedSearch;
76
84fb5b46 77use RT::System;
af59614d
MKG
78'RT::System'->AddRight( Staff => SubscribeDashboard => 'Subscribe to dashboards'); # loc_pair
79
80'RT::System'->AddRight( General => SeeDashboard => 'View system dashboards'); # loc_pair
81'RT::System'->AddRight( Admin => CreateDashboard => 'Create system dashboards'); # loc_pair
82'RT::System'->AddRight( Admin => ModifyDashboard => 'Modify system dashboards'); # loc_pair
83'RT::System'->AddRight( Admin => DeleteDashboard => 'Delete system dashboards'); # loc_pair
84
85'RT::System'->AddRight( Staff => SeeOwnDashboard => 'View personal dashboards'); # loc_pair
86'RT::System'->AddRight( Staff => CreateOwnDashboard => 'Create personal dashboards'); # loc_pair
87'RT::System'->AddRight( Staff => ModifyOwnDashboard => 'Modify personal dashboards'); # loc_pair
88'RT::System'->AddRight( Staff => DeleteOwnDashboard => 'Delete personal dashboards'); # loc_pair
89
84fb5b46
MKG
90
91=head2 ObjectName
92
93An object of this class is called "dashboard"
94
95=cut
96
5b0d0914 97sub ObjectName { "dashboard" } # loc
84fb5b46
MKG
98
99sub SaveAttribute {
100 my $self = shift;
101 my $object = shift;
102 my $args = shift;
103
104 return $object->AddAttribute(
105 'Name' => 'Dashboard',
106 'Description' => $args->{'Name'},
107 'Content' => {Panes => $args->{'Panes'}},
108 );
109}
110
111sub UpdateAttribute {
112 my $self = shift;
113 my $args = shift;
114
115 my ($status, $msg) = (1, undef);
116 if (defined $args->{'Panes'}) {
117 ($status, $msg) = $self->{'Attribute'}->SetSubValues(
118 Panes => $args->{'Panes'},
119 );
120 }
121
122 if ($status && $args->{'Name'}) {
123 ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'})
124 unless $self->Name eq $args->{'Name'};
125 }
126
127 if ($status && $args->{'Privacy'}) {
128 my ($new_obj_type, $new_obj_id) = split /-/, $args->{'Privacy'};
129 my ($obj_type, $obj_id) = split /-/, $self->Privacy;
130
131 my $attr = $self->{'Attribute'};
132 if ($new_obj_type ne $obj_type) {
133 ($status, $msg) = $attr->SetObjectType($new_obj_type);
134 }
135 if ($status && $new_obj_id != $obj_id ) {
136 ($status, $msg) = $attr->SetObjectId($new_obj_id);
137 }
138 $self->{'Privacy'} = $args->{'Privacy'} if $status;
139 }
140
141 return ($status, $msg);
142}
143
144=head2 PostLoadValidate
145
146Ensure that the ID corresponds to an actual dashboard object, since it's all
147attributes under the hood.
148
149=cut
150
151sub PostLoadValidate {
152 my $self = shift;
153 return (0, "Invalid object type") unless $self->{'Attribute'}->Name eq 'Dashboard';
154 return 1;
155}
156
157=head2 Panes
158
159Returns a hashref of pane name to portlets
160
161=cut
162
163sub Panes {
164 my $self = shift;
165 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
166 return $self->{'Attribute'}->SubValue('Panes') || {};
167}
168
169=head2 Portlets
170
171Returns the list of this dashboard's portlets, each a hashref with key
172C<portlet_type> being C<search> or C<component>.
173
174=cut
175
176sub Portlets {
177 my $self = shift;
178 return map { @$_ } values %{ $self->Panes };
179}
180
181=head2 Dashboards
182
183Returns a list of loaded sub-dashboards
184
185=cut
186
187sub Dashboards {
188 my $self = shift;
189 return map {
190 my $search = RT::Dashboard->new($self->CurrentUser);
191 $search->LoadById($_->{id});
192 $search
193 } grep { $_->{portlet_type} eq 'dashboard' } $self->Portlets;
194}
195
196=head2 Searches
197
198Returns a list of loaded saved searches
199
200=cut
201
202sub Searches {
203 my $self = shift;
204 return map {
205 my $search = RT::SavedSearch->new($self->CurrentUser);
206 $search->Load($_->{privacy}, $_->{id});
207 $search
208 } grep { $_->{portlet_type} eq 'search' } $self->Portlets;
209}
210
211=head2 ShowSearchName Portlet
212
213Returns an array for one saved search, suitable for passing to
214/Elements/ShowSearch.
215
216=cut
217
218sub ShowSearchName {
219 my $self = shift;
220 my $portlet = shift;
221
222 if ($portlet->{privacy} eq 'RT::System') {
223 return Name => $portlet->{description};
224 }
225
226 return SavedSearch => join('-', $portlet->{privacy}, 'SavedSearch', $portlet->{id});
227}
228
229=head2 PossibleHiddenSearches
230
231This will return a list of saved searches that are potentially not visible by
232all users for whom the dashboard is visible. You may pass in a privacy to
233use instead of the dashboard's privacy.
234
235=cut
236
237sub PossibleHiddenSearches {
238 my $self = shift;
239 my $privacy = shift || $self->Privacy;
240
241 return grep { !$_->IsVisibleTo($privacy) } $self->Searches, $self->Dashboards;
242}
243
244# _PrivacyObjects: returns a list of objects that can be used to load
245# dashboards from. You probably want to use the wrapper methods like
246# ObjectsForLoading, ObjectsForCreating, etc.
247
248sub _PrivacyObjects {
249 my $self = shift;
250
251 my @objects;
252
253 my $CurrentUser = $self->CurrentUser;
254 push @objects, $CurrentUser->UserObj;
255
256 my $groups = RT::Groups->new($CurrentUser);
257 $groups->LimitToUserDefinedGroups;
258 $groups->WithMember( PrincipalId => $CurrentUser->Id,
259 Recursively => 1 );
260 push @objects, @{ $groups->ItemsArrayRef };
261
262 push @objects, RT::System->new($CurrentUser);
263
264 return @objects;
265}
266
267# ACLs
268
269sub _CurrentUserCan {
270 my $self = shift;
271 my $privacy = shift || $self->Privacy;
272 my %args = @_;
273
274 if (!defined($privacy)) {
275 $RT::Logger->debug("No privacy provided to $self->_CurrentUserCan");
276 return 0;
277 }
278
279 my $object = $self->_GetObject($privacy);
280 return 0 unless $object;
281
282 my $level;
283
284 if ($object->isa('RT::User')) { $level = 'Own' }
285 elsif ($object->isa('RT::Group')) { $level = 'Group' }
286 elsif ($object->isa('RT::System')) { $level = '' }
287 else {
288 $RT::Logger->error("Unknown object $object from privacy $privacy");
289 return 0;
290 }
291
292 # users are mildly special-cased, since we actually have to check that
293 # the user is operating on himself
294 if ($object->isa('RT::User')) {
295 return 0 unless $object->Id == $self->CurrentUser->Id;
296 }
297
298 my $right = $args{FullRight}
299 || join('', $args{Right}, $level, 'Dashboard');
300
301 # all rights, except group rights, are global
302 $object = $RT::System unless $object->isa('RT::Group');
303
304 return $self->CurrentUser->HasRight(
305 Right => $right,
306 Object => $object,
307 );
308}
309
310sub CurrentUserCanSee {
311 my $self = shift;
312 my $privacy = shift;
313
314 $self->_CurrentUserCan($privacy, Right => 'See');
315}
316
317sub CurrentUserCanCreate {
318 my $self = shift;
319 my $privacy = shift;
320
321 $self->_CurrentUserCan($privacy, Right => 'Create');
322}
323
324sub CurrentUserCanModify {
325 my $self = shift;
326 my $privacy = shift;
327
328 $self->_CurrentUserCan($privacy, Right => 'Modify');
329}
330
331sub CurrentUserCanDelete {
332 my $self = shift;
333 my $privacy = shift;
334
335 $self->_CurrentUserCan($privacy, Right => 'Delete');
336}
337
338sub CurrentUserCanSubscribe {
339 my $self = shift;
340 my $privacy = shift;
341
342 $self->_CurrentUserCan($privacy, FullRight => 'SubscribeDashboard');
343}
344
345=head2 Subscription
346
347Returns the L<RT::Attribute> representing the current user's subscription
348to this dashboard if there is one; otherwise, returns C<undef>.
349
350=cut
351
352sub Subscription {
353 my $self = shift;
354
355 # no subscription to unloaded dashboards
356 return unless $self->id;
357
358 for my $sub ($self->CurrentUser->UserObj->Attributes->Named('Subscription')) {
359 return $sub if $sub->SubValue('DashboardId') == $self->id;
360 }
361
362 return;
363}
364
365sub ObjectsForLoading {
366 my $self = shift;
367 my %args = (
368 IncludeSuperuserGroups => 1,
369 @_
370 );
371 my @objects;
372
373 # If you've been granted the SeeOwnDashboard global right (which you
374 # could have by way of global user right or global group right), you
375 # get to see your own dashboards
376 my $CurrentUser = $self->CurrentUser;
377 push @objects, $CurrentUser->UserObj
378 if $CurrentUser->HasRight(Object => $RT::System, Right => 'SeeOwnDashboard');
379
380 # Find groups for which: (a) you are a member of the group, and (b)
381 # you have been granted SeeGroupDashboard on (by any means), and (c)
382 # have at least one dashboard
383 my $groups = RT::Groups->new($CurrentUser);
384 $groups->LimitToUserDefinedGroups;
385 $groups->ForWhichCurrentUserHasRight(
386 Right => 'SeeGroupDashboard',
387 IncludeSuperusers => $args{IncludeSuperuserGroups},
388 );
389 $groups->WithMember(
390 Recursively => 1,
391 PrincipalId => $CurrentUser->UserObj->PrincipalId
392 );
393 my $attrs = $groups->Join(
394 ALIAS1 => 'main',
395 FIELD1 => 'id',
396 TABLE2 => 'Attributes',
397 FIELD2 => 'ObjectId',
398 );
399 $groups->Limit(
400 ALIAS => $attrs,
401 FIELD => 'ObjectType',
402 VALUE => 'RT::Group',
403 );
404 $groups->Limit(
405 ALIAS => $attrs,
406 FIELD => 'Name',
407 VALUE => 'Dashboard',
408 );
409 push @objects, @{ $groups->ItemsArrayRef };
410
411 # Finally, if you have been granted the SeeDashboard right (which
412 # you could have by way of global user right or global group right),
413 # you can see system dashboards.
414 push @objects, RT::System->new($CurrentUser)
415 if $CurrentUser->HasRight(Object => $RT::System, Right => 'SeeDashboard');
416
417 return @objects;
418}
419
420sub CurrentUserCanCreateAny {
421 my $self = shift;
422 my @objects;
423
424 my $CurrentUser = $self->CurrentUser;
425 return 1
426 if $CurrentUser->HasRight(Object => $RT::System, Right => 'CreateOwnDashboard');
427
428 my $groups = RT::Groups->new($CurrentUser);
429 $groups->LimitToUserDefinedGroups;
430 $groups->ForWhichCurrentUserHasRight(
431 Right => 'CreateGroupDashboard',
432 IncludeSuperusers => 1,
433 );
434 return 1 if $groups->Count;
435
436 return 1
437 if $CurrentUser->HasRight(Object => $RT::System, Right => 'CreateDashboard');
438
439 return 0;
440}
441
dab09ea8
MKG
442=head2 Delete
443
444Deletes the dashboard and related subscriptions.
445Returns a tuple of status and message, where status is true upon success.
446
447=cut
448
449sub Delete {
450 my $self = shift;
451 my $id = $self->id;
452 my ( $status, $msg ) = $self->SUPER::Delete(@_);
453 if ( $status ) {
454 # delete all the subscriptions
455 my $subscriptions = RT::Attributes->new( RT->SystemUser );
456 $subscriptions->Limit(
457 FIELD => 'Name',
458 VALUE => 'Subscription',
459 );
460 $subscriptions->Limit(
461 FIELD => 'Description',
462 VALUE => "Subscription to dashboard $id",
463 );
464 while ( my $subscription = $subscriptions->Next ) {
465 $subscription->Delete();
466 }
467 }
468
469 return ( $status, $msg );
470}
471
84fb5b46
MKG
472RT::Base->_ImportOverlays();
473
4741;