1# BEGIN BPS TAGGED BLOCK {{{ 2# 3# COPYRIGHT: 4# 5# This software is Copyright (c) 1996-2021 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 51RT::System 52 53=head1 DESCRIPTION 54 55RT::System is a simple global object used as a focal point for things 56that are system-wide. 57 58It works sort of like an RT::Record, except it's really a single object that has 59an id of "1" when instantiated. 60 61This gets used by the ACL system so that you can have rights for the scope "RT::System" 62 63In the future, there will probably be other API goodness encapsulated here. 64 65=cut 66 67 68package RT::System; 69 70use strict; 71use warnings; 72 73use base qw/RT::Record/; 74 75use Role::Basic 'with'; 76with "RT::Record::Role::Roles", 77 "RT::Record::Role::Rights" => { -excludes => [qw/AvailableRights RightCategories/] }; 78 79use RT::ACL; 80use RT::ACE; 81use Data::GUID; 82 83__PACKAGE__->AddRight( Admin => SuperUser => 'Do anything and everything'); # loc 84__PACKAGE__->AddRight( Staff => ShowUserHistory => 'Show history of public user properties'); # loc 85__PACKAGE__->AddRight( Admin => AdminUsers => 'Create, modify and delete users'); # loc 86__PACKAGE__->AddRight( Admin => AdminCustomRoles => 'Create, modify and delete custom roles'); # loc 87__PACKAGE__->AddRight( Staff => ModifySelf => "Modify one's own RT account"); # loc 88__PACKAGE__->AddRight( Staff => ShowArticlesMenu => 'Show Articles menu'); # loc 89__PACKAGE__->AddRight( Admin => ShowConfigTab => 'Show Admin menu'); # loc 90__PACKAGE__->AddRight( Admin => ShowApprovalsTab => 'Show Approvals tab'); # loc 91__PACKAGE__->AddRight( Staff => ShowAssetsMenu => 'Show Assets menu'); # loc 92__PACKAGE__->AddRight( Staff => ShowGlobalTemplates => 'Show global templates'); # loc 93__PACKAGE__->AddRight( General => LoadSavedSearch => 'Allow loading of saved searches'); # loc 94__PACKAGE__->AddRight( General => CreateSavedSearch => 'Allow creation of saved searches'); # loc 95__PACKAGE__->AddRight( Admin => ExecuteCode => 'Allow writing Perl code in templates, scrips, etc'); # loc 96__PACKAGE__->AddRight( General => SeeSelfServiceGroupTicket => 'See tickets for other group members in SelfService' ); # loc 97__PACKAGE__->AddRight( Staff => ShowSearchAdvanced => 'Show search "Advanced" menu' ); # loc 98__PACKAGE__->AddRight( Staff => ShowSearchBulkUpdate => 'Show search "Bulk Update" menu' ); # loc 99 100=head2 AvailableRights 101 102Returns a hashref of available rights for this object. The keys are the 103right names and the values are a description of what the rights do. 104 105This method as well returns rights of other RT objects, like 106L<RT::Queue> or L<RT::Group>, to allow users to apply those rights 107globally. 108 109If an L<RT::Principal> is passed as the first argument, the available 110rights will be limited to ones which make sense for the principal. 111Currently only role groups are supported and rights announced by object 112types to which the role group doesn't apply are not returned. 113 114=cut 115 116sub AvailableRights { 117 my $self = shift; 118 my $principal = shift; 119 my $class = ref($self) || $self; 120 121 my @rights; 122 if ($principal and $principal->IsRoleGroup) { 123 my $role = $principal->Object->Name; 124 for my $class (keys %RT::ACE::RIGHTS) { 125 next unless $class->DOES('RT::Record::Role::Roles') and $class->HasRole($role) and $class ne "RT::System"; 126 push @rights, values %{ $RT::ACE::RIGHTS{$class} }; 127 } 128 } else { 129 @rights = map {values %{$_}} values %RT::ACE::RIGHTS; 130 } 131 132 my %rights; 133 $rights{$_->{Name}} = $_->{Description} for @rights; 134 135 delete $rights{ExecuteCode} if RT->Config->Get('DisallowExecuteCode'); 136 137 return \%rights; 138} 139 140=head2 RightCategories 141 142Returns a hashref where the keys are rights for this type of object and the 143values are the category (General, Staff, Admin) the right falls into. 144 145=cut 146 147sub RightCategories { 148 my $self = shift; 149 my $class = ref($self) || $self; 150 151 my %rights; 152 $rights{$_->{Name}} = $_->{Category} 153 for map {values %{$_}} values %RT::ACE::RIGHTS; 154 return \%rights; 155} 156 157sub _Init { 158 my $self = shift; 159 $self->SUPER::_Init (@_) if @_ && $_[0]; 160} 161 162=head2 id 163 164Returns RT::System's id. It's 1. 165 166=cut 167 168*Id = \&id; 169sub id { return 1 } 170 171sub UID { return "RT::System" } 172 173=head2 Load 174 175Since this object is pretending to be an RT::Record, we need a load method. 176It does nothing 177 178=cut 179 180sub Load { return 1 } 181sub Name { return 'RT System' } 182sub __Set { return 0 } 183sub __Value { return 0 } 184sub Create { return 0 } 185sub Delete { return 0 } 186 187sub SubjectTag { 188 my $self = shift; 189 my $queue = shift; 190 191 return $queue->SubjectTag if $queue; 192 193 my $queues = RT::Queues->new( $self->CurrentUser ); 194 $queues->Limit( FIELD => 'SubjectTag', OPERATOR => 'IS NOT', VALUE => 'NULL' ); 195 return $queues->DistinctFieldValues('SubjectTag'); 196} 197 198=head2 QueueCacheNeedsUpdate ( 1 ) 199 200Attribute to decide when SelectQueue needs to flush the list of queues 201and retrieve new ones. Set when queues are created, enabled/disabled 202and on certain acl changes. Should also better understand group management. 203 204If passed a true value, will update the attribute to be the current time. 205 206=cut 207 208sub QueueCacheNeedsUpdate { 209 my $self = shift; 210 my $update = shift; 211 212 if ($update) { 213 return $self->SetAttribute(Name => 'QueueCacheNeedsUpdate', Content => time); 214 } else { 215 my $cache = $self->FirstAttribute('QueueCacheNeedsUpdate'); 216 return (defined $cache ? $cache->Content : 0 ); 217 } 218} 219 220=head2 CustomRoleCacheNeedsUpdate ( 1 ) 221 222Attribute to decide when we need to flush the list of custom roles 223and re-register any changes. Set when roles are created, enabled/disabled, etc. 224 225If passed a true value, will update the attribute to be the current time. 226 227=cut 228 229sub CustomRoleCacheNeedsUpdate { 230 my $self = shift; 231 my $update = shift; 232 233 if ($update) { 234 return $self->SetAttribute(Name => 'CustomRoleCacheNeedsUpdate', Content => time); 235 } else { 236 my $cache = $self->FirstAttribute('CustomRoleCacheNeedsUpdate'); 237 return (defined $cache ? $cache->Content : 0 ); 238 } 239} 240 241=head2 ConfigCacheNeedsUpdate ( 1 ) 242 243Attribute to decide when we need to flush the database settings 244and re-register any changes. Set when settings are created, enabled/disabled, etc. 245 246If passed a true value, will update the attribute to be the current time. 247 248=cut 249 250sub ConfigCacheNeedsUpdate { 251 my $self = shift; 252 my $time = shift; 253 254 if ($time) { 255 return $self->SetAttribute(Name => 'ConfigCacheNeedsUpdate', Content => $time); 256 } else { 257 my $cache = $self->FirstAttribute('ConfigCacheNeedsUpdate'); 258 return (defined $cache ? $cache->Content : 0 ); 259 } 260} 261 262# This needs to be in RT::System as RT::Interface::Web and RT::Interface::Email both use this 263my $lifecycle_cache_time = time; 264sub MaybeRebuildLifecycleCache { 265 my $needs_update = RT->System->LifecycleCacheNeedsUpdate; 266 if ( $needs_update > $lifecycle_cache_time ) { 267 RT::Lifecycle->FillCache; 268 $lifecycle_cache_time = $needs_update; 269 } 270} 271 272=head2 LifecycleCacheNeedsUpdate ( 1 ) 273 274Attribute to decide when we need to flush the list of lifecycles 275and re-register any changes. This is needed for the lifecycle UI editor. 276 277If passed a true value, will update the attribute to be the current time. 278 279=cut 280 281sub LifecycleCacheNeedsUpdate { 282 my $self = shift; 283 my $update = shift; 284 285 if ($update) { 286 return $self->SetAttribute(Name => 'LifecycleCacheNeedsUpdate', Content => time); 287 } 288 else { 289 my $cache = $self->FirstAttribute('LifecycleCacheNeedsUpdate'); 290 return (defined $cache ? $cache->Content : 0); 291 } 292} 293 294=head2 AddUpgradeHistory package, data 295 296Adds an entry to the upgrade history database. The package can be either C<RT> 297for core RT upgrades, or the fully qualified name of a plugin. The data must be 298a hash reference. 299 300=cut 301 302sub AddUpgradeHistory { 303 my $self = shift; 304 my $package = shift; 305 my $data = shift; 306 307 $data->{timestamp} ||= time; 308 $data->{rt_version} ||= $RT::VERSION; 309 310 my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory'); 311 my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {}; 312 313 push @{ $upgrade_history->{$package} }, $data; 314 315 $self->SetAttribute( 316 Name => 'UpgradeHistory', 317 Content => $upgrade_history, 318 ); 319} 320 321=head2 UpgradeHistory [package] 322 323Returns the entries of RT's upgrade history. If a package is specified, the list 324of upgrades for that package will be returned. Otherwise a hash reference of 325C<< package => [upgrades] >> will be returned. 326 327=cut 328 329sub UpgradeHistory { 330 my $self = shift; 331 my $package = shift; 332 333 my $upgrade_history_attr = $self->FirstAttribute('UpgradeHistory'); 334 my $upgrade_history = $upgrade_history_attr ? $upgrade_history_attr->Content : {}; 335 336 if ($package) { 337 return @{ $upgrade_history->{$package} || [] }; 338 } 339 340 return $upgrade_history; 341} 342 343sub ParsedUpgradeHistory { 344 my $self = shift; 345 my $package = shift; 346 347 my $version_status = "Current version: "; 348 if ( $package eq 'RT' ){ 349 $version_status .= $RT::VERSION; 350 } elsif ( grep {/$package/} @{RT->Config->Get('Plugins')} ) { 351 no strict 'refs'; 352 $version_status .= ${ $package . '::VERSION' }; 353 } else { 354 $version_status = "Not currently loaded"; 355 } 356 357 my %ids; 358 my @lines; 359 360 my @events = $self->UpgradeHistory( $package ); 361 for my $event (@events) { 362 if ($event->{stage} eq 'before' or (($event->{action}||'') eq 'insert' and not $event->{full_id})) { 363 if (not $event->{full_id}) { 364 # For upgrade done in the 4.1 series without GUIDs 365 if (($event->{type}||'') eq 'full upgrade') { 366 $event->{full_id} = $event->{individual_id} = Data::GUID->new->as_string; 367 } else { 368 $event->{individual_id} = Data::GUID->new->as_string; 369 $event->{full_id} = (@lines ? $lines[-1]{full_id} : Data::GUID->new->as_string); 370 } 371 $event->{return_value} = [1] if $event->{stage} eq 'after'; 372 } 373 if ($ids{$event->{full_id}}) { 374 my $kids = $ids{$event->{full_id}}{sub_events} ||= []; 375 # Stitch non-"upgrade"s beneath the previous "upgrade" 376 if ( @{$kids} and $event->{action} ne 'upgrade' and $kids->[-1]{action} eq 'upgrade') { 377 push @{ $kids->[-1]{sub_events} }, $event; 378 } else { 379 push @{ $kids }, $event; 380 } 381 } else { 382 push @lines, $event; 383 } 384 $ids{$event->{individual_id}} = $event; 385 } elsif ($event->{stage} eq 'after') { 386 if (not $event->{individual_id}) { 387 if (($event->{type}||'') eq 'full upgrade') { 388 $lines[-1]{end} = $event->{timestamp} if @lines; 389 } elsif (($event->{type}||'') eq 'individual upgrade') { 390 $lines[-1]{sub_events}[-1]{end} = $event->{timestamp} 391 if @lines and @{ $lines[-1]{sub_events} }; 392 } 393 } elsif ($ids{$event->{individual_id}}) { 394 my $end = $event; 395 $event = $ids{$event->{individual_id}}; 396 $event->{end} = $end->{timestamp}; 397 398 $end->{return_value} = [ split ', ', $end->{return_value}, 2 ] 399 if $end->{return_value} and not ref $end->{return_value}; 400 $event->{return_value} = $end->{return_value}; 401 $event->{content} ||= $end->{content}; 402 } 403 } 404 } 405 406 return ($version_status, @lines); 407} 408 409=head2 ExternalStorage 410 411Accessor for the storage engine selected by L<RT::ExternalStorage>. Will 412be undefined if external storage is not configured. 413 414=cut 415 416sub ExternalStorage { 417 my $self = shift; 418 if (@_) { 419 $self->{ExternalStorage} = shift; 420 } 421 return $self->{ExternalStorage}; 422} 423 424=head2 ExternalStorageURLFor object 425 426Returns a URL for direct linking to an L<RT::ExternalStorage> 427engine. Will return C<undef> if external storage is not configured, or 428if direct linking is disabled in config (C<$ExternalStorageDirectLink>), 429or if the external storage engine doesn't support hyperlinking (as in 430L<RT::ExternalStorage::Disk>), or finally, if the object is for whatever 431reason not present in external storage. 432 433=cut 434 435sub ExternalStorageURLFor { 436 my $self = shift; 437 my $Object = shift; 438 439 # external storage not configured 440 return undef if !$self->ExternalStorage; 441 442 # external storage direct links disabled 443 return undef if !RT->Config->Get('ExternalStorageDirectLink'); 444 445 return undef unless $Object->ContentEncoding eq 'external'; 446 447 return $self->ExternalStorage->DownloadURLFor($Object); 448} 449 450RT::Base->_ImportOverlays(); 451 4521; 453