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