1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::DynamicField; 10 11use strict; 12use warnings; 13 14use parent qw(Kernel::System::EventHandler); 15 16use Kernel::System::VariableCheck qw(:all); 17 18our @ObjectDependencies = ( 19 'Kernel::Config', 20 'Kernel::System::Cache', 21 'Kernel::System::DB', 22 'Kernel::System::Log', 23 'Kernel::System::Valid', 24 'Kernel::System::YAML', 25); 26 27=head1 NAME 28 29Kernel::System::DynamicField 30 31=head1 DESCRIPTION 32 33DynamicFields backend 34 35=head1 PUBLIC INTERFACE 36 37=head2 new() 38 39create a DynamicField object. Do not use it directly, instead use: 40 41 my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 42 43=cut 44 45sub new { 46 my ( $Type, %Param ) = @_; 47 48 # allocate new hash for object 49 my $Self = {}; 50 bless( $Self, $Type ); 51 52 # get the cache TTL (in seconds) 53 $Self->{CacheTTL} = $Kernel::OM->Get('Kernel::Config')->Get('DynamicField::CacheTTL') || 3600; 54 55 # set lower if database is case sensitive 56 $Self->{Lower} = ''; 57 if ( $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('CaseSensitive') ) { 58 $Self->{Lower} = 'LOWER'; 59 } 60 61 # init of event handler 62 $Self->EventHandlerInit( 63 Config => 'DynamicField::EventModulePost', 64 ); 65 66 return $Self; 67} 68 69=head2 DynamicFieldAdd() 70 71add new Dynamic Field config 72 73returns id of new Dynamic field if successful or undef otherwise 74 75 my $ID = $DynamicFieldObject->DynamicFieldAdd( 76 InternalField => 0, # optional, 0 or 1, internal fields are protected 77 Name => 'NameForField', # mandatory 78 Label => 'a description', # mandatory, label to show 79 FieldOrder => 123, # mandatory, display order 80 FieldType => 'Text', # mandatory, selects the DF backend to use for this field 81 ObjectType => 'Article', # this controls which object the dynamic field links to 82 # allow only lowercase letters 83 Config => $ConfigHashRef, # it is stored on YAML format 84 # to individual articles, otherwise to tickets 85 Reorder => 1, # or 0, to trigger reorder function, default 1 86 ValidID => 1, 87 UserID => 123, 88 ); 89 90Returns: 91 92 $ID = 567; 93 94=cut 95 96sub DynamicFieldAdd { 97 my ( $Self, %Param ) = @_; 98 99 # check needed stuff 100 for my $Key (qw(Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) { 101 if ( !$Param{$Key} ) { 102 $Kernel::OM->Get('Kernel::System::Log')->Log( 103 Priority => 'error', 104 Message => "Need $Key!" 105 ); 106 return; 107 } 108 } 109 110 # check needed structure for some fields 111 if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) { 112 $Kernel::OM->Get('Kernel::System::Log')->Log( 113 Priority => 'error', 114 Message => "Not valid letters on Name:$Param{Name}!" 115 ); 116 return; 117 } 118 119 # get database object 120 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 121 122 # check if Name already exists 123 return if !$DBObject->Prepare( 124 SQL => "SELECT id FROM dynamic_field WHERE $Self->{Lower}(name) = $Self->{Lower}(?)", 125 Bind => [ \$Param{Name} ], 126 Limit => 1, 127 ); 128 129 my $NameExists; 130 while ( my @Data = $DBObject->FetchrowArray() ) { 131 $NameExists = 1; 132 } 133 134 if ($NameExists) { 135 $Kernel::OM->Get('Kernel::System::Log')->Log( 136 Priority => 'error', 137 Message => "A dynamic field with the name '$Param{Name}' already exists.", 138 ); 139 return; 140 } 141 142 if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) { 143 $Kernel::OM->Get('Kernel::System::Log')->Log( 144 Priority => 'error', 145 Message => "Not valid number on FieldOrder:$Param{FieldOrder}!" 146 ); 147 return; 148 } 149 150 # dump config as string 151 my $Config = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => $Param{Config} ); 152 153 # Make sure the resulting string has the UTF-8 flag. YAML only sets it if 154 # part of the data already had it. 155 utf8::upgrade($Config); 156 157 my $InternalField = $Param{InternalField} ? 1 : 0; 158 159 # sql 160 return if !$DBObject->Do( 161 SQL => 162 'INSERT INTO dynamic_field (internal_field, name, label, field_Order, field_type, object_type,' 163 . 164 ' config, valid_id, create_time, create_by, change_time, change_by)' . 165 ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)', 166 Bind => [ 167 \$InternalField, \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType}, 168 \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{UserID}, 169 ], 170 ); 171 172 # get cache object 173 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 174 175 # delete cache 176 $CacheObject->CleanUp( 177 Type => 'DynamicField', 178 ); 179 $CacheObject->CleanUp( 180 Type => 'DynamicFieldValue', 181 ); 182 183 my $DynamicField = $Self->DynamicFieldGet( 184 Name => $Param{Name}, 185 ); 186 187 return if !$DynamicField->{ID}; 188 189 # trigger event 190 $Self->EventHandler( 191 Event => 'DynamicFieldAdd', 192 Data => { 193 NewData => $DynamicField, 194 }, 195 UserID => $Param{UserID}, 196 ); 197 198 if ( !exists $Param{Reorder} || $Param{Reorder} ) { 199 200 # re-order field list 201 $Self->_DynamicFieldReorder( 202 ID => $DynamicField->{ID}, 203 FieldOrder => $DynamicField->{FieldOrder}, 204 Mode => 'Add', 205 ); 206 } 207 208 return $DynamicField->{ID}; 209} 210 211=head2 DynamicFieldGet() 212 213get Dynamic Field attributes 214 215 my $DynamicField = $DynamicFieldObject->DynamicFieldGet( 216 ID => 123, # ID or Name must be provided 217 Name => 'DynamicField', 218 ); 219 220Returns: 221 222 $DynamicField = { 223 ID => 123, 224 InternalField => 0, 225 Name => 'NameForField', 226 Label => 'The label to show', 227 FieldOrder => 123, 228 FieldType => 'Text', 229 ObjectType => 'Article', 230 Config => $ConfigHashRef, 231 ValidID => 1, 232 CreateTime => '2011-02-08 15:08:00', 233 ChangeTime => '2011-06-11 17:22:00', 234 }; 235 236=cut 237 238sub DynamicFieldGet { 239 my ( $Self, %Param ) = @_; 240 241 # check needed stuff 242 if ( !$Param{ID} && !$Param{Name} ) { 243 $Kernel::OM->Get('Kernel::System::Log')->Log( 244 Priority => 'error', 245 Message => 'Need ID or Name!' 246 ); 247 return; 248 } 249 250 # get cache object 251 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 252 253 # check cache 254 my $CacheKey; 255 if ( $Param{ID} ) { 256 $CacheKey = 'DynamicFieldGet::ID::' . $Param{ID}; 257 } 258 else { 259 $CacheKey = 'DynamicFieldGet::Name::' . $Param{Name}; 260 261 } 262 my $Cache = $CacheObject->Get( 263 Type => 'DynamicField', 264 Key => $CacheKey, 265 ); 266 return $Cache if $Cache; 267 268 # get database object 269 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 270 271 # sql 272 if ( $Param{ID} ) { 273 return if !$DBObject->Prepare( 274 SQL => 275 'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,' 276 . 277 ' valid_id, create_time, change_time ' . 278 'FROM dynamic_field WHERE id = ?', 279 Bind => [ \$Param{ID} ], 280 ); 281 } 282 else { 283 return if !$DBObject->Prepare( 284 SQL => 285 'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,' 286 . 287 ' valid_id, create_time, change_time ' . 288 'FROM dynamic_field WHERE name = ?', 289 Bind => [ \$Param{Name} ], 290 ); 291 } 292 293 # get yaml object 294 my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); 295 296 my %Data; 297 while ( my @Data = $DBObject->FetchrowArray() ) { 298 299 my $Config = $YAMLObject->Load( Data => $Data[7] ); 300 301 %Data = ( 302 ID => $Data[0], 303 InternalField => $Data[1], 304 Name => $Data[2], 305 Label => $Data[3], 306 FieldOrder => $Data[4], 307 FieldType => $Data[5], 308 ObjectType => $Data[6], 309 Config => $Config, 310 ValidID => $Data[8], 311 CreateTime => $Data[9], 312 ChangeTime => $Data[10], 313 ); 314 } 315 316 if (%Data) { 317 318 # Set the cache only, if the YAML->Load was successful (see bug#12483). 319 if ( $Data{Config} ) { 320 321 $CacheObject->Set( 322 Type => 'DynamicField', 323 Key => $CacheKey, 324 Value => \%Data, 325 TTL => $Self->{CacheTTL}, 326 ); 327 } 328 329 $Data{Config} ||= {}; 330 } 331 332 return \%Data; 333} 334 335=head2 DynamicFieldUpdate() 336 337update Dynamic Field content into database 338 339returns 1 on success or undef on error 340 341 my $Success = $DynamicFieldObject->DynamicFieldUpdate( 342 ID => 1234, # mandatory 343 Name => 'NameForField', # mandatory 344 Label => 'a description', # mandatory, label to show 345 FieldOrder => 123, # mandatory, display order 346 FieldType => 'Text', # mandatory, selects the DF backend to use for this field 347 ObjectType => 'Article', # this controls which object the dynamic field links to 348 # allow only lowercase letters 349 Config => $ConfigHashRef, # it is stored on YAML format 350 # to individual articles, otherwise to tickets 351 ValidID => 1, 352 Reorder => 1, # or 0, to trigger reorder function, default 1 353 UserID => 123, 354 ); 355 356=cut 357 358sub DynamicFieldUpdate { 359 my ( $Self, %Param ) = @_; 360 361 # check needed stuff 362 for my $Key (qw(ID Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) { 363 if ( !$Param{$Key} ) { 364 $Kernel::OM->Get('Kernel::System::Log')->Log( 365 Priority => 'error', 366 Message => "Need $Key!" 367 ); 368 return; 369 } 370 } 371 372 my $Reorder; 373 if ( !exists $Param{Reorder} || $Param{Reorder} eq 1 ) { 374 $Reorder = 1; 375 } 376 377 my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); 378 379 # dump config as string 380 my $Config = $YAMLObject->Dump( 381 Data => $Param{Config}, 382 ); 383 384 # Make sure the resulting string has the UTF-8 flag. YAML only sets it if 385 # part of the data already had it. 386 utf8::upgrade($Config); 387 388 return if !$YAMLObject->Load( Data => $Config ); 389 390 # check needed structure for some fields 391 if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) { 392 $Kernel::OM->Get('Kernel::System::Log')->Log( 393 Priority => 'error', 394 Message => "Not valid letters on Name:$Param{Name} or ObjectType:$Param{ObjectType}!", 395 ); 396 return; 397 } 398 399 # get database object 400 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 401 402 # check if Name already exists 403 return if !$DBObject->Prepare( 404 SQL => "SELECT id FROM dynamic_field " 405 . "WHERE $Self->{Lower}(name) = $Self->{Lower}(?) " 406 . "AND id != ?", 407 Bind => [ \$Param{Name}, \$Param{ID} ], 408 LIMIT => 1, 409 ); 410 411 my $NameExists; 412 while ( my @Data = $DBObject->FetchrowArray() ) { 413 $NameExists = 1; 414 } 415 416 if ($NameExists) { 417 $Kernel::OM->Get('Kernel::System::Log')->Log( 418 Priority => 'error', 419 Message => "A dynamic field with the name '$Param{Name}' already exists.", 420 ); 421 return; 422 } 423 424 if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) { 425 $Kernel::OM->Get('Kernel::System::Log')->Log( 426 Priority => 'error', 427 Message => "Not valid number on FieldOrder:$Param{FieldOrder}!", 428 ); 429 return; 430 } 431 432 # get the old dynamic field data 433 my $OldDynamicField = $Self->DynamicFieldGet( 434 ID => $Param{ID}, 435 ); 436 437 # check if FieldOrder is changed 438 my $ChangedOrder; 439 if ( $OldDynamicField->{FieldOrder} ne $Param{FieldOrder} ) { 440 $ChangedOrder = 1; 441 } 442 443 # sql 444 return if !$DBObject->Do( 445 SQL => 'UPDATE dynamic_field SET name = ?, label = ?, field_order =?, field_type = ?, ' 446 . 'object_type = ?, config = ?, valid_id = ?, change_time = current_timestamp, ' 447 . ' change_by = ? WHERE id = ?', 448 Bind => [ 449 \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType}, 450 \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{ID}, 451 ], 452 ); 453 454 # get cache object 455 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 456 457 # delete cache 458 $CacheObject->CleanUp( 459 Type => 'DynamicField', 460 ); 461 $CacheObject->CleanUp( 462 Type => 'DynamicFieldValue', 463 ); 464 465 # get the new dynamic field data 466 my $NewDynamicField = $Self->DynamicFieldGet( 467 ID => $Param{ID}, 468 ); 469 470 # trigger event 471 $Self->EventHandler( 472 Event => 'DynamicFieldUpdate', 473 Data => { 474 NewData => $NewDynamicField, 475 OldData => $OldDynamicField, 476 }, 477 UserID => $Param{UserID}, 478 ); 479 480 # re-order field list if a change in the order was made 481 if ( $Reorder && $ChangedOrder ) { 482 my $Success = $Self->_DynamicFieldReorder( 483 ID => $Param{ID}, 484 FieldOrder => $Param{FieldOrder}, 485 Mode => 'Update', 486 OldFieldOrder => $OldDynamicField->{FieldOrder}, 487 ); 488 } 489 490 return 1; 491} 492 493=head2 DynamicFieldDelete() 494 495delete a Dynamic field entry. You need to make sure that all values are 496deleted before calling this function, otherwise it will fail on DBMS which check 497referential integrity. 498 499returns 1 if successful or undef otherwise 500 501 my $Success = $DynamicFieldObject->DynamicFieldDelete( 502 ID => 123, 503 UserID => 123, 504 Reorder => 1, # or 0, to trigger reorder function, default 1 505 ); 506 507=cut 508 509sub DynamicFieldDelete { 510 my ( $Self, %Param ) = @_; 511 512 # check needed stuff 513 for my $Key (qw(ID UserID)) { 514 if ( !$Param{$Key} ) { 515 $Kernel::OM->Get('Kernel::System::Log')->Log( 516 Priority => 'error', 517 Message => "Need $Key!" 518 ); 519 return; 520 } 521 } 522 523 # check if exists 524 my $DynamicField = $Self->DynamicFieldGet( 525 ID => $Param{ID}, 526 ); 527 return if !IsHashRefWithData($DynamicField); 528 529 # re-order before delete 530 if ( !exists $Param{Reorder} || $Param{Reorder} ) { 531 my $Success = $Self->_DynamicFieldReorder( 532 ID => $DynamicField->{ID}, 533 FieldOrder => $DynamicField->{FieldOrder}, 534 Mode => 'Delete', 535 ); 536 } 537 538 # delete dynamic field 539 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 540 SQL => 'DELETE FROM dynamic_field WHERE id = ?', 541 Bind => [ \$Param{ID} ], 542 ); 543 544 # get cache object 545 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 546 547 # delete cache 548 $CacheObject->CleanUp( 549 Type => 'DynamicField', 550 ); 551 $CacheObject->CleanUp( 552 Type => 'DynamicFieldValue', 553 ); 554 555 # trigger event 556 $Self->EventHandler( 557 Event => 'DynamicFieldDelete', 558 Data => { 559 NewData => $DynamicField, 560 }, 561 UserID => $Param{UserID}, 562 ); 563 564 return 1; 565} 566 567=head2 DynamicFieldList() 568 569get DynamicField list ordered by the the "Field Order" field in the DB 570 571 my $List = $DynamicFieldObject->DynamicFieldList(); 572 573 or 574 575 my $List = $DynamicFieldObject->DynamicFieldList( 576 Valid => 0, # optional, defaults to 1 577 578 # object type (optional) as STRING or as ARRAYREF 579 ObjectType => 'Ticket', 580 ObjectType => ['Ticket', 'Article'], 581 582 ResultType => 'HASH', # optional, 'ARRAY' or 'HASH', defaults to 'ARRAY' 583 584 FieldFilter => { # optional, only active fields (non 0) will be returned 585 ItemOne => 1, 586 ItemTwo => 2, 587 ItemThree => 1, 588 ItemFour => 1, 589 ItemFive => 0, 590 }, 591 592 ); 593 594Returns: 595 596 $List = { 597 1 => 'ItemOne', 598 2 => 'ItemTwo', 599 3 => 'ItemThree', 600 4 => 'ItemFour', 601 }; 602 603 or 604 605 $List = ( 606 1, 607 2, 608 3, 609 4 610 ); 611 612=cut 613 614sub DynamicFieldList { 615 my ( $Self, %Param ) = @_; 616 617 # to store fieldIDs white-list 618 my %AllowedFieldIDs; 619 620 if ( defined $Param{FieldFilter} && ref $Param{FieldFilter} eq 'HASH' ) { 621 622 # fill the fieldIDs white-list 623 FIELDNAME: 624 for my $FieldName ( sort keys %{ $Param{FieldFilter} } ) { 625 next FIELDNAME if !$Param{FieldFilter}->{$FieldName}; 626 627 my $FieldConfig = $Self->DynamicFieldGet( Name => $FieldName ); 628 next FIELDNAME if !IsHashRefWithData($FieldConfig); 629 next FIELDNAME if !$FieldConfig->{ID}; 630 631 $AllowedFieldIDs{ $FieldConfig->{ID} } = 1; 632 } 633 } 634 635 # check cache 636 my $Valid = 1; 637 if ( defined $Param{Valid} && $Param{Valid} eq '0' ) { 638 $Valid = 0; 639 } 640 641 # set cache key object type component depending on the ObjectType parameter 642 my $ObjectType = 'All'; 643 if ( IsArrayRefWithData( $Param{ObjectType} ) ) { 644 $ObjectType = join '_', sort @{ $Param{ObjectType} }; 645 } 646 elsif ( IsStringWithData( $Param{ObjectType} ) ) { 647 $ObjectType = $Param{ObjectType}; 648 } 649 650 my $ResultType = $Param{ResultType} || 'ARRAY'; 651 $ResultType = $ResultType eq 'HASH' ? 'HASH' : 'ARRAY'; 652 653 # get cache object 654 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 655 656 my $CacheKey = 'DynamicFieldList::Valid::' 657 . $Valid 658 . '::ObjectType::' 659 . $ObjectType 660 . '::ResultType::' 661 . $ResultType; 662 my $Cache = $CacheObject->Get( 663 Type => 'DynamicField', 664 Key => $CacheKey, 665 ); 666 667 if ($Cache) { 668 669 # check if FieldFilter is not set 670 if ( !defined $Param{FieldFilter} ) { 671 672 # return raw data from cache 673 return $Cache; 674 } 675 elsif ( ref $Param{FieldFilter} ne 'HASH' ) { 676 $Kernel::OM->Get('Kernel::System::Log')->Log( 677 Priority => 'error', 678 Message => 'FieldFilter must be a HASH reference!', 679 ); 680 return; 681 } 682 683 # otherwise apply the filter 684 my $FilteredData; 685 686 # check if cache is ARRAY ref 687 if ( $ResultType eq 'ARRAY' ) { 688 689 FIELDID: 690 for my $FieldID ( @{$Cache} ) { 691 next FIELDID if !$AllowedFieldIDs{$FieldID}; 692 693 push @{$FilteredData}, $FieldID; 694 } 695 696 # return filtered data from cache 697 return $FilteredData; 698 } 699 700 # otherwise is a HASH ref 701 else { 702 703 FIELDID: 704 for my $FieldID ( sort keys %{$Cache} ) { 705 next FIELDID if !$AllowedFieldIDs{$FieldID}; 706 707 $FilteredData->{$FieldID} = $Cache->{$FieldID}; 708 } 709 } 710 711 # return filtered data from cache 712 return $FilteredData; 713 } 714 715 else { 716 my $SQL = 'SELECT id, name, field_order FROM dynamic_field'; 717 718 # get database object 719 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 720 721 if ($Valid) { 722 723 # get valid object 724 my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid'); 725 726 $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')'; 727 728 if ( $Param{ObjectType} ) { 729 if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { 730 $SQL .= 731 " AND object_type = '" 732 . $DBObject->Quote( $Param{ObjectType} ) . "'"; 733 } 734 elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { 735 my $ObjectTypeString = 736 join ',', 737 map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; 738 $SQL .= " AND object_type IN ($ObjectTypeString)"; 739 740 } 741 } 742 } 743 else { 744 if ( $Param{ObjectType} ) { 745 if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { 746 $SQL .= 747 " WHERE object_type = '" 748 . $DBObject->Quote( $Param{ObjectType} ) . "'"; 749 } 750 elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { 751 my $ObjectTypeString = 752 join ',', 753 map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; 754 $SQL .= " WHERE object_type IN ($ObjectTypeString)"; 755 } 756 } 757 } 758 759 $SQL .= " ORDER BY field_order, id"; 760 761 return if !$DBObject->Prepare( SQL => $SQL ); 762 763 if ( $ResultType eq 'HASH' ) { 764 my %Data; 765 766 while ( my @Row = $DBObject->FetchrowArray() ) { 767 $Data{ $Row[0] } = $Row[1]; 768 } 769 770 # set cache 771 $CacheObject->Set( 772 Type => 'DynamicField', 773 Key => $CacheKey, 774 Value => \%Data, 775 TTL => $Self->{CacheTTL}, 776 ); 777 778 # check if FieldFilter is not set 779 if ( !defined $Param{FieldFilter} ) { 780 781 # return raw data from DB 782 return \%Data; 783 } 784 elsif ( ref $Param{FieldFilter} ne 'HASH' ) { 785 $Kernel::OM->Get('Kernel::System::Log')->Log( 786 Priority => 'error', 787 Message => 'FieldFilter must be a HASH reference!', 788 ); 789 return; 790 } 791 792 my %FilteredData; 793 FIELDID: 794 for my $FieldID ( sort keys %Data ) { 795 next FIELDID if !$AllowedFieldIDs{$FieldID}; 796 797 $FilteredData{$FieldID} = $Data{$FieldID}; 798 } 799 800 # return filtered data from DB 801 return \%FilteredData; 802 } 803 804 else { 805 806 my @Data; 807 while ( my @Row = $DBObject->FetchrowArray() ) { 808 push @Data, $Row[0]; 809 } 810 811 # set cache 812 $CacheObject->Set( 813 Type => 'DynamicField', 814 Key => $CacheKey, 815 Value => \@Data, 816 TTL => $Self->{CacheTTL}, 817 ); 818 819 # check if FieldFilter is not set 820 if ( !defined $Param{FieldFilter} ) { 821 822 # return raw data from DB 823 return \@Data; 824 } 825 elsif ( ref $Param{FieldFilter} ne 'HASH' ) { 826 $Kernel::OM->Get('Kernel::System::Log')->Log( 827 Priority => 'error', 828 Message => 'FieldFilter must be a HASH reference!', 829 ); 830 return; 831 } 832 833 my @FilteredData; 834 FIELDID: 835 for my $FieldID (@Data) { 836 next FIELDID if !$AllowedFieldIDs{$FieldID}; 837 838 push @FilteredData, $FieldID; 839 } 840 841 # return filtered data from DB 842 return \@FilteredData; 843 } 844 } 845 846 return; 847} 848 849=head2 DynamicFieldListGet() 850 851get DynamicField list with complete data ordered by the "Field Order" field in the DB 852 853 my $List = $DynamicFieldObject->DynamicFieldListGet(); 854 855 or 856 857 my $List = $DynamicFieldObject->DynamicFieldListGet( 858 Valid => 0, # optional, defaults to 1 859 860 # object type (optional) as STRING or as ARRAYREF 861 ObjectType => 'Ticket', 862 ObjectType => ['Ticket', 'Article'], 863 864 FieldFilter => { # optional, only active fields (non 0) will be returned 865 nameforfield => 1, 866 fieldname => 2, 867 other => 0, 868 otherfield => 0, 869 }, 870 871 ); 872 873Returns: 874 875 $List = ( 876 { 877 ID => 123, 878 InternalField => 0, 879 Name => 'nameforfield', 880 Label => 'The label to show', 881 FieldType => 'Text', 882 ObjectType => 'Article', 883 Config => $ConfigHashRef, 884 ValidID => 1, 885 CreateTime => '2011-02-08 15:08:00', 886 ChangeTime => '2011-06-11 17:22:00', 887 }, 888 { 889 ID => 321, 890 InternalField => 0, 891 Name => 'fieldname', 892 Label => 'It is not a label', 893 FieldType => 'Text', 894 ObjectType => 'Ticket', 895 Config => $ConfigHashRef, 896 ValidID => 1, 897 CreateTime => '2010-09-11 10:08:00', 898 ChangeTime => '2011-01-01 01:01:01', 899 }, 900 ... 901 ); 902 903=cut 904 905sub DynamicFieldListGet { 906 my ( $Self, %Param ) = @_; 907 908 # check cache 909 my $Valid = 1; 910 if ( defined $Param{Valid} && $Param{Valid} eq '0' ) { 911 $Valid = 0; 912 } 913 914 # set cache key object type component depending on the ObjectType parameter 915 my $ObjectType = 'All'; 916 if ( IsArrayRefWithData( $Param{ObjectType} ) ) { 917 $ObjectType = join '_', sort @{ $Param{ObjectType} }; 918 } 919 elsif ( IsStringWithData( $Param{ObjectType} ) ) { 920 $ObjectType = $Param{ObjectType}; 921 } 922 923 # get cache object 924 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 925 926 my $CacheKey = 'DynamicFieldListGet::Valid::' . $Valid . '::ObjectType::' . $ObjectType; 927 my $Cache = $CacheObject->Get( 928 Type => 'DynamicField', 929 Key => $CacheKey, 930 ); 931 932 if ($Cache) { 933 934 # check if FieldFilter is not set 935 if ( !defined $Param{FieldFilter} ) { 936 937 # return raw data from cache 938 return $Cache; 939 } 940 elsif ( ref $Param{FieldFilter} ne 'HASH' ) { 941 $Kernel::OM->Get('Kernel::System::Log')->Log( 942 Priority => 'error', 943 Message => 'FieldFilter must be a HASH reference!', 944 ); 945 return; 946 } 947 948 my $FilteredData; 949 950 DYNAMICFIELD: 951 for my $DynamicFieldConfig ( @{$Cache} ) { 952 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 953 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 954 next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} }; 955 956 push @{$FilteredData}, $DynamicFieldConfig; 957 } 958 959 # return filtered data from cache 960 return $FilteredData; 961 } 962 963 # get database object 964 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 965 966 my @Data; 967 my $SQL = 'SELECT id, name, field_order FROM dynamic_field'; 968 969 if ($Valid) { 970 971 # get valid object 972 my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid'); 973 974 $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')'; 975 976 if ( $Param{ObjectType} ) { 977 if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { 978 $SQL .= 979 " AND object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; 980 } 981 elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { 982 my $ObjectTypeString = 983 join ',', 984 map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; 985 $SQL .= " AND object_type IN ($ObjectTypeString)"; 986 987 } 988 } 989 } 990 else { 991 if ( $Param{ObjectType} ) { 992 if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { 993 $SQL .= 994 " WHERE object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; 995 } 996 elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { 997 my $ObjectTypeString = 998 join ',', 999 map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; 1000 $SQL .= " WHERE object_type IN ($ObjectTypeString)"; 1001 } 1002 } 1003 } 1004 1005 $SQL .= " ORDER BY field_order, id"; 1006 1007 return if !$DBObject->Prepare( SQL => $SQL ); 1008 1009 my @DynamicFieldIDs; 1010 while ( my @Row = $DBObject->FetchrowArray() ) { 1011 push @DynamicFieldIDs, $Row[0]; 1012 } 1013 1014 for my $ItemID (@DynamicFieldIDs) { 1015 1016 my $DynamicField = $Self->DynamicFieldGet( 1017 ID => $ItemID, 1018 ); 1019 push @Data, $DynamicField; 1020 } 1021 1022 # set cache 1023 $CacheObject->Set( 1024 Type => 'DynamicField', 1025 Key => $CacheKey, 1026 Value => \@Data, 1027 TTL => $Self->{CacheTTL}, 1028 ); 1029 1030 # check if FieldFilter is not set 1031 if ( !defined $Param{FieldFilter} ) { 1032 1033 # return raw data from DB 1034 return \@Data; 1035 } 1036 elsif ( ref $Param{FieldFilter} ne 'HASH' ) { 1037 $Kernel::OM->Get('Kernel::System::Log')->Log( 1038 Priority => 'error', 1039 Message => 'FieldFilter must be a HASH reference!', 1040 ); 1041 return; 1042 } 1043 1044 my $FilteredData; 1045 1046 DYNAMICFIELD: 1047 for my $DynamicFieldConfig (@Data) { 1048 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1049 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1050 next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} }; 1051 1052 push @{$FilteredData}, $DynamicFieldConfig; 1053 } 1054 1055 # return filtered data from DB 1056 return $FilteredData; 1057} 1058 1059=head2 DynamicFieldOrderReset() 1060 1061sets the order of all dynamic fields based on a consecutive number list starting with number 1. 1062This function will remove duplicate order numbers and gaps in the numbering. 1063 1064 my $Success = $DynamicFieldObject->DynamicFieldOrderReset(); 1065 1066Returns: 1067 1068 $Success = 1; # or 0 in case of error 1069 1070=cut 1071 1072sub DynamicFieldOrderReset { 1073 my ( $Self, %Param ) = @_; 1074 1075 # get all fields 1076 my $DynamicFieldList = $Self->DynamicFieldListGet( 1077 Valid => 0, 1078 ); 1079 1080 # to set the field order 1081 my $Counter; 1082 1083 # loop through all the dynamic fields 1084 DYNAMICFIELD: 1085 for my $DynamicField ( @{$DynamicFieldList} ) { 1086 1087 # prepare the new field order 1088 $Counter++; 1089 1090 # skip wrong fields (if any) 1091 next DYNAMICFIELD if !IsHashRefWithData($DynamicField); 1092 1093 # skip fields with the correct order 1094 next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter; 1095 1096 $DynamicField->{FieldOrder} = $Counter; 1097 1098 # update the database 1099 my $Success = $Self->DynamicFieldUpdate( 1100 %{$DynamicField}, 1101 UserID => 1, 1102 Reorder => 0, 1103 ); 1104 1105 # check if the update was successful 1106 if ( !$Success ) { 1107 $Kernel::OM->Get('Kernel::System::Log')->Log( 1108 Priority => 'error', 1109 Message => 'An error was detected while re ordering the field list on field ' 1110 . "DynamicField->{Name}!", 1111 ); 1112 return; 1113 } 1114 } 1115 1116 return 1; 1117} 1118 1119=head2 DynamicFieldOrderCheck() 1120 1121checks for duplicate order numbers and gaps in the numbering. 1122 1123 my $Success = $DynamicFieldObject->DynamicFieldOrderCheck(); 1124 1125Returns: 1126 1127 $Success = 1; # or 0 in case duplicates or gaps in the dynamic fields 1128 # order numbering 1129 1130=cut 1131 1132sub DynamicFieldOrderCheck { 1133 my ( $Self, %Param ) = @_; 1134 1135 # get all fields 1136 my $DynamicFieldList = $Self->DynamicFieldListGet( 1137 Valid => 0, 1138 ); 1139 1140 # to had a correct order reference 1141 my $Counter; 1142 1143 # flag to be activated if the order is not correct 1144 my $OrderError; 1145 1146 # loop through all the dynamic fields 1147 DYNAMICFIELD: 1148 for my $DynamicField ( @{$DynamicFieldList} ) { 1149 $Counter++; 1150 1151 # skip wrong fields (if any) 1152 next DYNAMICFIELD if !IsHashRefWithData($DynamicField); 1153 1154 # skip fields with correct order 1155 next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter; 1156 1157 # when finding a field with wrong order, set OrderError flag and exit loop 1158 $OrderError = 1; 1159 last DYNAMICFIELD; 1160 } 1161 1162 return if $OrderError; 1163 1164 return 1; 1165} 1166 1167=head2 ObjectMappingGet() 1168 1169(a) Fetches object ID(s) for given object name(s). 1170(b) Fetches object name(s) for given object ID(s). 1171 1172NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, 1173like customer user logins and customer company IDs. 1174 1175 my $ObjectMapping = $DynamicFieldObject->ObjectMappingGet( 1176 ObjectName => $ObjectName, # Name or array ref of names of the object(s) to get the ID(s) for 1177 # Note: either give ObjectName or ObjectID 1178 ObjectID => $ObjectID, # ID or array ref of IDs of the object(s) to get the name(s) for 1179 # Note: either give ObjectName or ObjectID 1180 ObjectType => 'CustomerUser', # Type of object to get mapping for 1181 ); 1182 1183 Returns for parameter ObjectID: 1184 $ObjectMapping = { 1185 ObjectID => ObjectName, 1186 ObjectID => ObjectName, 1187 ObjectID => ObjectName, 1188 # ... 1189 }; 1190 1191 Returns for parameter ObjectName: 1192 $ObjectMapping = { 1193 ObjectName => ObjectID, 1194 ObjectName => ObjectID, 1195 ObjectName => ObjectID, 1196 # ... 1197 }; 1198 1199=cut 1200 1201sub ObjectMappingGet { 1202 my ( $Self, %Param ) = @_; 1203 1204 # check needed stuff 1205 for my $Needed (qw( ObjectType )) { 1206 if ( !$Param{$Needed} ) { 1207 $Kernel::OM->Get('Kernel::System::Log')->Log( 1208 Priority => 'error', 1209 Message => "Need $Needed!" 1210 ); 1211 return; 1212 } 1213 } 1214 1215 if ( $Param{ObjectName} && $Param{ObjectID} ) { 1216 $Kernel::OM->Get('Kernel::System::Log')->Log( 1217 Priority => 'error', 1218 Message => "Either give parameter ObjectName or ObjectID, not both." 1219 ); 1220 return; 1221 } 1222 1223 if ( !$Param{ObjectName} && !$Param{ObjectID} ) { 1224 $Kernel::OM->Get('Kernel::System::Log')->Log( 1225 Priority => 'error', 1226 Message => "You have to give parameter ObjectName or ObjectID." 1227 ); 1228 return; 1229 } 1230 1231 # Get config object 1232 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1233 1234 # Get configuration for this object type 1235 my $Config = $ConfigObject->Get("DynamicFields::ObjectType") || {}; 1236 my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; 1237 1238 if ( !IsHashRefWithData($ObjecTypesConfig) ) { 1239 $Kernel::OM->Get('Kernel::System::Log')->Log( 1240 Priority => 'error', 1241 Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", 1242 ); 1243 return; 1244 } 1245 1246 if ( !$ObjecTypesConfig->{UseObjectName} ) { 1247 $Kernel::OM->Get('Kernel::System::Log')->Log( 1248 Priority => 'error', 1249 Message => "Dynamic field object type $Param{ObjectType} does not support this function", 1250 ); 1251 return; 1252 } 1253 1254 my $Type = $Param{ObjectName} ? 'ObjectName' : 'ObjectID'; 1255 if ( !IsArrayRefWithData( $Param{$Type} ) ) { 1256 $Param{$Type} = [ 1257 $Param{$Type}, 1258 ]; 1259 } 1260 my %LookupValues = map { $_ => '?' } @{ $Param{$Type} }; 1261 1262 my $CacheKey = 'ObjectMappingGet::' 1263 . $Type . '::' 1264 . ( join ',', sort keys %LookupValues ) . '::' 1265 . $Param{ObjectType}; 1266 my $CacheType = 'DynamicFieldObjectMapping' . $Type; 1267 1268 # Get cache object. 1269 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1270 my $Cache = $CacheObject->Get( 1271 Type => $CacheType, 1272 Key => $CacheKey, 1273 ); 1274 1275 return $Cache if IsHashRefWithData($Cache); 1276 1277 my $SQL; 1278 if ( $Type eq 'ObjectID' ) { 1279 $SQL = ' 1280 SELECT object_id, object_name 1281 FROM dynamic_field_obj_id_name 1282 WHERE object_id IN (' . ( join ', ', values %LookupValues ) . ') 1283 AND object_type = ?'; 1284 } 1285 else { 1286 $SQL = ' 1287 SELECT object_name, object_id 1288 FROM dynamic_field_obj_id_name 1289 WHERE object_name IN (' . ( join ', ', values %LookupValues ) . ') 1290 AND object_type = ?'; 1291 } 1292 1293 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1294 return if !$DBObject->Prepare( 1295 SQL => $SQL, 1296 Bind => [ 1297 \keys %LookupValues, 1298 \$Param{ObjectType}, 1299 ], 1300 ); 1301 1302 my %ObjectMapping; 1303 while ( my @Data = $DBObject->FetchrowArray() ) { 1304 $ObjectMapping{ $Data[0] } = $Data[1]; 1305 } 1306 1307 # set cache 1308 my $CacheTTL = $ConfigObject->Get('DynamicField::CacheTTL') || 60 * 60 * 12; 1309 $CacheObject->Set( 1310 Type => $CacheType, 1311 Key => $CacheKey, 1312 Value => \%ObjectMapping, 1313 TTL => $CacheTTL, 1314 ); 1315 1316 return \%ObjectMapping; 1317} 1318 1319=head2 ObjectMappingCreate() 1320 1321Creates an object mapping for the given given object name. 1322 1323NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, 1324like customer user logins and customer company IDs. 1325 1326 my $ObjectID = $DynamicFieldObject->ObjectMappingCreate( 1327 ObjectName => 'customer-1', # Name of the object to create the mapping for 1328 ObjectType => 'CustomerUser', # Type of object to create the mapping for 1329 ); 1330 1331=cut 1332 1333sub ObjectMappingCreate { 1334 my ( $Self, %Param ) = @_; 1335 1336 # check needed stuff 1337 for my $Needed (qw( ObjectName ObjectType )) { 1338 if ( !defined $Param{$Needed} || !length $Param{$Needed} ) { 1339 $Kernel::OM->Get('Kernel::System::Log')->Log( 1340 Priority => 'error', 1341 Message => "Need $Needed!" 1342 ); 1343 return; 1344 } 1345 } 1346 1347 # Get configuration for this object type 1348 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {}; 1349 my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; 1350 1351 if ( !IsHashRefWithData($ObjecTypesConfig) ) { 1352 $Kernel::OM->Get('Kernel::System::Log')->Log( 1353 Priority => 'error', 1354 Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", 1355 ); 1356 return; 1357 } 1358 1359 if ( !$ObjecTypesConfig->{UseObjectName} ) { 1360 $Kernel::OM->Get('Kernel::System::Log')->Log( 1361 Priority => 'error', 1362 Message => "Dynamic field object type $Param{ObjectType} does not support this function", 1363 ); 1364 return; 1365 } 1366 1367 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1368 return if !$DBObject->Do( 1369 SQL => ' 1370 INSERT INTO dynamic_field_obj_id_name 1371 (object_name, object_type) 1372 VALUES 1373 (?, ?)', 1374 Bind => [ 1375 \$Param{ObjectName}, 1376 \$Param{ObjectType}, 1377 ], 1378 ); 1379 1380 return if !$DBObject->Prepare( 1381 SQL => ' 1382 SELECT object_id 1383 FROM dynamic_field_obj_id_name 1384 WHERE object_name = ? 1385 AND object_type = ?', 1386 Bind => [ 1387 \$Param{ObjectName}, 1388 \$Param{ObjectType}, 1389 ], 1390 Limit => 1, 1391 ); 1392 1393 my $ObjectID; 1394 while ( my @Data = $DBObject->FetchrowArray() ) { 1395 $ObjectID = $Data[0]; 1396 } 1397 1398 return $ObjectID; 1399} 1400 1401=head2 ObjectMappingNameChange() 1402 1403Changes name of given object mapping. 1404 1405NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, 1406like customer user logins and customer company IDs. 1407 1408 1409 my $Success = $DynamicFieldObject->ObjectMappingNameChange( 1410 OldObjectName => 'customer-1', 1411 NewObjectName => 'customer-2', 1412 ObjectType => 'CustomerUser', # Type of object to change name for 1413 ); 1414 1415 Returns 1 on success. 1416 1417=cut 1418 1419sub ObjectMappingNameChange { 1420 my ( $Self, %Param ) = @_; 1421 1422 # check needed stuff 1423 for my $Needed (qw( OldObjectName NewObjectName ObjectType )) { 1424 if ( !defined $Param{$Needed} || !length $Param{$Needed} ) { 1425 $Kernel::OM->Get('Kernel::System::Log')->Log( 1426 Priority => 'error', 1427 Message => "Need $Needed!" 1428 ); 1429 return; 1430 } 1431 } 1432 1433 # Get configuration for this object type 1434 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {}; 1435 my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; 1436 1437 if ( !IsHashRefWithData($ObjecTypesConfig) ) { 1438 $Kernel::OM->Get('Kernel::System::Log')->Log( 1439 Priority => 'error', 1440 Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", 1441 ); 1442 return; 1443 } 1444 1445 if ( !$ObjecTypesConfig->{UseObjectName} ) { 1446 $Kernel::OM->Get('Kernel::System::Log')->Log( 1447 Priority => 'error', 1448 Message => "Dynamic field object type $Param{ObjectType} does not support this function", 1449 ); 1450 return; 1451 } 1452 1453 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1454 return if !$DBObject->Do( 1455 SQL => ' 1456 UPDATE dynamic_field_obj_id_name 1457 SET object_name = ? 1458 WHERE object_name = ? 1459 AND object_type = ?', 1460 Bind => [ 1461 \$Param{NewObjectName}, 1462 \$Param{OldObjectName}, 1463 \$Param{ObjectType}, 1464 ], 1465 ); 1466 1467 # Clean up cache for type DynamicFieldValueObjectName. 1468 # A cleanup based on the changed object name is not possible. 1469 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1470 $CacheObject->CleanUp( 1471 Type => 'DynamicFieldObjectMappingObjectID', 1472 ); 1473 $CacheObject->CleanUp( 1474 Type => 'DynamicFieldObjectMappingObjectName', 1475 ); 1476 1477 return 1; 1478} 1479 1480sub DESTROY { 1481 my $Self = shift; 1482 1483 # execute all transaction events 1484 $Self->EventHandlerTransaction(); 1485 1486 return 1; 1487} 1488 1489=begin Internal: 1490 1491=cut 1492 1493=head2 _DynamicFieldReorder() 1494 1495re-order the list of fields. 1496 1497 $Success = $DynamicFieldObject->_DynamicFieldReorder( 1498 ID => 123, # mandatory, the field ID that triggers the re-order 1499 Mode => 'Add', # || Update || Delete 1500 FieldOrder => 2, # mandatory, the FieldOrder from the trigger field 1501 ); 1502 1503 $Success = $DynamicFieldObject->_DynamicFieldReorder( 1504 ID => 123, # mandatory, the field ID that triggers the re-order 1505 Mode => 'Update', # || Update || Delete 1506 FieldOrder => 2, # mandatory, the FieldOrder from the trigger field 1507 OldFieldOrder => 10, # mandatory for Mode = 'Update', the FieldOrder before the 1508 # update 1509 ); 1510 1511=cut 1512 1513sub _DynamicFieldReorder { 1514 my ( $Self, %Param ) = @_; 1515 1516 # check needed stuff 1517 for my $Needed (qw(ID FieldOrder Mode)) { 1518 if ( !$Param{$Needed} ) { 1519 $Kernel::OM->Get('Kernel::System::Log')->Log( 1520 Priority => 'error', 1521 Message => 'Need $Needed!' 1522 ); 1523 return; 1524 } 1525 } 1526 1527 if ( $Param{Mode} eq 'Update' ) { 1528 1529 # check needed stuff 1530 if ( !$Param{OldFieldOrder} ) { 1531 $Kernel::OM->Get('Kernel::System::Log')->Log( 1532 Priority => 'error', 1533 Message => 'Need OldFieldOrder!' 1534 ); 1535 return; 1536 } 1537 } 1538 1539 # get the Dynamic Field trigger 1540 my $DynamicFieldTrigger = $Self->DynamicFieldGet( 1541 ID => $Param{ID}, 1542 ); 1543 1544 # extract the field order from the params 1545 my $TriggerFieldOrder = $Param{FieldOrder}; 1546 1547 # get all fields 1548 my $DynamicFieldList = $Self->DynamicFieldListGet( 1549 Valid => 0, 1550 ); 1551 1552 # to store the fields that need to be updated 1553 my @NeedToUpdateList; 1554 1555 # to add or subtract the field order by 1 1556 my $Substract; 1557 1558 # update and add has different algorithms to select the fields to be updated 1559 # check if update 1560 if ( $Param{Mode} eq 'Update' ) { 1561 my $OldFieldOrder = $Param{OldFieldOrder}; 1562 1563 # if the new order and the old order are equal no operation should be performed 1564 # this is a double check from DynamicFieldUpdate (is case of the function is called 1565 # from outside) 1566 return if $TriggerFieldOrder eq $OldFieldOrder; 1567 1568 # set subtract mode for selected fields 1569 if ( $TriggerFieldOrder > $OldFieldOrder ) { 1570 $Substract = 1; 1571 } 1572 1573 # identify fields that needs to be updated 1574 DYNAMICFIELD: 1575 for my $DynamicField ( @{$DynamicFieldList} ) { 1576 1577 # skip wrong fields (if any) 1578 next DYNAMICFIELD if !IsHashRefWithData($DynamicField); 1579 1580 my $CurrentOrder = $DynamicField->{FieldOrder}; 1581 1582 # skip fields with lower order number 1583 next DYNAMICFIELD 1584 if $CurrentOrder < $OldFieldOrder && $CurrentOrder < $TriggerFieldOrder; 1585 1586 # skip trigger field 1587 next DYNAMICFIELD 1588 if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); 1589 1590 # skip this and the rest if has greater order number 1591 last DYNAMICFIELD 1592 if $CurrentOrder > $OldFieldOrder && $CurrentOrder > $TriggerFieldOrder; 1593 1594 push @NeedToUpdateList, $DynamicField; 1595 } 1596 } 1597 1598 # check if delete action 1599 elsif ( $Param{Mode} eq 'Delete' ) { 1600 1601 $Substract = 1; 1602 1603 # identify fields that needs to be updated 1604 DYNAMICFIELD: 1605 for my $DynamicField ( @{$DynamicFieldList} ) { 1606 1607 # skip wrong fields (if any) 1608 next DYNAMICFIELD if !IsHashRefWithData($DynamicField); 1609 1610 my $CurrentOrder = $DynamicField->{FieldOrder}; 1611 1612 # skip fields with lower order number 1613 next DYNAMICFIELD 1614 if $CurrentOrder < $TriggerFieldOrder; 1615 1616 # skip trigger field 1617 next DYNAMICFIELD 1618 if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); 1619 1620 push @NeedToUpdateList, $DynamicField; 1621 } 1622 } 1623 1624 # otherwise is add action 1625 else { 1626 1627 # identify fields that needs to be updated 1628 DYNAMICFIELD: 1629 for my $DynamicField ( @{$DynamicFieldList} ) { 1630 1631 # skip wrong fields (if any) 1632 next DYNAMICFIELD if !IsHashRefWithData($DynamicField); 1633 1634 my $CurrentOrder = $DynamicField->{FieldOrder}; 1635 1636 # skip fields with lower order number 1637 next DYNAMICFIELD 1638 if $CurrentOrder < $TriggerFieldOrder; 1639 1640 # skip trigger field 1641 next DYNAMICFIELD 1642 if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); 1643 1644 push @NeedToUpdateList, $DynamicField; 1645 } 1646 } 1647 1648 # update the fields order incrementing or decrementing by 1 1649 for my $DynamicField (@NeedToUpdateList) { 1650 1651 # hash ref validation is not needed since it was validated before 1652 # check if need to add or subtract 1653 if ($Substract) { 1654 1655 # subtract 1 to the dynamic field order value 1656 $DynamicField->{FieldOrder}--; 1657 } 1658 else { 1659 1660 # add 1 to the dynamic field order value 1661 $DynamicField->{FieldOrder}++; 1662 } 1663 1664 # update the database 1665 my $Success = $Self->DynamicFieldUpdate( 1666 %{$DynamicField}, 1667 UserID => 1, 1668 Reorder => 0, 1669 ); 1670 1671 # check if the update was successful 1672 if ( !$Success ) { 1673 $Kernel::OM->Get('Kernel::System::Log')->Log( 1674 Priority => 'error', 1675 Message => 'An error was detected while re ordering the field list on field ' 1676 . "DynamicField->{Name}!", 1677 ); 1678 return; 1679 } 1680 } 1681 1682 # delete cache 1683 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 1684 Type => 'DynamicField', 1685 ); 1686 1687 return 1; 1688} 1689 1690=end Internal: 1691 1692=head1 TERMS AND CONDITIONS 1693 1694This software is part of the OTRS project (L<https://otrs.org/>). 1695 1696This software comes with ABSOLUTELY NO WARRANTY. For details, see 1697the enclosed file COPYING for license information (GPL). If you 1698did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 1699 1700=cut 1701 17021; 1703