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 49package RT::Class; 50 51use strict; 52use warnings; 53use base 'RT::Record'; 54 55 56use RT::System; 57use RT::CustomFields; 58use RT::ACL; 59use RT::Articles; 60use RT::ObjectClass; 61use RT::ObjectClasses; 62 63use Role::Basic 'with'; 64with "RT::Record::Role::Rights"; 65 66sub Table {'Classes'} 67 68=head2 Load IDENTIFIER 69 70Loads a class, either by name or by id 71 72=cut 73 74sub Load { 75 my $self = shift; 76 my $id = shift ; 77 78 return unless $id; 79 if ( $id =~ /^\d+$/ ) { 80 $self->SUPER::Load($id); 81 } 82 else { 83 $self->LoadByCols( Name => $id ); 84 } 85} 86 87__PACKAGE__->AddRight( Staff => SeeClass => 'See that this class exists'); # loc 88__PACKAGE__->AddRight( Staff => CreateArticle => 'Create articles in this class'); # loc 89__PACKAGE__->AddRight( General => ShowArticle => 'See articles in this class'); # loc 90__PACKAGE__->AddRight( Staff => ShowArticleHistory => 'See changes to articles in this class'); # loc 91__PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc 92__PACKAGE__->AddRight( Staff => ModifyArticle => 'Modify articles in this class'); # loc 93__PACKAGE__->AddRight( Staff => ModifyArticleTopics => 'Modify topics for articles in this class'); # loc 94__PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc 95__PACKAGE__->AddRight( Staff => SetInitialCustomField => 'Add custom field values only at object creation time'); # loc 96__PACKAGE__->AddRight( Admin => AdminClass => 'Modify metadata and custom fields for this class'); # loc 97__PACKAGE__->AddRight( Admin => AdminTopics => 'Modify topic hierarchy associated with this class'); # loc 98__PACKAGE__->AddRight( Admin => ShowACL => 'Display Access Control List'); # loc 99__PACKAGE__->AddRight( Admin => ModifyACL => 'Create, modify and delete Access Control List entries'); # loc 100__PACKAGE__->AddRight( Staff => DisableArticle => 'Disable articles in this class'); # loc 101 102# {{{ Create 103 104=head2 Create PARAMHASH 105 106Create takes a hash of values and creates a row in the database: 107 108 varchar(255) 'Name'. 109 varchar(255) 'Description'. 110 int(11) 'SortOrder'. 111 112=cut 113 114sub Create { 115 my $self = shift; 116 my %args = ( 117 Name => '', 118 Description => '', 119 SortOrder => '0', 120 HotList => 0, 121 @_ 122 ); 123 124 unless ( 125 $self->CurrentUser->HasRight( 126 Right => 'AdminClass', 127 Object => $RT::System 128 ) 129 ) 130 { 131 return ( 0, $self->loc('Permission Denied') ); 132 } 133 134 $self->SUPER::Create( 135 Name => $args{'Name'}, 136 Description => $args{'Description'}, 137 SortOrder => $args{'SortOrder'}, 138 HotList => $args{'HotList'}, 139 ); 140 141} 142 143sub ValidateName { 144 my $self = shift; 145 my $newval = shift; 146 147 return undef unless ($newval); 148 my $obj = RT::Class->new($RT::SystemUser); 149 $obj->Load($newval); 150 return undef if $obj->id && ( !$self->id || $self->id != $obj->id ); 151 return $self->SUPER::ValidateName($newval); 152 153} 154 155# }}} 156 157# }}} 158 159# {{{ ACCESS CONTROL 160 161# {{{ sub _Set 162sub _Set { 163 my $self = shift; 164 165 unless ( $self->CurrentUserHasRight('AdminClass') ) { 166 return ( 0, $self->loc('Permission Denied') ); 167 } 168 return ( $self->SUPER::_Set(@_) ); 169} 170 171# }}} 172 173# {{{ sub _Value 174 175sub _Value { 176 my $self = shift; 177 178 unless ( $self->CurrentUserHasRight('SeeClass') ) { 179 return (undef); 180 } 181 182 return ( $self->__Value(@_) ); 183} 184 185# }}} 186 187sub ArticleCustomFields { 188 my $self = shift; 189 190 191 my $cfs = RT::CustomFields->new( $self->CurrentUser ); 192 if ( $self->CurrentUserHasRight('SeeClass') ) { 193 $cfs->SetContextObject( $self ); 194 $cfs->LimitToGlobalOrObjectId( $self->Id ); 195 $cfs->LimitToLookupType( RT::Article->CustomFieldLookupType ); 196 $cfs->ApplySortOrder; 197 } 198 return ($cfs); 199} 200 201 202=head1 AppliedTo 203 204Returns collection of Queues this Class is applied to. 205Doesn't takes into account if object is applied globally. 206 207=cut 208 209sub AppliedTo { 210 my $self = shift; 211 212 my ($res, $ocfs_alias) = $self->_AppliedTo; 213 return $res unless $res; 214 215 $res->Limit( 216 ALIAS => $ocfs_alias, 217 FIELD => 'id', 218 OPERATOR => 'IS NOT', 219 VALUE => 'NULL', 220 ); 221 222 return $res; 223} 224 225=head1 NotAppliedTo 226 227Returns collection of Queues this Class is not applied to. 228 229Doesn't takes into account if object is applied globally. 230 231=cut 232 233sub NotAppliedTo { 234 my $self = shift; 235 236 my ($res, $ocfs_alias) = $self->_AppliedTo; 237 return $res unless $res; 238 239 $res->Limit( 240 ALIAS => $ocfs_alias, 241 FIELD => 'id', 242 OPERATOR => 'IS', 243 VALUE => 'NULL', 244 ); 245 246 return $res; 247} 248 249sub _AppliedTo { 250 my $self = shift; 251 252 my $res = RT::Queues->new( $self->CurrentUser ); 253 254 $res->OrderBy( FIELD => 'Name' ); 255 my $ocfs_alias = $res->Join( 256 TYPE => 'LEFT', 257 ALIAS1 => 'main', 258 FIELD1 => 'id', 259 TABLE2 => 'ObjectClasses', 260 FIELD2 => 'ObjectId', 261 ); 262 $res->Limit( 263 LEFTJOIN => $ocfs_alias, 264 ALIAS => $ocfs_alias, 265 FIELD => 'Class', 266 VALUE => $self->id, 267 ); 268 return ($res, $ocfs_alias); 269} 270 271=head2 IsApplied 272 273Takes object id and returns corresponding L<RT::ObjectClass> 274record if this Class is applied to the object. Use 0 to check 275if Class is applied globally. 276 277=cut 278 279sub IsApplied { 280 my $self = shift; 281 my $id = shift; 282 return unless defined $id; 283 my $oc = RT::ObjectClass->new( $self->CurrentUser ); 284 $oc->LoadByCols( Class=> $self->id, ObjectId => $id, 285 ObjectType => ( $id ? 'RT::Queue' : 'RT::System' )); 286 return undef unless $oc->id; 287 return $oc; 288} 289 290=head2 AddToObject OBJECT 291 292Apply this Class to a single object, to start with we support Queues 293 294Takes an object 295 296=cut 297 298 299sub AddToObject { 300 my $self = shift; 301 my $object = shift; 302 my $id = $object->Id || 0; 303 304 unless ( $object->CurrentUserHasRight('AdminClass') ) { 305 return ( 0, $self->loc('Permission Denied') ); 306 } 307 308 my $queue = RT::Queue->new( $self->CurrentUser ); 309 if ( $id ) { 310 my ($ok, $msg) = $queue->Load( $id ); 311 unless ($ok) { 312 return ( 0, $self->loc('Invalid Queue, unable to apply Class: [_1]',$msg ) ); 313 } 314 315 } 316 317 if ( $self->IsApplied( $id ) ) { 318 return ( 0, $self->loc("Class is already applied to [_1]",$queue->Name) ); 319 } 320 321 if ( $id ) { 322 # applying locally 323 return (0, $self->loc("Class is already applied Globally") ) 324 if $self->IsApplied( 0 ); 325 } 326 else { 327 my $applied = RT::ObjectClasses->new( $self->CurrentUser ); 328 $applied->LimitToClass( $self->id ); 329 while ( my $record = $applied->Next ) { 330 $record->Delete; 331 } 332 } 333 334 my $oc = RT::ObjectClass->new( $self->CurrentUser ); 335 my ( $oid, $msg ) = $oc->Create( 336 ObjectId => $id, Class => $self->id, 337 ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ), 338 ); 339 return ( $oid, $msg ); 340} 341 342 343=head2 RemoveFromObject OBJECT 344 345Remove this class from a single queue object 346 347=cut 348 349sub RemoveFromObject { 350 my $self = shift; 351 my $object = shift; 352 my $id = $object->Id || 0; 353 354 unless ( $object->CurrentUserHasRight('AdminClass') ) { 355 return ( 0, $self->loc('Permission Denied') ); 356 } 357 358 my $ocf = $self->IsApplied( $id ); 359 unless ( $ocf ) { 360 return ( 0, $self->loc("This class does not apply to that object") ); 361 } 362 363 # XXX: Delete doesn't return anything 364 my ( $oid, $msg ) = $ocf->Delete; 365 return ( $oid, $msg ); 366} 367 368sub SubjectOverride { 369 my $self = shift; 370 my $override = $self->FirstAttribute('SubjectOverride'); 371 return $override ? $override->Content : 0; 372} 373 374sub SetSubjectOverride { 375 my $self = shift; 376 my $override = shift; 377 378 if ( $override == $self->SubjectOverride ) { 379 return (0, "SubjectOverride is already set to that"); 380 } 381 382 my $cf = RT::CustomField->new($self->CurrentUser); 383 $cf->Load($override); 384 385 if ( $override ) { 386 my ($ok, $msg) = $self->SetAttribute( Name => 'SubjectOverride', Content => $override ); 387 return ($ok, $ok ? $self->loc('Added Subject Override: [_1]', $cf->Name) : 388 $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); 389 } else { 390 my ($ok, $msg) = $self->DeleteAttribute('SubjectOverride'); 391 return ($ok, $ok ? $self->loc('Removed Subject Override') : 392 $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); 393 } 394} 395 396=head2 id 397 398Returns the current value of id. 399(In the database, id is stored as int(11).) 400 401 402=cut 403 404 405=head2 Name 406 407Returns the current value of Name. 408(In the database, Name is stored as varchar(255).) 409 410 411 412=head2 SetName VALUE 413 414 415Set Name to VALUE. 416Returns (1, 'Status message') on success and (0, 'Error Message') on failure. 417(In the database, Name will be stored as a varchar(255).) 418 419 420=cut 421 422 423=head2 Description 424 425Returns the current value of Description. 426(In the database, Description is stored as varchar(255).) 427 428 429 430=head2 SetDescription VALUE 431 432 433Set Description to VALUE. 434Returns (1, 'Status message') on success and (0, 'Error Message') on failure. 435(In the database, Description will be stored as a varchar(255).) 436 437 438=cut 439 440 441=head2 SortOrder 442 443Returns the current value of SortOrder. 444(In the database, SortOrder is stored as int(11).) 445 446 447 448=head2 SetSortOrder VALUE 449 450 451Set SortOrder to VALUE. 452Returns (1, 'Status message') on success and (0, 'Error Message') on failure. 453(In the database, SortOrder will be stored as a int(11).) 454 455 456=cut 457 458 459=head2 Disabled 460 461Returns the current value of Disabled. 462(In the database, Disabled is stored as int(2).) 463 464 465 466=head2 SetDisabled VALUE 467 468 469Set Disabled to VALUE. 470Returns (1, 'Status message') on success and (0, 'Error Message') on failure. 471(In the database, Disabled will be stored as a int(2).) 472 473 474=cut 475 476 477=head2 HotList 478 479Returns the current value of HotList. 480(In the database, HotList is stored as int(2).) 481 482 483 484=head2 SetHotList VALUE 485 486 487Set HotList to VALUE. 488Returns (1, 'Status message') on success and (0, 'Error Message') on failure. 489(In the database, HotList will be stored as a int(2).) 490 491 492=cut 493 494 495=head2 Creator 496 497Returns the current value of Creator. 498(In the database, Creator is stored as int(11).) 499 500 501=cut 502 503 504=head2 Created 505 506Returns the current value of Created. 507(In the database, Created is stored as datetime.) 508 509 510=cut 511 512 513=head2 LastUpdatedBy 514 515Returns the current value of LastUpdatedBy. 516(In the database, LastUpdatedBy is stored as int(11).) 517 518 519=cut 520 521 522=head2 LastUpdated 523 524Returns the current value of LastUpdated. 525(In the database, LastUpdated is stored as datetime.) 526 527 528=cut 529 530 531 532sub _CoreAccessible { 533 { 534 535 id => 536 {read => 1, type => 'int(11)', default => ''}, 537 Name => 538 {read => 1, write => 1, type => 'varchar(255)', default => ''}, 539 Description => 540 {read => 1, write => 1, type => 'varchar(255)', default => ''}, 541 SortOrder => 542 {read => 1, write => 1, type => 'int(11)', default => '0'}, 543 Disabled => 544 {read => 1, write => 1, type => 'int(2)', default => '0'}, 545 HotList => 546 {read => 1, write => 1, type => 'int(2)', default => '0'}, 547 Creator => 548 {read => 1, auto => 1, type => 'int(11)', default => '0'}, 549 Created => 550 {read => 1, auto => 1, type => 'datetime', default => ''}, 551 LastUpdatedBy => 552 {read => 1, auto => 1, type => 'int(11)', default => '0'}, 553 LastUpdated => 554 {read => 1, auto => 1, type => 'datetime', default => ''}, 555 556 } 557}; 558 559sub FindDependencies { 560 my $self = shift; 561 my ($walker, $deps) = @_; 562 563 $self->SUPER::FindDependencies($walker, $deps); 564 565 my $articles = RT::Articles->new( $self->CurrentUser ); 566 $articles->Limit( FIELD => "Class", VALUE => $self->Id ); 567 $deps->Add( in => $articles ); 568 569 my $topics = RT::Topics->new( $self->CurrentUser ); 570 $topics->LimitToObject( $self ); 571 $deps->Add( in => $topics ); 572 573 my $objectclasses = RT::ObjectClasses->new( $self->CurrentUser ); 574 $objectclasses->LimitToClass( $self->Id ); 575 $deps->Add( in => $objectclasses ); 576 577 # Custom Fields on things _in_ this class (CFs on the class itself 578 # have already been dealt with) 579 my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser ); 580 $ocfs->Limit( FIELD => 'ObjectId', 581 OPERATOR => '=', 582 VALUE => $self->id, 583 ENTRYAGGREGATOR => 'OR' ); 584 $ocfs->Limit( FIELD => 'ObjectId', 585 OPERATOR => '=', 586 VALUE => 0, 587 ENTRYAGGREGATOR => 'OR' ); 588 my $cfs = $ocfs->Join( 589 ALIAS1 => 'main', 590 FIELD1 => 'CustomField', 591 TABLE2 => 'CustomFields', 592 FIELD2 => 'id', 593 ); 594 $ocfs->Limit( ALIAS => $cfs, 595 FIELD => 'LookupType', 596 OPERATOR => 'STARTSWITH', 597 VALUE => 'RT::Class-' ); 598 $deps->Add( in => $ocfs ); 599} 600 601sub PreInflate { 602 my $class = shift; 603 my ($importer, $uid, $data) = @_; 604 605 $class->SUPER::PreInflate( $importer, $uid, $data ); 606 607 return if $importer->MergeBy( "Name", $class, $uid, $data ); 608 609 return 1; 610} 611 612RT::Base->_ImportOverlays(); 613 6141; 615 616