Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / System.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 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::System
52
53 =head1 DESCRIPTION
54
55 RT::System is a simple global object used as a focal point for things
56 that are system-wide.
57
58 It works sort of like an RT::Record, except it's really a single object that has
59 an id of "1" when instantiated.
60
61 This gets used by the ACL system so that you can have rights for the scope "RT::System"
62
63 In the future, there will probably be other API goodness encapsulated here.
64
65 =cut
66
67
68 package RT::System;
69
70 use strict;
71 use warnings;
72
73 use base qw/RT::Record/;
74
75 use Role::Basic 'with';
76 with "RT::Record::Role::Roles",
77      "RT::Record::Role::Rights" => { -excludes => [qw/AvailableRights RightCategories/] };
78
79 use RT::ACL;
80 use RT::ACE;
81 use Data::GUID;
82
83 __PACKAGE__->AddRight( Admin   => SuperUser           => 'Do anything and everything'); # loc_pair
84 __PACKAGE__->AddRight( Staff   => ShowUserHistory     => 'Show history of public user properties'); # loc_pair
85 __PACKAGE__->AddRight( Admin   => AdminUsers          => 'Create, modify and delete users'); # loc_pair
86 __PACKAGE__->AddRight( Staff   => ModifySelf          => "Modify one's own RT account"); # loc_pair
87 __PACKAGE__->AddRight( Staff   => ShowArticlesMenu    => 'Show Articles menu'); # loc_pair
88 __PACKAGE__->AddRight( Admin   => ShowConfigTab       => 'Show Admin menu'); # loc_pair
89 __PACKAGE__->AddRight( Admin   => ShowApprovalsTab    => 'Show Approvals tab'); # loc_pair
90 __PACKAGE__->AddRight( Staff   => ShowGlobalTemplates => 'Show global templates'); # loc_pair
91 __PACKAGE__->AddRight( General => LoadSavedSearch     => 'Allow loading of saved searches'); # loc_pair
92 __PACKAGE__->AddRight( General => CreateSavedSearch   => 'Allow creation of saved searches'); # loc_pair
93 __PACKAGE__->AddRight( Admin   => ExecuteCode         => 'Allow writing Perl code in templates, scrips, etc'); # loc_pair
94
95 =head2 AvailableRights
96
97 Returns a hashref of available rights for this object.  The keys are the
98 right names and the values are a description of what the rights do.
99
100 This method as well returns rights of other RT objects, like
101 L<RT::Queue> or L<RT::Group>, to allow users to apply those rights
102 globally.
103
104 If an L<RT::Principal> is passed as the first argument, the available
105 rights will be limited to ones which make sense for the principal.
106 Currently only role groups are supported and rights announced by object
107 types to which the role group doesn't apply are not returned.
108
109 =cut
110
111 sub AvailableRights {
112     my $self = shift;
113     my $principal = shift;
114     my $class = ref($self) || $self;
115
116     my @rights;
117     if ($principal and $principal->IsRoleGroup) {
118         my $role = $principal->Object->Name;
119         for my $class (keys %RT::ACE::RIGHTS) {
120             next unless $class->DOES('RT::Record::Role::Roles') and $class->HasRole($role);
121             push @rights, values %{ $RT::ACE::RIGHTS{$class} };
122         }
123     } else {
124         @rights = map {values %{$_}} values %RT::ACE::RIGHTS;
125     }
126
127     my %rights;
128     $rights{$_->{Name}} = $_->{Description} for @rights;
129
130     delete $rights{ExecuteCode} if RT->Config->Get('DisallowExecuteCode');
131
132     return \%rights;
133 }
134
135 =head2 RightCategories
136
137 Returns a hashref where the keys are rights for this type of object and the
138 values are the category (General, Staff, Admin) the right falls into.
139
140 =cut
141
142 sub RightCategories {
143     my $self = shift;
144     my $class = ref($self) || $self;
145
146     my %rights;
147     $rights{$_->{Name}} = $_->{Category}
148         for map {values %{$_}} values %RT::ACE::RIGHTS;
149     return \%rights;
150 }
151
152 sub _Init {
153     my $self = shift;
154     $self->SUPER::_Init (@_) if @_ && $_[0];
155 }
156
157 =head2 id
158
159 Returns RT::System's id. It's 1. 
160
161 =cut
162
163 *Id = \&id;
164 sub id { return 1 }
165
166 sub UID { return "RT::System" }
167
168 =head2 Load
169
170 Since this object is pretending to be an RT::Record, we need a load method.
171 It does nothing
172
173 =cut
174
175 sub Load    { return 1 }
176 sub Name    { return 'RT System' }
177 sub __Set   { return 0 }
178 sub __Value { return 0 }
179 sub Create  { return 0 }
180 sub Delete  { return 0 }
181
182 sub SubjectTag {
183     my $self = shift;
184     my $queue = shift;
185
186     return $queue->SubjectTag if $queue;
187
188     my $queues = RT::Queues->new( $self->CurrentUser );
189     $queues->Limit( FIELD => 'SubjectTag', OPERATOR => 'IS NOT', VALUE => 'NULL' );
190     return $queues->DistinctFieldValues('SubjectTag');
191 }
192
193 =head2 QueueCacheNeedsUpdate ( 1 )
194
195 Attribute to decide when SelectQueue needs to flush the list of queues
196 and retrieve new ones.  Set when queues are created, enabled/disabled
197 and on certain acl changes.  Should also better understand group management.
198
199 If passed a true value, will update the attribute to be the current time.
200
201 =cut
202
203 sub QueueCacheNeedsUpdate {
204     my $self = shift;
205     my $update = shift;
206
207     if ($update) {
208         return $self->SetAttribute(Name => 'QueueCacheNeedsUpdate', Content => time);
209     } else {
210         my $cache = $self->FirstAttribute('QueueCacheNeedsUpdate');
211         return (defined $cache ? $cache->Content : 0 );
212     }
213 }
214
215 =head2 AddUpgradeHistory package, data
216
217 Adds an entry to the upgrade history database. The package can be either C<RT>
218 for core RT upgrades, or the fully qualified name of a plugin. The data must be
219 a hash reference.
220
221 =cut
222
223 sub AddUpgradeHistory {
224     my $self  = shift;
225     my $package = shift;
226     my $data  = shift;
227
228     $data->{timestamp}  ||= time;
229     $data->{rt_version} ||= $RT::VERSION;
230
231     my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory');
232     my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {};
233
234     push @{ $upgrade_history->{$package} }, $data;
235
236     $self->SetAttribute(
237         Name    => 'UpgradeHistory',
238         Content => $upgrade_history,
239     );
240 }
241
242 =head2 UpgradeHistory [package]
243
244 Returns the entries of RT's upgrade history. If a package is specified, the list
245 of upgrades for that package will be returned. Otherwise a hash reference of
246 C<< package => [upgrades] >> will be returned.
247
248 =cut
249
250 sub UpgradeHistory {
251     my $self  = shift;
252     my $package = shift;
253
254     my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory');
255     my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {};
256
257     if ($package) {
258         return @{ $upgrade_history->{$package} || [] };
259     }
260
261     return $upgrade_history;
262 }
263
264 sub ParsedUpgradeHistory {
265     my $self = shift;
266     my $package = shift;
267
268     my $version_status = "Current version: ";
269     if ( $package eq 'RT' ){
270         $version_status .= $RT::VERSION;
271     } elsif ( grep {/$package/} @{RT->Config->Get('Plugins')} ) {
272         no strict 'refs';
273         $version_status .= ${ $package . '::VERSION' };
274     } else {
275         $version_status = "Not currently loaded";
276     }
277
278     my %ids;
279     my @lines;
280
281     my @events = $self->UpgradeHistory( $package );
282     for my $event (@events) {
283         if ($event->{stage} eq 'before' or (($event->{action}||'') eq 'insert' and not $event->{full_id})) {
284             if (not $event->{full_id}) {
285                 # For upgrade done in the 4.1 series without GUIDs
286                 if (($event->{type}||'') eq 'full upgrade') {
287                     $event->{full_id} = $event->{individual_id} = Data::GUID->new->as_string;
288                 } else {
289                     $event->{individual_id} = Data::GUID->new->as_string;
290                     $event->{full_id} = (@lines ? $lines[-1]{full_id} : Data::GUID->new->as_string);
291                 }
292                 $event->{return_value} = [1] if $event->{stage} eq 'after';
293             }
294             if ($ids{$event->{full_id}}) {
295                 my $kids = $ids{$event->{full_id}}{sub_events} ||= [];
296                 # Stitch non-"upgrade"s beneath the previous "upgrade"
297                 if ( @{$kids} and $event->{action} ne 'upgrade' and $kids->[-1]{action} eq 'upgrade') {
298                     push @{ $kids->[-1]{sub_events} }, $event;
299                 } else {
300                     push @{ $kids }, $event;
301                 }
302             } else {
303                 push @lines, $event;
304             }
305             $ids{$event->{individual_id}} = $event;
306         } elsif ($event->{stage} eq 'after') {
307             if (not $event->{individual_id}) {
308                 if (($event->{type}||'') eq 'full upgrade') {
309                     $lines[-1]{end} = $event->{timestamp} if @lines;
310                 } elsif (($event->{type}||'') eq 'individual upgrade') {
311                     $lines[-1]{sub_events}[-1]{end} = $event->{timestamp}
312                         if @lines and @{ $lines[-1]{sub_events} };
313                 }
314             } elsif ($ids{$event->{individual_id}}) {
315                 my $end = $event;
316                 $event = $ids{$event->{individual_id}};
317                 $event->{end} = $end->{timestamp};
318
319                 $end->{return_value} = [ split ', ', $end->{return_value}, 2 ]
320                     if $end->{return_value} and not ref $end->{return_value};
321                 $event->{return_value} = $end->{return_value};
322                 $event->{content} ||= $end->{content};
323             }
324         }
325     }
326
327     return ($version_status, @lines);
328 }
329
330
331 RT::Base->_ImportOverlays();
332
333 1;