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::Calendar; 10 11use strict; 12use warnings; 13 14use Digest::MD5; 15use MIME::Base64 (); 16 17use Kernel::System::EventHandler; 18use Kernel::System::VariableCheck qw(:all); 19use vars qw(@ISA); 20 21our @ObjectDependencies = ( 22 'Kernel::Config', 23 'Kernel::System::Cache', 24 'Kernel::System::Calendar::Appointment', 25 'Kernel::System::DynamicField', 26 'Kernel::System::Encode', 27 'Kernel::System::Group', 28 'Kernel::System::DB', 29 'Kernel::System::Log', 30 'Kernel::System::Main', 31 'Kernel::System::Queue', 32 'Kernel::System::Storable', 33 'Kernel::System::Ticket', 34 'Kernel::System::Valid', 35); 36 37=head1 NAME 38 39Kernel::System::Calendar - calendar lib 40 41=head1 DESCRIPTION 42 43All calendar functions. 44 45=head1 PUBLIC INTERFACE 46 47=head2 new() 48 49create an object. Do not use it directly, instead use: 50 51 use Kernel::System::ObjectManager; 52 local $Kernel::OM = Kernel::System::ObjectManager->new(); 53 my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar'); 54 55=cut 56 57sub new { 58 my ( $Type, %Param ) = @_; 59 60 # allocate new hash for object 61 my $Self = {%Param}; 62 bless( $Self, $Type ); 63 64 @ISA = qw( 65 Kernel::System::EventHandler 66 ); 67 68 # init of event handler 69 $Self->EventHandlerInit( 70 Config => 'AppointmentCalendar::EventModulePost', 71 ); 72 73 $Self->{CacheType} = 'Calendar'; 74 $Self->{CacheTTL} = 60 * 60 * 24 * 20; 75 76 return $Self; 77} 78 79=head2 CalendarCreate() 80 81creates a new calendar for given user. 82 83 my %Calendar = $CalendarObject->CalendarCreate( 84 CalendarName => 'Meetings', # (required) Personal calendar name 85 GroupID => 3, # (required) GroupID 86 Color => '#FF7700', # (required) Color in hexadecimal RGB notation 87 UserID => 4, # (required) UserID 88 89 TicketAppointments => [ # (optional) Ticket appointments, array ref of hashes 90 { 91 StartDate => 'FirstResponse', 92 EndDate => 'Plus_5', 93 QueueID => [ 2 ], 94 SearchParams => { 95 Title => 'This is a title', 96 Types => 'This is a type', 97 }, 98 }, 99 ], 100 101 ValidID => 1, # (optional) Default is 1. 102 ); 103 104returns Calendar hash if successful: 105 %Calendar = ( 106 CalendarID => 2, 107 GroupID => 3, 108 CalendarName => 'Meetings', 109 CreateTime => '2016-01-01 08:00:00', 110 CreateBy => 4, 111 ChangeTime => '2016-01-01 08:00:00', 112 ChangeBy => 4, 113 ValidID => 1, 114 ); 115 116Events: 117 CalendarCreate 118 119=cut 120 121sub CalendarCreate { 122 my ( $Self, %Param ) = @_; 123 124 # check needed stuff 125 for my $Needed (qw(CalendarName GroupID Color UserID)) { 126 if ( !$Param{$Needed} ) { 127 $Kernel::OM->Get('Kernel::System::Log')->Log( 128 Priority => 'error', 129 Message => "Need $Needed!", 130 ); 131 return; 132 } 133 } 134 135 # check color 136 if ( !( $Param{Color} =~ /#[A-F0-9]{3,6}/i ) ) { 137 $Kernel::OM->Get('Kernel::System::Log')->Log( 138 Priority => 'error', 139 Message => 'Color must be in hexadecimal RGB notation, eg. #FFFFFF.', 140 ); 141 return; 142 } 143 144 # reset ticket appointments 145 if ( !( scalar @{ $Param{TicketAppointments} // [] } ) ) { 146 $Param{TicketAppointments} = undef; 147 } 148 149 # make it uppercase for the sake of consistency 150 $Param{Color} = uc $Param{Color}; 151 152 my $ValidID = defined $Param{ValidID} ? $Param{ValidID} : 1; 153 154 my %Calendar = $Self->CalendarGet( 155 CalendarName => $Param{CalendarName}, 156 ); 157 158 # return if calendar with same name already exists 159 return if %Calendar; 160 161 # create salt string 162 my $SaltString = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( 163 Length => 64, 164 ); 165 166 # serialize and encode ticket appointment data 167 my $TicketAppointments; 168 if ( $Param{TicketAppointments} ) { 169 $TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Serialize( 170 Data => $Param{TicketAppointments}, 171 ); 172 $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput($TicketAppointments); 173 $TicketAppointments = MIME::Base64::encode_base64($TicketAppointments); 174 } 175 176 my $SQL = ' 177 INSERT INTO calendar 178 (group_id, name, salt_string, color, ticket_appointments, create_time, create_by, 179 change_time, change_by, valid_id) 180 VALUES (?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?, ?) 181 '; 182 183 # create db record 184 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 185 SQL => $SQL, 186 Bind => [ 187 \$Param{GroupID}, \$Param{CalendarName}, \$SaltString, \$Param{Color}, 188 \$TicketAppointments, \$Param{UserID}, \$Param{UserID}, \$ValidID 189 ], 190 ); 191 192 %Calendar = $Self->CalendarGet( 193 CalendarName => $Param{CalendarName}, 194 UserID => $Param{UserID}, 195 ); 196 return if !%Calendar; 197 198 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 199 200 # cache value 201 $CacheObject->Set( 202 Type => $Self->{CacheType}, 203 Key => $Calendar{CalendarID}, 204 Value => \%Calendar, 205 TTL => $Self->{CacheTTL}, 206 ); 207 208 # reset CalendarList 209 $CacheObject->CleanUp( 210 Type => 'CalendarList', 211 ); 212 213 # fire event 214 $Self->EventHandler( 215 Event => 'CalendarCreate', 216 Data => { 217 %Calendar, 218 }, 219 UserID => $Param{UserID}, 220 ); 221 222 return %Calendar; 223} 224 225=head2 CalendarGet() 226 227Get calendar by name or id. 228 229 my %Calendar = $CalendarObject->CalendarGet( 230 CalendarName => 'Meetings', # (required) Calendar name 231 # or 232 CalendarID => 4, # (required) CalendarID 233 234 UserID => 2, # (optional) UserID - System will check if user has access to calendar if provided 235 ); 236 237Returns Calendar data: 238 239 %Calendar = ( 240 CalendarID => 2, 241 GroupID => 3, 242 CalendarName => 'Meetings', 243 Color => '#FF7700', 244 TicketAppointments => [ 245 { 246 StartDate => 'FirstResponse', 247 EndDate => 'Plus_5', 248 QueueID => [ 2 ], 249 SearchParams => { 250 Title => 'This is a title', 251 Types => 'This is a type', 252 }, 253 }, 254 ], 255 CreateTime => '2016-01-01 08:00:00', 256 CreateBy => 1, 257 ChangeTime => '2016-01-01 08:00:00', 258 ChangeBy => 1, 259 ValidID => 1, 260 ); 261 262=cut 263 264sub CalendarGet { 265 my ( $Self, %Param ) = @_; 266 267 # check needed stuff 268 if ( !$Param{CalendarID} && !$Param{CalendarName} ) { 269 $Kernel::OM->Get('Kernel::System::Log')->Log( 270 Priority => 'error', 271 Message => "Need CalendarID or CalendarName!", 272 ); 273 return; 274 } 275 276 my %Calendar; 277 278 if ( $Param{CalendarID} ) { 279 280 # check if value is cached 281 my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get( 282 Type => $Self->{CacheType}, 283 Key => $Param{CalendarID}, 284 ); 285 286 if ( IsHashRefWithData($Data) ) { 287 %Calendar = %{$Data}; 288 } 289 } 290 291 if ( !%Calendar ) { 292 293 # create db object 294 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 295 296 my $SQL = ' 297 SELECT id, group_id, name, color, ticket_appointments, create_time, create_by, 298 change_time, change_by, valid_id 299 FROM calendar 300 WHERE 301 '; 302 303 my @Bind; 304 if ( $Param{CalendarID} ) { 305 $SQL .= ' 306 id=? 307 '; 308 push @Bind, \$Param{CalendarID}; 309 } 310 else { 311 $SQL .= ' 312 name=? 313 '; 314 push @Bind, \$Param{CalendarName}; 315 } 316 317 # db query 318 return if !$DBObject->Prepare( 319 SQL => $SQL, 320 Bind => \@Bind, 321 Limit => 1, 322 ); 323 324 while ( my @Row = $DBObject->FetchrowArray() ) { 325 326 # decode and deserialize ticket appointment data 327 my $TicketAppointments; 328 if ( $Row[4] ) { 329 my $DecodedData = MIME::Base64::decode_base64( $Row[4] ); 330 $TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Deserialize( 331 Data => $DecodedData, 332 ); 333 $TicketAppointments = undef if ref $TicketAppointments ne 'ARRAY'; 334 } 335 336 $Calendar{CalendarID} = $Row[0]; 337 $Calendar{GroupID} = $Row[1]; 338 $Calendar{CalendarName} = $Row[2]; 339 $Calendar{Color} = $Row[3]; 340 $Calendar{TicketAppointments} = $TicketAppointments; 341 $Calendar{CreateTime} = $Row[5]; 342 $Calendar{CreateBy} = $Row[6]; 343 $Calendar{ChangeTime} = $Row[7]; 344 $Calendar{ChangeBy} = $Row[8]; 345 $Calendar{ValidID} = $Row[9]; 346 } 347 348 if ( $Param{CalendarID} ) { 349 350 # cache 351 $Kernel::OM->Get('Kernel::System::Cache')->Set( 352 Type => $Self->{CacheType}, 353 Key => $Param{CalendarID}, 354 Value => \%Calendar, 355 TTL => $Self->{CacheTTL}, 356 ); 357 } 358 359 if ( $Param{UserID} && $Calendar{GroupID} ) { 360 361 # get user groups 362 my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( 363 UserID => $Param{UserID}, 364 Type => 'ro', 365 ); 366 367 if ( !grep { $Calendar{GroupID} == $_ } keys %GroupList ) { 368 %Calendar = (); 369 } 370 } 371 } 372 373 return %Calendar; 374} 375 376=head2 CalendarList() 377 378Get calendar list. 379 380 my @Result = $CalendarObject->CalendarList( 381 UserID => 4, # (optional) For permission check 382 Permission => 'rw', # (optional) Required permission (default ro) 383 ValidID => 1, # (optional) Default 0. 384 # 0 - All states 385 # 1 - All valid 386 # 2 - All invalid 387 # 3 - All temporary invalid 388 ); 389 390Returns: 391 392 @Result = [ 393 { 394 CalendarID => 2, 395 GroupID => 3, 396 CalendarName => 'Meetings', 397 Color => '#FF7700', 398 CreateTime => '2016-01-01 08:00:00', 399 CreateBy => 3, 400 ChangeTime => '2016-01-01 08:00:00', 401 ChangeBy => 3, 402 ValidID => 1, 403 }, 404 { 405 CalendarID => 3, 406 GroupID => 3, 407 CalendarName => 'Customer presentations', 408 Color => '#BB00BB', 409 CreateTime => '2016-01-01 08:00:00', 410 CreateBy => 3, 411 ChangeTime => '2016-01-01 08:00:00', 412 ChangeBy => 3, 413 ValidID => 0, 414 }, 415 ... 416 ]; 417 418=cut 419 420sub CalendarList { 421 my ( $Self, %Param ) = @_; 422 423 # Make different cache type for list (so we can clear cache by this value) 424 my $CacheType = 'CalendarList'; 425 my $CacheKeyUser = $Param{UserID} || 'all-user-ids'; 426 my $CacheKeyValid = $Param{ValidID} || 'all-valid-ids'; 427 428 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 429 430 # get cached value if exists 431 my $Data = $CacheObject->Get( 432 Type => $CacheType, 433 Key => "$CacheKeyUser-$CacheKeyValid", 434 ); 435 436 if ( !IsArrayRefWithData($Data) ) { 437 438 # create needed objects 439 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 440 441 my $SQL = ' 442 SELECT id, group_id, name, color, create_time, create_by, change_time, change_by, 443 valid_id 444 FROM calendar 445 WHERE 1=1 446 '; 447 my @Bind; 448 449 if ( $Param{ValidID} ) { 450 $SQL .= ' AND valid_id=? '; 451 push @Bind, \$Param{ValidID}; 452 } 453 $SQL .= 'ORDER BY id ASC'; 454 455 # db query 456 return if !$DBObject->Prepare( 457 SQL => $SQL, 458 Bind => \@Bind, 459 ); 460 461 my @Result; 462 while ( my @Row = $DBObject->FetchrowArray() ) { 463 my %Calendar; 464 $Calendar{CalendarID} = $Row[0]; 465 $Calendar{GroupID} = $Row[1]; 466 $Calendar{CalendarName} = $Row[2]; 467 $Calendar{Color} = $Row[3]; 468 $Calendar{CreateTime} = $Row[4]; 469 $Calendar{CreateBy} = $Row[5]; 470 $Calendar{ChangeTime} = $Row[6]; 471 $Calendar{ChangeBy} = $Row[7]; 472 $Calendar{ValidID} = $Row[8]; 473 push @Result, \%Calendar; 474 } 475 476 # cache data 477 $CacheObject->Set( 478 Type => $CacheType, 479 Key => "$CacheKeyUser-$CacheKeyValid", 480 Value => \@Result, 481 TTL => $Self->{CacheTTL}, 482 ); 483 484 $Data = \@Result; 485 } 486 487 if ( $Param{UserID} ) { 488 489 # get user groups 490 my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( 491 UserID => $Param{UserID}, 492 Type => $Param{Permission} || 'ro', 493 ); 494 495 my @Result; 496 497 for my $Item ( @{$Data} ) { 498 if ( grep { $Item->{GroupID} == $_ } keys %GroupList ) { 499 push @Result, $Item; 500 } 501 } 502 503 $Data = \@Result; 504 } 505 506 return @{$Data}; 507} 508 509=head2 CalendarUpdate() 510 511updates an existing calendar. 512 513 my $Success = $CalendarObject->CalendarUpdate( 514 CalendarID => 1, # (required) CalendarID 515 GroupID => 2, # (required) Calendar group 516 CalendarName => 'Meetings', # (required) Personal calendar name 517 Color => '#FF9900', # (required) Color in hexadecimal RGB notation 518 UserID => 4, # (required) UserID (who made update) 519 ValidID => 1, # (required) ValidID 520 521 TicketAppointments => [ # (optional) Ticket appointments, array ref of hashes 522 { 523 StartDate => 'FirstResponse', 524 EndDate => 'Plus_5', 525 QueueID => [ 2 ], 526 SearchParams => { 527 Title => 'This is a title', 528 Types => 'This is a type', 529 }, 530 }, 531 ], 532 ); 533 534Returns 1 if successful. 535 536Events: 537 CalendarUpdate 538 539=cut 540 541sub CalendarUpdate { 542 my ( $Self, %Param ) = @_; 543 544 # check needed stuff 545 for my $Needed (qw(CalendarID GroupID CalendarName Color UserID ValidID)) { 546 if ( !$Param{$Needed} ) { 547 $Kernel::OM->Get('Kernel::System::Log')->Log( 548 Priority => 'error', 549 Message => "Need $Needed!", 550 ); 551 return; 552 } 553 } 554 555 # check color 556 if ( !( $Param{Color} =~ /#[A-F0-9]{3,6}/i ) ) { 557 $Kernel::OM->Get('Kernel::System::Log')->Log( 558 Priority => 'error', 559 Message => 'Color must be in hexadecimal RGB notation, eg. #FFFFFF.', 560 ); 561 return; 562 } 563 564 # reset ticket appointments 565 if ( !( scalar @{ $Param{TicketAppointments} // [] } ) ) { 566 $Param{TicketAppointments} = undef; 567 } 568 569 # make it uppercase for the sake of consistency 570 $Param{Color} = uc $Param{Color}; 571 572 # serialize and encode ticket appointment data 573 my $TicketAppointments; 574 if ( $Param{TicketAppointments} ) { 575 $TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Serialize( 576 Data => $Param{TicketAppointments}, 577 ); 578 $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput($TicketAppointments); 579 $TicketAppointments = MIME::Base64::encode_base64($TicketAppointments); 580 } 581 582 my $SQL = ' 583 UPDATE calendar 584 SET group_id=?, name=?, color=?, ticket_appointments=?, change_time=current_timestamp, 585 change_by=?, valid_id=? 586 '; 587 588 my @Bind; 589 push @Bind, \$Param{GroupID}, \$Param{CalendarName}, \$Param{Color}, \$TicketAppointments, 590 \$Param{UserID}, \$Param{ValidID}; 591 592 $SQL .= ' 593 WHERE id=? 594 '; 595 push @Bind, \$Param{CalendarID}; 596 597 # create db record 598 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 599 SQL => $SQL, 600 Bind => \@Bind, 601 ); 602 603 # get cache object 604 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 605 606 # clear cache 607 $CacheObject->CleanUp( 608 Type => 'CalendarList', 609 ); 610 611 $CacheObject->Delete( 612 Type => $Self->{CacheType}, 613 Key => $Param{CalendarID}, 614 ); 615 616 # fire event 617 $Self->EventHandler( 618 Event => 'CalendarUpdate', 619 Data => { 620 CalendarID => $Param{CalendarID}, 621 }, 622 UserID => $Param{UserID}, 623 ); 624 625 return 1; 626} 627 628=head2 CalendarImport() 629 630import a calendar 631 632 my $Success = $CalendarObject->CalendarImport( 633 Data => { 634 CalendarData => { 635 CalendarID => 2, 636 GroupID => 3, 637 CalendarName => 'Meetings', 638 Color => '#FF7700', 639 ValidID => 1, 640 }, 641 AppointmentData => { 642 { 643 AppointmentID => 2, 644 ParentID => 1, 645 CalendarID => 1, 646 UniqueID => '20160101T160000-71E386@localhost', 647 ... 648 }, 649 ... 650 }, 651 }, 652 OverwriteExistingEntities => 0, # (optional) Overwrite existing calendar and appointments, default: 0 653 # Calendar with same name will be overwritten 654 # Appointments with same UniqueID in existing calendar will be overwritten 655 UserID => 1, 656 ); 657 658returns 1 if successful 659 660=cut 661 662sub CalendarImport { 663 my ( $Self, %Param ) = @_; 664 665 # check needed stuff 666 for my $Needed (qw(Data UserID)) { 667 if ( !$Param{$Needed} ) { 668 $Kernel::OM->Get('Kernel::System::Log')->Log( 669 Priority => 'error', 670 Message => "Need $Needed!", 671 ); 672 return; 673 } 674 } 675 676 return if !IsHashRefWithData( $Param{Data} ); 677 return if !IsHashRefWithData( $Param{Data}->{CalendarData} ); 678 679 if ( 680 defined $Param{Data}->{CalendarData}->{TicketAppointments} 681 && IsArrayRefWithData( $Param{Data}->{CalendarData}->{TicketAppointments} ) 682 ) 683 { 684 # Get queue create permissions for the user. 685 my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( 686 UserID => $Param{UserID}, 687 Type => 'create', 688 ); 689 690 my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); 691 692 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 693 694 # Queue field in ticket appointments is mandatory, check if it's present and valid. 695 for my $Rule ( @{ $Param{Data}->{CalendarData}->{TicketAppointments} } ) { 696 if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) { 697 698 QUEUE_ID: 699 for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) { 700 my %QueueData = $QueueObject->QueueGet( ID => $QueueID ); 701 702 if ( 703 !grep { $_ eq $QueueData{ValidID} } @ValidIDs 704 || !$UserGroups{ $QueueData{GroupID} } 705 ) 706 { 707 $Kernel::OM->Get('Kernel::System::Log')->Log( 708 Priority => 'error', 709 Message => "Invalid queue ID $QueueID in ticket appointment rule or no permissions!", 710 ); 711 return; 712 } 713 } 714 } 715 else { 716 $Kernel::OM->Get('Kernel::System::Log')->Log( 717 Priority => 'error', 718 Message => 'Need queue ID in ticket appointment rules!', 719 ); 720 return; 721 } 722 } 723 } 724 725 # check for an existing calendar 726 my %ExistingCalendar = $Self->CalendarGet( 727 CalendarName => $Param{Data}->{CalendarData}->{CalendarName}, 728 ); 729 730 my $CalendarID; 731 732 # create new calendar 733 if ( !IsHashRefWithData( \%ExistingCalendar ) ) { 734 my %Calendar = $Self->CalendarCreate( 735 %{ $Param{Data}->{CalendarData} }, 736 UserID => $Param{UserID}, 737 ); 738 return if !$Calendar{CalendarID}; 739 740 $CalendarID = $Calendar{CalendarID}; 741 } 742 743 # update existing calendar 744 else { 745 if ( $Param{OverwriteExistingEntities} ) { 746 my $Success = $Self->CalendarUpdate( 747 %{ $Param{Data}->{CalendarData} }, 748 CalendarID => $ExistingCalendar{CalendarID}, 749 UserID => $Param{UserID}, 750 ); 751 return if !$Success; 752 } 753 754 $CalendarID = $ExistingCalendar{CalendarID}; 755 } 756 757 # import appointments 758 if ( $CalendarID && IsArrayRefWithData( $Param{Data}->{AppointmentData} ) ) { 759 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 760 my $AppointmentID; 761 762 APPOINTMENT: 763 for my $Appointment ( @{ $Param{Data}->{AppointmentData} } ) { 764 765 # add to existing calendar 766 $Appointment->{CalendarID} = $CalendarID; 767 768 # create new appointment if NOT overwriting existing entities 769 $Appointment->{UniqueID} = undef if !$Param{OverwriteExistingEntities}; 770 771 # skip adding automatic recurring occurrences 772 if ( $Appointment->{Recurring} ) { 773 $Appointment->{RecurringRaw} = 1; 774 } 775 776 # set parent id to last appointment id 777 if ( $Appointment->{ParentID} ) { 778 $Appointment->{ParentID} = $AppointmentID; 779 } 780 781 $AppointmentID = $AppointmentObject->AppointmentCreate( 782 %{$Appointment}, 783 UserID => $Param{UserID}, 784 ); 785 return if !$AppointmentID; 786 } 787 } 788 789 return 1; 790} 791 792=head2 CalendarExport() 793 794export a calendar 795 796 my %Data = $CalendarObject->CalendarExport( 797 CalendarID => 2, 798 UserID => 1, 799 } 800 801returns calendar hash with data: 802 803 %Data = ( 804 CalendarData => { 805 CalendarID => 2, 806 GroupID => 3, 807 CalendarName => 'Meetings', 808 Color => '#FF7700', 809 ValidID => 1, 810 }, 811 AppointmentData => ( 812 { 813 AppointmentID => 2, 814 ParentID => 1, 815 CalendarID => 1, 816 UniqueID => '20160101T160000-71E386@localhost', 817 ... 818 }, 819 ... 820 ), 821 ); 822 823=cut 824 825sub CalendarExport { 826 my ( $Self, %Param ) = @_; 827 828 # check needed stuff 829 for my $Needed (qw(CalendarID UserID)) { 830 if ( !$Param{$Needed} ) { 831 $Kernel::OM->Get('Kernel::System::Log')->Log( 832 Priority => 'error', 833 Message => "Need $Needed!", 834 ); 835 return; 836 } 837 } 838 839 # get calendar data 840 my %CalendarData = $Self->CalendarGet( 841 CalendarID => $Param{CalendarID}, 842 UserID => $Param{UserID}, 843 ); 844 return if !IsHashRefWithData( \%CalendarData ); 845 846 # get appointment object 847 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 848 849 # get list of appointments 850 my @Appointments = $AppointmentObject->AppointmentList( 851 CalendarID => $Param{CalendarID}, 852 Result => 'ARRAY', 853 ); 854 855 my @AppointmentData; 856 857 APPOINTMENTID: 858 for my $AppointmentID (@Appointments) { 859 my %Appointment = $AppointmentObject->AppointmentGet( 860 AppointmentID => $AppointmentID, 861 ); 862 next APPOINTMENTID if !%Appointment; 863 next APPOINTMENTID if $Appointment{TicketAppointmentRuleID}; 864 865 push @AppointmentData, \%Appointment; 866 } 867 868 my %Result = ( 869 CalendarData => \%CalendarData, 870 AppointmentData => \@AppointmentData, 871 ); 872 873 return %Result; 874} 875 876=head2 CalendarPermissionGet() 877 878Get permission level for given CalendarID and UserID. 879 880 my $Permission = $CalendarObject->CalendarPermissionGet( 881 CalendarID => 1, # (required) CalendarID 882 UserID => 4, # (required) UserID 883 ); 884 885Returns: 886 887 $Permission = 'rw'; # 'ro', 'rw', ... 888 889=cut 890 891sub CalendarPermissionGet { 892 my ( $Self, %Param ) = @_; 893 894 # check needed stuff 895 for my $Needed (qw(CalendarID UserID)) { 896 if ( !$Param{$Needed} ) { 897 $Kernel::OM->Get('Kernel::System::Log')->Log( 898 Priority => 'error', 899 Message => "Need $Needed!", 900 ); 901 return; 902 } 903 } 904 905 # make sure super user has read/write permission 906 return 'rw' if $Param{UserID} eq 1; 907 908 my %Calendar = $Self->CalendarGet( 909 CalendarID => $Param{CalendarID}, 910 ); 911 912 my $Result = ''; 913 914 my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); 915 916 TYPE: 917 for my $Type (qw(ro move_into create rw)) { 918 919 my %GroupData = $GroupObject->PermissionUserGet( 920 UserID => $Param{UserID}, 921 Type => $Type, 922 ); 923 924 if ( $GroupData{ $Calendar{GroupID} } ) { 925 $Result = $Type; 926 } 927 else { 928 last TYPE; 929 } 930 } 931 932 return $Result; 933} 934 935=head2 TicketAppointmentProcessTicket() 936 937Handle the automatic ticket appointments for the ticket. 938 939 $CalendarObject->TicketAppointmentProcessTicket( 940 TicketID => 1, 941 ); 942 943This method does not have return value. 944 945=cut 946 947sub TicketAppointmentProcessTicket { 948 my ( $Self, %Param ) = @_; 949 950 # check needed stuff 951 if ( !$Param{TicketID} ) { 952 $Kernel::OM->Get('Kernel::System::Log')->Log( 953 Priority => 'error', 954 Message => 'Need TicketID!', 955 ); 956 return; 957 } 958 959 # get all valid calendars 960 my @Calendars = $Self->CalendarList( 961 ValidID => 1, 962 ); 963 return if !@Calendars; 964 965 # get ticket appointment types 966 my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet(); 967 968 # get ticket object 969 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 970 971 # go through all calendars with defined ticket appointments 972 CALENDAR: 973 for my $Calendar (@Calendars) { 974 my %CalendarData = $Self->CalendarGet( 975 CalendarID => $Calendar->{CalendarID}, 976 ); 977 next CALENDAR if !$CalendarData{TicketAppointments}; 978 979 TICKET_APPOINTMENTS: 980 for my $TicketAppointments ( @{ $CalendarData{TicketAppointments} } ) { 981 982 # check appointment types 983 for my $Field (qw(StartDate EndDate)) { 984 985 # allow special time presets for EndDate 986 if ( $Field ne 'EndDate' && !( $TicketAppointments->{$Field} =~ /^Plus_/ ) ) { 987 988 # skip if ticket appointment type is invalid 989 if ( !$TicketAppointmentTypes{ $TicketAppointments->{$Field} } ) { 990 next TICKET_APPOINTMENTS; 991 } 992 } 993 } 994 995 # check if ticket satisfies the search filter from the ticket appointment rule 996 # pass all configured parameters to ticket search, including ticket id 997 my $Filtered = $TicketObject->TicketSearch( 998 Result => 'COUNT', 999 TicketID => $Param{TicketID}, 1000 QueueIDs => $TicketAppointments->{QueueID}, 1001 UserID => 1, 1002 %{ $TicketAppointments->{SearchParam} // {} }, 1003 ); 1004 1005 # ticket was found 1006 if ($Filtered) { 1007 1008 # process ticket appointment rule 1009 $Self->TicketAppointmentProcessRule( 1010 CalendarID => $Calendar->{CalendarID}, 1011 Config => \%TicketAppointmentTypes, 1012 Rule => $TicketAppointments, 1013 TicketID => $Param{TicketID}, 1014 ); 1015 } 1016 1017 # ticket was not found 1018 else { 1019 1020 # remove any existing ticket appointment 1021 $Self->TicketAppointmentDelete( 1022 CalendarID => $Calendar->{CalendarID}, 1023 TicketID => $Param{TicketID}, 1024 RuleID => $TicketAppointments->{RuleID}, 1025 ); 1026 } 1027 } 1028 } 1029 1030 if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) { 1031 $Kernel::OM->Get('Kernel::System::Log')->Log( 1032 Priority => 'debug', 1033 Message => "Processed ticket appointments for ticket $Param{TicketID}.", 1034 ); 1035 } 1036 1037 return; 1038} 1039 1040=head2 TicketAppointmentProcessCalendar() 1041 1042Handle the automatic ticket appointments for the calendar. 1043 1044 my %Result = $CalendarObject->TicketAppointmentProcessCalendar( 1045 CalendarID => 1, 1046 ); 1047 1048Returns log of processed tickets and rules: 1049 1050 %Result = ( 1051 Process => [ 1052 { 1053 TicketID => 1, 1054 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 1055 Success => 1, 1056 }, 1057 { 1058 TicketID => 2, 1059 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 1060 Success => 1, 1061 }, 1062 ], 1063 Cleanup => [ 1064 { 1065 RuleID => 'b272a035ed82d65a927a99300e00c9b5', 1066 Success => 1, 1067 }, 1068 ], 1069 ); 1070 1071=cut 1072 1073sub TicketAppointmentProcessCalendar { 1074 my ( $Self, %Param ) = @_; 1075 1076 # check needed stuff 1077 if ( !$Param{CalendarID} ) { 1078 $Kernel::OM->Get('Kernel::System::Log')->Log( 1079 Priority => 'error', 1080 Message => 'Need CalendarID!', 1081 ); 1082 return; 1083 } 1084 1085 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1086 1087 # Get calendar configuration. 1088 my %Calendar = $Self->CalendarGet( 1089 CalendarID => $Param{CalendarID}, 1090 ); 1091 if ( !%Calendar ) { 1092 $Kernel::OM->Get('Kernel::System::Log')->Log( 1093 Priority => 'error', 1094 Message => "Could not find calendar $Param{CalendarID}!", 1095 ); 1096 return; 1097 } 1098 1099 # Get ticket appointment types 1100 my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet(); 1101 1102 my @Process; 1103 my @Cleanup; 1104 my %RuleIDLookup; 1105 1106 # Check ticket appointments config. 1107 if ( $Calendar{TicketAppointments} && IsArrayRefWithData( $Calendar{TicketAppointments} ) ) { 1108 1109 # Get active rule IDs from the calendar configuration. 1110 %RuleIDLookup = map { $_->{RuleID} => 1 } @{ $Calendar{TicketAppointments} }; 1111 1112 TICKET_APPOINTMENTS: 1113 for my $TicketAppointments ( @{ $Calendar{TicketAppointments} } ) { 1114 1115 # Check appointment types. 1116 for my $Field (qw(StartDate EndDate)) { 1117 1118 # Allow special time presets for EndDate. 1119 if ( $Field ne 'EndDate' && !( $TicketAppointments->{$Field} =~ /^Plus_/ ) ) { 1120 1121 # Skip if ticket appointment type is invalid. 1122 if ( !$TicketAppointmentTypes{ $TicketAppointments->{$Field} } ) { 1123 next TICKET_APPOINTMENTS; 1124 } 1125 } 1126 } 1127 1128 # Get previously created ticket appointments for this rule. 1129 my %OldAppointments = $Self->_TicketAppointmentList( 1130 CalendarID => $Param{CalendarID}, 1131 RuleID => $TicketAppointments->{RuleID}, 1132 ); 1133 1134 # Find tickets that match search filter 1135 my @TicketIDs = $TicketObject->TicketSearch( 1136 Result => 'ARRAY', 1137 QueueIDs => $TicketAppointments->{QueueID}, 1138 UserID => 1, 1139 %{ $TicketAppointments->{SearchParam} // {} }, 1140 ); 1141 1142 # Process each ticket based on ticket appointment rule. 1143 TICKETID: 1144 for my $TicketID ( sort @TicketIDs ) { 1145 my $Success = $Self->TicketAppointmentProcessRule( 1146 CalendarID => $Param{CalendarID}, 1147 Config => \%TicketAppointmentTypes, 1148 Rule => $TicketAppointments, 1149 TicketID => $TicketID, 1150 ); 1151 1152 push @Process, { 1153 TicketID => $TicketID, 1154 RuleID => $TicketAppointments->{RuleID}, 1155 Success => $Success, 1156 }; 1157 } 1158 1159 # Remove previously created ticket appointments if they don't match the rule anymore. 1160 OLDTICKETID: 1161 for my $OldTicketID ( sort keys %OldAppointments ) { 1162 next OLDTICKETID if grep { $OldTicketID == $_ } @TicketIDs; 1163 1164 my $Success = $Self->TicketAppointmentDelete( 1165 AppointmentID => $OldAppointments{$OldTicketID}, 1166 TicketID => $OldTicketID, 1167 CalendarID => $Param{CalendarID}, 1168 RuleID => $TicketAppointments->{RuleID}, 1169 ); 1170 1171 push @Cleanup, { 1172 AppointmentID => $OldAppointments{$OldTicketID}, 1173 TicketID => $OldTicketID, 1174 RuleID => $TicketAppointments->{RuleID}, 1175 Success => $Success, 1176 }; 1177 } 1178 } 1179 } 1180 1181 my @RuleIDs = $Self->TicketAppointmentRuleIDsGet( 1182 CalendarID => $Param{CalendarID}, 1183 ); 1184 1185 # Remove ticket appointments for missing rules. 1186 for my $RuleID (@RuleIDs) { 1187 if ( !$RuleIDLookup{$RuleID} ) { 1188 my $Success = $Self->TicketAppointmentDelete( 1189 CalendarID => $Param{CalendarID}, 1190 RuleID => $RuleID, 1191 ); 1192 1193 push @Cleanup, { 1194 RuleID => $RuleID, 1195 Success => $Success, 1196 }; 1197 } 1198 } 1199 1200 if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) { 1201 $Kernel::OM->Get('Kernel::System::Log')->Log( 1202 Priority => 'debug', 1203 Message => "Processed ticket appointments for calendar $Param{CalendarID}.", 1204 ); 1205 } 1206 1207 return ( 1208 Process => \@Process, 1209 Cleanup => \@Cleanup, 1210 ); 1211} 1212 1213=head2 TicketAppointmentProcessRule() 1214 1215Process the ticket appointment rule and create, update or delete appointment if necessary. 1216 1217 my $Success = $CalendarObject->TicketAppointmentProcessRule( 1218 CalendarID => 1, 1219 Config => { 1220 DynamicField_TestDate => { 1221 Module => 'Kernel::System::Calendar::Ticket::DynamicField', 1222 }, 1223 ... 1224 }, 1225 Rule => { 1226 StartDate => 'DynamicField_TestDate', 1227 EndDate => 'Plus_5', 1228 QueueID => [ 2 ], 1229 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 1230 SearchParams => { 1231 Title => 'Welcome*', 1232 }, 1233 }, 1234 TicketID => 1, 1235 ); 1236 1237Returns 1 if successful. 1238 1239=cut 1240 1241sub TicketAppointmentProcessRule { 1242 my ( $Self, %Param ) = @_; 1243 1244 # check needed stuff 1245 for my $Needed (qw(CalendarID Config Rule TicketID)) { 1246 if ( !$Param{$Needed} ) { 1247 $Kernel::OM->Get('Kernel::System::Log')->Log( 1248 Priority => 'error', 1249 Message => "Need $Needed!", 1250 ); 1251 return; 1252 } 1253 } 1254 1255 return if !IsHashRefWithData( $Param{Config} ); 1256 return if !IsHashRefWithData( $Param{Rule} ); 1257 1258 my $Error; 1259 my $AppointmentType; 1260 my %AppointmentData; 1261 1262 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 1263 1264 # get start and end time values 1265 for my $Field (qw(StartDate EndDate)) { 1266 my $Type = $Param{Rule}->{$Field}; 1267 1268 # appointment fields are named differently 1269 my $AppointmentField = $Field; 1270 $AppointmentField =~ s/Date$/Time/; 1271 1272 # check if we are dealing with a registered type 1273 if ( $Param{Config}->{$Type} && $Param{Config}->{$Type}->{Module} ) { 1274 my $GenericModule = $Param{Config}->{$Type}->{Module}; 1275 1276 # get the time value via the module method 1277 if ( $MainObject->Require($GenericModule) ) { 1278 $AppointmentData{$AppointmentField} = $GenericModule->new( %{$Self} )->GetTime( 1279 Type => $Type, 1280 TicketID => $Param{TicketID}, 1281 ); 1282 $Error = 1 if !$AppointmentData{$AppointmentField}; 1283 } 1284 } 1285 1286 # time presets are valid only for end time and existing start time 1287 elsif ( $Field eq 'EndDate' && $AppointmentData{StartTime} ) { 1288 if ( $Type =~ /^Plus_([0-9]+)$/ ) { 1289 my $Preset = int $1; 1290 1291 # Get start time. 1292 my $StartTimeObject = $Kernel::OM->Create( 1293 'Kernel::System::DateTime', 1294 ObjectParams => { 1295 String => $AppointmentData{StartTime}, 1296 }, 1297 ); 1298 1299 # Calculate end time using preset value. 1300 my $EndTimeObject = $StartTimeObject->Clone(); 1301 $EndTimeObject->Add( 1302 Minutes => $Preset, 1303 ); 1304 1305 $AppointmentData{EndTime} = $EndTimeObject->ToString(); 1306 } 1307 else { 1308 $Error = 1; 1309 $Kernel::OM->Get('Kernel::System::Log')->Log( 1310 Priority => 'error', 1311 Message => "Invalid time preset: $Type", 1312 ); 1313 } 1314 } 1315 1316 # unknown type 1317 else { 1318 $Error = 1; 1319 } 1320 } 1321 1322 # Prevent end time before start time. 1323 if ( $AppointmentData{StartTime} && $AppointmentData{EndTime} ) { 1324 my $StartTimeObject = $Kernel::OM->Create( 1325 'Kernel::System::DateTime', 1326 ObjectParams => { 1327 String => $AppointmentData{StartTime}, 1328 }, 1329 ); 1330 my $EndTimeObject = $Kernel::OM->Create( 1331 'Kernel::System::DateTime', 1332 ObjectParams => { 1333 String => $AppointmentData{EndTime}, 1334 }, 1335 ); 1336 if ( $EndTimeObject < $StartTimeObject ) { 1337 $AppointmentData{EndTime} = $AppointmentData{StartTime}; 1338 } 1339 } 1340 1341 # get appointment title 1342 if ( !$Error ) { 1343 1344 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1345 1346 my $TicketHook = $ConfigObject->Get('Ticket::Hook'); 1347 my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider'); 1348 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( 1349 TicketID => $Param{TicketID}, 1350 DynamicFields => 0, 1351 UserID => 1, 1352 ); 1353 $AppointmentData{Title} = "[$TicketHook$TicketHookDivider$Ticket{TicketNumber}] $Ticket{Title}"; 1354 } 1355 1356 my $Success; 1357 1358 # check if ticket appointment already exists 1359 my $AppointmentID = $Self->_TicketAppointmentGet( 1360 CalendarID => $Param{CalendarID}, 1361 TicketID => $Param{TicketID}, 1362 RuleID => $Param{Rule}->{RuleID}, 1363 ); 1364 1365 # ticket appointment was found 1366 if ($AppointmentID) { 1367 1368 # delete the ticket appointment, if error was raised 1369 if ($Error) { 1370 $Success = $Self->TicketAppointmentDelete( 1371 CalendarID => $Param{CalendarID}, 1372 TicketID => $Param{TicketID}, 1373 RuleID => $Param{Rule}->{RuleID}, 1374 AppointmentID => $AppointmentID, 1375 ); 1376 } 1377 1378 # update the ticket appointment, otherwise 1379 else { 1380 $Success = $Self->_TicketAppointmentUpdate( 1381 CalendarID => $Param{CalendarID}, 1382 AppointmentID => $AppointmentID, 1383 TicketID => $Param{TicketID}, 1384 RuleID => $Param{Rule}->{RuleID}, 1385 %AppointmentData, 1386 ); 1387 } 1388 } 1389 1390 # create ticket appointment if not found 1391 elsif ( !$Error ) { 1392 $Success = $Self->_TicketAppointmentCreate( 1393 CalendarID => $Param{CalendarID}, 1394 TicketID => $Param{TicketID}, 1395 RuleID => $Param{Rule}->{RuleID}, 1396 %AppointmentData, 1397 ); 1398 } 1399 1400 return $Success; 1401} 1402 1403=head2 TicketAppointmentUpdateTicket() 1404 1405Updates the ticket with data from ticket appointment. 1406 1407 $CalendarObject->TicketAppointmentUpdateTicket( 1408 AppointmentID => 1, 1409 TicketID => 1, 1410 ); 1411 1412This method does not have return value. 1413 1414=cut 1415 1416sub TicketAppointmentUpdateTicket { 1417 my ( $Self, %Param ) = @_; 1418 1419 # check needed stuff 1420 for my $Needed (qw(AppointmentID TicketID)) { 1421 if ( !$Param{$Needed} ) { 1422 $Kernel::OM->Get('Kernel::System::Log')->Log( 1423 Priority => 'error', 1424 Message => "Need $Needed!", 1425 ); 1426 return; 1427 } 1428 } 1429 1430 # get appointment data 1431 my %AppointmentData = $Kernel::OM->Get('Kernel::System::Calendar::Appointment')->AppointmentGet( 1432 AppointmentID => $Param{AppointmentID}, 1433 ); 1434 1435 # stop if not ticket appointment 1436 return if !$AppointmentData{TicketAppointmentRuleID}; 1437 1438 # get ticket appointment rule 1439 my $Rule = $Self->TicketAppointmentRuleGet( 1440 CalendarID => $AppointmentData{CalendarID}, 1441 RuleID => $AppointmentData{TicketAppointmentRuleID}, 1442 ); 1443 return if !IsHashRefWithData($Rule); 1444 1445 # get ticket appointment types 1446 my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet(); 1447 1448 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 1449 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1450 1451 # process start and end time values 1452 for my $Field (qw(StartDate EndDate)) { 1453 my $Type = $Rule->{$Field}; 1454 1455 # appointment fields are named differently 1456 my $AppointmentField = $Field; 1457 $AppointmentField =~ s/Date$/Time/; 1458 1459 # check if we are dealing with a registered type 1460 if ( $TicketAppointmentTypes{$Type} && $TicketAppointmentTypes{$Type}->{Module} ) { 1461 my $GenericModule = $TicketAppointmentTypes{$Type}->{Module}; 1462 1463 # set the time value via the module method 1464 if ( $MainObject->Require($GenericModule) ) { 1465 1466 # loop protection: prevent ticket event module from running 1467 $TicketObject->{'_TicketAppointments::AlreadyProcessed'}->{ $Param{TicketID} }++; 1468 1469 my $Success = $GenericModule->new( %{$Self} )->SetTime( 1470 Type => $Type, 1471 Value => $AppointmentData{$AppointmentField}, 1472 TicketID => $Param{TicketID}, 1473 ); 1474 if ( !$Success ) { 1475 $Kernel::OM->Get('Kernel::System::Log')->Log( 1476 Priority => 'error', 1477 Message => "Error setting $Type for ticket $Param{TicketID}!", 1478 ); 1479 } 1480 } 1481 } 1482 } 1483 1484 if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) { 1485 $Kernel::OM->Get('Kernel::System::Log')->Log( 1486 Priority => 'debug', 1487 Message => "Updated ticket $Param{TicketID} from appointment $Param{AppointmentID}.", 1488 ); 1489 } 1490 1491 return; 1492} 1493 1494=head2 TicketAppointmentTicketID() 1495 1496get ticket id of a ticket appointment. 1497 1498 my $TicketID = $CalendarObject->TicketAppointmentTicketID( 1499 AppointmentID => 1, 1500 ); 1501 1502returns appointment ID if successful. 1503 1504=cut 1505 1506sub TicketAppointmentTicketID { 1507 my ( $Self, %Param ) = @_; 1508 1509 # check needed stuff 1510 if ( !$Param{AppointmentID} ) { 1511 $Kernel::OM->Get('Kernel::System::Log')->Log( 1512 Priority => 'error', 1513 Message => 'Need AppointmentID!', 1514 ); 1515 return; 1516 } 1517 1518 # get database object 1519 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1520 1521 # db query 1522 return if !$DBObject->Prepare( 1523 SQL => ' 1524 SELECT ticket_id 1525 FROM calendar_appointment_ticket 1526 WHERE appointment_id = ? 1527 ', 1528 Bind => [ \$Param{AppointmentID}, ], 1529 Limit => 1, 1530 ); 1531 1532 my $TicketID; 1533 while ( my @Row = $DBObject->FetchrowArray() ) { 1534 $TicketID = $Row[0]; 1535 } 1536 1537 return $TicketID; 1538} 1539 1540=head2 TicketAppointmentRuleIDsGet() 1541 1542get used ticket appointment rules for specific calendar. 1543 1544 my @RuleIDs = $CalendarObject->TicketAppointmentRuleIDsGet( 1545 CalendarID => 1, 1546 TicketID => 1, # (optional) Return rules used only for specific ticket 1547 ); 1548 1549returns array of rule IDs if found. 1550 1551=cut 1552 1553sub TicketAppointmentRuleIDsGet { 1554 my ( $Self, %Param ) = @_; 1555 1556 # check needed stuff 1557 for my $Needed (qw(CalendarID)) { 1558 if ( !$Param{$Needed} ) { 1559 $Kernel::OM->Get('Kernel::System::Log')->Log( 1560 Priority => 'error', 1561 Message => "Need $Needed!", 1562 ); 1563 return; 1564 } 1565 } 1566 1567 my $SQL = ' 1568 SELECT rule_id 1569 FROM calendar_appointment_ticket 1570 WHERE calendar_id = ? 1571 '; 1572 my @Bind; 1573 push @Bind, \$Param{CalendarID}; 1574 1575 # specific ticket query condition 1576 if ( $Param{TicketID} ) { 1577 $SQL .= ' 1578 AND ticket_id = ? 1579 '; 1580 push @Bind, \$Param{TicketID}; 1581 } 1582 1583 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1584 1585 # db query 1586 return if !$DBObject->Prepare( 1587 SQL => $SQL, 1588 Bind => \@Bind, 1589 ); 1590 1591 my %RuleIDs; 1592 while ( my @Row = $DBObject->FetchrowArray() ) { 1593 $RuleIDs{ $Row[0] } = 1; 1594 } 1595 1596 # return unique rule ids 1597 return keys %RuleIDs; 1598} 1599 1600=head2 TicketAppointmentRuleGet() 1601 1602get ticket appointment rule. 1603 1604 my $Rule = $CalendarObject->TicketAppointmentRuleGet( 1605 CalendarID => 1, 1606 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 1607 ); 1608 1609returns rule hash: 1610 1611=cut 1612 1613sub TicketAppointmentRuleGet { 1614 my ( $Self, %Param ) = @_; 1615 1616 # check needed stuff 1617 for my $Needed (qw(CalendarID RuleID)) { 1618 if ( !$Param{$Needed} ) { 1619 $Kernel::OM->Get('Kernel::System::Log')->Log( 1620 Priority => 'error', 1621 Message => "Need $Needed!", 1622 ); 1623 return; 1624 } 1625 } 1626 1627 my %Calendar = $Self->CalendarGet( 1628 CalendarID => $Param{CalendarID}, 1629 ); 1630 return if !$Calendar{TicketAppointments}; 1631 1632 my $Result; 1633 1634 RULE: 1635 for my $Rule ( @{ $Calendar{TicketAppointments} || [] } ) { 1636 if ( $Rule->{RuleID} eq $Param{RuleID} ) { 1637 $Result = $Rule; 1638 last RULE; 1639 } 1640 } 1641 return if !$Result; 1642 1643 return $Result; 1644} 1645 1646=head2 TicketAppointmentTypesGet() 1647 1648get defined ticket appointment types from config. 1649 1650 my %TicketAppointmentTypes = $CalendarObject->TicketAppointmentTypesGet(); 1651 1652returns hash of appointment types: 1653 1654 %TicketAppointmentTypes = (); 1655 1656=cut 1657 1658sub TicketAppointmentTypesGet { 1659 my ( $Self, %Param ) = @_; 1660 1661 # get ticket appointment types 1662 my $TicketAppointmentConfig = $Kernel::OM->Get('Kernel::Config')->Get('AppointmentCalendar::TicketAppointmentType') 1663 // {}; 1664 return if !$TicketAppointmentConfig; 1665 1666 my %TicketAppointmentTypes; 1667 1668 my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 1669 1670 TYPE: 1671 for my $TypeKey ( sort keys %{$TicketAppointmentConfig} ) { 1672 next TYPE if !$TicketAppointmentConfig->{$TypeKey}->{Key}; 1673 1674 if ( $TypeKey =~ /DynamicField$/ ) { 1675 1676 # get list of all valid date and date/time dynamic fields 1677 my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet( 1678 ObjectType => 'Ticket', 1679 ); 1680 1681 DYNAMICFIELD: 1682 for my $DynamicField ( @{$DynamicFieldList} ) { 1683 next DYNAMICFIELD if $DynamicField->{FieldType} ne 'Date' && $DynamicField->{FieldType} ne 'DateTime'; 1684 1685 my $Key = sprintf( $TicketAppointmentConfig->{$TypeKey}->{Key}, $DynamicField->{Name} ); 1686 $TicketAppointmentTypes{$Key} = $TicketAppointmentConfig->{$TypeKey}; 1687 } 1688 1689 next TYPE; 1690 } 1691 1692 $TicketAppointmentTypes{ $TicketAppointmentConfig->{$TypeKey}->{Key} } = 1693 $TicketAppointmentConfig->{$TypeKey}; 1694 } 1695 1696 return %TicketAppointmentTypes; 1697} 1698 1699=head2 TicketAppointmentDelete() 1700 1701delete ticket appointment(s). 1702 1703 my $Success = $CalendarObject->TicketAppointmentDelete( 1704 CalendarID => 1, # (required) CalendarID 1705 RuleID => '9bb20ea035e7a9930652a9d82d00c725', # (required) RuleID 1706 # or 1707 TicketID => 1, # (required) Ticket ID 1708 1709 AppointmentID => 1, # (optional) Appointment ID is known 1710 ); 1711 1712returns 1 if successful. 1713 1714=cut 1715 1716sub TicketAppointmentDelete { 1717 my ( $Self, %Param ) = @_; 1718 1719 if ( ( !$Param{CalendarID} || !$Param{RuleID} ) && !$Param{TicketID} ) { 1720 $Kernel::OM->Get('Kernel::System::Log')->Log( 1721 Priority => 'error', 1722 Message => 'Need CalendarID and RuleID, or TicketID!', 1723 ); 1724 return; 1725 } 1726 1727 my @AppointmentIDs; 1728 push @AppointmentIDs, $Param{AppointmentID} if $Param{AppointmentID}; 1729 1730 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1731 1732 # Appointment ID is unknown. 1733 if ( !@AppointmentIDs ) { 1734 my $SQL = ' 1735 SELECT appointment_id 1736 FROM calendar_appointment_ticket 1737 WHERE 1=1 1738 '; 1739 my @Bind; 1740 1741 if ( $Param{CalendarID} && $Param{RuleID} ) { 1742 $SQL .= ' 1743 AND calendar_id = ? AND rule_id = ? 1744 '; 1745 push @Bind, \$Param{CalendarID}, \$Param{RuleID}; 1746 } 1747 1748 if ( $Param{TicketID} ) { 1749 $SQL .= ' 1750 AND ticket_id = ? 1751 '; 1752 push @Bind, \$Param{TicketID}; 1753 } 1754 1755 # db query 1756 return if !$DBObject->Prepare( 1757 SQL => $SQL, 1758 Bind => \@Bind, 1759 ); 1760 1761 while ( my @Row = $DBObject->FetchrowArray() ) { 1762 push @AppointmentIDs, $Row[0]; 1763 } 1764 } 1765 1766 # Remove the relation(s) from database. 1767 my $SQL = ' 1768 DELETE FROM calendar_appointment_ticket 1769 WHERE 1=1 1770 '; 1771 my @Bind; 1772 1773 if ( $Param{CalendarID} && $Param{RuleID} ) { 1774 $SQL .= ' 1775 AND calendar_id = ? AND rule_id = ? 1776 '; 1777 push @Bind, \$Param{CalendarID}, \$Param{RuleID}; 1778 } 1779 1780 if ( $Param{TicketID} ) { 1781 $SQL .= ' 1782 AND ticket_id = ? 1783 '; 1784 push @Bind, \$Param{TicketID}; 1785 } 1786 1787 return if !$DBObject->Do( 1788 SQL => $SQL, 1789 Bind => \@Bind, 1790 ); 1791 1792 # get appointment object 1793 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 1794 1795 # cleanup ticket appointments 1796 APPOINTMENT_ID: 1797 for my $AppointmentID (@AppointmentIDs) { 1798 1799 # check if appointment exists 1800 next APPOINTMENT_ID if !$AppointmentObject->AppointmentGet( 1801 AppointmentID => $AppointmentID, 1802 ); 1803 1804 # delete the appointment 1805 return if !$AppointmentObject->AppointmentDelete( 1806 AppointmentID => $AppointmentID, 1807 UserID => 1, 1808 ); 1809 } 1810 1811 return 1; 1812} 1813 1814=head2 GetAccessToken() 1815 1816get access token for the calendar. 1817 1818 my $Token = $CalendarObject->GetAccessToken( 1819 CalendarID => 1, # (required) CalendarID 1820 UserLogin => 'agent-1', # (required) User login 1821 ); 1822 1823Returns: 1824 1825 $Token = 'rw'; 1826 1827=cut 1828 1829sub GetAccessToken { 1830 my ( $Self, %Param ) = @_; 1831 1832 # check needed stuff 1833 for my $Needed (qw(CalendarID UserLogin)) { 1834 if ( !$Param{$Needed} ) { 1835 $Kernel::OM->Get('Kernel::System::Log')->Log( 1836 Priority => 'error', 1837 Message => "Need $Needed!", 1838 ); 1839 return; 1840 } 1841 } 1842 1843 # create db object 1844 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1845 1846 # db query 1847 return if !$DBObject->Prepare( 1848 SQL => 'SELECT salt_string FROM calendar WHERE id = ?', 1849 Bind => [ \$Param{CalendarID} ], 1850 Limit => 1, 1851 ); 1852 1853 # fetch the result 1854 my $SaltString; 1855 while ( my @Row = $DBObject->FetchrowArray() ) { 1856 $SaltString = $Row[0]; 1857 } 1858 1859 return if !$SaltString; 1860 1861 # Encode user login to UTF8 representation, since MD5 is defined only for strings of bytes. 1862 # If login contains a Unicode character, MD5 might fail otherwise. Please see bug#12593 for 1863 # more information. 1864 $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{UserLogin} ); 1865 1866 # calculate md5 sum 1867 my $String = "$Param{UserLogin}-$SaltString"; 1868 my $MD5 = Digest::MD5->new()->add($String)->hexdigest(); 1869 1870 return $MD5; 1871} 1872 1873=head2 GetTextColor() 1874 1875Returns best text color for supplied background, based on luminosity difference algorithm. 1876 1877 my $BestTextColor = $CalendarObject->GetTextColor( 1878 Background => '#FFF', # (required) must be in valid hexadecimal RGB notation 1879 ); 1880 1881Returns: 1882 1883 $BestTextColor = '#000'; 1884 1885=cut 1886 1887sub GetTextColor { 1888 my ( $Self, %Param ) = @_; 1889 1890 # check needed stuff 1891 for my $Needed (qw(Background)) { 1892 if ( !$Param{$Needed} ) { 1893 $Kernel::OM->Get('Kernel::System::Log')->Log( 1894 Priority => 'error', 1895 Message => "Need $Needed!", 1896 ); 1897 return; 1898 } 1899 } 1900 1901 # check color 1902 if ( !( $Param{Background} =~ /#[A-F0-9]{3,6}/i ) ) { 1903 $Kernel::OM->Get('Kernel::System::Log')->Log( 1904 Priority => 'error', 1905 Message => 'Background must be in hexadecimal RGB notation, eg. #FFFFFF.', 1906 ); 1907 return; 1908 } 1909 1910 # check if value is cached 1911 my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1912 Type => $Self->{CacheType} . 'GetTextColor', 1913 Key => $Param{Background}, 1914 ); 1915 return $Data if $Data; 1916 1917 # get RGB values 1918 my @BackgroundColor; 1919 my $RGBHex = substr( $Param{Background}, 1 ); 1920 1921 # six character hexadecimal string (eg. #FFFFFF) 1922 if ( length $RGBHex == 6 ) { 1923 $BackgroundColor[0] = hex substr( $RGBHex, 0, 2 ); 1924 $BackgroundColor[1] = hex substr( $RGBHex, 2, 2 ); 1925 $BackgroundColor[2] = hex substr( $RGBHex, 4, 2 ); 1926 } 1927 1928 # three character hexadecimal string (eg. #FFF) 1929 elsif ( length $RGBHex == 3 ) { 1930 $BackgroundColor[0] = hex( substr( $RGBHex, 0, 1 ) . substr( $RGBHex, 0, 1 ) ); 1931 $BackgroundColor[1] = hex( substr( $RGBHex, 1, 1 ) . substr( $RGBHex, 1, 1 ) ); 1932 $BackgroundColor[2] = hex( substr( $RGBHex, 2, 1 ) . substr( $RGBHex, 1, 1 ) ); 1933 } 1934 1935 # invalid hexadecimal string 1936 else { 1937 $Kernel::OM->Get('Kernel::System::Log')->Log( 1938 Priority => 'error', 1939 Message => 'Background must be in valid 3 or 6 character hexadecimal RGB notation, eg. #FFF or #FFFFFF.', 1940 ); 1941 return; 1942 } 1943 1944 # predefined text colors 1945 my %TextColors = ( 1946 White => [ '255', '255', '255' ], 1947 Gray => [ '128', '128', '128' ], 1948 Black => [ '0', '0', '0' ], 1949 ); 1950 1951 # calculate background luminosity 1952 my $BackgroundLum = 1953 0.2126 * ( $BackgroundColor[0] / 255**2.2 ) + 1954 0.7152 * ( $BackgroundColor[1] / 255**2.2 ) + 1955 0.0722 * ( $BackgroundColor[2] / 255**2.2 ); 1956 1957 # calculate luminosity difference 1958 my %LumDiff; 1959 for my $TextColor ( sort keys %TextColors ) { 1960 my $TextLum = 1961 0.2126 * ( $TextColors{$TextColor}->[0] / 255**2.2 ) + 1962 0.7152 * ( $TextColors{$TextColor}->[1] / 255**2.2 ) + 1963 0.0722 * ( $TextColors{$TextColor}->[2] / 255**2.2 ); 1964 1965 if ( $BackgroundLum > $TextLum ) { 1966 $LumDiff{$TextColor} = ( $BackgroundLum + 0.05 ) / ( $TextLum + 0.05 ); 1967 } 1968 else { 1969 $LumDiff{$TextColor} = ( $TextLum + 0.05 ) / ( $BackgroundLum + 0.05 ); 1970 } 1971 } 1972 1973 # get maximum luminosity difference 1974 my ($MaxLumDiff) = sort { $b <=> $a } values %LumDiff; 1975 return if !$MaxLumDiff; 1976 1977 # identify best suited color 1978 my ($BestTextColor) = grep { $LumDiff{$_} eq $MaxLumDiff } keys %LumDiff; 1979 return if !$BestTextColor; 1980 1981 # convert to hex string 1982 my $TextColor = sprintf( 1983 '#%X%X%X', 1984 $TextColors{$BestTextColor}->[0], 1985 $TextColors{$BestTextColor}->[1], 1986 $TextColors{$BestTextColor}->[2], 1987 ); 1988 1989 # cache 1990 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1991 Type => $Self->{CacheType} . 'GetTextColor', 1992 Key => $Param{Background}, 1993 Value => $TextColor, 1994 TTL => $Self->{CacheTTL}, 1995 ); 1996 1997 return $TextColor; 1998} 1999 2000=begin Internal: 2001 2002=head2 _TicketAppointmentGet() 2003 2004get ticket appointment id if exists. 2005 2006 my $AppointmentID = $CalendarObject->_TicketAppointmentGet( 2007 CalendarID => 1, 2008 TicketID => 1, 2009 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 2010 ); 2011 2012returns appointment ID if successful. 2013 2014=cut 2015 2016sub _TicketAppointmentGet { 2017 my ( $Self, %Param ) = @_; 2018 2019 # check needed stuff 2020 for my $Needed (qw(CalendarID TicketID RuleID)) { 2021 if ( !$Param{$Needed} ) { 2022 $Kernel::OM->Get('Kernel::System::Log')->Log( 2023 Priority => 'error', 2024 Message => "Need $Needed!", 2025 ); 2026 return; 2027 } 2028 } 2029 2030 # get database object 2031 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2032 2033 # db query 2034 return if !$DBObject->Prepare( 2035 SQL => ' 2036 SELECT appointment_id 2037 FROM calendar_appointment_ticket 2038 WHERE calendar_id = ? AND ticket_id = ? AND rule_id = ? 2039 ', 2040 Bind => [ \$Param{CalendarID}, \$Param{TicketID}, \$Param{RuleID}, ], 2041 Limit => 1, 2042 ); 2043 2044 my $AppointmentID; 2045 while ( my @Row = $DBObject->FetchrowArray() ) { 2046 $AppointmentID = $Row[0]; 2047 } 2048 2049 return $AppointmentID; 2050} 2051 2052=head2 _TicketAppointmentList() 2053 2054Get list of ticket appointments based on a rule. 2055 2056 my %Appointments = $CalendarObject->_TicketAppointmentList( 2057 CalendarID => 1, 2058 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 2059 Key => 'TicketID', # (optional) Return result will be based on this key. 2060 Default: TicketID, Possible: TicketID|AppointmentID 2061 ); 2062 2063Returns list of ticket appointments, where key will be either TicketID (default) or AppointmentID: 2064 2065%Appointments = ( 2066 1 => 1, 2067 2 => 2, 2068 ... 2069); 2070 2071=cut 2072 2073sub _TicketAppointmentList { 2074 my ( $Self, %Param ) = @_; 2075 2076 for my $Needed (qw(CalendarID RuleID)) { 2077 if ( !$Param{$Needed} ) { 2078 $Kernel::OM->Get('Kernel::System::Log')->Log( 2079 Priority => 'error', 2080 Message => "Need $Needed!", 2081 ); 2082 return; 2083 } 2084 } 2085 2086 $Param{Key} ||= 'TicketID'; 2087 2088 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2089 2090 return if !$DBObject->Prepare( 2091 SQL => ' 2092 SELECT appointment_id, ticket_id 2093 FROM calendar_appointment_ticket 2094 WHERE calendar_id = ? AND rule_id = ? 2095 ', 2096 Bind => [ \$Param{CalendarID}, \$Param{RuleID}, ], 2097 ); 2098 2099 my %Result; 2100 while ( my @Row = $DBObject->FetchrowArray() ) { 2101 $Result{ $Row[1] } = $Row[0]; 2102 } 2103 2104 if ( $Param{Key} eq 'AppointmentID' ) { 2105 %Result = reverse %Result; 2106 } 2107 2108 return %Result; 2109} 2110 2111=head2 _TicketAppointmentCreate() 2112 2113create ticket appointment. 2114 2115 my $Success = $CalendarObject->_TicketAppointmentCreate( 2116 CalendarID => 1, 2117 TicketID => 1, 2118 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 2119 Title => '[Ticket#20160823810000010] Some Ticket Title', 2120 StartTime => '2016-08-23 00:00:00', 2121 EndTime => '2016-08-24 00:00:00', 2122 ); 2123 2124returns 1 if successful. 2125 2126=cut 2127 2128sub _TicketAppointmentCreate { 2129 my ( $Self, %Param ) = @_; 2130 2131 # check needed stuff 2132 for my $Needed (qw(CalendarID TicketID RuleID Title StartTime EndTime)) { 2133 if ( !$Param{$Needed} ) { 2134 $Kernel::OM->Get('Kernel::System::Log')->Log( 2135 Priority => 'error', 2136 Message => "Need $Needed!", 2137 ); 2138 return; 2139 } 2140 } 2141 2142 # create appointment 2143 my $AppointmentID = $Kernel::OM->Get('Kernel::System::Calendar::Appointment')->AppointmentCreate( 2144 CalendarID => $Param{CalendarID}, 2145 Title => $Param{Title}, 2146 StartTime => $Param{StartTime}, 2147 EndTime => $Param{EndTime}, 2148 TicketAppointmentRuleID => $Param{RuleID}, 2149 UserID => 1, 2150 ); 2151 return if !$AppointmentID; 2152 2153 # save the relation in database 2154 return $Kernel::OM->Get('Kernel::System::DB')->Do( 2155 SQL => ' 2156 INSERT INTO calendar_appointment_ticket 2157 (calendar_id, ticket_id, appointment_id, rule_id) 2158 VALUES (?, ?, ?, ?) 2159 ', 2160 Bind => [ \$Param{CalendarID}, \$Param{TicketID}, \$AppointmentID, \$Param{RuleID}, ], 2161 ); 2162} 2163 2164=head2 _TicketAppointmentUpdate() 2165 2166update ticket appointment. 2167 2168 my $Success = $CalendarObject->_TicketAppointmentUpdate( 2169 AppointmentID => 1, 2170 TicketID => 1, 2171 RuleID => '9bb20ea035e7a9930652a9d82d00c725', 2172 Title => '[Ticket#20160823810000010] Some Ticket Title', 2173 StartTime => '2016-08-23 00:00:00', 2174 EndTime => '2016-08-24 00:00:00', 2175 ); 2176 2177returns 1 if successful. 2178 2179=cut 2180 2181sub _TicketAppointmentUpdate { 2182 my ( $Self, %Param ) = @_; 2183 2184 # check needed stuff 2185 for my $Needed (qw(AppointmentID TicketID RuleID Title StartTime EndTime)) { 2186 if ( !$Param{$Needed} ) { 2187 $Kernel::OM->Get('Kernel::System::Log')->Log( 2188 Priority => 'error', 2189 Message => "Need $Needed!", 2190 ); 2191 return; 2192 } 2193 } 2194 2195 # get appointment object 2196 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 2197 2198 # get current ticket appointment data 2199 my %Appointment = $AppointmentObject->AppointmentGet( 2200 AppointmentID => $Param{AppointmentID}, 2201 ); 2202 2203 # ticket appointment does not exist 2204 if ( !$Appointment{AppointmentID} ) { 2205 2206 # remove the relation as well 2207 $Self->TicketAppointmentDelete( 2208 %Param, 2209 ); 2210 2211 # create new ticket appointment 2212 return $Self->_TicketAppointmentCreate( 2213 %Param, 2214 ); 2215 } 2216 2217 # loop protection: prevent appointment event module from running 2218 $Self->{'_TicketAppointments::TicketUpdate'}->{ $Appointment{AppointmentID} }++; 2219 2220 # update ticket appointment 2221 return $AppointmentObject->AppointmentUpdate( 2222 %Appointment, 2223 Title => $Param{Title}, 2224 StartTime => $Param{StartTime}, 2225 EndTime => $Param{EndTime}, 2226 TicketAppointmentRuleID => $Param{RuleID}, 2227 UserID => 1, 2228 ); 2229} 2230 22311; 2232 2233=end Internal: 2234 2235=head1 TERMS AND CONDITIONS 2236 2237This software is part of the OTRS project (L<https://otrs.org/>). 2238 2239This software comes with ABSOLUTELY NO WARRANTY. For details, see 2240the enclosed file COPYING for license information (GPL). If you 2241did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 2242 2243=cut 2244