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::Appointment; 10 11use strict; 12use warnings; 13 14use Digest::MD5; 15 16use vars qw(@ISA); 17 18use Kernel::System::VariableCheck qw(:all); 19use Kernel::System::EventHandler; 20 21our @ObjectDependencies = ( 22 'Kernel::Config', 23 'Kernel::System::Cache', 24 'Kernel::System::Calendar', 25 'Kernel::System::DB', 26 'Kernel::System::Daemon::SchedulerDB', 27 'Kernel::System::DateTime', 28 'Kernel::System::Group', 29 'Kernel::System::Log', 30 'Kernel::System::Main', 31 'Kernel::System::Scheduler', 32); 33 34=head1 NAME 35 36Kernel::System::Calendar::Appointment - calendar appointment lib 37 38=head1 DESCRIPTION 39 40All appointment functions. 41 42=head1 PUBLIC INTERFACE 43 44=head2 new() 45 46create an object. Do not use it directly, instead use: 47 48 use Kernel::System::ObjectManager; 49 local $Kernel::OM = Kernel::System::ObjectManager->new(); 50 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 51 52=cut 53 54sub new { 55 my ( $Type, %Param ) = @_; 56 57 # allocate new hash for object 58 my $Self = {%Param}; 59 bless( $Self, $Type ); 60 61 @ISA = qw( 62 Kernel::System::EventHandler 63 ); 64 65 # init of event handler 66 $Self->EventHandlerInit( 67 Config => 'AppointmentCalendar::EventModulePost', 68 ); 69 70 $Self->{CacheType} = 'Appointment'; 71 $Self->{CacheTTL} = 60 * 60 * 24 * 20; 72 73 return $Self; 74} 75 76=head2 AppointmentCreate() 77 78creates a new appointment. 79 80 my $AppointmentID = $AppointmentObject->AppointmentCreate( 81 ParentID => 1, # (optional) valid ParentID for recurring appointments 82 CalendarID => 1, # (required) valid CalendarID 83 UniqueID => 'jwioji-fwjio', # (optional) provide desired UniqueID; if there is already existing Appointment 84 # with same UniqueID, system will delete it 85 Title => 'Webinar', # (required) Title 86 Description => 'How to use Process tickets...', # (optional) Description 87 Location => 'Straubing', # (optional) Location 88 StartTime => '2016-01-01 16:00:00', # (required) 89 EndTime => '2016-01-01 17:00:00', # (required) 90 AllDay => 0, # (optional) default 0 91 TeamID => [ 1 ], # (optional) must be an array reference if supplied 92 ResourceID => [ 1, 3 ], # (optional) must be an array reference if supplied 93 Recurring => 1, # (optional) flag the appointment as recurring (parent only!) 94 RecurringRaw => 1, # (optional) skip loop for recurring appointments (do not create occurrences!) 95 RecurrenceType => 'Daily', # (required if Recurring) Possible "Daily", "Weekly", "Monthly", "Yearly", 96 # "CustomWeekly", "CustomMonthly", "CustomYearly" 97 98 RecurrenceFrequency => [1, 3, 5], # (required if Custom Recurring) Recurrence pattern 99 # for CustomWeekly: 1-Mon, 2-Tue,..., 7-Sun 100 # for CustomMonthly: 1-1st, 2-2nd,.., 31th 101 # for CustomYearly: 1-Jan, 2-Feb,..., 12-Dec 102 # ... 103 RecurrenceCount => 1, # (optional) How many Appointments to create 104 RecurrenceInterval => 2, # (optional) Repeating interval (default 1) 105 RecurrenceUntil => '2016-01-10 00:00:00', # (optional) Until date 106 RecurrenceID => '2016-01-10 00:00:00', # (optional) Expected start time for this occurrence 107 RecurrenceExclude => [ # (optional) Which specific occurrences to exclude 108 '2016-01-10 00:00:00', 109 '2016-01-11 00:00:00', 110 ], 111 NotificationTime => '2016-01-01 17:00:00', # (optional) Point of time to execute the notification event 112 NotificationTemplate => 'Custom', # (optional) Template to be used for notification point of time 113 NotificationCustom => 'relative', # (optional) Type of the custom template notification point of time 114 # Possible "relative", "datetime" 115 NotificationCustomRelativeUnitCount => '12', # (optional) minutes, hours or days count for custom template 116 NotificationCustomRelativeUnit => 'minutes', # (optional) minutes, hours or days unit for custom template 117 NotificationCustomRelativePointOfTime => 'beforestart', # (optional) Point of execute for custom templates 118 # Possible "beforestart", "afterstart", "beforeend", "afterend" 119 NotificationCustomDateTime => '2016-01-01 17:00:00', # (optional) Notification date time for custom template 120 TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # (optional) Ticket appointment rule ID (for ticket appointments only!) 121 UserID => 1, # (required) UserID 122 ); 123 124returns parent AppointmentID if successful 125 126Events: 127 AppointmentCreate 128 129=cut 130 131sub AppointmentCreate { 132 my ( $Self, %Param ) = @_; 133 134 # check needed stuff 135 for my $Needed (qw(CalendarID Title StartTime EndTime UserID)) { 136 if ( !$Param{$Needed} ) { 137 $Kernel::OM->Get('Kernel::System::Log')->Log( 138 Priority => 'error', 139 Message => "Need $Needed!", 140 ); 141 return; 142 } 143 } 144 145 # prepare possible notification params 146 $Self->_AppointmentNotificationPrepare( 147 Data => \%Param, 148 ); 149 150 # if Recurring is provided, additional parameters must be present 151 if ( $Param{Recurring} ) { 152 153 my @RecurrenceTypes = ( 154 "Daily", "Weekly", "Monthly", "Yearly", 155 "CustomDaily", "CustomWeekly", "CustomMonthly", "CustomYearly" 156 ); 157 158 if ( 159 !$Param{RecurrenceType} 160 || !grep { $_ eq $Param{RecurrenceType} } @RecurrenceTypes 161 ) 162 { 163 $Kernel::OM->Get('Kernel::System::Log')->Log( 164 Priority => 'error', 165 Message => "RecurrenceType invalid!", 166 ); 167 return; 168 } 169 170 if ( 171 ( 172 $Param{RecurrenceType} eq 'CustomWeekly' 173 || $Param{RecurrenceType} eq 'CustomMonthly' 174 || $Param{RecurrenceType} eq 'CustomYearly' 175 ) 176 && !$Param{RecurrenceFrequency} 177 ) 178 { 179 $Kernel::OM->Get('Kernel::System::Log')->Log( 180 Priority => 'error', 181 Message => "RecurrenceFrequency needed!", 182 ); 183 return; 184 } 185 } 186 187 $Param{RecurrenceInterval} ||= 1; 188 189 if ( $Param{UniqueID} && !$Param{ParentID} ) { 190 my %Appointment = $Self->AppointmentGet( 191 UniqueID => $Param{UniqueID}, 192 CalendarID => $Param{CalendarID}, 193 ); 194 195 # delete existing appointment with same UniqueID 196 if ( %Appointment && $Appointment{AppointmentID} ) { 197 $Self->AppointmentDelete( 198 AppointmentID => $Appointment{AppointmentID}, 199 UserID => $Param{UserID}, 200 ); 201 } 202 } 203 204 # check ParentID 205 if ( $Param{ParentID} && !IsInteger( $Param{ParentID} ) ) { 206 $Kernel::OM->Get('Kernel::System::Log')->Log( 207 Priority => 'error', 208 Message => "ParentID must be a number!", 209 ); 210 return; 211 } 212 213 # Check StartTime. 214 my $StartTimeObject = $Kernel::OM->Create( 215 'Kernel::System::DateTime', 216 ObjectParams => { 217 String => $Param{StartTime}, 218 }, 219 ); 220 if ( !$StartTimeObject ) { 221 $Kernel::OM->Get('Kernel::System::Log')->Log( 222 Priority => 'error', 223 Message => "Invalid StartTime!", 224 ); 225 return; 226 } 227 228 # check UniqueID 229 if ( !$Param{UniqueID} ) { 230 $Param{UniqueID} = $Self->GetUniqueID( 231 CalendarID => $Param{CalendarID}, 232 StartTime => $Param{StartTime}, 233 UserID => $Param{UserID}, 234 ); 235 } 236 237 # Check EndTime. 238 my $EndTimeObject = $Kernel::OM->Create( 239 'Kernel::System::DateTime', 240 ObjectParams => { 241 String => $Param{EndTime}, 242 }, 243 ); 244 if ( !$EndTimeObject ) { 245 $Kernel::OM->Get('Kernel::System::Log')->Log( 246 Priority => 'error', 247 Message => "Invalid EndTime!", 248 ); 249 return; 250 } 251 252 # check if array refs 253 my %Arrays; 254 for my $Parameter ( 255 qw(TeamID ResourceID RecurrenceFrequency RecurrenceExclude) 256 ) 257 { 258 if ( $Param{$Parameter} && @{ $Param{$Parameter} // [] } ) { 259 if ( !IsArrayRefWithData( $Param{$Parameter} ) ) { 260 $Kernel::OM->Get('Kernel::System::Log')->Log( 261 Priority => 'error', 262 Message => "$Parameter not ARRAYREF!", 263 ); 264 return; 265 } 266 267 my @Array = @{ $Param{$Parameter} }; 268 269 # remove undefined values 270 @Array = grep { defined $_ } @Array; 271 272 $Arrays{$Parameter} = join( ',', @Array ) if @Array; 273 } 274 } 275 276 # check if numbers 277 for my $Parameter ( 278 qw(Recurring RecurrenceCount RecurrenceInterval) 279 ) 280 { 281 if ( $Param{$Parameter} && !IsInteger( $Param{$Parameter} ) ) { 282 $Kernel::OM->Get('Kernel::System::Log')->Log( 283 Priority => 'error', 284 Message => "$Parameter must be a number!", 285 ); 286 return; 287 } 288 } 289 290 # check RecurrenceUntil 291 if ( $Param{RecurrenceUntil} ) { 292 293 # Usually hour, minute and second = 0. In this case, take time from StartTime. 294 $Param{RecurrenceUntil} = $Self->_TimeCheck( 295 OriginalTime => $Param{StartTime}, 296 Time => $Param{RecurrenceUntil}, 297 ); 298 299 my $RecurrenceUntilObject = $Kernel::OM->Create( 300 'Kernel::System::DateTime', 301 ObjectParams => { 302 String => $Param{RecurrenceUntil}, 303 }, 304 ); 305 306 if ( 307 !$RecurrenceUntilObject 308 || $StartTimeObject > $RecurrenceUntilObject 309 ) 310 { 311 $Kernel::OM->Get('Kernel::System::Log')->Log( 312 Priority => 'error', 313 Message => "Invalid RecurrenceUntil!", 314 ); 315 return; 316 } 317 } 318 319 # get db object 320 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 321 my @Bind; 322 323 # parent ID supplied 324 my $ParentIDCol = my $ParentIDVal = ''; 325 if ( $Param{ParentID} ) { 326 $ParentIDCol = 'parent_id,'; 327 $ParentIDVal = '?,'; 328 push @Bind, \$Param{ParentID}; 329 330 # turn off all recurring fields 331 delete $Param{Recurring}; 332 delete $Param{RecurrenceType}; 333 delete $Param{RecurrenceFrequency}; 334 delete $Param{RecurrenceCount}; 335 delete $Param{RecurrenceInterval}; 336 delete $Param{RecurrenceUntil}; 337 } 338 339 push @Bind, \$Param{CalendarID}, \$Param{UniqueID}, \$Param{Title}, \$Param{Description}, 340 \$Param{Location}, \$Param{StartTime}, \$Param{EndTime}, \$Param{AllDay}, 341 \$Arrays{TeamID}, \$Arrays{ResourceID}, \$Param{Recurring}, \$Param{RecurrenceType}, 342 \$Arrays{RecurrenceFrequency}, \$Param{RecurrenceCount}, \$Param{RecurrenceInterval}, 343 \$Param{RecurrenceUntil}, \$Param{RecurrenceID}, \$Arrays{RecurrenceExclude}, 344 \$Param{NotificationDate}, \$Param{NotificationTemplate}, \$Param{NotificationCustom}, 345 \$Param{NotificationCustomRelativeUnitCount}, \$Param{NotificationCustomRelativeUnit}, 346 \$Param{NotificationCustomRelativePointOfTime}, \$Param{NotificationCustomDateTime}, 347 \$Param{TicketAppointmentRuleID}, \$Param{UserID}, \$Param{UserID}; 348 349 my $SQL = " 350 INSERT INTO calendar_appointment 351 ($ParentIDCol calendar_id, unique_id, title, description, location, start_time, 352 end_time, all_day, team_id, resource_id, recurring, recur_type, recur_freq, recur_count, 353 recur_interval, recur_until, recur_id, recur_exclude, notify_time, notify_template, 354 notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, 355 notify_custom_date, ticket_appointment_rule_id, create_time, create_by, change_time, 356 change_by) 357 VALUES ($ParentIDVal ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 358 ?, ?, current_timestamp, ?, current_timestamp, ?) 359 "; 360 361 # create db record 362 return if !$DBObject->Do( 363 SQL => $SQL, 364 Bind => \@Bind, 365 ); 366 367 my $AppointmentID; 368 369 # return parent id for appointment occurrences 370 if ( $Param{ParentID} ) { 371 $AppointmentID = $Param{ParentID}; 372 } 373 374 # get appointment id for parent appointment 375 else { 376 return if !$DBObject->Prepare( 377 SQL => ' 378 SELECT id FROM calendar_appointment 379 WHERE unique_id=? AND parent_id IS NULL 380 ', 381 Bind => [ \$Param{UniqueID} ], 382 Limit => 1, 383 ); 384 385 while ( my @Row = $DBObject->FetchrowArray() ) { 386 $AppointmentID = $Row[0] || ''; 387 } 388 389 # return if there is not appointment created 390 if ( !$AppointmentID ) { 391 $Kernel::OM->Get('Kernel::System::Log')->Log( 392 Priority => 'error', 393 Message => 'Can\'t get AppointmentID from INSERT!', 394 ); 395 return; 396 } 397 } 398 399 # add recurring appointments 400 if ( $Param{Recurring} && !$Param{RecurringRaw} ) { 401 return if !$Self->_AppointmentRecurringCreate( 402 ParentID => $AppointmentID, 403 Appointment => \%Param, 404 ); 405 } 406 407 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 408 409 # clean up list methods cache 410 $CacheObject->CleanUp( 411 Type => $Self->{CacheType} . 'List' . $Param{CalendarID}, 412 ); 413 $CacheObject->CleanUp( 414 Type => $Self->{CacheType} . 'Days' . $Param{UserID}, 415 ); 416 417 # fire event 418 $Self->EventHandler( 419 Event => 'AppointmentCreate', 420 Data => { 421 AppointmentID => $AppointmentID, 422 CalendarID => $Param{CalendarID}, 423 }, 424 UserID => $Param{UserID}, 425 ); 426 427 return $AppointmentID; 428} 429 430=head2 AppointmentList() 431 432get a hash of Appointments. 433 434 my @Appointments = $AppointmentObject->AppointmentList( 435 CalendarID => 1, # (required) Valid CalendarID 436 Title => '*', # (optional) Filter by title, wildcard supported 437 Description => '*', # (optional) Filter by description, wildcard supported 438 Location => '*', # (optional) Filter by location, wildcard supported 439 StartTime => '2016-01-01 00:00:00', # (optional) Filter by start date 440 EndTime => '2016-02-01 00:00:00', # (optional) Filter by end date 441 TeamID => 1, # (optional) Filter by team 442 ResourceID => 2, # (optional) Filter by resource 443 Result => 'HASH', # (optional), HASH|ARRAY 444 ); 445 446returns an array of hashes with select Appointment data or simple array of AppointmentIDs: 447 448Result => 'HASH': 449 450 @Appointments = [ 451 { 452 AppointmentID => 1, 453 CalendarID => 1, 454 UniqueID => '20160101T160000-71E386@localhost', 455 Title => 'Webinar', 456 Description => 'How to use Process tickets...', 457 Location => 'Straubing', 458 StartTime => '2016-01-01 16:00:00', 459 EndTime => '2016-01-01 17:00:00', 460 AllDay => 0, 461 Recurring => 1, # for recurring (parent) appointments only 462 }, 463 { 464 AppointmentID => 2, 465 ParentID => 1, # for recurred (child) appointments only 466 CalendarID => 1, 467 UniqueID => '20160101T180000-A78B57@localhost', 468 Title => 'Webinar', 469 Description => 'How to use Process tickets...', 470 Location => 'Straubing', 471 StartTime => '2016-01-02 16:00:00', 472 EndTime => '2016-01-02 17:00:00', 473 TeamID => [ 1 ], 474 ResourceID => [ 1, 3 ], 475 AllDay => 0, 476 }, 477 { 478 AppointmentID => 3, 479 CalendarID => 1, 480 UniqueID => '20160101T180000-A78B57@localhost', 481 Title => 'Webinar', 482 Description => 'How to use Process tickets...', 483 Location => 'Straubing', 484 StartTime => '2016-01-02 16:00:00', 485 EndTime => '2016-01-02 17:00:00', 486 TimezoneID => 1, 487 TeamID => [ 1 ], 488 ResourceID => [ 1, 3 ], 489 NotificationDate => '2016-01-02 16:10:00', 490 NotificationTemplate => 'Custom', 491 NotificationCustom => 'relative', 492 NotificationCustomRelativeUnitCount => '10', 493 NotificationCustomRelativeUnit => 'minutes', 494 NotificationCustomRelativePointOfTime => 'afterstart', 495 NotificationCustomDateTime => '2016-01-02 16:00:00', 496 TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # for ticket appointments only! 497 }, 498 ... 499 ]; 500 501Result => 'ARRAY': 502 503 @Appointments = [ 1, 2, ... ] 504 505=cut 506 507sub AppointmentList { 508 my ( $Self, %Param ) = @_; 509 510 # check needed stuff 511 for my $Needed (qw(CalendarID)) { 512 if ( !$Param{$Needed} ) { 513 $Kernel::OM->Get('Kernel::System::Log')->Log( 514 Priority => 'error', 515 Message => "Need $Needed!", 516 ); 517 return; 518 } 519 } 520 521 # output array of hashes by default 522 $Param{Result} = $Param{Result} || 'HASH'; 523 524 # cache keys 525 my $CacheType = $Self->{CacheType} . 'List' . $Param{CalendarID}; 526 my $CacheKeyTitle = $Param{Title} || 'any'; 527 my $CacheKeyDesc = $Param{Description} || 'any'; 528 my $CacheKeyLocation = $Param{Location} || 'any'; 529 my $CacheKeyStart = $Param{StartTime} || 'any'; 530 my $CacheKeyEnd = $Param{EndTime} || 'any'; 531 my $CacheKeyTeam = $Param{TeamID} || 'any'; 532 my $CacheKeyResource = $Param{ResourceID} || 'any'; 533 534 if ( defined $Param{Title} && $Param{Title} =~ /^[\*]+$/ ) { 535 $CacheKeyTitle = 'any'; 536 } 537 if ( defined $Param{Description} && $Param{Description} =~ /^[\*]+$/ ) { 538 $CacheKeyDesc = 'any'; 539 } 540 if ( defined $Param{Location} && $Param{Location} =~ /^[\*]+$/ ) { 541 $CacheKeyLocation = 'any'; 542 } 543 544 my $CacheKey 545 = "$CacheKeyTitle-$CacheKeyDesc-$CacheKeyLocation-$CacheKeyStart-$CacheKeyEnd-$CacheKeyTeam-$CacheKeyResource-$Param{Result}"; 546 547 # check cache 548 my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get( 549 Type => $CacheType, 550 Key => $CacheKey, 551 ); 552 553 if ( ref $Data eq 'ARRAY' ) { 554 return @{$Data}; 555 } 556 557 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 558 559 # check time 560 if ( $Param{StartTime} ) { 561 my $StartTimeObject = $Kernel::OM->Create( 562 'Kernel::System::DateTime', 563 ObjectParams => { 564 String => $Param{StartTime}, 565 }, 566 ); 567 if ( !$StartTimeObject ) { 568 $Kernel::OM->Get('Kernel::System::Log')->Log( 569 Priority => 'error', 570 Message => "StartTime invalid!", 571 ); 572 return; 573 } 574 } 575 if ( $Param{EndTime} ) { 576 my $EndTimeObject = $Kernel::OM->Create( 577 'Kernel::System::DateTime', 578 ObjectParams => { 579 String => $Param{EndTime}, 580 }, 581 ); 582 if ( !$EndTimeObject ) { 583 $Kernel::OM->Get('Kernel::System::Log')->Log( 584 Priority => 'error', 585 Message => "EndTime invalid!", 586 ); 587 return; 588 } 589 } 590 591 my $SQL = ' 592 SELECT id, parent_id, calendar_id, unique_id, title, description, location, start_time, 593 end_time, team_id, resource_id, all_day, recurring, notify_time, notify_template, 594 notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, 595 notify_custom_date, ticket_appointment_rule_id 596 FROM calendar_appointment 597 WHERE calendar_id=? 598 '; 599 600 my @Bind; 601 602 push @Bind, \$Param{CalendarID}; 603 604 # Filter title, description and location fields by using QueryCondition method, which will 605 # return backend specific SQL statements in order to provide case insensitive match and 606 # wildcard support. 607 FILTER: 608 for my $Filter (qw(Title Description Location)) { 609 next FILTER if !$Param{$Filter}; 610 $SQL .= ' AND ' . $DBObject->QueryCondition( 611 Key => lc $Filter, 612 Value => $Param{$Filter}, 613 SearchPrefix => '*', 614 SearchSuffix => '*', 615 ); 616 } 617 618 if ( $Param{StartTime} && $Param{EndTime} ) { 619 620 $SQL .= ' AND ( 621 (start_time >= ? AND start_time < ?) OR 622 (end_time > ? AND end_time <= ?) OR 623 (start_time <= ? AND end_time >= ?) 624 ) '; 625 push @Bind, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, 626 \$Param{EndTime}; 627 } 628 elsif ( $Param{StartTime} && !$Param{EndTime} ) { 629 630 $SQL .= ' AND end_time >= ? '; 631 push @Bind, \$Param{StartTime}; 632 } 633 elsif ( !$Param{StartTime} && $Param{EndTime} ) { 634 635 $SQL .= ' AND start_time <= ? '; 636 push @Bind, \$Param{EndTime}; 637 } 638 639 $SQL .= ' ORDER BY id ASC'; 640 641 # db query 642 return if !$DBObject->Prepare( 643 SQL => $SQL, 644 Bind => \@Bind, 645 ); 646 647 my @Result; 648 649 ROW: 650 while ( my @Row = $DBObject->FetchrowArray() ) { 651 652 # team id 653 my @TeamID = split( ',', $Row[9] // '' ); 654 if ( $Param{TeamID} ) { 655 next ROW if !grep { $_ == $Param{TeamID} } @TeamID; 656 } 657 658 # resource id 659 $Row[10] = $Row[10] ? $Row[10] : 0; 660 my @ResourceID = $Row[10] =~ /,/ ? split( ',', $Row[10] ) : ( $Row[10] ); 661 if ( $Param{ResourceID} ) { 662 next ROW if !grep { $_ == $Param{ResourceID} } @ResourceID; 663 } 664 665 my %Appointment = ( 666 AppointmentID => $Row[0], 667 ParentID => $Row[1], 668 CalendarID => $Row[2], 669 UniqueID => $Row[3], 670 Title => $Row[4], 671 Description => $Row[5], 672 Location => $Row[6], 673 StartTime => $Row[7], 674 EndTime => $Row[8], 675 TeamID => \@TeamID, 676 ResourceID => \@ResourceID, 677 AllDay => $Row[11], 678 Recurring => $Row[12], 679 NotificationDate => $Row[13] || '', 680 NotificationTemplate => $Row[14], 681 NotificationCustom => $Row[15], 682 NotificationCustomRelativeUnitCount => $Row[16], 683 NotificationCustomRelativeUnit => $Row[17], 684 NotificationCustomRelativePointOfTime => $Row[18], 685 NotificationCustomDateTime => $Row[19] || '', 686 TicketAppointmentRuleID => $Row[20], 687 ); 688 push @Result, \%Appointment; 689 } 690 691 # if Result was ARRAY, output only IDs 692 if ( $Param{Result} eq 'ARRAY' ) { 693 my @ResultList; 694 for my $Appointment (@Result) { 695 push @ResultList, $Appointment->{AppointmentID}; 696 } 697 @Result = @ResultList; 698 } 699 700 # cache 701 $Kernel::OM->Get('Kernel::System::Cache')->Set( 702 Type => $CacheType, 703 Key => $CacheKey, 704 Value => \@Result, 705 TTL => $Self->{CacheTTL}, 706 ); 707 708 return @Result; 709} 710 711=head2 AppointmentDays() 712 713get a hash of days with Appointments in all user calendars. 714 715 my %AppointmentDays = $AppointmentObject->AppointmentDays( 716 StartTime => '2016-01-01 00:00:00', # (optional) Filter by start date 717 EndTime => '2016-02-01 00:00:00', # (optional) Filter by end date 718 UserID => 1, # (required) Valid UserID 719 ); 720 721returns a hash with days as keys and number of Appointments as values: 722 723 %AppointmentDays = { 724 '2016-01-01' => 1, 725 '2016-01-13' => 2, 726 '2016-01-30' => 1, 727 }; 728 729=cut 730 731sub AppointmentDays { 732 my ( $Self, %Param ) = @_; 733 734 # check needed stuff 735 for my $Needed (qw(UserID)) { 736 if ( !$Param{$Needed} ) { 737 $Kernel::OM->Get('Kernel::System::Log')->Log( 738 Priority => 'error', 739 Message => "Need $Needed!", 740 ); 741 return; 742 } 743 } 744 745 # cache keys 746 my $CacheType = $Self->{CacheType} . 'Days' . $Param{UserID}; 747 my $CacheKeyStart = $Param{StartTime} || 'any'; 748 my $CacheKeyEnd = $Param{EndTime} || 'any'; 749 750 # check time 751 if ( $Param{StartTime} ) { 752 my $StartTimeObject = $Kernel::OM->Create( 753 'Kernel::System::DateTime', 754 ObjectParams => { 755 String => $Param{StartTime}, 756 }, 757 ); 758 if ( !$StartTimeObject ) { 759 $Kernel::OM->Get('Kernel::System::Log')->Log( 760 Priority => 'error', 761 Message => "StartTime invalid!", 762 ); 763 return; 764 } 765 } 766 if ( $Param{EndTime} ) { 767 my $EndTimeObject = $Kernel::OM->Create( 768 'Kernel::System::DateTime', 769 ObjectParams => { 770 String => $Param{EndTime}, 771 }, 772 ); 773 if ( !$EndTimeObject ) { 774 $Kernel::OM->Get('Kernel::System::Log')->Log( 775 Priority => 'error', 776 Message => "EndTime invalid!", 777 ); 778 return; 779 } 780 } 781 782 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 783 784 # check cache 785 my $Data = $CacheObject->Get( 786 Type => $CacheType, 787 Key => "$CacheKeyStart-$CacheKeyEnd", 788 ); 789 790 if ( ref $Data eq 'HASH' ) { 791 return %{$Data}; 792 } 793 794 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 795 796 # get user groups 797 my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( 798 UserID => $Param{UserID}, 799 Type => 'ro', 800 ); 801 my @GroupIDs = sort keys %GroupList; 802 803 my $SQL = " 804 SELECT ca.start_time, ca.end_time 805 FROM calendar_appointment ca 806 JOIN calendar c ON ca.calendar_id = c.id 807 WHERE c.group_id IN ( ${\(join ', ', @GroupIDs)} ) 808 "; 809 810 my @Bind; 811 812 if ( $Param{StartTime} && $Param{EndTime} ) { 813 814 $SQL .= 'AND ( 815 (ca.start_time >= ? AND ca.start_time < ?) OR 816 (ca.end_time > ? AND ca.end_time <= ?) OR 817 (ca.start_time <= ? AND ca.end_time >= ?) 818 ) '; 819 push @Bind, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, 820 \$Param{EndTime}; 821 } 822 elsif ( $Param{StartTime} && !$Param{EndTime} ) { 823 824 $SQL .= 'AND ca.end_time >= ? '; 825 push @Bind, \$Param{StartTime}; 826 } 827 elsif ( !$Param{StartTime} && $Param{EndTime} ) { 828 829 $SQL .= 'AND ca.start_time <= ? '; 830 push @Bind, \$Param{EndTime}; 831 } 832 833 $SQL .= 'ORDER BY ca.id ASC'; 834 835 # db query 836 return if !$DBObject->Prepare( 837 SQL => $SQL, 838 Bind => \@Bind, 839 ); 840 841 my %Result; 842 843 while ( my @Row = $DBObject->FetchrowArray() ) { 844 845 my ( $StartTime, $EndTime, $StartTimeSystem, $EndTimeSystem ); 846 847 # StartTime 848 if ( $Param{StartTime} ) { 849 $StartTime = $Row[0] lt $Param{StartTime} ? $Param{StartTime} : $Row[0]; 850 } 851 else { 852 $StartTime = $Row[0]; 853 } 854 855 # EndTime 856 if ( $Param{EndTime} ) { 857 $EndTime = $Row[1] gt $Param{EndTime} ? $Param{EndTime} : $Row[1]; 858 } 859 else { 860 $EndTime = $Row[1]; 861 } 862 863 # Get system times. 864 my $StartTimeObject = $Kernel::OM->Create( 865 'Kernel::System::DateTime', 866 ObjectParams => { 867 String => $StartTime, 868 }, 869 ); 870 my $EndTimeObject = $Kernel::OM->Create( 871 'Kernel::System::DateTime', 872 ObjectParams => { 873 String => $EndTime, 874 }, 875 ); 876 877 for ( 878 my $LoopTimeObject = $StartTimeObject->Clone(); 879 $LoopTimeObject < $EndTimeObject; 880 $LoopTimeObject->Add( Days => 1 ) 881 ) 882 { 883 my $LoopTime = $LoopTimeObject->ToString(); 884 885 $LoopTime =~ s/\s.*?$//gsm; 886 887 if ( $Result{$LoopTime} ) { 888 $Result{$LoopTime}++; 889 } 890 else { 891 $Result{$LoopTime} = 1; 892 } 893 } 894 } 895 896 # cache 897 $CacheObject->Set( 898 Type => $CacheType, 899 Key => "$CacheKeyStart-$CacheKeyEnd", 900 Value => \%Result, 901 TTL => $Self->{CacheTTL}, 902 ); 903 904 return %Result; 905} 906 907=head2 AppointmentGet() 908 909Get appointment data. 910 911 my %Appointment = $AppointmentObject->AppointmentGet( 912 AppointmentID => 1, # (required) 913 # or 914 UniqueID => '20160101T160000-71E386@localhost', # (required) will return only parent for recurring appointments 915 CalendarID => 1, # (required) 916 ); 917 918Returns a hash: 919 920 %Appointment = ( 921 AppointmentID => 2, 922 ParentID => 1, # only for recurred (child) appointments 923 CalendarID => 1, 924 UniqueID => '20160101T160000-71E386@localhost', 925 Title => 'Webinar', 926 Description => 'How to use Process tickets...', 927 Location => 'Straubing', 928 StartTime => '2016-01-01 16:00:00', 929 EndTime => '2016-01-01 17:00:00', 930 AllDay => 0, 931 TeamID => [ 1 ], 932 ResourceID => [ 1, 3 ], 933 Recurring => 1, 934 RecurrenceType => 'Daily', 935 RecurrenceFrequency => 1, 936 RecurrenceCount => 1, 937 RecurrenceInterval => 2, 938 RecurrenceUntil => '2016-01-10 00:00:00', 939 RecurrenceID => '2016-01-10 00:00:00', 940 RecurrenceExclude => [ 941 '2016-01-10 00:00:00', 942 '2016-01-11 00:00:00', 943 ], 944 NotificationTime => '2016-01-01 17:0:00', 945 NotificationTemplate => 'Custom', 946 NotificationCustomUnitCount => '12', 947 NotificationCustomUnit => 'minutes', 948 NotificationCustomUnitPointOfTime => 'beforestart', 949 950 TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # for ticket appointments only! 951 CreateTime => '2016-01-01 00:00:00', 952 CreateBy => 2, 953 ChangeTime => '2016-01-01 00:00:00', 954 ChangeBy => 2, 955 ); 956 957=cut 958 959sub AppointmentGet { 960 my ( $Self, %Param ) = @_; 961 962 # check needed stuff 963 if ( 964 !$Param{AppointmentID} 965 && !( $Param{UniqueID} && $Param{CalendarID} ) 966 ) 967 { 968 $Kernel::OM->Get('Kernel::System::Log')->Log( 969 Priority => 'error', 970 Message => "Need AppointmentID or UniqueID and CalendarID!", 971 ); 972 return; 973 } 974 975 my $Data; 976 977 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 978 979 if ( $Param{AppointmentID} ) { 980 981 # check cache 982 $Data = $CacheObject->Get( 983 Type => $Self->{CacheType}, 984 Key => $Param{AppointmentID}, 985 ); 986 } 987 988 if ( ref $Data eq 'HASH' ) { 989 return %{$Data}; 990 } 991 992 # needed objects 993 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 994 995 my @Bind; 996 my $SQL = ' 997 SELECT id, parent_id, calendar_id, unique_id, title, description, location, start_time, 998 end_time, all_day, team_id, resource_id, recurring, recur_type, recur_freq, recur_count, 999 recur_interval, recur_until, recur_id, recur_exclude, notify_time, notify_template, 1000 notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, 1001 notify_custom_date, ticket_appointment_rule_id, create_time, create_by, change_time, 1002 change_by 1003 FROM calendar_appointment 1004 WHERE 1005 '; 1006 1007 if ( $Param{AppointmentID} ) { 1008 $SQL .= 'id=? '; 1009 push @Bind, \$Param{AppointmentID}; 1010 } 1011 else { 1012 $SQL .= 'unique_id=? AND calendar_id=? AND parent_id IS NULL '; 1013 push @Bind, \$Param{UniqueID}, \$Param{CalendarID}; 1014 } 1015 1016 # db query 1017 return if !$DBObject->Prepare( 1018 SQL => $SQL, 1019 Bind => \@Bind, 1020 Limit => 1, 1021 ); 1022 1023 my %Result; 1024 1025 while ( my @Row = $DBObject->FetchrowArray() ) { 1026 1027 # team id 1028 my @TeamID = split( ',', $Row[10] // '' ); 1029 1030 # resource id 1031 my @ResourceID = split( ',', $Row[11] // '0' ); 1032 1033 # recurrence frequency 1034 my @RecurrenceFrequency = $Row[14] ? split( ',', $Row[14] ) : undef; 1035 1036 # recurrence exclude 1037 my @RecurrenceExclude = $Row[19] ? split( ',', $Row[19] ) : undef; 1038 1039 $Result{AppointmentID} = $Row[0]; 1040 $Result{ParentID} = $Row[1]; 1041 $Result{CalendarID} = $Row[2]; 1042 $Result{UniqueID} = $Row[3]; 1043 $Result{Title} = $Row[4]; 1044 $Result{Description} = $Row[5]; 1045 $Result{Location} = $Row[6]; 1046 $Result{StartTime} = $Row[7]; 1047 $Result{EndTime} = $Row[8]; 1048 $Result{AllDay} = $Row[9]; 1049 $Result{TeamID} = \@TeamID; 1050 $Result{ResourceID} = \@ResourceID; 1051 $Result{Recurring} = $Row[12]; 1052 $Result{RecurrenceType} = $Row[13]; 1053 $Result{RecurrenceFrequency} = \@RecurrenceFrequency; 1054 $Result{RecurrenceCount} = $Row[15]; 1055 $Result{RecurrenceInterval} = $Row[16]; 1056 $Result{RecurrenceUntil} = $Row[17]; 1057 $Result{RecurrenceID} = $Row[18]; 1058 $Result{RecurrenceExclude} = \@RecurrenceExclude; 1059 $Result{NotificationDate} = $Row[20] || ''; 1060 $Result{NotificationTemplate} = $Row[21] || ''; 1061 $Result{NotificationCustom} = $Row[22] || ''; 1062 $Result{NotificationCustomRelativeUnitCount} = $Row[23] || 0; 1063 $Result{NotificationCustomRelativeUnit} = $Row[24] || ''; 1064 $Result{NotificationCustomRelativePointOfTime} = $Row[25] || ''; 1065 $Result{NotificationCustomDateTime} = $Row[26] || ''; 1066 $Result{TicketAppointmentRuleID} = $Row[27]; 1067 $Result{CreateTime} = $Row[28]; 1068 $Result{CreateBy} = $Row[29]; 1069 $Result{ChangeTime} = $Row[30]; 1070 $Result{ChangeBy} = $Row[31]; 1071 } 1072 1073 if ( $Param{AppointmentID} ) { 1074 1075 # cache 1076 $CacheObject->Set( 1077 Type => $Self->{CacheType}, 1078 Key => $Param{AppointmentID}, 1079 Value => \%Result, 1080 TTL => $Self->{CacheTTL}, 1081 ); 1082 } 1083 1084 return %Result; 1085} 1086 1087=head2 AppointmentUpdate() 1088 1089updates an existing appointment. 1090 1091 my $Success = $AppointmentObject->AppointmentUpdate( 1092 AppointmentID => 2, # (required) 1093 CalendarID => 1, # (required) Valid CalendarID 1094 Title => 'Webinar', # (required) Title 1095 Description => 'How to use Process tickets...', # (optional) Description 1096 Location => 'Straubing', # (optional) Location 1097 StartTime => '2016-01-01 16:00:00', # (required) 1098 EndTime => '2016-01-01 17:00:00', # (required) 1099 AllDay => 0, # (optional) Default 0 1100 Team => 1, # (optional) 1101 ResourceID => [ 1, 3 ], # (optional) must be an array reference if supplied 1102 Recurring => 1, # (optional) flag the appointment as recurring (parent only!) 1103 1104 RecurrenceType => 'Daily', # (required if Recurring) Possible "Daily", "Weekly", "Monthly", "Yearly", 1105 # "CustomWeekly", "CustomMonthly", "CustomYearly" 1106 1107 RecurrenceFrequency => 1, # (required if Custom Recurring) Recurrence pattern 1108 # for CustomWeekly: 1-Mon, 2-Tue,..., 7-Sun 1109 # for CustomMonthly: 1-Jan, 2-Feb,..., 12-Dec 1110 # ... 1111 RecurrenceCount => 1, # (optional) How many Appointments to create 1112 RecurrenceInterval => 2, # (optional) Repeating interval (default 1) 1113 RecurrenceUntil => '2016-01-10 00:00:00', # (optional) Until date 1114 NotificationTime => '2016-01-01 17:00:00', # (optional) Point of time to execute the notification event 1115 NotificationTemplate => 'Custom', # (optional) Template to be used for notification point of time 1116 NotificationCustom => 'relative', # (optional) Type of the custom template notification point of time 1117 # Possible "relative", "datetime" 1118 NotificationCustomRelativeUnitCount => '12', # (optional) minutes, hours or days count for custom template 1119 NotificationCustomRelativeUnit => 'minutes', # (optional) minutes, hours or days unit for custom template 1120 NotificationCustomRelativePointOfTime => 'beforestart', # (optional) Point of execute for custom templates 1121 # Possible "beforestart", "afterstart", "beforeend", "afterend" 1122 NotificationCustomDateTime => '2016-01-01 17:00:00', # (optional) Notification date time for custom template 1123 TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # (optional) Ticket appointment rule ID (for ticket appointments only!) 1124 UserID => 1, # (required) UserID 1125 ); 1126 1127returns 1 if successful: 1128 $Success = 1; 1129 1130Events: 1131 AppointmentUpdate 1132 1133=cut 1134 1135sub AppointmentUpdate { 1136 my ( $Self, %Param ) = @_; 1137 1138 # check needed stuff 1139 for my $Needed (qw(AppointmentID CalendarID Title StartTime EndTime UserID)) { 1140 if ( !$Param{$Needed} ) { 1141 $Kernel::OM->Get('Kernel::System::Log')->Log( 1142 Priority => 'error', 1143 Message => "Need $Needed!", 1144 ); 1145 return; 1146 } 1147 } 1148 1149 # prepare possible notification params 1150 my $Success = $Self->_AppointmentNotificationPrepare( 1151 Data => \%Param, 1152 ); 1153 1154 # if Recurring is provided, additional parameters must be present 1155 if ( $Param{Recurring} ) { 1156 1157 my @RecurrenceTypes = ( 1158 "Daily", "Weekly", "Monthly", "Yearly", 1159 "CustomDaily", "CustomWeekly", "CustomMonthly", "CustomYearly" 1160 ); 1161 1162 if ( 1163 !$Param{RecurrenceType} 1164 || !grep { $_ eq $Param{RecurrenceType} } @RecurrenceTypes 1165 ) 1166 { 1167 $Kernel::OM->Get('Kernel::System::Log')->Log( 1168 Priority => 'error', 1169 Message => "RecurrenceType invalid!", 1170 ); 1171 return; 1172 } 1173 1174 if ( 1175 ( 1176 $Param{RecurrenceType} eq 'CustomWeekly' 1177 || $Param{RecurrenceType} eq 'CustomMonthly' 1178 || $Param{RecurrenceType} eq 'CustomYearly' 1179 ) 1180 && !$Param{RecurrenceFrequency} 1181 ) 1182 { 1183 $Kernel::OM->Get('Kernel::System::Log')->Log( 1184 Priority => 'error', 1185 Message => "RecurrenceFrequency needed!", 1186 ); 1187 return; 1188 } 1189 } 1190 1191 $Param{RecurrenceInterval} ||= 1; 1192 1193 # Check StartTime. 1194 my $StartTimeObject = $Kernel::OM->Create( 1195 'Kernel::System::DateTime', 1196 ObjectParams => { 1197 String => $Param{StartTime}, 1198 }, 1199 ); 1200 if ( !$StartTimeObject ) { 1201 $Kernel::OM->Get('Kernel::System::Log')->Log( 1202 Priority => 'error', 1203 Message => "StartTime invalid!", 1204 ); 1205 return; 1206 } 1207 1208 # Check EndTime. 1209 my $EndTimeObject = $Kernel::OM->Create( 1210 'Kernel::System::DateTime', 1211 ObjectParams => { 1212 String => $Param{EndTime}, 1213 }, 1214 ); 1215 if ( !$EndTimeObject ) { 1216 $Kernel::OM->Get('Kernel::System::Log')->Log( 1217 Priority => 'error', 1218 Message => "EndTime invalid!", 1219 ); 1220 return; 1221 } 1222 1223 # needed objects 1224 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1225 1226 # check if array refs 1227 my %Arrays; 1228 for my $Parameter ( 1229 qw(TeamID ResourceID RecurrenceFrequency) 1230 ) 1231 { 1232 if ( $Param{$Parameter} && @{ $Param{$Parameter} // [] } ) { 1233 if ( !IsArrayRefWithData( $Param{$Parameter} ) ) { 1234 $Kernel::OM->Get('Kernel::System::Log')->Log( 1235 Priority => 'error', 1236 Message => "$Parameter not ARRAYREF!", 1237 ); 1238 return; 1239 } 1240 1241 my @Array = @{ $Param{$Parameter} }; 1242 1243 # remove undefined values 1244 @Array = grep { defined $_ } @Array; 1245 1246 $Arrays{$Parameter} = join( ',', @Array ) if @Array; 1247 } 1248 } 1249 1250 # check if numbers 1251 for my $Parameter ( 1252 qw(Recurring RecurrenceCount RecurrenceInterval) 1253 ) 1254 { 1255 if ( $Param{$Parameter} && !IsInteger( $Param{$Parameter} ) ) { 1256 $Kernel::OM->Get('Kernel::System::Log')->Log( 1257 Priority => 'error', 1258 Message => "$Parameter must be a number!", 1259 ); 1260 return; 1261 } 1262 } 1263 1264 # check RecurrenceUntil 1265 if ( $Param{RecurrenceUntil} ) { 1266 1267 # Usually hour, minute and second = 0. In this case, take time from StartTime. 1268 $Param{RecurrenceUntil} = $Self->_TimeCheck( 1269 OriginalTime => $Param{StartTime}, 1270 Time => $Param{RecurrenceUntil}, 1271 ); 1272 1273 my $RecurrenceUntilObject = $Kernel::OM->Create( 1274 'Kernel::System::DateTime', 1275 ObjectParams => { 1276 String => $Param{RecurrenceUntil}, 1277 }, 1278 ); 1279 1280 if ( 1281 !$RecurrenceUntilObject 1282 || $StartTimeObject > $RecurrenceUntilObject 1283 ) 1284 { 1285 $Kernel::OM->Get('Kernel::System::Log')->Log( 1286 Priority => 'error', 1287 Message => "RecurrenceUntil invalid!", 1288 ); 1289 return; 1290 } 1291 } 1292 1293 # get previous CalendarID 1294 my $PreviousCalendarID = $Self->_AppointmentGetCalendarID( 1295 AppointmentID => $Param{AppointmentID}, 1296 ); 1297 1298 # set recurrence exclude list 1299 my @RecurrenceExclude = @{ $Param{RecurrenceExclude} // [] }; 1300 1301 # get RecurrenceID 1302 my $RecurrenceID = $Self->_AppointmentGetRecurrenceID( 1303 AppointmentID => $Param{AppointmentID}, 1304 ); 1305 1306 # use exclude list to flag the recurring occurrence as updated 1307 if ($RecurrenceID) { 1308 @RecurrenceExclude = ($RecurrenceID); 1309 } 1310 1311 # reset exclude list if recurrence is turned off 1312 elsif ( !$Param{Recurring} ) { 1313 @RecurrenceExclude = (); 1314 } 1315 1316 # remove undefined values 1317 @RecurrenceExclude = grep { defined $_ } @RecurrenceExclude; 1318 1319 # serialize data 1320 my $RecurrenceExclude = join( ',', @RecurrenceExclude ) || undef; 1321 1322 # delete existing recurred appointments 1323 my $DeleteSuccess = $Self->_AppointmentRecurringDelete( 1324 ParentID => $Param{AppointmentID}, 1325 ); 1326 1327 if ( !$DeleteSuccess ) { 1328 $Kernel::OM->Get('Kernel::System::Log')->Log( 1329 Priority => 'error', 1330 Message => "Unable to delete recurring Appointment!", 1331 ); 1332 return; 1333 } 1334 1335 # update appointment 1336 my $SQL = ' 1337 UPDATE calendar_appointment 1338 SET 1339 calendar_id=?, title=?, description=?, location=?, start_time=?, end_time=?, all_day=?, 1340 team_id=?, resource_id=?, recurring=?, recur_type=?, recur_freq=?, recur_count=?, 1341 recur_interval=?, recur_until=?, recur_exclude=?, notify_time=?, notify_template=?, 1342 notify_custom=?, notify_custom_unit_count=?, notify_custom_unit=?, 1343 notify_custom_unit_point=?, notify_custom_date=?, ticket_appointment_rule_id=?, 1344 change_time=current_timestamp, change_by=? 1345 WHERE id=? 1346 '; 1347 1348 # update db record 1349 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 1350 SQL => $SQL, 1351 Bind => [ 1352 \$Param{CalendarID}, \$Param{Title}, \$Param{Description}, \$Param{Location}, 1353 \$Param{StartTime}, \$Param{EndTime}, \$Param{AllDay}, \$Arrays{TeamID}, 1354 \$Arrays{ResourceID}, \$Param{Recurring}, \$Param{RecurrenceType}, 1355 \$Arrays{RecurrenceFrequency}, \$Param{RecurrenceCount}, \$Param{RecurrenceInterval}, 1356 \$Param{RecurrenceUntil}, \$RecurrenceExclude, \$Param{NotificationDate}, 1357 \$Param{NotificationTemplate}, \$Param{NotificationCustom}, 1358 \$Param{NotificationCustomRelativeUnitCount}, \$Param{NotificationCustomRelativeUnit}, 1359 \$Param{NotificationCustomRelativePointOfTime}, \$Param{NotificationCustomDateTime}, 1360 \$Param{TicketAppointmentRuleID}, \$Param{UserID}, \$Param{AppointmentID}, 1361 ], 1362 ); 1363 1364 # add recurred appointments again 1365 if ( $Param{Recurring} ) { 1366 return if !$Self->_AppointmentRecurringCreate( 1367 ParentID => $Param{AppointmentID}, 1368 Appointment => \%Param, 1369 ); 1370 } 1371 1372 # delete cache 1373 $CacheObject->Delete( 1374 Type => $Self->{CacheType}, 1375 Key => $Param{AppointmentID}, 1376 ); 1377 1378 # clean up list methods cache 1379 my @CalendarIDs = ( $Param{CalendarID} ); 1380 push @CalendarIDs, $PreviousCalendarID if $PreviousCalendarID ne $Param{CalendarID}; 1381 for my $CalendarID (@CalendarIDs) { 1382 $CacheObject->CleanUp( 1383 Type => $Self->{CacheType} . 'List' . $CalendarID, 1384 ); 1385 } 1386 $CacheObject->CleanUp( 1387 Type => $Self->{CacheType} . 'Days' . $Param{UserID}, 1388 ); 1389 1390 # fire event 1391 $Self->EventHandler( 1392 Event => 'AppointmentUpdate', 1393 Data => { 1394 AppointmentID => $Param{AppointmentID}, 1395 CalendarID => $Param{CalendarID}, 1396 }, 1397 UserID => $Param{UserID}, 1398 ); 1399 1400 return 1; 1401} 1402 1403=head2 AppointmentDelete() 1404 1405deletes an existing appointment. 1406 1407 my $Success = $AppointmentObject->AppointmentDelete( 1408 AppointmentID => 1, # (required) 1409 UserID => 1, # (required) 1410 ); 1411 1412returns 1 if successful: 1413 $Success = 1; 1414 1415Events: 1416 AppointmentDelete 1417 1418=cut 1419 1420sub AppointmentDelete { 1421 my ( $Self, %Param ) = @_; 1422 1423 # check needed stuff 1424 for my $Needed (qw(AppointmentID UserID)) { 1425 if ( !$Param{$Needed} ) { 1426 $Kernel::OM->Get('Kernel::System::Log')->Log( 1427 Priority => 'error', 1428 Message => "Need $Needed!", 1429 ); 1430 return; 1431 } 1432 } 1433 1434 # needed objects 1435 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1436 1437 # get CalendarID 1438 my $CalendarID = $Self->_AppointmentGetCalendarID( 1439 AppointmentID => $Param{AppointmentID}, 1440 ); 1441 1442 # check user's permissions for this calendar 1443 my $Permission = $Kernel::OM->Get('Kernel::System::Calendar')->CalendarPermissionGet( 1444 CalendarID => $CalendarID, 1445 UserID => $Param{UserID}, 1446 ); 1447 1448 my @RequiredPermissions = ( 'create', 'rw' ); 1449 1450 if ( !grep { $Permission eq $_ } @RequiredPermissions ) { 1451 $Kernel::OM->Get('Kernel::System::Log')->Log( 1452 Priority => 'error', 1453 Message => "User($Param{UserID}) has no permission to delete Appointment($Param{AppointmentID})!", 1454 ); 1455 return; 1456 } 1457 1458 my %Appointment = $Self->AppointmentGet( 1459 AppointmentID => $Param{AppointmentID}, 1460 ); 1461 1462 # save exclusion info to parent appointment 1463 if ( $Appointment{ParentID} && $Appointment{RecurrenceID} ) { 1464 $Self->_AppointmentRecurringExclude( 1465 ParentID => $Appointment{ParentID}, 1466 RecurrenceID => $Appointment{RecurrenceID}, 1467 ); 1468 } 1469 1470 # delete recurring appointments 1471 my $DeleteRecurringSuccess = $Self->_AppointmentRecurringDelete( 1472 ParentID => $Param{AppointmentID}, 1473 ); 1474 1475 if ( !$DeleteRecurringSuccess ) { 1476 $Kernel::OM->Get('Kernel::System::Log')->Log( 1477 Priority => 'error', 1478 Message => 'Recurring appointments couldn\'t be deleted!', 1479 ); 1480 return; 1481 } 1482 1483 # delete appointment 1484 my $SQL = ' 1485 DELETE FROM calendar_appointment 1486 WHERE id=? 1487 '; 1488 1489 # delete db record 1490 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 1491 SQL => $SQL, 1492 Bind => [ 1493 \$Param{AppointmentID}, 1494 ], 1495 ); 1496 1497 # Fire event. 1498 $Self->EventHandler( 1499 Event => 'AppointmentDelete', 1500 Data => { 1501 AppointmentID => $Param{AppointmentID}, 1502 CalendarID => $CalendarID, 1503 }, 1504 UserID => $Param{UserID}, 1505 ); 1506 1507 # delete cache 1508 $CacheObject->Delete( 1509 Type => $Self->{CacheType}, 1510 Key => $Param{AppointmentID}, 1511 ); 1512 1513 # clean up list methods cache 1514 $CacheObject->CleanUp( 1515 Type => $Self->{CacheType} . 'List' . $CalendarID, 1516 ); 1517 $CacheObject->CleanUp( 1518 Type => $Self->{CacheType} . 'Days' . $Param{UserID}, 1519 ); 1520 1521 return 1; 1522} 1523 1524=head2 AppointmentDeleteOccurrence() 1525 1526deletes a single recurring appointment occurrence. 1527 1528 my $Success = $AppointmentObject->AppointmentDeleteOccurrence( 1529 UniqueID => '20160101T160000-71E386@localhost', # (required) 1530 RecurrenceID => '2016-01-10 00:00:00', # (required) 1531 UserID => 1, # (required) 1532 ); 1533 1534returns 1 if successful: 1535 $Success = 1; 1536 1537=cut 1538 1539sub AppointmentDeleteOccurrence { 1540 my ( $Self, %Param ) = @_; 1541 1542 # check needed stuff 1543 for my $Needed (qw(UniqueID CalendarID RecurrenceID UserID)) { 1544 if ( !$Param{$Needed} ) { 1545 $Kernel::OM->Get('Kernel::System::Log')->Log( 1546 Priority => 'error', 1547 Message => "Need $Needed!", 1548 ); 1549 return; 1550 } 1551 } 1552 1553 # get db object 1554 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1555 1556 # db query 1557 return if !$DBObject->Prepare( 1558 SQL => ' 1559 SELECT id FROM calendar_appointment 1560 WHERE unique_id=? AND calendar_id=? AND recur_id=?', 1561 Bind => [ \$Param{UniqueID}, \$Param{CalendarID}, \$Param{RecurrenceID} ], 1562 Limit => 1, 1563 ); 1564 1565 my %Appointment; 1566 1567 # get additional info 1568 while ( my @Row = $DBObject->FetchrowArray() ) { 1569 $Appointment{AppointmentID} = $Row[0]; 1570 } 1571 return if !%Appointment; 1572 1573 # delete db record 1574 return if !$DBObject->Do( 1575 SQL => 'DELETE FROM calendar_appointment WHERE id=?', 1576 Bind => [ \$Appointment{AppointmentID} ], 1577 Limit => 1, 1578 ); 1579 1580 # get cache object 1581 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1582 1583 # delete cache 1584 $CacheObject->Delete( 1585 Type => $Self->{CacheType}, 1586 Key => $Appointment{AppointmentID}, 1587 ); 1588 1589 # clean up list methods cache 1590 $CacheObject->CleanUp( 1591 Type => $Self->{CacheType} . 'List' . $Param{CalendarID}, 1592 ); 1593 $CacheObject->CleanUp( 1594 Type => $Self->{CacheType} . 'Days' . $Param{UserID}, 1595 ); 1596 1597 return 1; 1598} 1599 1600=head2 GetUniqueID() 1601 1602Returns UniqueID containing appointment start time, random hash and system C<FQDN>. 1603 1604 my $UniqueID = $AppointmentObject->GetUniqueID( 1605 CalendarID => 1, # (required) 1606 StartTime => '2016-01-01 00:00:00', # (required) 1607 UserID => 1, # (required) 1608 ); 1609 1610 $UniqueID = '20160101T000000-B9909D@localhost'; 1611 1612=cut 1613 1614sub GetUniqueID { 1615 my ( $Self, %Param ) = @_; 1616 1617 # check needed stuff 1618 for my $Needed (qw(CalendarID StartTime UserID)) { 1619 if ( !$Param{$Needed} ) { 1620 $Kernel::OM->Get('Kernel::System::Log')->Log( 1621 Priority => 'error', 1622 Message => "Need $Needed!", 1623 ); 1624 return; 1625 } 1626 } 1627 1628 # calculate a hash 1629 my $RandomString = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( Length => 32 ); 1630 my $String = "$Param{CalendarID}-$RandomString-$Param{UserID}"; 1631 my $Digest = unpack( 'N', Digest::MD5->new()->add($String)->digest() ); 1632 my $DigestHex = sprintf( '%x', $Digest ); 1633 my $Hash = uc( sprintf( "%.6s", $DigestHex ) ); 1634 1635 # Prepare start timestamp for UniqueID. 1636 my $StartTimeObject = $Kernel::OM->Create( 1637 'Kernel::System::DateTime', 1638 ObjectParams => { 1639 String => $Param{StartTime}, 1640 }, 1641 ); 1642 return if !$StartTimeObject; 1643 my $StartTimeStrg = $StartTimeObject->ToString(); 1644 $StartTimeStrg =~ s/[-:]//g; 1645 $StartTimeStrg =~ s/\s/T/; 1646 1647 # get system FQDN 1648 my $FQDN = $Kernel::OM->Get('Kernel::Config')->Get('FQDN'); 1649 1650 # return UniqueID 1651 return "$StartTimeStrg-$Hash\@$FQDN"; 1652} 1653 1654=head2 AppointmentUpcomingGet() 1655 1656Get appointment data for upcoming appointment start or end. 1657 1658 my @UpcomingAppointments = $AppointmentObject->AppointmentUpcomingGet( 1659 Timestamp => '2016-08-02 03:59:00', # get appointments for the related notification timestamp 1660 ); 1661 1662Returns appointment data of AppointmentGet(). 1663 1664=cut 1665 1666sub AppointmentUpcomingGet { 1667 my ( $Self, %Param ) = @_; 1668 1669 # get current timestamp 1670 my $CurrentTimestamp; 1671 1672 # create needed sql query based on the current or a given timestamp 1673 my $SQL = 'SELECT id, parent_id, calendar_id, unique_id FROM calendar_appointment '; 1674 1675 if ( $Param{Timestamp} ) { 1676 $CurrentTimestamp = $Param{Timestamp}; 1677 $SQL .= "WHERE notify_time = ? "; 1678 } 1679 else { 1680 $CurrentTimestamp = $Kernel::OM->Create('Kernel::System::DateTime')->ToString(); 1681 $SQL .= "WHERE notify_time >= ? "; 1682 } 1683 1684 $SQL .= 'ORDER BY notify_time ASC'; 1685 1686 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1687 1688 # db query 1689 return if !$DBObject->Prepare( 1690 SQL => $SQL, 1691 Bind => [ \$CurrentTimestamp ], 1692 ); 1693 1694 my @ResultRaw; 1695 1696 while ( my @Row = $DBObject->FetchrowArray() ) { 1697 1698 my %UpcomingAppointment; 1699 1700 $UpcomingAppointment{AppointmentID} = $Row[0]; 1701 $UpcomingAppointment{ParentID} = $Row[1]; 1702 $UpcomingAppointment{CalendarID} = $Row[2]; 1703 $UpcomingAppointment{UniqueID} = $Row[3]; 1704 1705 push @ResultRaw, \%UpcomingAppointment; 1706 } 1707 1708 my @Results; 1709 1710 APPOINTMENTDATA: 1711 for my $AppointmentData (@ResultRaw) { 1712 1713 next APPOINTMENTDATA if !IsHashRefWithData($AppointmentData); 1714 next APPOINTMENTDATA if !$AppointmentData->{CalendarID}; 1715 next APPOINTMENTDATA if !$AppointmentData->{AppointmentID}; 1716 1717 my %Appointment = $Self->AppointmentGet( %{$AppointmentData} ); 1718 1719 push @Results, \%Appointment; 1720 } 1721 1722 return @Results; 1723} 1724 1725=head2 AppointmentFutureTasksDelete() 1726 1727Delete all calendar appointment future tasks. 1728 1729 my $Success = $AppointmentObject->AppointmentFutureTasksDelete(); 1730 1731returns: 1732 1733 True if future task deletion was successful, otherwise false. 1734 1735=cut 1736 1737sub AppointmentFutureTasksDelete { 1738 my ( $Self, %Param ) = @_; 1739 1740 # get a local scheduler db object 1741 my $SchedulerObject = $Kernel::OM->Get('Kernel::System::Scheduler'); 1742 1743 # get a list of already stored future tasks 1744 my @FutureTaskList = $SchedulerObject->FutureTaskList( 1745 Type => 'CalendarAppointment', 1746 ); 1747 1748 # flush obsolete future tasks 1749 if ( IsArrayRefWithData( \@FutureTaskList ) ) { 1750 1751 FUTURETASK: 1752 for my $FutureTask (@FutureTaskList) { 1753 1754 next FUTURETASK if !$FutureTask; 1755 next FUTURETASK if !IsHashRefWithData($FutureTask); 1756 1757 my $Success = $SchedulerObject->FutureTaskDelete( 1758 TaskID => $FutureTask->{TaskID}, 1759 ); 1760 1761 if ( !$Success ) { 1762 $Kernel::OM->Get('Kernel::System::Log')->Log( 1763 Priority => 'error', 1764 Message => "Could not delete future task with id $FutureTask->{TaskID}!", 1765 ); 1766 return; 1767 } 1768 } 1769 } 1770 1771 return 1; 1772} 1773 1774=head2 AppointmentFutureTasksUpdate() 1775 1776Update OTRS daemon future task list for upcoming appointments. 1777 1778 my $Success = $AppointmentObject->AppointmentFutureTasksUpdate(); 1779 1780returns: 1781 1782 True if future task update was successful, otherwise false. 1783 1784=cut 1785 1786sub AppointmentFutureTasksUpdate { 1787 my ( $Self, %Param ) = @_; 1788 1789 # get appointment data for upcoming appointments 1790 my @UpcomingAppointments = $Self->AppointmentUpcomingGet(); 1791 1792 # check for no upcoming appointments 1793 if ( !IsArrayRefWithData( \@UpcomingAppointments ) ) { 1794 1795 # flush obsolete future tasks 1796 my $Success = $Self->AppointmentFutureTasksDelete(); 1797 1798 if ( !$Success ) { 1799 $Kernel::OM->Get('Kernel::System::Log')->Log( 1800 Priority => 'error', 1801 Message => 'Could not delete appointment future tasks!', 1802 ); 1803 return; 1804 } 1805 1806 return 1; 1807 } 1808 1809 # get a local scheduler db object 1810 my $SchedulerObject = $Kernel::OM->Get('Kernel::System::Scheduler'); 1811 1812 # get a list of already stored future tasks 1813 my @FutureTaskList = $SchedulerObject->FutureTaskList( 1814 Type => 'CalendarAppointment', 1815 ); 1816 1817 # check for invalid task count (just one task max allowed) 1818 if ( scalar @FutureTaskList > 1 ) { 1819 1820 # flush obsolete future tasks 1821 my $Success = $Self->AppointmentFutureTasksDelete(); 1822 1823 if ( !$Success ) { 1824 $Kernel::OM->Get('Kernel::System::Log')->Log( 1825 Priority => 'error', 1826 Message => 'Could not delete appointment future tasks!', 1827 ); 1828 return; 1829 } 1830 } 1831 1832 # check if it is needed to update the future task list 1833 if ( IsArrayRefWithData( \@FutureTaskList ) ) { 1834 my $UpdateNeeded = 0; 1835 1836 FUTURETASK: 1837 for my $FutureTask (@FutureTaskList) { 1838 1839 if ( 1840 !IsHashRefWithData($FutureTask) 1841 || !$FutureTask->{TaskID} 1842 || !$FutureTask->{ExecutionTime} 1843 ) 1844 { 1845 $UpdateNeeded = 1; 1846 last FUTURETASK; 1847 } 1848 1849 # get the stored future task 1850 my %FutureTaskData = $Kernel::OM->Get('Kernel::System::Daemon::SchedulerDB')->FutureTaskGet( 1851 TaskID => $FutureTask->{TaskID}, 1852 ); 1853 1854 if ( !IsHashRefWithData( \%FutureTaskData ) ) { 1855 $UpdateNeeded = 1; 1856 last FUTURETASK; 1857 } 1858 1859 # Get date time objects of stored and upcoming times to compare. 1860 my $FutureTaskTimeObject = $Kernel::OM->Create( 1861 'Kernel::System::DateTime', 1862 ObjectParams => { 1863 String => $FutureTaskData{Data}->{NotifyTime}, 1864 }, 1865 ); 1866 my $UpcomingAppointmentTimeObject = $Kernel::OM->Create( 1867 'Kernel::System::DateTime', 1868 ObjectParams => { 1869 String => $UpcomingAppointments[0]->{NotificationDate}, 1870 }, 1871 ); 1872 1873 # Do nothing if the upcoming notification time equals the stored value. 1874 if ( $UpcomingAppointmentTimeObject != $FutureTaskTimeObject ) { 1875 $UpdateNeeded = 1; 1876 last FUTURETASK; 1877 } 1878 } 1879 1880 if ($UpdateNeeded) { 1881 1882 # flush obsolete future tasks 1883 my $Success = $Self->AppointmentFutureTasksDelete(); 1884 1885 if ( !$Success ) { 1886 $Kernel::OM->Get('Kernel::System::Log')->Log( 1887 Priority => 'error', 1888 Message => 'Could not delete appointment future tasks!', 1889 ); 1890 return; 1891 } 1892 } 1893 else { 1894 return 1; 1895 } 1896 } 1897 1898 # schedule new future tasks for notification actions 1899 my $TaskID = $SchedulerObject->TaskAdd( 1900 ExecutionTime => $UpcomingAppointments[0]->{NotificationDate}, 1901 Name => 'AppointmentNotification', 1902 Type => 'CalendarAppointment', 1903 Data => { 1904 NotifyTime => $UpcomingAppointments[0]->{NotificationDate}, 1905 }, 1906 ); 1907 1908 if ( !$TaskID ) { 1909 $Kernel::OM->Get('Kernel::System::Log')->Log( 1910 Priority => 'error', 1911 Message => 1912 "Could not schedule future task for AppointmentID $UpcomingAppointments[0]->{AppointmentID}!", 1913 ); 1914 return; 1915 } 1916 1917 return 1; 1918} 1919 1920=head2 _AppointmentNotificationPrepare() 1921 1922Prepare appointment notification data. 1923 1924 my $Success = $AppointmentObject->_AppointmentNotificationPrepare(); 1925 1926returns: 1927 1928 True if preparation was successful, otherwise false. 1929 1930=cut 1931 1932sub _AppointmentNotificationPrepare { 1933 my ( $Self, %Param ) = @_; 1934 1935 # check needed stuff 1936 for my $Needed (qw(Data)) { 1937 if ( !$Param{$Needed} ) { 1938 $Kernel::OM->Get('Kernel::System::Log')->Log( 1939 Priority => 'error', 1940 Message => "Need $Needed!", 1941 ); 1942 return; 1943 } 1944 } 1945 1946 # reset notification data if needed 1947 if ( !$Param{Data}->{NotificationTemplate} ) { 1948 1949 for my $PossibleParam ( 1950 qw( 1951 NotificationDate NotificationTemplate NotificationCustom NotificationCustomRelativeUnitCount 1952 NotificationCustomRelativeUnit NotificationCustomRelativePointOfTime NotificationCustomDateTime 1953 ) 1954 ) 1955 { 1956 $Param{Data}->{$PossibleParam} = undef; 1957 } 1958 } 1959 1960 # prepare possible notification params 1961 for my $PossibleParam ( 1962 qw( 1963 NotificationTemplate NotificationCustom NotificationCustomRelativeUnit 1964 NotificationCustomRelativePointOfTime 1965 ) 1966 ) 1967 { 1968 $Param{Data}->{$PossibleParam} ||= ''; 1969 } 1970 1971 # special check for relative unit count as it can be zero 1972 # (empty and negative values will be treated as zero to avoid errors) 1973 if ( 1974 !IsNumber( $Param{Data}->{NotificationCustomRelativeUnitCount} ) 1975 || $Param{Data}->{NotificationCustomRelativeUnitCount} <= 0 1976 ) 1977 { 1978 $Param{Data}->{NotificationCustomRelativeUnitCount} = 0; 1979 } 1980 1981 # set empty datetime strings to undef 1982 for my $PossibleParam (qw(NotificationDate NotificationCustomDateTime)) { 1983 $Param{Data}->{$PossibleParam} ||= undef; 1984 } 1985 1986 return if !$Param{Data}->{NotificationTemplate}; 1987 1988 # 1989 # template Start 1990 # 1991 if ( $Param{Data}->{NotificationTemplate} eq 'Start' ) { 1992 1993 # setup the appointment start date as notification date 1994 $Param{Data}->{NotificationDate} = $Param{Data}->{StartTime}; 1995 } 1996 1997 # 1998 # template time before start 1999 # 2000 elsif ( 2001 $Param{Data}->{NotificationTemplate} ne 'Custom' 2002 && IsNumber( $Param{Data}->{NotificationTemplate} ) 2003 && $Param{Data}->{NotificationTemplate} > 0 2004 ) 2005 { 2006 2007 return if !IsNumber( $Param{Data}->{NotificationTemplate} ); 2008 2009 # offset template (before start datetime) used 2010 my $Offset = $Param{Data}->{NotificationTemplate}; 2011 2012 # Get date time object of appointment start time. 2013 my $StartTimeObject = $Kernel::OM->Create( 2014 'Kernel::System::DateTime', 2015 ObjectParams => { 2016 String => $Param{Data}->{StartTime}, 2017 }, 2018 ); 2019 2020 # Subtract offset in seconds for new notification date time. 2021 $StartTimeObject->Subtract( 2022 Seconds => $Offset, 2023 ); 2024 2025 $Param{Data}->{NotificationDate} = $StartTimeObject->ToString(); 2026 } 2027 2028 # 2029 # template Custom 2030 # 2031 else { 2032 2033 # Compute date of custom relative input. 2034 if ( $Param{Data}->{NotificationCustom} eq 'relative' ) { 2035 2036 my $CustomUnitCount = $Param{Data}->{NotificationCustomRelativeUnitCount}; 2037 my $CustomUnit = $Param{Data}->{NotificationCustomRelativeUnit}; 2038 my $CustomUnitPoint = $Param{Data}->{NotificationCustomRelativePointOfTime}; 2039 2040 # setup the count to compute for the offset 2041 my %UnitOffsetCompute = ( 2042 minutes => 60, 2043 hours => 3600, 2044 days => 86400, 2045 ); 2046 2047 my $NotificationLocalTimeObject; 2048 2049 # Compute from start time. 2050 if ( $CustomUnitPoint eq 'beforestart' || $CustomUnitPoint eq 'afterstart' ) { 2051 $NotificationLocalTimeObject = $Kernel::OM->Create( 2052 'Kernel::System::DateTime', 2053 ObjectParams => { 2054 String => $Param{Data}->{StartTime}, 2055 }, 2056 ); 2057 } 2058 2059 # Compute from end time. 2060 elsif ( $CustomUnitPoint eq 'beforeend' || $CustomUnitPoint eq 'afterend' ) { 2061 $NotificationLocalTimeObject = $Kernel::OM->Create( 2062 'Kernel::System::DateTime', 2063 ObjectParams => { 2064 String => $Param{Data}->{EndTime}, 2065 }, 2066 ); 2067 } 2068 2069 # Not supported point of time. 2070 else { 2071 return; 2072 } 2073 2074 # compute the offset to be used 2075 my $Offset = ( $CustomUnitCount * $UnitOffsetCompute{$CustomUnit} ); 2076 2077 # save the newly computed notification datetime string 2078 if ( $CustomUnitPoint eq 'beforestart' || $CustomUnitPoint eq 'beforeend' ) { 2079 $NotificationLocalTimeObject->Subtract( 2080 Seconds => $Offset, 2081 ); 2082 $Param{Data}->{NotificationDate} = $NotificationLocalTimeObject->ToString(); 2083 } 2084 else { 2085 $NotificationLocalTimeObject->Add( 2086 Seconds => $Offset, 2087 ); 2088 $Param{Data}->{NotificationDate} = $NotificationLocalTimeObject->ToString(); 2089 } 2090 } 2091 2092 # Compute date of custom date/time input. 2093 elsif ( $Param{Data}->{NotificationCustom} eq 'datetime' ) { 2094 2095 $Param{Data}->{NotificationCustom} = 'datetime'; 2096 2097 # validation 2098 if ( !IsStringWithData( $Param{Data}->{NotificationCustomDateTime} ) ) { 2099 return; 2100 } 2101 2102 # save the given date time values as notification datetime string (i.e. 2016-06-28 02:00:00) 2103 $Param{Data}->{NotificationDate} = $Param{Data}->{NotificationCustomDateTime}; 2104 } 2105 } 2106 2107 if ( !IsStringWithData( $Param{Data}->{NotificationDate} ) ) { 2108 $Param{Data}->{NotificationDate} = undef; 2109 } 2110 2111 if ( !IsStringWithData( $Param{Data}->{NotificationCustomDateTime} ) ) { 2112 $Param{Data}->{NotificationCustomDateTime} = undef; 2113 } 2114 2115 return 1; 2116} 2117 2118=head2 AppointmentNotification() 2119 2120Will be triggered by the OTRS daemon to fire events for appointments, 2121that reaches it's reminder (notification) time. 2122 2123 my $Success = $AppointmentObject->AppointmentNotification(); 2124 2125returns: 2126 2127 True if notify action was successful, otherwise false. 2128 2129=cut 2130 2131sub AppointmentNotification { 2132 my ( $Self, %Param ) = @_; 2133 2134 # check needed stuff 2135 for my $Needed (qw(NotifyTime)) { 2136 if ( !$Param{$Needed} ) { 2137 $Kernel::OM->Get('Kernel::System::Log')->Log( 2138 Priority => 'error', 2139 Message => "Need $Needed!", 2140 ); 2141 return; 2142 } 2143 } 2144 2145 # get appointments for the related notification timestamp 2146 my @UpcomingAppointments = $Self->AppointmentUpcomingGet( 2147 Timestamp => $Param{NotifyTime}, 2148 ); 2149 2150 return if !IsArrayRefWithData( \@UpcomingAppointments ); 2151 2152 # sleep at least 1 second to make sure the timestamp doesn't 2153 # equals the last one for update upcoming future tasks 2154 sleep 1; 2155 2156 UPCOMINGAPPOINTMENT: 2157 for my $UpcomingAppointment (@UpcomingAppointments) { 2158 2159 next UPCOMINGAPPOINTMENT if !$UpcomingAppointment; 2160 next UPCOMINGAPPOINTMENT if !IsHashRefWithData($UpcomingAppointment); 2161 next UPCOMINGAPPOINTMENT if !$UpcomingAppointment->{AppointmentID}; 2162 2163 # fire event 2164 $Self->EventHandler( 2165 Event => 'AppointmentNotification', 2166 Data => { 2167 AppointmentID => $UpcomingAppointment->{AppointmentID}, 2168 CalendarID => $UpcomingAppointment->{CaledarID}, 2169 }, 2170 UserID => 1, 2171 ); 2172 } 2173 2174 return 1; 2175} 2176 2177=begin Internal: 2178 2179=cut 2180 2181sub _AppointmentRecurringCreate { 2182 my ( $Self, %Param ) = @_; 2183 2184 # check needed stuff 2185 for my $Needed (qw(ParentID Appointment)) { 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 my $StartTimeObject = $Kernel::OM->Create( 2196 'Kernel::System::DateTime', 2197 ObjectParams => { 2198 String => $Param{Appointment}->{StartTime}, 2199 }, 2200 ); 2201 my $EndTimeObject = $Kernel::OM->Create( 2202 'Kernel::System::DateTime', 2203 ObjectParams => { 2204 String => $Param{Appointment}->{EndTime}, 2205 }, 2206 ); 2207 2208 my @RecurrenceExclude = @{ $Param{Appointment}->{RecurrenceExclude} // [] }; 2209 2210 # remove undefined values 2211 @RecurrenceExclude = grep { defined $_ } @RecurrenceExclude; 2212 2213 # reset the parameter for occurrences 2214 $Param{Appointment}->{RecurrenceExclude} = undef; 2215 2216 my $OriginalStartTimeObject = $StartTimeObject->Clone(); 2217 my $OriginalEndTimeObject = $EndTimeObject->Clone(); 2218 my $Step = 0; 2219 2220 # until ... 2221 if ( $Param{Appointment}->{RecurrenceUntil} ) { 2222 my $RecurrenceUntilObject = $Kernel::OM->Create( 2223 'Kernel::System::DateTime', 2224 ObjectParams => { 2225 String => $Param{Appointment}->{RecurrenceUntil}, 2226 }, 2227 ); 2228 2229 UNTIL_TIME: 2230 while ( $StartTimeObject <= $RecurrenceUntilObject ) { 2231 $Step += $Param{Appointment}->{RecurrenceInterval}; 2232 2233 # calculate recurring times 2234 $StartTimeObject = $Self->_CalculateRecurrenceTime( 2235 Appointment => $Param{Appointment}, 2236 Step => $Step, 2237 OriginalTime => $OriginalStartTimeObject, 2238 CurrentTime => $StartTimeObject, 2239 ); 2240 2241 last UNTIL_TIME if !$StartTimeObject; 2242 last UNTIL_TIME if $StartTimeObject > $RecurrenceUntilObject; 2243 2244 $EndTimeObject = $StartTimeObject->Clone(); 2245 $EndTimeObject->Add( 2246 Seconds => 2247 $OriginalEndTimeObject->Delta( DateTimeObject => $OriginalStartTimeObject )->{AbsoluteSeconds}, 2248 ); 2249 2250 my $StartTime = $StartTimeObject->ToString(); 2251 my $EndTime = $EndTimeObject->ToString(); 2252 2253 # Bugfix: on some systems with older perl version system might calculate timezone difference. 2254 $StartTime = $Self->_TimeCheck( 2255 OriginalTime => $Param{Appointment}->{StartTime}, 2256 Time => $StartTime, 2257 ); 2258 $EndTime = $Self->_TimeCheck( 2259 OriginalTime => $Param{Appointment}->{EndTime}, 2260 Time => $EndTime, 2261 ); 2262 2263 # skip excluded appointments 2264 next UNTIL_TIME if grep { $StartTime eq $_ } @RecurrenceExclude; 2265 2266 $Self->AppointmentCreate( 2267 %{ $Param{Appointment} }, 2268 ParentID => $Param{ParentID}, 2269 StartTime => $StartTime, 2270 EndTime => $EndTime, 2271 RecurrenceID => $StartTime, 2272 ); 2273 } 2274 } 2275 2276 # for ... time(s) 2277 elsif ( $Param{Appointment}->{RecurrenceCount} ) { 2278 2279 COUNT: 2280 for ( 1 .. $Param{Appointment}->{RecurrenceCount} - 1 ) { 2281 $Step += $Param{Appointment}->{RecurrenceInterval}; 2282 2283 # calculate recurring times 2284 $StartTimeObject = $Self->_CalculateRecurrenceTime( 2285 Appointment => $Param{Appointment}, 2286 Step => $Step, 2287 OriginalTime => $OriginalStartTimeObject, 2288 CurrentTime => $StartTimeObject, 2289 ); 2290 2291 last COUNT if !$StartTimeObject; 2292 2293 $EndTimeObject = $StartTimeObject->Clone(); 2294 $EndTimeObject->Add( 2295 Seconds => 2296 $OriginalEndTimeObject->Delta( DateTimeObject => $OriginalStartTimeObject )->{AbsoluteSeconds}, 2297 ); 2298 2299 my $StartTime = $StartTimeObject->ToString(); 2300 my $EndTime = $EndTimeObject->ToString(); 2301 2302 # Bugfix: on some systems with older perl version system might calculate timezone difference. 2303 $StartTime = $Self->_TimeCheck( 2304 OriginalTime => $Param{Appointment}->{StartTime}, 2305 Time => $StartTime, 2306 ); 2307 $EndTime = $Self->_TimeCheck( 2308 OriginalTime => $Param{Appointment}->{EndTime}, 2309 Time => $EndTime, 2310 ); 2311 2312 # skip excluded appointments 2313 next COUNT if grep { $StartTime eq $_ } @RecurrenceExclude; 2314 2315 $Self->AppointmentCreate( 2316 %{ $Param{Appointment} }, 2317 ParentID => $Param{ParentID}, 2318 StartTime => $StartTime, 2319 EndTime => $EndTime, 2320 RecurrenceID => $StartTime, 2321 ); 2322 } 2323 } 2324 2325 return 1; 2326} 2327 2328sub _AppointmentRecurringDelete { 2329 my ( $Self, %Param ) = @_; 2330 2331 # check needed stuff 2332 for my $Needed (qw(ParentID)) { 2333 if ( !$Param{$Needed} ) { 2334 $Kernel::OM->Get('Kernel::System::Log')->Log( 2335 Priority => 'error', 2336 Message => "Need $Needed!", 2337 ); 2338 return; 2339 } 2340 } 2341 2342 # delete recurring appointments 2343 my $SQL = ' 2344 DELETE FROM calendar_appointment 2345 WHERE parent_id=? 2346 '; 2347 2348 # delete db record 2349 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 2350 SQL => $SQL, 2351 Bind => [ 2352 \$Param{ParentID}, 2353 ], 2354 ); 2355 2356 return 1; 2357} 2358 2359sub _AppointmentRecurringExclude { 2360 my ( $Self, %Param ) = @_; 2361 2362 # check needed stuff 2363 for my $Needed (qw(ParentID RecurrenceID)) { 2364 if ( !$Param{$Needed} ) { 2365 $Kernel::OM->Get('Kernel::System::Log')->Log( 2366 Priority => 'error', 2367 Message => "Need $Needed!", 2368 ); 2369 return; 2370 } 2371 } 2372 2373 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 2374 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2375 2376 # db query 2377 return if !$DBObject->Prepare( 2378 SQL => 'SELECT recur_exclude FROM calendar_appointment WHERE id=?', 2379 Bind => [ \$Param{ParentID} ], 2380 ); 2381 2382 # get existing exclusions 2383 my @RecurrenceExclude; 2384 while ( my @Row = $DBObject->FetchrowArray() ) { 2385 @RecurrenceExclude = split( ',', $Row[0] ) if $Row[0]; 2386 } 2387 push @RecurrenceExclude, $Param{RecurrenceID}; 2388 @RecurrenceExclude = sort @RecurrenceExclude; 2389 2390 # join into string 2391 my $RecurrenceExclude; 2392 if (@RecurrenceExclude) { 2393 $RecurrenceExclude = join( ',', @RecurrenceExclude ); 2394 } 2395 2396 # update db record 2397 return if !$DBObject->Do( 2398 SQL => 'UPDATE calendar_appointment SET recur_exclude=? WHERE id=?', 2399 Bind => [ \$RecurrenceExclude, \$Param{ParentID} ], 2400 ); 2401 2402 # delete cache 2403 $CacheObject->Delete( 2404 Type => $Self->{CacheType}, 2405 Key => $Param{ParentID}, 2406 ); 2407 2408 return 1; 2409} 2410 2411sub _AppointmentGetCalendarID { 2412 my ( $Self, %Param ) = @_; 2413 2414 # check needed stuff 2415 for my $Needed (qw(AppointmentID)) { 2416 if ( !defined $Param{$Needed} ) { 2417 $Kernel::OM->Get('Kernel::System::Log')->Log( 2418 Priority => 'error', 2419 Message => "Need $Needed!", 2420 ); 2421 return; 2422 } 2423 } 2424 2425 # sql query 2426 my $SQL = 'SELECT calendar_id FROM calendar_appointment WHERE id=?'; 2427 my @Bind = ( \$Param{AppointmentID} ); 2428 2429 # get database object 2430 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2431 2432 # start query 2433 return if !$DBObject->Prepare( 2434 SQL => $SQL, 2435 Bind => \@Bind, 2436 Limit => 1, 2437 ); 2438 2439 my $CalendarID; 2440 while ( my @Row = $DBObject->FetchrowArray() ) { 2441 $CalendarID = $Row[0]; 2442 } 2443 2444 return $CalendarID; 2445} 2446 2447sub _AppointmentGetRecurrenceID { 2448 my ( $Self, %Param ) = @_; 2449 2450 # check needed stuff 2451 for my $Needed (qw(AppointmentID)) { 2452 if ( !defined $Param{$Needed} ) { 2453 $Kernel::OM->Get('Kernel::System::Log')->Log( 2454 Priority => 'error', 2455 Message => "Need $Needed!", 2456 ); 2457 return; 2458 } 2459 } 2460 2461 # sql query 2462 my $SQL = 'SELECT recur_id FROM calendar_appointment WHERE id=?'; 2463 my @Bind = ( \$Param{AppointmentID} ); 2464 2465 # get database object 2466 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2467 2468 # start query 2469 return if !$DBObject->Prepare( 2470 SQL => $SQL, 2471 Bind => \@Bind, 2472 Limit => 1, 2473 ); 2474 2475 my $RecurrenceID; 2476 while ( my @Row = $DBObject->FetchrowArray() ) { 2477 $RecurrenceID = $Row[0]; 2478 } 2479 2480 return $RecurrenceID; 2481} 2482 2483sub _CalculateRecurrenceTime { 2484 my ( $Self, %Param ) = @_; 2485 2486 # check needed stuff 2487 for my $Needed (qw(Appointment Step OriginalTime CurrentTime)) { 2488 if ( !defined $Param{$Needed} ) { 2489 $Kernel::OM->Get('Kernel::System::Log')->Log( 2490 Priority => 'error', 2491 Message => "Need $Needed!", 2492 ); 2493 return; 2494 } 2495 } 2496 2497 my $OriginalTimeObject = $Param{OriginalTime}; 2498 2499 # We will modify this object throughout the function. 2500 my $CurrentTimeObject = $Param{CurrentTime}; 2501 2502 if ( $Param{Appointment}->{RecurrenceType} eq 'Daily' ) { 2503 2504 # Add one day. 2505 $CurrentTimeObject->Add( 2506 Days => 1, 2507 ); 2508 } 2509 elsif ( $Param{Appointment}->{RecurrenceType} eq 'Weekly' ) { 2510 2511 # Add 7 days. 2512 $CurrentTimeObject->Add( 2513 Days => 7, 2514 ); 2515 } 2516 elsif ( $Param{Appointment}->{RecurrenceType} eq 'Monthly' ) { 2517 my $TempTimeObject = $OriginalTimeObject->Clone(); 2518 2519 # Remember start day. 2520 my $StartDay = $TempTimeObject->Get()->{Day}; 2521 2522 # Add months based on current step. 2523 $TempTimeObject->Add( 2524 Months => $Param{Step}, 2525 ); 2526 2527 # Get end day. 2528 my $EndDay = $TempTimeObject->Get()->{Day}; 2529 2530 # Check if month doesn't have enough days (for example: January 31 + 1 month = March 1). 2531 if ( $StartDay != $EndDay ) { 2532 $TempTimeObject->Subtract( 2533 Days => $EndDay, 2534 ); 2535 } 2536 2537 $CurrentTimeObject = $TempTimeObject->Clone(); 2538 } 2539 elsif ( $Param{Appointment}->{RecurrenceType} eq 'Yearly' ) { 2540 my $TempTimeObject = $OriginalTimeObject->Clone(); 2541 2542 # Remember start day. 2543 my $StartDay = $TempTimeObject->Get()->{Day}; 2544 2545 # Add years based on current step. 2546 $TempTimeObject->Add( 2547 Years => $Param{Step}, 2548 ); 2549 2550 # Get end day. 2551 my $EndDay = $TempTimeObject->Get()->{Day}; 2552 2553 # Check if month doesn't have enough days (for example: January 31 + 1 month = March 1). 2554 if ( $StartDay != $EndDay ) { 2555 $TempTimeObject->Subtract( 2556 Days => $EndDay, 2557 ); 2558 } 2559 2560 $CurrentTimeObject = $TempTimeObject->Clone(); 2561 } 2562 elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomDaily' ) { 2563 2564 # Add number of days. 2565 $CurrentTimeObject->Add( 2566 Days => $Param{Appointment}->{RecurrenceInterval}, 2567 ); 2568 } 2569 elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomWeekly' ) { 2570 2571 # this block covers following use case: 2572 # each n-th Monday and Friday 2573 2574 my $Found; 2575 2576 # loop up to 7*n times (7 days in week * frequency) 2577 LOOP: 2578 for ( my $Counter = 0; $Counter < 7 * $Param{Appointment}->{RecurrenceInterval}; $Counter++ ) { 2579 2580 # Add one day. 2581 $CurrentTimeObject->Add( 2582 Days => 1, 2583 ); 2584 2585 my $CWDiff = $Self->_CWDiff( 2586 CurrentTime => $CurrentTimeObject, 2587 OriginalTime => $OriginalTimeObject, 2588 ); 2589 2590 next LOOP if $CWDiff % $Param{Appointment}->{RecurrenceInterval}; 2591 2592 my $WeekDay = $CurrentTimeObject->Get()->{DayOfWeek}; 2593 2594 # check if SystemTime match requirements 2595 if ( grep { $WeekDay == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} } ) { 2596 $Found = 1; 2597 last LOOP; 2598 } 2599 } 2600 2601 return if !$Found; 2602 } 2603 2604 elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomMonthly' ) { 2605 2606 # Occurs every 2nd month on 5th, 10th and 15th day 2607 my $Found; 2608 2609 # loop through each day (max one year), and check if day matches. 2610 DAY: 2611 for ( my $Counter = 0; $Counter < 31 * 366; $Counter++ ) { 2612 2613 # Add one day. 2614 $CurrentTimeObject->Add( 2615 Days => 1, 2616 ); 2617 2618 # Skip month if needed 2619 next DAY 2620 if ( $CurrentTimeObject->Get()->{Month} - $OriginalTimeObject->Get()->{Month} ) 2621 % $Param{Appointment}->{RecurrenceInterval}; 2622 2623 # next day if this day should be skipped 2624 next DAY 2625 if !grep { $CurrentTimeObject->Get()->{Day} == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} }; 2626 2627 $Found = 1; 2628 last DAY; 2629 } 2630 return if !$Found; 2631 } 2632 elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomYearly' ) { 2633 2634 # this block covers following use case: 2635 # Occurs each 3th year, January 18th and March 18th 2636 my $Found; 2637 2638 my $RecurrenceUntilObject; 2639 if ( $Param{Appointment}->{RecurrenceUntil} ) { 2640 $RecurrenceUntilObject = $Kernel::OM->Create( 2641 'Kernel::System::DateTime', 2642 ObjectParams => { 2643 String => $Param{Appointment}->{RecurrenceUntil}, 2644 }, 2645 ); 2646 } 2647 2648 my $NextDayObject = $CurrentTimeObject->Clone(); 2649 $NextDayObject->Add( 2650 Days => 1, 2651 ); 2652 2653 MONTH: 2654 for ( my $Counter = 1;; $Counter++ ) { 2655 my $TempTimeObject = $OriginalTimeObject->Clone(); 2656 2657 # remember start day 2658 my $StartDay = $TempTimeObject->Get()->{Day}; 2659 2660 $TempTimeObject->Add( 2661 Months => $Counter, 2662 ); 2663 2664 # get end day 2665 my $EndDay = $TempTimeObject->Get()->{Day}; 2666 2667 # check if month doesn't have enough days (for example: january 31 + 1 month = march 01) 2668 if ( $StartDay != $EndDay ) { 2669 $TempTimeObject->Subtract( 2670 Days => $EndDay, 2671 ); 2672 } 2673 2674 $CurrentTimeObject = $TempTimeObject->Clone(); 2675 2676 # skip this time, since it was already checked 2677 next MONTH if $CurrentTimeObject < $NextDayObject; 2678 2679 # check loop conditions (according to Until / ) 2680 if ($RecurrenceUntilObject) { 2681 last MONTH if $CurrentTimeObject > $RecurrenceUntilObject; 2682 } 2683 else { 2684 last MONTH 2685 if $Counter 2686 > 12 * $Param{Appointment}->{RecurrenceInterval} * $Param{Appointment}->{RecurrenceCount}; 2687 } 2688 2689 # check if year is OK 2690 next MONTH 2691 if ( $CurrentTimeObject->Get()->{Year} - $OriginalTimeObject->Get()->{Year} ) 2692 % $Param{Appointment}->{RecurrenceInterval}; 2693 2694 # next month if this month should be skipped 2695 next MONTH 2696 if !grep { $CurrentTimeObject->Get()->{Month} == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} }; 2697 2698 $Found = 1; 2699 last MONTH; 2700 } 2701 return if !$Found; 2702 } 2703 else { 2704 return; 2705 } 2706 2707 return $CurrentTimeObject; 2708} 2709 2710=head2 _TimeCheck() 2711 2712Check if Time and OriginalTime have same hour, minute and second value, and return timestamp with 2713values (hour, minute and second) as in Time. 2714 2715 my $Result = $Self->_TimeCheck( 2716 OriginalTime => '2016-01-01 00:01:00', # (required) 2717 Time => '2016-02-01 00:02:00', # (required) 2718 ); 2719 2720Returns: 2721 $Result = '2016-02-01 00:01:00'; 2722 2723=cut 2724 2725sub _TimeCheck { 2726 my ( $Self, %Param ) = @_; 2727 2728 for my $Needed (qw(OriginalTime Time)) { 2729 if ( !defined $Param{$Needed} ) { 2730 $Kernel::OM->Get('Kernel::System::Log')->Log( 2731 Priority => 'error', 2732 Message => "Need $Needed!", 2733 ); 2734 return; 2735 } 2736 } 2737 2738 my $Result = ''; 2739 2740 $Param{OriginalTime} =~ /(.*?)\s(.*?)$/; 2741 my $OriginalDate = $1; 2742 my $OriginalTime = $2; 2743 2744 $Param{Time} =~ /(.*?)\s(.*?)$/; 2745 my $Date = $1; 2746 2747 $Result = "$Date $OriginalTime"; 2748 return $Result; 2749} 2750 2751=head2 _CWDiff() 2752 2753Returns how many calendar weeks has passed between two unix times. 2754 2755 my $CWDiff = $Self->_CWDiff( 2756 CurrentTime => $CurrentTimeObject, (required) Date time object with current time 2757 OriginalTime => $OriginalTimeObject, (required) Date time object with original time 2758 ); 2759 2760returns: 2761 $CWDiff = 5; 2762 2763=cut 2764 2765sub _CWDiff { 2766 my ( $Self, %Param ) = @_; 2767 2768 for my $Needed (qw(CurrentTime OriginalTime)) { 2769 if ( !defined $Param{$Needed} ) { 2770 $Kernel::OM->Get('Kernel::System::Log')->Log( 2771 Priority => 'error', 2772 Message => "Need $Needed!", 2773 ); 2774 return; 2775 } 2776 } 2777 2778 my $OriginalTimeObject = $Param{OriginalTime}; 2779 my $CurrentTimeObject = $Param{CurrentTime}; 2780 2781 my $StartYear = $OriginalTimeObject->Get()->{Year}; 2782 my $EndYear = $CurrentTimeObject->Get()->{Year}; 2783 2784 my $Result = $CurrentTimeObject->{CPANDateTimeObject}->week_number() 2785 - $OriginalTimeObject->{CPANDateTimeObject}->week_number(); 2786 2787 # If date is end of the year and date CW starts with 1, we need to include additional year. 2788 if ( $Result < 0 && $CurrentTimeObject->Get()->{Day} == 31 && $CurrentTimeObject->Get()->{Month} == 12 ) { 2789 $EndYear++; 2790 } 2791 2792 for my $Year ( $StartYear .. $EndYear - 1 ) { 2793 my $CW = 0; 2794 my $Day = 31; 2795 2796 while ( $CW < 50 ) { 2797 2798 # To get how many CW's are in this year, we set temporary date to 31-dec. 2799 my $DateTimeObject = $Kernel::OM->Create( 2800 'Kernel::System::DateTime', 2801 ObjectParams => { 2802 String => "$Year-12-$Day 23:59:00", 2803 }, 2804 ); 2805 2806 $CW = $DateTimeObject->{CPANDateTimeObject}->week_number(); 2807 $Day--; 2808 } 2809 2810 $Result += $CW; 2811 } 2812 2813 return $Result; 2814} 2815 28161; 2817 2818=end Internal: 2819 2820=head1 TERMS AND CONDITIONS 2821 2822This software is part of the OTRS project (L<https://otrs.org/>). 2823 2824This software comes with ABSOLUTELY NO WARRANTY. For details, see 2825the enclosed file COPYING for license information (GPL). If you 2826did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 2827 2828=cut 2829