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::Modules::AgentAppointmentEdit; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15use Kernel::Language qw(Translatable); 16 17our $ObjectManagerDisabled = 1; 18 19sub new { 20 my ( $Type, %Param ) = @_; 21 22 my $Self = {%Param}; 23 bless( $Self, $Type ); 24 25 $Self->{EmptyString} = '-'; 26 27 return $Self; 28} 29 30sub Run { 31 my ( $Self, %Param ) = @_; 32 33 my $Output; 34 35 # get param object 36 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 37 38 # get names of all parameters 39 my @ParamNames = $ParamObject->GetParamNames(); 40 41 # get params 42 my %GetParam; 43 PARAMNAME: 44 for my $Key (@ParamNames) { 45 46 # skip the Action parameter, it's giving BuildDateSelection problems for some reason 47 next PARAMNAME if $Key eq 'Action'; 48 49 $GetParam{$Key} = $ParamObject->GetParam( Param => $Key ); 50 } 51 52 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 53 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 54 my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar'); 55 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 56 my $PluginObject = $Kernel::OM->Get('Kernel::System::Calendar::Plugin'); 57 58 my $JSON = $LayoutObject->JSONEncode( Data => [] ); 59 60 my %PermissionLevel = ( 61 'ro' => 1, 62 'move_into' => 2, 63 'create' => 3, 64 'note' => 4, 65 'owner' => 5, 66 'priority' => 6, 67 'rw' => 7, 68 ); 69 70 my $Permissions = 'rw'; 71 72 # challenge token check 73 $LayoutObject->ChallengeTokenCheck(); 74 75 # ------------------------------------------------------------ # 76 # edit mask 77 # ------------------------------------------------------------ # 78 if ( $Self->{Subaction} eq 'EditMask' ) { 79 80 # get all user's valid calendars 81 my $ValidID = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup( 82 Valid => 'valid', 83 ); 84 my @Calendars = $CalendarObject->CalendarList( 85 UserID => $Self->{UserID}, 86 ValidID => $ValidID, 87 ); 88 89 # transform data for select box 90 my @CalendarData = map { 91 { 92 Key => $_->{CalendarID}, 93 Value => $_->{CalendarName}, 94 } 95 } @Calendars; 96 97 # transform data for ID lookup 98 my %CalendarLookup = map { 99 $_->{CalendarID} => $_->{CalendarName} 100 } @Calendars; 101 102 for my $Calendar (@CalendarData) { 103 104 # check permissions 105 my $CalendarPermission = $CalendarObject->CalendarPermissionGet( 106 CalendarID => $Calendar->{Key}, 107 UserID => $Self->{UserID}, 108 ); 109 110 if ( $PermissionLevel{$CalendarPermission} < 3 ) { 111 112 # permissions < create 113 $Calendar->{Disabled} = 1; 114 } 115 } 116 117 # define year boundaries 118 my ( %YearPeriodPast, %YearPeriodFuture ); 119 for my $Field (qw (Start End RecurrenceUntil)) { 120 $YearPeriodPast{$Field} = $YearPeriodFuture{$Field} = 5; 121 } 122 123 # do not use date selection time zone calculation by default 124 my $OverrideTimeZone = 1; 125 126 my %Appointment; 127 if ( $GetParam{AppointmentID} ) { 128 %Appointment = $AppointmentObject->AppointmentGet( 129 AppointmentID => $GetParam{AppointmentID}, 130 ); 131 132 # non-existent appointment 133 if ( !$Appointment{AppointmentID} ) { 134 my $Output = $LayoutObject->Error( 135 Message => Translatable('Appointment not found!'), 136 ); 137 return $LayoutObject->Attachment( 138 NoCache => 1, 139 ContentType => 'text/html', 140 Charset => $LayoutObject->{UserCharset}, 141 Content => $Output, 142 Type => 'inline', 143 ); 144 } 145 146 # use time zone calculation if editing existing appointments 147 # but only if not dealing with an all day appointment 148 else { 149 $OverrideTimeZone = $Appointment{AllDay} || 0; 150 } 151 152 # check permissions 153 $Permissions = $CalendarObject->CalendarPermissionGet( 154 CalendarID => $Appointment{CalendarID}, 155 UserID => $Self->{UserID}, 156 ); 157 158 # Get start time components. 159 my $StartTimeObject = $Kernel::OM->Create( 160 'Kernel::System::DateTime', 161 ObjectParams => { 162 String => $Appointment{StartTime}, 163 }, 164 ); 165 my $StartTimeSettings = $StartTimeObject->Get(); 166 my $StartTimeComponents; 167 for my $Key ( sort keys %{$StartTimeSettings} ) { 168 $StartTimeComponents->{"Start$Key"} = $StartTimeSettings->{$Key}; 169 } 170 171 # Get end time components. 172 my $EndTimeObject = $Kernel::OM->Create( 173 'Kernel::System::DateTime', 174 ObjectParams => { 175 String => $Appointment{EndTime}, 176 }, 177 ); 178 179 # End times for all day appointments are inclusive, subtract whole day. 180 if ( $Appointment{AllDay} ) { 181 $EndTimeObject->Subtract( 182 Days => 1, 183 ); 184 if ( $EndTimeObject < $StartTimeObject ) { 185 $EndTimeObject = $StartTimeObject->Clone(); 186 } 187 } 188 189 my $EndTimeSettings = $EndTimeObject->Get(); 190 my $EndTimeComponents; 191 for my $Key ( sort keys %{$EndTimeSettings} ) { 192 $EndTimeComponents->{"End$Key"} = $EndTimeSettings->{$Key}; 193 } 194 195 %Appointment = ( %Appointment, %{$StartTimeComponents}, %{$EndTimeComponents} ); 196 197 # Get recurrence until components. 198 if ( $Appointment{RecurrenceUntil} ) { 199 my $RecurrenceUntilTimeObject = $Kernel::OM->Create( 200 'Kernel::System::DateTime', 201 ObjectParams => { 202 String => $Appointment{RecurrenceUntil}, 203 }, 204 ); 205 my $RecurrenceUntilSettings = $RecurrenceUntilTimeObject->Get(); 206 my $RecurrenceUntilComponents; 207 for my $Key ( sort keys %{$EndTimeSettings} ) { 208 $RecurrenceUntilComponents->{"RecurrenceUntil$Key"} = $RecurrenceUntilSettings->{$Key}; 209 } 210 211 %Appointment = ( %Appointment, %{$RecurrenceUntilComponents} ); 212 } 213 214 # Recalculate year boundaries for build selection method. 215 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 216 my $DateTimeSettings = $DateTimeObject->Get(); 217 218 for my $Field (qw(Start End RecurrenceUntil)) { 219 if ( $Appointment{"${Field}Year"} ) { 220 my $Diff = $Appointment{"${Field}Year"} - $DateTimeSettings->{Year}; 221 if ( $Diff > 0 && abs $Diff > $YearPeriodFuture{$Field} ) { 222 $YearPeriodFuture{$Field} = abs $Diff; 223 } 224 elsif ( $Diff < 0 && abs $Diff > $YearPeriodPast{$Field} ) { 225 $YearPeriodPast{$Field} = abs $Diff; 226 } 227 } 228 } 229 230 if ( $Appointment{Recurring} ) { 231 my $RecurrenceType = $GetParam{RecurrenceType} || $Appointment{RecurrenceType}; 232 233 if ( $RecurrenceType eq 'CustomWeekly' ) { 234 235 my $DayOffset = $Self->_DayOffsetGet( 236 Time => $Appointment{StartTime}, 237 ); 238 239 if ( defined $GetParam{Days} ) { 240 241 # check parameters 242 $Appointment{Days} = $GetParam{Days}; 243 } 244 else { 245 my @Days = @{ $Appointment{RecurrenceFrequency} }; 246 247 # display selected days according to user timezone 248 if ($DayOffset) { 249 for my $Day (@Days) { 250 $Day += $DayOffset; 251 252 if ( $Day == 8 ) { 253 $Day = 1; 254 } 255 } 256 } 257 258 $Appointment{Days} = join( ",", @Days ); 259 } 260 } 261 elsif ( $RecurrenceType eq 'CustomMonthly' ) { 262 263 my $DayOffset = $Self->_DayOffsetGet( 264 Time => $Appointment{StartTime}, 265 ); 266 267 if ( defined $GetParam{MonthDays} ) { 268 269 # check parameters 270 $Appointment{MonthDays} = $GetParam{MonthDays}; 271 } 272 else { 273 my @MonthDays = @{ $Appointment{RecurrenceFrequency} }; 274 275 # display selected days according to user timezone 276 if ($DayOffset) { 277 for my $MonthDay (@MonthDays) { 278 $MonthDay += $DayOffset; 279 if ( $DateTimeSettings->{Day} == 32 ) { 280 $MonthDay = 1; 281 } 282 } 283 } 284 $Appointment{MonthDays} = join( ",", @MonthDays ); 285 } 286 } 287 elsif ( $RecurrenceType eq 'CustomYearly' ) { 288 289 my $DayOffset = $Self->_DayOffsetGet( 290 Time => $Appointment{StartTime}, 291 ); 292 293 if ( defined $GetParam{Months} ) { 294 295 # check parameters 296 $Appointment{Months} = $GetParam{Months}; 297 } 298 else { 299 my @Months = @{ $Appointment{RecurrenceFrequency} }; 300 $Appointment{Months} = join( ",", @Months ); 301 } 302 } 303 } 304 305 # Check if dealing with ticket appointment. 306 if ( $Appointment{TicketAppointmentRuleID} ) { 307 $GetParam{TicketID} = $CalendarObject->TicketAppointmentTicketID( 308 AppointmentID => $Appointment{AppointmentID}, 309 ); 310 311 my $Rule = $CalendarObject->TicketAppointmentRuleGet( 312 CalendarID => $Appointment{CalendarID}, 313 RuleID => $Appointment{TicketAppointmentRuleID}, 314 ); 315 316 # Get date types from the ticket appointment rule. 317 if ( IsHashRefWithData($Rule) ) { 318 for my $Type (qw(StartDate EndDate)) { 319 if ( 320 $Rule->{$Type} eq 'FirstResponseTime' 321 || $Rule->{$Type} eq 'UpdateTime' 322 || $Rule->{$Type} eq 'SolutionTime' 323 || $Rule->{$Type} eq 'PendingTime' 324 ) 325 { 326 $GetParam{ReadOnlyStart} = 1 if $Type eq 'StartDate'; 327 $GetParam{ReadOnlyDuration} = 1 if $Type eq 'EndDate'; 328 } 329 elsif ( $Rule->{$Type} =~ /^Plus_[0-9]+$/ ) { 330 $GetParam{ReadOnlyDuration} = 1; 331 } 332 } 333 } 334 } 335 } 336 337 # get selected timestamp 338 my $SelectedTimestamp = sprintf( 339 "%04d-%02d-%02d 00:00:00", 340 $Appointment{StartYear} // $GetParam{StartYear}, 341 $Appointment{StartMonth} // $GetParam{StartMonth}, 342 $Appointment{StartDay} // $GetParam{StartDay} 343 ); 344 345 # Get current date components. 346 my $SelectedSystemTimeObject = $Kernel::OM->Create( 347 'Kernel::System::DateTime', 348 ObjectParams => { 349 String => $SelectedTimestamp, 350 }, 351 ); 352 my $SelectedSystemTimeSettings = $SelectedSystemTimeObject->Get(); 353 354 # Set current date components if not defined. 355 $Appointment{Days} //= $SelectedSystemTimeSettings->{DayOfWeek}; 356 $Appointment{MonthDays} //= $SelectedSystemTimeSettings->{Day}; 357 $Appointment{Months} //= $SelectedSystemTimeSettings->{Month}; 358 359 # calendar ID selection 360 my $CalendarID = $Appointment{CalendarID} // $GetParam{CalendarID}; 361 362 # calendar name 363 if ($CalendarID) { 364 $Param{CalendarName} = $CalendarLookup{$CalendarID}; 365 } 366 367 # calendar selection 368 $Param{CalendarIDStrg} = $LayoutObject->BuildSelection( 369 Data => \@CalendarData, 370 SelectedID => $CalendarID, 371 Name => 'CalendarID', 372 Multiple => 0, 373 Class => 'Modernize Validate_Required', 374 PossibleNone => 1, 375 ); 376 377 # all day 378 if ( 379 $GetParam{AllDay} || 380 ( $GetParam{AppointmentID} && $Appointment{AllDay} ) 381 ) 382 { 383 $Param{AllDayString} = Translatable('Yes'); 384 $Param{AllDayChecked} = 'checked="checked"'; 385 386 # start date 387 $Param{StartDate} = sprintf( 388 "%04d-%02d-%02d", 389 $Appointment{StartYear} // $GetParam{StartYear}, 390 $Appointment{StartMonth} // $GetParam{StartMonth}, 391 $Appointment{StartDay} // $GetParam{StartDay}, 392 ); 393 394 # end date 395 $Param{EndDate} = sprintf( 396 "%04d-%02d-%02d", 397 $Appointment{EndYear} // $GetParam{EndYear}, 398 $Appointment{EndMonth} // $GetParam{EndMonth}, 399 $Appointment{EndDay} // $GetParam{EndDay}, 400 ); 401 } 402 else { 403 $Param{AllDayString} = Translatable('No'); 404 $Param{AllDayChecked} = ''; 405 406 # start date 407 $Param{StartDate} = sprintf( 408 "%04d-%02d-%02d %02d:%02d:00", 409 $Appointment{StartYear} // $GetParam{StartYear}, 410 $Appointment{StartMonth} // $GetParam{StartMonth}, 411 $Appointment{StartDay} // $GetParam{StartDay}, 412 $Appointment{StartHour} // $GetParam{StartHour}, 413 $Appointment{StartMinute} // $GetParam{StartMinute}, 414 ); 415 416 # end date 417 $Param{EndDate} = sprintf( 418 "%04d-%02d-%02d %02d:%02d:00", 419 $Appointment{EndYear} // $GetParam{EndYear}, 420 $Appointment{EndMonth} // $GetParam{EndMonth}, 421 $Appointment{EndDay} // $GetParam{EndDay}, 422 $Appointment{EndHour} // $GetParam{EndHour}, 423 $Appointment{EndMinute} // $GetParam{EndMinute}, 424 ); 425 } 426 427 # start date string 428 $Param{StartDateString} = $LayoutObject->BuildDateSelection( 429 %GetParam, 430 %Appointment, 431 Prefix => 'Start', 432 StartHour => $Appointment{StartHour} // $GetParam{StartHour}, 433 StartMinute => $Appointment{StartMinute} // $GetParam{StartMinute}, 434 Format => 'DateInputFormatLong', 435 ValidateDateBeforePrefix => 'End', 436 Validate => $Appointment{TicketAppointmentRuleID} && $GetParam{ReadOnlyStart} ? 0 : 1, 437 YearPeriodPast => $YearPeriodPast{Start}, 438 YearPeriodFuture => $YearPeriodFuture{Start}, 439 OverrideTimeZone => $OverrideTimeZone, 440 ); 441 442 # end date string 443 $Param{EndDateString} = $LayoutObject->BuildDateSelection( 444 %GetParam, 445 %Appointment, 446 Prefix => 'End', 447 EndHour => $Appointment{EndHour} // $GetParam{EndHour}, 448 EndMinute => $Appointment{EndMinute} // $GetParam{EndMinute}, 449 Format => 'DateInputFormatLong', 450 ValidateDateAfterPrefix => 'Start', 451 Validate => $Appointment{TicketAppointmentRuleID} && $GetParam{ReadOnlyDuration} ? 0 : 1, 452 YearPeriodPast => $YearPeriodPast{End}, 453 YearPeriodFuture => $YearPeriodFuture{End}, 454 OverrideTimeZone => $OverrideTimeZone, 455 ); 456 457 # get main object 458 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 459 460 # check if team object is registered 461 if ( $MainObject->Require( 'Kernel::System::Calendar::Team', Silent => 1 ) ) { 462 463 my $TeamIDs = $Appointment{TeamID}; 464 if ( !$TeamIDs ) { 465 my @TeamIDs = $ParamObject->GetArray( Param => 'TeamID[]' ); 466 $TeamIDs = \@TeamIDs; 467 } 468 469 my $ResourceIDs = $Appointment{ResourceID}; 470 if ( !$ResourceIDs ) { 471 my @ResourceIDs = $ParamObject->GetArray( Param => 'ResourceID[]' ); 472 $ResourceIDs = \@ResourceIDs; 473 } 474 475 # get needed objects 476 my $TeamObject = $Kernel::OM->Get('Kernel::System::Calendar::Team'); 477 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 478 479 # get allowed team list for current user 480 my %TeamList = $TeamObject->AllowedTeamList( 481 PreventEmpty => 1, 482 UserID => $Self->{UserID}, 483 ); 484 485 # team names 486 my @TeamNames; 487 for my $TeamID ( @{$TeamIDs} ) { 488 push @TeamNames, $TeamList{$TeamID} if $TeamList{$TeamID}; 489 } 490 if ( scalar @TeamNames ) { 491 $Param{TeamNames} = join( '<br>', @TeamNames ); 492 } 493 else { 494 $Param{TeamNames} = $Self->{EmptyString}; 495 } 496 497 # team list string 498 $Param{TeamIDStrg} = $LayoutObject->BuildSelection( 499 Data => \%TeamList, 500 SelectedID => $TeamIDs, 501 Name => 'TeamID', 502 Multiple => 1, 503 Class => 'Modernize', 504 PossibleNone => 1, 505 ); 506 507 # iterate through selected teams 508 my %TeamUserListAll; 509 TEAMID: 510 for my $TeamID ( @{$TeamIDs} ) { 511 next TEAMID if !$TeamID; 512 513 # get list of team members 514 my %TeamUserList = $TeamObject->TeamUserList( 515 TeamID => $TeamID, 516 UserID => $Self->{UserID}, 517 ); 518 519 # get user data 520 for my $UserID ( sort keys %TeamUserList ) { 521 my %User = $UserObject->GetUserData( 522 UserID => $UserID, 523 ); 524 $TeamUserList{$UserID} = $User{UserFullname}; 525 } 526 527 %TeamUserListAll = ( %TeamUserListAll, %TeamUserList ); 528 } 529 530 # resource names 531 my @ResourceNames; 532 for my $ResourceID ( @{$ResourceIDs} ) { 533 push @ResourceNames, $TeamUserListAll{$ResourceID} if $TeamUserListAll{$ResourceID}; 534 } 535 if ( scalar @ResourceNames ) { 536 $Param{ResourceNames} = join( '<br>', @ResourceNames ); 537 } 538 else { 539 $Param{ResourceNames} = $Self->{EmptyString}; 540 } 541 542 # team user list string 543 $Param{ResourceIDStrg} = $LayoutObject->BuildSelection( 544 Data => \%TeamUserListAll, 545 SelectedID => $ResourceIDs, 546 Name => 'ResourceID', 547 Multiple => 1, 548 Class => 'Modernize', 549 PossibleNone => 1, 550 ); 551 } 552 553 my $SelectedRecurrenceType = 0; 554 my $SelectedRecurrenceCustomType = 'CustomDaily'; # default 555 556 if ( $Appointment{Recurring} ) { 557 558 # from appointment 559 $SelectedRecurrenceType = $GetParam{RecurrenceType} || $Appointment{RecurrenceType}; 560 if ( $SelectedRecurrenceType =~ /Custom/ ) { 561 $SelectedRecurrenceCustomType = $SelectedRecurrenceType; 562 $SelectedRecurrenceType = 'Custom'; 563 } 564 } 565 566 # recurrence type 567 my @RecurrenceTypes = ( 568 { 569 Key => '0', 570 Value => Translatable('Never'), 571 }, 572 { 573 Key => 'Daily', 574 Value => Translatable('Every Day'), 575 }, 576 { 577 Key => 'Weekly', 578 Value => Translatable('Every Week'), 579 }, 580 { 581 Key => 'Monthly', 582 Value => Translatable('Every Month'), 583 }, 584 { 585 Key => 'Yearly', 586 Value => Translatable('Every Year'), 587 }, 588 { 589 Key => 'Custom', 590 Value => Translatable('Custom'), 591 }, 592 ); 593 my %RecurrenceTypeLookup = map { 594 $_->{Key} => $_->{Value} 595 } @RecurrenceTypes; 596 $Param{RecurrenceValue} = $LayoutObject->{LanguageObject}->Translate( 597 $RecurrenceTypeLookup{$SelectedRecurrenceType}, 598 ); 599 600 # recurrence type selection 601 $Param{RecurrenceTypeString} = $LayoutObject->BuildSelection( 602 Data => \@RecurrenceTypes, 603 SelectedID => $SelectedRecurrenceType, 604 Name => 'RecurrenceType', 605 Multiple => 0, 606 Class => 'Modernize', 607 PossibleNone => 0, 608 ); 609 610 # recurrence custom type 611 my @RecurrenceCustomTypes = ( 612 { 613 Key => 'CustomDaily', 614 Value => Translatable('Daily'), 615 }, 616 { 617 Key => 'CustomWeekly', 618 Value => Translatable('Weekly'), 619 }, 620 { 621 Key => 'CustomMonthly', 622 Value => Translatable('Monthly'), 623 }, 624 { 625 Key => 'CustomYearly', 626 Value => Translatable('Yearly'), 627 }, 628 ); 629 my %RecurrenceCustomTypeLookup = map { 630 $_->{Key} => $_->{Value} 631 } @RecurrenceCustomTypes; 632 my $RecurrenceCustomType = $RecurrenceCustomTypeLookup{$SelectedRecurrenceCustomType}; 633 $Param{RecurrenceValue} .= ', ' . $LayoutObject->{LanguageObject}->Translate( 634 lc $RecurrenceCustomType, 635 ) if $RecurrenceCustomType && $SelectedRecurrenceType eq 'Custom'; 636 637 # recurrence custom type selection 638 $Param{RecurrenceCustomTypeString} = $LayoutObject->BuildSelection( 639 Data => \@RecurrenceCustomTypes, 640 SelectedID => $SelectedRecurrenceCustomType, 641 Name => 'RecurrenceCustomType', 642 Class => 'Modernize', 643 ); 644 645 # recurrence interval 646 my $SelectedInterval = $GetParam{RecurrenceInterval} || $Appointment{RecurrenceInterval} || 1; 647 if ( $Appointment{RecurrenceInterval} ) { 648 my %RecurrenceIntervalLookup = ( 649 'CustomDaily' => $LayoutObject->{LanguageObject}->Translate( 650 'day(s)', 651 ), 652 'CustomWeekly' => $LayoutObject->{LanguageObject}->Translate( 653 'week(s)', 654 ), 655 'CustomMonthly' => $LayoutObject->{LanguageObject}->Translate( 656 'month(s)', 657 ), 658 'CustomYearly' => $LayoutObject->{LanguageObject}->Translate( 659 'year(s)', 660 ), 661 ); 662 663 if ( $RecurrenceCustomType && $SelectedRecurrenceType eq 'Custom' ) { 664 $Param{RecurrenceValue} .= ', ' 665 . $LayoutObject->{LanguageObject}->Translate('every') 666 . ' ' . $Appointment{RecurrenceInterval} . ' ' 667 . $RecurrenceIntervalLookup{$SelectedRecurrenceCustomType}; 668 } 669 } 670 671 # add interval selection (1-31) 672 my @RecurrenceCustomInterval; 673 for ( my $DayNumber = 1; $DayNumber < 32; $DayNumber++ ) { 674 push @RecurrenceCustomInterval, { 675 Key => $DayNumber, 676 Value => $DayNumber, 677 }; 678 } 679 $Param{RecurrenceIntervalString} = $LayoutObject->BuildSelection( 680 Data => \@RecurrenceCustomInterval, 681 SelectedID => $SelectedInterval, 682 Name => 'RecurrenceInterval', 683 ); 684 685 # recurrence limit 686 my $RecurrenceLimit = 1; 687 if ( $Appointment{RecurrenceCount} ) { 688 $RecurrenceLimit = 2; 689 690 if ($SelectedRecurrenceType) { 691 $Param{RecurrenceValue} .= ', ' . $LayoutObject->{LanguageObject}->Translate( 692 'for %s time(s)', $Appointment{RecurrenceCount}, 693 ); 694 } 695 } 696 697 # recurrence limit string 698 $Param{RecurrenceLimitString} = $LayoutObject->BuildSelection( 699 Data => [ 700 { 701 Key => 1, 702 Value => Translatable('until ...'), 703 }, 704 { 705 Key => 2, 706 Value => Translatable('for ... time(s)'), 707 }, 708 ], 709 SelectedID => $RecurrenceLimit, 710 Name => 'RecurrenceLimit', 711 Multiple => 0, 712 Class => 'Modernize', 713 PossibleNone => 0, 714 ); 715 716 my $RecurrenceUntilDiffTime = 0; 717 if ( !$Appointment{RecurrenceUntil} ) { 718 719 # Get current and start time for difference. 720 my $SystemTime = $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch(); 721 my $StartTimeObject = $Kernel::OM->Create( 722 'Kernel::System::DateTime', 723 ObjectParams => { 724 Year => $Appointment{StartYear} // $GetParam{StartYear}, 725 Month => $Appointment{StartMonth} // $GetParam{StartMonth}, 726 Day => $Appointment{StartDay} // $GetParam{StartDay}, 727 Hour => $Appointment{StartHour} // $GetParam{StartHour}, 728 Minute => $Appointment{StartMinute} // $GetParam{StartMinute}, 729 Second => 0, 730 }, 731 ); 732 my $StartTime = $StartTimeObject->ToEpoch(); 733 734 $RecurrenceUntilDiffTime = $StartTime - $SystemTime + 60 * 60 * 24 * 3; # start +3 days 735 } 736 else { 737 $Param{RecurrenceUntil} = sprintf( 738 "%04d-%02d-%02d", 739 $Appointment{RecurrenceUntilYear}, 740 $Appointment{RecurrenceUntilMonth}, 741 $Appointment{RecurrenceUntilDay}, 742 ); 743 744 if ($SelectedRecurrenceType) { 745 $Param{RecurrenceValue} .= ', ' . $LayoutObject->{LanguageObject}->Translate( 746 'until %s', $Param{RecurrenceUntil}, 747 ); 748 } 749 } 750 751 # recurrence until date string 752 $Param{RecurrenceUntilString} = $LayoutObject->BuildDateSelection( 753 %Appointment, 754 %GetParam, 755 Prefix => 'RecurrenceUntil', 756 Format => 'DateInputFormat', 757 DiffTime => $RecurrenceUntilDiffTime, 758 ValidateDateAfterPrefix => 'Start', 759 Validate => 1, 760 YearPeriodPast => $YearPeriodPast{RecurrenceUntil}, 761 YearPeriodFuture => $YearPeriodFuture{RecurrenceUntil}, 762 OverrideTimeZone => $OverrideTimeZone, 763 ); 764 765 # notification template 766 my @NotificationTemplates = ( 767 { 768 Key => '0', 769 Value => $LayoutObject->{LanguageObject}->Translate('No notification'), 770 }, 771 { 772 Key => 'Start', 773 Value => $LayoutObject->{LanguageObject}->Translate( '%s minute(s) before', 0 ), 774 }, 775 { 776 Key => '300', 777 Value => $LayoutObject->{LanguageObject}->Translate( '%s minute(s) before', 5 ), 778 }, 779 { 780 Key => '900', 781 Value => $LayoutObject->{LanguageObject}->Translate( '%s minute(s) before', 15 ), 782 }, 783 { 784 Key => '1800', 785 Value => $LayoutObject->{LanguageObject}->Translate( '%s minute(s) before', 30 ), 786 }, 787 { 788 Key => '3600', 789 Value => $LayoutObject->{LanguageObject}->Translate( '%s hour(s) before', 1 ), 790 }, 791 { 792 Key => '7200', 793 Value => $LayoutObject->{LanguageObject}->Translate( '%s hour(s) before', 2 ), 794 }, 795 { 796 Key => '43200', 797 Value => $LayoutObject->{LanguageObject}->Translate( '%s hour(s) before', 12 ), 798 }, 799 { 800 Key => '86400', 801 Value => $LayoutObject->{LanguageObject}->Translate( '%s day(s) before', 1 ), 802 }, 803 { 804 Key => '172800', 805 Value => $LayoutObject->{LanguageObject}->Translate( '%s day(s) before', 2 ), 806 }, 807 { 808 Key => '604800', 809 Value => $LayoutObject->{LanguageObject}->Translate( '%s week before', 1 ), 810 }, 811 { 812 Key => 'Custom', 813 Value => $LayoutObject->{LanguageObject}->Translate('Custom'), 814 }, 815 ); 816 my %NotificationTemplateLookup = map { 817 $_->{Key} => $_->{Value} 818 } @NotificationTemplates; 819 my $SelectedNotificationTemplate = $Appointment{NotificationTemplate} || '0'; 820 $Param{NotificationValue} = $NotificationTemplateLookup{$SelectedNotificationTemplate}; 821 822 # notification selection 823 $Param{NotificationStrg} = $LayoutObject->BuildSelection( 824 Data => \@NotificationTemplates, 825 SelectedID => $SelectedNotificationTemplate, 826 Name => 'NotificationTemplate', 827 Multiple => 0, 828 Class => 'Modernize', 829 PossibleNone => 0, 830 ); 831 832 # notification custom units 833 my @NotificationCustomUnits = ( 834 { 835 Key => 'minutes', 836 Value => $LayoutObject->{LanguageObject}->Translate('minute(s)'), 837 }, 838 { 839 Key => 'hours', 840 Value => $LayoutObject->{LanguageObject}->Translate('hour(s)'), 841 }, 842 { 843 Key => 'days', 844 Value => $LayoutObject->{LanguageObject}->Translate('day(s)'), 845 }, 846 ); 847 my %NotificationCustomUnitLookup = map { 848 $_->{Key} => $_->{Value} 849 } @NotificationCustomUnits; 850 my $SelectedNotificationCustomUnit = $Appointment{NotificationCustomRelativeUnit} || 'minutes'; 851 852 # notification custom units selection 853 $Param{NotificationCustomUnitsStrg} = $LayoutObject->BuildSelection( 854 Data => \@NotificationCustomUnits, 855 SelectedID => $SelectedNotificationCustomUnit, 856 Name => 'NotificationCustomRelativeUnit', 857 Multiple => 0, 858 Class => 'Modernize', 859 PossibleNone => 0, 860 ); 861 862 # notification custom units point of time 863 my @NotificationCustomUnitsPointOfTime = ( 864 { 865 Key => 'beforestart', 866 Value => $LayoutObject->{LanguageObject}->Translate('before the appointment starts'), 867 }, 868 { 869 Key => 'afterstart', 870 Value => $LayoutObject->{LanguageObject}->Translate('after the appointment has been started'), 871 }, 872 { 873 Key => 'beforeend', 874 Value => $LayoutObject->{LanguageObject}->Translate('before the appointment ends'), 875 }, 876 { 877 Key => 'afterend', 878 Value => $LayoutObject->{LanguageObject}->Translate('after the appointment has been ended'), 879 }, 880 ); 881 my %NotificationCustomUnitPointOfTimeLookup = map { 882 $_->{Key} => $_->{Value} 883 } @NotificationCustomUnitsPointOfTime; 884 my $SelectedNotificationCustomUnitPointOfTime = $Appointment{NotificationCustomRelativePointOfTime} 885 || 'beforestart'; 886 887 # notification custom units point of time selection 888 $Param{NotificationCustomUnitsPointOfTimeStrg} = $LayoutObject->BuildSelection( 889 Data => \@NotificationCustomUnitsPointOfTime, 890 SelectedID => $SelectedNotificationCustomUnitPointOfTime, 891 Name => 'NotificationCustomRelativePointOfTime', 892 Multiple => 0, 893 Class => 'Modernize', 894 PossibleNone => 0, 895 ); 896 897 # Extract the date units for the custom date selection. 898 my $NotificationCustomDateTimeSettings = {}; 899 if ( $Appointment{NotificationCustomDateTime} ) { 900 my $NotificationCustomDateTimeObject = $Kernel::OM->Create( 901 'Kernel::System::DateTime', 902 ObjectParams => { 903 String => $Appointment{NotificationCustomDateTime}, 904 }, 905 ); 906 $NotificationCustomDateTimeSettings = $NotificationCustomDateTimeObject->Get(); 907 } 908 909 # notification custom date selection 910 $Param{NotificationCustomDateTimeStrg} = $LayoutObject->BuildDateSelection( 911 Prefix => 'NotificationCustomDateTime', 912 NotificationCustomDateTimeYear => $NotificationCustomDateTimeSettings->{Year}, 913 NotificationCustomDateTimeMonth => $NotificationCustomDateTimeSettings->{Month}, 914 NotificationCustomDateTimeDay => $NotificationCustomDateTimeSettings->{Day}, 915 NotificationCustomDateTimeHour => $NotificationCustomDateTimeSettings->{Hour}, 916 NotificationCustomDateTimeMinute => $NotificationCustomDateTimeSettings->{Minute}, 917 Format => 'DateInputFormatLong', 918 YearPeriodPast => $YearPeriodPast{Start}, 919 YearPeriodFuture => $YearPeriodFuture{Start}, 920 ); 921 922 # prepare radio button for custom date time and relative input 923 $Appointment{NotificationCustom} ||= ''; 924 925 if ( $Appointment{NotificationCustom} eq 'datetime' ) { 926 $Param{NotificationCustomDateTimeInputRadio} = 'checked="checked"'; 927 } 928 elsif ( $Appointment{NotificationCustom} eq 'relative' ) { 929 $Param{NotificationCustomRelativeInputRadio} = 'checked="checked"'; 930 } 931 else { 932 $Param{NotificationCustomRelativeInputRadio} = 'checked="checked"'; 933 } 934 935 # notification custom string value 936 if ( $Appointment{NotificationCustom} eq 'datetime' ) { 937 $Param{NotificationValue} .= ', ' . $LayoutObject->{LanguageObject}->FormatTimeString( 938 $Appointment{NotificationCustomDateTime}, 939 'DateFormat' 940 ); 941 } 942 elsif ( $Appointment{NotificationCustom} eq 'relative' ) { 943 if ( 944 $Appointment{NotificationCustomRelativeUnit} 945 && $Appointment{NotificationCustomRelativePointOfTime} 946 ) 947 { 948 $Appointment{NotificationCustomRelativeUnitCount} ||= 0; 949 $Param{NotificationValue} .= ', ' 950 . $Appointment{NotificationCustomRelativeUnitCount} 951 . ' ' 952 . $NotificationCustomUnitLookup{$SelectedNotificationCustomUnit} 953 . ' ' 954 . $NotificationCustomUnitPointOfTimeLookup{$SelectedNotificationCustomUnitPointOfTime}; 955 } 956 } 957 958 # get plugin list 959 $Param{PluginList} = $PluginObject->PluginList(); 960 961 # new appointment plugin search 962 if ( $GetParam{PluginKey} && ( $GetParam{Search} || $GetParam{ObjectID} ) ) { 963 964 if ( grep { $_ eq $GetParam{PluginKey} } keys %{ $Param{PluginList} } ) { 965 966 # search using plugin 967 my $ResultList = $PluginObject->PluginSearch( 968 %GetParam, 969 UserID => $Self->{UserID}, 970 ); 971 972 $Param{PluginData}->{ $GetParam{PluginKey} } = []; 973 my @LinkArray = sort keys %{$ResultList}; 974 975 # add possible links 976 for my $LinkID (@LinkArray) { 977 push @{ $Param{PluginData}->{ $GetParam{PluginKey} } }, { 978 LinkID => $LinkID, 979 LinkName => $ResultList->{$LinkID}, 980 LinkURL => sprintf( 981 $Param{PluginList}->{ $GetParam{PluginKey} }->{PluginURL}, 982 $LinkID 983 ), 984 }; 985 } 986 987 $Param{PluginList}->{ $GetParam{PluginKey} }->{LinkList} = $LayoutObject->JSONEncode( 988 Data => \@LinkArray, 989 ); 990 } 991 } 992 993 # edit appointment plugin links 994 elsif ( $GetParam{AppointmentID} ) { 995 996 for my $PluginKey ( sort keys %{ $Param{PluginList} } ) { 997 my $LinkList = $PluginObject->PluginLinkList( 998 AppointmentID => $GetParam{AppointmentID}, 999 PluginKey => $PluginKey, 1000 UserID => $Self->{UserID}, 1001 ); 1002 my @LinkArray; 1003 1004 $Param{PluginData}->{$PluginKey} = []; 1005 for my $LinkID ( sort keys %{$LinkList} ) { 1006 push @{ $Param{PluginData}->{$PluginKey} }, $LinkList->{$LinkID}; 1007 push @LinkArray, $LinkList->{$LinkID}->{LinkID}; 1008 } 1009 1010 $Param{PluginList}->{$PluginKey}->{LinkList} = $LayoutObject->JSONEncode( 1011 Data => \@LinkArray, 1012 ); 1013 } 1014 } 1015 1016 # html mask output 1017 $LayoutObject->Block( 1018 Name => 'EditMask', 1019 Data => { 1020 %Param, 1021 %GetParam, 1022 %Appointment, 1023 PermissionLevel => $PermissionLevel{$Permissions}, 1024 }, 1025 ); 1026 1027 $LayoutObject->AddJSData( 1028 Key => 'CalendarPermissionLevel', 1029 Value => $PermissionLevel{$Permissions}, 1030 ); 1031 $LayoutObject->AddJSData( 1032 Key => 'EditAppointmentID', 1033 Value => $Appointment{AppointmentID} // '', 1034 ); 1035 $LayoutObject->AddJSData( 1036 Key => 'EditParentID', 1037 Value => $Appointment{ParentID} // '', 1038 ); 1039 1040 # get registered location links 1041 my $LocationLinkConfig = $ConfigObject->Get('AgentAppointmentEdit::Location::Link') // {}; 1042 for my $ConfigKey ( sort keys %{$LocationLinkConfig} ) { 1043 1044 # show link icon 1045 $LayoutObject->Block( 1046 Name => 'LocationLink', 1047 Data => { 1048 Location => $Appointment{Location} // '', 1049 %{ $LocationLinkConfig->{$ConfigKey} }, 1050 }, 1051 ); 1052 } 1053 1054 my $Output = $LayoutObject->Output( 1055 TemplateFile => 'AgentAppointmentEdit', 1056 Data => { 1057 %Param, 1058 %GetParam, 1059 %Appointment, 1060 }, 1061 AJAX => 1, 1062 ); 1063 return $LayoutObject->Attachment( 1064 NoCache => 1, 1065 ContentType => 'text/html', 1066 Charset => $LayoutObject->{UserCharset}, 1067 Content => $Output, 1068 Type => 'inline', 1069 ); 1070 } 1071 1072 # ------------------------------------------------------------ # 1073 # add/edit appointment 1074 # ------------------------------------------------------------ # 1075 elsif ( $Self->{Subaction} eq 'EditAppointment' ) { 1076 my %Appointment; 1077 if ( $GetParam{AppointmentID} ) { 1078 %Appointment = $AppointmentObject->AppointmentGet( 1079 AppointmentID => $GetParam{AppointmentID}, 1080 ); 1081 1082 # check permissions 1083 $Permissions = $CalendarObject->CalendarPermissionGet( 1084 CalendarID => $Appointment{CalendarID}, 1085 UserID => $Self->{UserID}, 1086 ); 1087 1088 my $RequiredPermission = 2; 1089 if ( $GetParam{CalendarID} && $GetParam{CalendarID} != $Appointment{CalendarID} ) { 1090 1091 # in order to move appointment to another calendar, user needs "create" permission 1092 $RequiredPermission = 3; 1093 } 1094 1095 if ( $PermissionLevel{$Permissions} < $RequiredPermission ) { 1096 1097 # no permission 1098 1099 # build JSON output 1100 $JSON = $LayoutObject->JSONEncode( 1101 Data => { 1102 Success => 0, 1103 Error => Translatable('No permission!'), 1104 }, 1105 ); 1106 1107 # send JSON response 1108 return $LayoutObject->Attachment( 1109 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 1110 Content => $JSON, 1111 Type => 'inline', 1112 NoCache => 1, 1113 ); 1114 } 1115 } 1116 1117 if ( $GetParam{AllDay} ) { 1118 $GetParam{StartTime} = sprintf( 1119 "%04d-%02d-%02d 00:00:00", 1120 $GetParam{StartYear}, $GetParam{StartMonth}, $GetParam{StartDay} 1121 ); 1122 $GetParam{EndTime} = sprintf( 1123 "%04d-%02d-%02d 00:00:00", 1124 $GetParam{EndYear}, $GetParam{EndMonth}, $GetParam{EndDay} 1125 ); 1126 1127 my $StartTimeObject = $Kernel::OM->Create( 1128 'Kernel::System::DateTime', 1129 ObjectParams => { 1130 String => $GetParam{StartTime}, 1131 }, 1132 ); 1133 my $EndTimeObject = $Kernel::OM->Create( 1134 'Kernel::System::DateTime', 1135 ObjectParams => { 1136 String => $GetParam{EndTime}, 1137 }, 1138 ); 1139 1140 # Prevent storing end time before start time. 1141 if ( $EndTimeObject < $StartTimeObject ) { 1142 $EndTimeObject = $StartTimeObject->Clone(); 1143 } 1144 1145 # Make end time inclusive, add whole day. 1146 $EndTimeObject->Add( 1147 Days => 1, 1148 ); 1149 $GetParam{EndTime} = $EndTimeObject->ToString(); 1150 } 1151 elsif ( $GetParam{Recurring} && $GetParam{UpdateType} && $GetParam{UpdateDelta} ) { 1152 1153 my $StartTimeObject = $Kernel::OM->Create( 1154 'Kernel::System::DateTime', 1155 ObjectParams => { 1156 String => $Appointment{StartTime}, 1157 }, 1158 ); 1159 my $EndTimeObject = $Kernel::OM->Create( 1160 'Kernel::System::DateTime', 1161 ObjectParams => { 1162 String => $Appointment{EndTime}, 1163 }, 1164 ); 1165 1166 # Calculate new start/end times. 1167 if ( $GetParam{UpdateType} eq 'StartTime' ) { 1168 $StartTimeObject->Add( 1169 Seconds => $GetParam{UpdateDelta}, 1170 ); 1171 $GetParam{StartTime} = $StartTimeObject->ToString(); 1172 } 1173 elsif ( $GetParam{UpdateType} eq 'EndTime' ) { 1174 $EndTimeObject->Add( 1175 Seconds => $GetParam{UpdateDelta}, 1176 ); 1177 $GetParam{EndTime} = $EndTimeObject->ToString(); 1178 } 1179 else { 1180 $StartTimeObject->Add( 1181 Seconds => $GetParam{UpdateDelta}, 1182 ); 1183 $EndTimeObject->Add( 1184 Seconds => $GetParam{UpdateDelta}, 1185 ); 1186 $GetParam{StartTime} = $StartTimeObject->ToString(); 1187 $GetParam{EndTime} = $EndTimeObject->ToString(); 1188 } 1189 } 1190 else { 1191 if ( 1192 defined $GetParam{StartYear} 1193 && defined $GetParam{StartMonth} 1194 && defined $GetParam{StartDay} 1195 && defined $GetParam{StartHour} 1196 && defined $GetParam{StartMinute} 1197 ) 1198 { 1199 $GetParam{StartTime} = sprintf( 1200 "%04d-%02d-%02d %02d:%02d:00", 1201 $GetParam{StartYear}, $GetParam{StartMonth}, $GetParam{StartDay}, 1202 $GetParam{StartHour}, $GetParam{StartMinute} 1203 ); 1204 1205 # Convert start time to local time. 1206 my $StartTimeObject = $Kernel::OM->Create( 1207 'Kernel::System::DateTime', 1208 ObjectParams => { 1209 String => $GetParam{StartTime}, 1210 TimeZone => $Self->{UserTimeZone}, 1211 }, 1212 ); 1213 if ( $Self->{UserTimeZone} ) { 1214 $StartTimeObject->ToOTRSTimeZone(); 1215 } 1216 $GetParam{StartTime} = $StartTimeObject->ToString(); 1217 } 1218 else { 1219 $GetParam{StartTime} = $Appointment{StartTime}; 1220 } 1221 1222 if ( 1223 defined $GetParam{EndYear} 1224 && defined $GetParam{EndMonth} 1225 && defined $GetParam{EndDay} 1226 && defined $GetParam{EndHour} 1227 && defined $GetParam{EndMinute} 1228 ) 1229 { 1230 $GetParam{EndTime} = sprintf( 1231 "%04d-%02d-%02d %02d:%02d:00", 1232 $GetParam{EndYear}, $GetParam{EndMonth}, $GetParam{EndDay}, 1233 $GetParam{EndHour}, $GetParam{EndMinute} 1234 ); 1235 1236 # Convert end time to local time. 1237 my $EndTimeObject = $Kernel::OM->Create( 1238 'Kernel::System::DateTime', 1239 ObjectParams => { 1240 String => $GetParam{EndTime}, 1241 TimeZone => $Self->{UserTimeZone}, 1242 }, 1243 ); 1244 if ( $Self->{UserTimeZone} ) { 1245 $EndTimeObject->ToOTRSTimeZone(); 1246 } 1247 1248 # Get already calculated local start time. 1249 my $StartTimeObject = $Kernel::OM->Create( 1250 'Kernel::System::DateTime', 1251 ObjectParams => { 1252 String => $GetParam{StartTime}, 1253 }, 1254 ); 1255 1256 # Prevent storing end time before start time. 1257 if ( $EndTimeObject < $StartTimeObject ) { 1258 $EndTimeObject = $StartTimeObject->Clone(); 1259 } 1260 1261 $GetParam{EndTime} = $EndTimeObject->ToString(); 1262 } 1263 else { 1264 $GetParam{EndTime} = $Appointment{EndTime}; 1265 } 1266 } 1267 1268 # Prevent recurrence until dates before start time. 1269 if ( $Appointment{Recurring} && $Appointment{RecurrenceUntil} ) { 1270 my $StartTimeObject = $Kernel::OM->Create( 1271 'Kernel::System::DateTime', 1272 ObjectParams => { 1273 String => $GetParam{StartTime}, 1274 }, 1275 ); 1276 my $RecurrenceUntilObject = $Kernel::OM->Create( 1277 'Kernel::System::DateTime', 1278 ObjectParams => { 1279 String => $Appointment{RecurrenceUntil}, 1280 }, 1281 ); 1282 if ( $RecurrenceUntilObject < $StartTimeObject ) { 1283 $Appointment{RecurrenceUntil} = $GetParam{StartTime}; 1284 } 1285 } 1286 1287 # recurring appointment 1288 if ( $GetParam{Recurring} && $GetParam{RecurrenceType} ) { 1289 1290 if ( 1291 $GetParam{RecurrenceType} eq 'Daily' 1292 || $GetParam{RecurrenceType} eq 'Weekly' 1293 || $GetParam{RecurrenceType} eq 'Monthly' 1294 || $GetParam{RecurrenceType} eq 'Yearly' 1295 ) 1296 { 1297 $GetParam{RecurrenceInterval} = 1; 1298 } 1299 elsif ( $GetParam{RecurrenceType} eq 'Custom' ) { 1300 1301 if ( $GetParam{RecurrenceCustomType} eq 'CustomWeekly' ) { 1302 if ( $GetParam{Days} ) { 1303 my @Days = split( ",", $GetParam{Days} ); 1304 $GetParam{RecurrenceFrequency} = \@Days; 1305 } 1306 else { 1307 my $StartTimeObject = $Kernel::OM->Create( 1308 'Kernel::System::DateTime', 1309 ObjectParams => { 1310 String => $GetParam{StartTime}, 1311 }, 1312 ); 1313 my $StartTimeSettings = $StartTimeObject->Get(); 1314 $GetParam{RecurrenceFrequency} = [ $StartTimeSettings->{DayOfWeek} ]; 1315 } 1316 } 1317 elsif ( $GetParam{RecurrenceCustomType} eq 'CustomMonthly' ) { 1318 if ( $GetParam{MonthDays} ) { 1319 my @MonthDays = split( ",", $GetParam{MonthDays} ); 1320 $GetParam{RecurrenceFrequency} = \@MonthDays; 1321 } 1322 else { 1323 my $StartTimeObject = $Kernel::OM->Create( 1324 'Kernel::System::DateTime', 1325 ObjectParams => { 1326 String => $GetParam{StartTime}, 1327 }, 1328 ); 1329 my $StartTimeSettings = $StartTimeObject->Get(); 1330 $GetParam{RecurrenceFrequency} = [ $StartTimeSettings->{Day} ]; 1331 } 1332 } 1333 elsif ( $GetParam{RecurrenceCustomType} eq 'CustomYearly' ) { 1334 if ( $GetParam{Months} ) { 1335 my @Months = split( ",", $GetParam{Months} ); 1336 $GetParam{RecurrenceFrequency} = \@Months; 1337 } 1338 else { 1339 my $StartTimeObject = $Kernel::OM->Create( 1340 'Kernel::System::DateTime', 1341 ObjectParams => { 1342 String => $GetParam{StartTime}, 1343 }, 1344 ); 1345 my $StartTimeSettings = $StartTimeObject->Get(); 1346 $GetParam{RecurrenceFrequency} = [ $StartTimeSettings->{Month} ]; 1347 } 1348 } 1349 1350 $GetParam{RecurrenceType} = $GetParam{RecurrenceCustomType}; 1351 } 1352 1353 # until ... 1354 if ( 1355 $GetParam{RecurrenceLimit} eq '1' 1356 && $GetParam{RecurrenceUntilYear} 1357 && $GetParam{RecurrenceUntilMonth} 1358 && $GetParam{RecurrenceUntilDay} 1359 ) 1360 { 1361 $GetParam{RecurrenceUntil} = sprintf( 1362 "%04d-%02d-%02d 00:00:00", 1363 $GetParam{RecurrenceUntilYear}, $GetParam{RecurrenceUntilMonth}, 1364 $GetParam{RecurrenceUntilDay} 1365 ); 1366 1367 # Prevent recurrence until dates before start time. 1368 my $StartTimeObject = $Kernel::OM->Create( 1369 'Kernel::System::DateTime', 1370 ObjectParams => { 1371 String => $GetParam{StartTime}, 1372 }, 1373 ); 1374 my $RecurrenceUntilObject = $Kernel::OM->Create( 1375 'Kernel::System::DateTime', 1376 ObjectParams => { 1377 String => $GetParam{RecurrenceUntil}, 1378 }, 1379 ); 1380 if ( $RecurrenceUntilObject < $StartTimeObject ) { 1381 $GetParam{RecurrenceUntil} = $GetParam{StartTime}; 1382 } 1383 1384 $GetParam{RecurrenceCount} = undef; 1385 } 1386 1387 # for ... time(s) 1388 elsif ( $GetParam{RecurrenceLimit} eq '2' ) { 1389 $GetParam{RecurrenceUntil} = undef; 1390 } 1391 } 1392 1393 # Determine notification custom type, if supplied. 1394 if ( defined $GetParam{NotificationTemplate} ) { 1395 if ( $GetParam{NotificationTemplate} ne 'Custom' ) { 1396 $GetParam{NotificationCustom} = ''; 1397 } 1398 elsif ( $GetParam{NotificationCustomRelativeInput} ) { 1399 $GetParam{NotificationCustom} = 'relative'; 1400 } 1401 elsif ( $GetParam{NotificationCustomDateTimeInput} ) { 1402 $GetParam{NotificationCustom} = 'datetime'; 1403 1404 $GetParam{NotificationCustomDateTime} = sprintf( 1405 "%04d-%02d-%02d %02d:%02d:00", 1406 $GetParam{NotificationCustomDateTimeYear}, 1407 $GetParam{NotificationCustomDateTimeMonth}, 1408 $GetParam{NotificationCustomDateTimeDay}, 1409 $GetParam{NotificationCustomDateTimeHour}, 1410 $GetParam{NotificationCustomDateTimeMinute} 1411 ); 1412 1413 my $NotificationCustomDateTimeObject = $Kernel::OM->Create( 1414 'Kernel::System::DateTime', 1415 ObjectParams => { 1416 String => $GetParam{NotificationCustomDateTime}, 1417 TimeZone => $Self->{UserTimeZone}, 1418 }, 1419 ); 1420 1421 if ( $Self->{UserTimeZone} ) { 1422 $NotificationCustomDateTimeObject->ToOTRSTimeZone(); 1423 } 1424 1425 $GetParam{NotificationCustomDateTime} = $NotificationCustomDateTimeObject->ToString(); 1426 } 1427 } 1428 1429 # team 1430 if ( $GetParam{'TeamID[]'} ) { 1431 my @TeamIDs = $ParamObject->GetArray( Param => 'TeamID[]' ); 1432 $GetParam{TeamID} = \@TeamIDs; 1433 } 1434 else { 1435 $GetParam{TeamID} = undef; 1436 } 1437 1438 # resources 1439 if ( $GetParam{'ResourceID[]'} ) { 1440 my @ResourceID = $ParamObject->GetArray( Param => 'ResourceID[]' ); 1441 $GetParam{ResourceID} = \@ResourceID; 1442 } 1443 else { 1444 $GetParam{ResourceID} = undef; 1445 } 1446 1447 # Check if dealing with ticket appointment. 1448 if ( $Appointment{TicketAppointmentRuleID} ) { 1449 1450 # Make sure readonly values stay unchanged. 1451 $GetParam{Title} = $Appointment{Title}; 1452 $GetParam{CalendarID} = $Appointment{CalendarID}; 1453 $GetParam{AllDay} = undef; 1454 $GetParam{Recurring} = undef; 1455 1456 my $Rule = $CalendarObject->TicketAppointmentRuleGet( 1457 CalendarID => $Appointment{CalendarID}, 1458 RuleID => $Appointment{TicketAppointmentRuleID}, 1459 ); 1460 1461 # Recalculate end time based on time preset. 1462 if ( IsHashRefWithData($Rule) ) { 1463 if ( $Rule->{EndDate} =~ /^Plus_([0-9]+)$/ && $GetParam{StartTime} ) { 1464 my $Preset = int $1; 1465 1466 my $EndTimeObject = $Kernel::OM->Create( 1467 'Kernel::System::DateTime', 1468 ObjectParams => { 1469 String => $GetParam{StartTime}, # base on start time 1470 }, 1471 ); 1472 1473 # Calculate end time using preset value. 1474 $EndTimeObject->Add( 1475 Minutes => $Preset, 1476 ); 1477 $GetParam{EndTime} = $EndTimeObject->ToString(); 1478 } 1479 } 1480 } 1481 1482 my $Success; 1483 1484 # reset empty parameters 1485 for my $Param ( sort keys %GetParam ) { 1486 if ( !$GetParam{$Param} ) { 1487 $GetParam{$Param} = undef; 1488 } 1489 } 1490 1491 # pass current user ID 1492 $GetParam{UserID} = $Self->{UserID}; 1493 1494 # Get passed plugin parameters. 1495 my @PluginParams = grep { $_ =~ /^Plugin_/ } keys %GetParam; 1496 1497 if (%Appointment) { 1498 1499 # Continue only if coming from edit screen 1500 # (there is at least one passed plugin parameter). 1501 if (@PluginParams) { 1502 1503 # Get all related appointments before the update. 1504 my @RelatedAppointments = ( $Appointment{AppointmentID} ); 1505 my @CalendarAppointments = $AppointmentObject->AppointmentList( 1506 CalendarID => $Appointment{CalendarID}, 1507 ); 1508 1509 # If we are dealing with a parent, include any child appointments. 1510 push @RelatedAppointments, 1511 map { 1512 $_->{AppointmentID} 1513 } 1514 grep { 1515 defined $_->{ParentID} 1516 && $_->{ParentID} eq $Appointment{AppointmentID} 1517 } @CalendarAppointments; 1518 1519 # Remove all existing links. 1520 for my $CurrentAppointmentID (@RelatedAppointments) { 1521 my $Success = $PluginObject->PluginLinkDelete( 1522 AppointmentID => $CurrentAppointmentID, 1523 UserID => $Self->{UserID}, 1524 ); 1525 1526 if ( !$Success ) { 1527 $Kernel::OM->Get('Kernel::System::Log')->Log( 1528 Priority => 'error', 1529 Message => "Links could not be deleted for appointment $CurrentAppointmentID!", 1530 ); 1531 } 1532 } 1533 } 1534 1535 $Success = $AppointmentObject->AppointmentUpdate( 1536 %Appointment, 1537 %GetParam, 1538 ); 1539 } 1540 else { 1541 $Success = $AppointmentObject->AppointmentCreate( 1542 %GetParam, 1543 ); 1544 } 1545 1546 my $AppointmentID = $GetParam{AppointmentID} ? $GetParam{AppointmentID} : $Success; 1547 1548 if ($AppointmentID) { 1549 1550 # Continue only if coming from edit screen 1551 # (there is at least one passed plugin parameter). 1552 if (@PluginParams) { 1553 1554 # Get fresh appointment data. 1555 %Appointment = $AppointmentObject->AppointmentGet( 1556 AppointmentID => $AppointmentID, 1557 ); 1558 1559 # Process all related appointments. 1560 my @RelatedAppointments = ($AppointmentID); 1561 my @CalendarAppointments = $AppointmentObject->AppointmentList( 1562 CalendarID => $Appointment{CalendarID}, 1563 ); 1564 1565 # If we are dealing with a parent, include any child appointments as well. 1566 push @RelatedAppointments, 1567 map { 1568 $_->{AppointmentID} 1569 } 1570 grep { 1571 defined $_->{ParentID} 1572 && $_->{ParentID} eq $AppointmentID 1573 } @CalendarAppointments; 1574 1575 # Process passed plugin parameters. 1576 for my $PluginParam (@PluginParams) { 1577 my $PluginData = $Kernel::OM->Get('Kernel::System::JSON')->Decode( 1578 Data => $GetParam{$PluginParam}, 1579 ); 1580 my $PluginKey = $PluginParam; 1581 $PluginKey =~ s/^Plugin_//; 1582 1583 # Execute link add method of the plugin. 1584 if ( IsArrayRefWithData($PluginData) ) { 1585 for my $LinkID ( @{$PluginData} ) { 1586 for my $CurrentAppointmentID (@RelatedAppointments) { 1587 my $Link = $PluginObject->PluginLinkAdd( 1588 AppointmentID => $CurrentAppointmentID, 1589 PluginKey => $PluginKey, 1590 PluginData => $LinkID, 1591 UserID => $Self->{UserID}, 1592 ); 1593 1594 if ( !$Link ) { 1595 $Kernel::OM->Get('Kernel::System::Log')->Log( 1596 Priority => 'error', 1597 Message => "Link could not be created for appointment $CurrentAppointmentID!", 1598 ); 1599 } 1600 } 1601 } 1602 } 1603 } 1604 } 1605 } 1606 1607 # build JSON output 1608 $JSON = $LayoutObject->JSONEncode( 1609 Data => { 1610 Success => $Success ? 1 : 0, 1611 AppointmentID => $AppointmentID, 1612 }, 1613 ); 1614 } 1615 1616 # ------------------------------------------------------------ # 1617 # delete mask 1618 # ------------------------------------------------------------ # 1619 elsif ( $Self->{Subaction} eq 'DeleteAppointment' ) { 1620 1621 if ( $GetParam{AppointmentID} ) { 1622 my %Appointment = $AppointmentObject->AppointmentGet( 1623 AppointmentID => $GetParam{AppointmentID}, 1624 ); 1625 1626 my $Success = 0; 1627 my $Error = ''; 1628 1629 # Prevent deleting ticket appointment. 1630 if ( $Appointment{TicketAppointmentRuleID} ) { 1631 $Error = Translatable('Cannot delete ticket appointment!'); 1632 } 1633 else { 1634 1635 # Get all related appointments before the deletion. 1636 my @RelatedAppointments = ( $Appointment{AppointmentID} ); 1637 my @CalendarAppointments = $AppointmentObject->AppointmentList( 1638 CalendarID => $Appointment{CalendarID}, 1639 ); 1640 1641 # If we are dealing with a parent, include any child appointments. 1642 push @RelatedAppointments, 1643 map { 1644 $_->{AppointmentID} 1645 } 1646 grep { 1647 defined $_->{ParentID} 1648 && $_->{ParentID} eq $Appointment{AppointmentID} 1649 } @CalendarAppointments; 1650 1651 # Remove all existing links. 1652 for my $CurrentAppointmentID (@RelatedAppointments) { 1653 my $Success = $PluginObject->PluginLinkDelete( 1654 AppointmentID => $CurrentAppointmentID, 1655 UserID => $Self->{UserID}, 1656 ); 1657 1658 if ( !$Success ) { 1659 $Kernel::OM->Get('Kernel::System::Log')->Log( 1660 Priority => 'error', 1661 Message => "Links could not be deleted for appointment $CurrentAppointmentID!", 1662 ); 1663 } 1664 } 1665 1666 $Success = $AppointmentObject->AppointmentDelete( 1667 %GetParam, 1668 UserID => $Self->{UserID}, 1669 ); 1670 1671 if ( !$Success ) { 1672 $Error = Translatable('No permissions!'); 1673 } 1674 } 1675 1676 # build JSON output 1677 $JSON = $LayoutObject->JSONEncode( 1678 Data => { 1679 Success => $Success, 1680 Error => $Error, 1681 AppointmentID => $GetParam{AppointmentID}, 1682 }, 1683 ); 1684 } 1685 } 1686 1687 # ------------------------------------------------------------ # 1688 # update preferences 1689 # ------------------------------------------------------------ # 1690 elsif ( $Self->{Subaction} eq 'UpdatePreferences' ) { 1691 1692 my $Success = 0; 1693 1694 if ( 1695 $GetParam{OverviewScreen} && ( 1696 $GetParam{DefaultView} || $GetParam{CalendarSelection} 1697 || ( $GetParam{ShownResources} && $GetParam{TeamID} ) 1698 || $GetParam{ShownAppointments} 1699 ) 1700 ) 1701 { 1702 my $PreferenceKey; 1703 my $PreferenceKeySuffix = ''; 1704 1705 if ( $GetParam{DefaultView} ) { 1706 $PreferenceKey = 'DefaultView'; 1707 } 1708 elsif ( $GetParam{CalendarSelection} ) { 1709 $PreferenceKey = 'CalendarSelection'; 1710 } 1711 elsif ( $GetParam{ShownResources} && $GetParam{TeamID} ) { 1712 $PreferenceKey = 'ShownResources'; 1713 $PreferenceKeySuffix = "-$GetParam{TeamID}"; 1714 } 1715 elsif ( $GetParam{ShownAppointments} ) { 1716 $PreferenceKey = 'ShownAppointments'; 1717 } 1718 1719 # set user preferences 1720 $Success = $Kernel::OM->Get('Kernel::System::User')->SetPreferences( 1721 Key => 'User' . $GetParam{OverviewScreen} . $PreferenceKey . $PreferenceKeySuffix, 1722 Value => $GetParam{$PreferenceKey}, 1723 UserID => $Self->{UserID}, 1724 ); 1725 } 1726 1727 elsif ( $GetParam{OverviewScreen} && $GetParam{RestoreDefaultSettings} ) { 1728 my $PreferenceKey; 1729 my $PreferenceKeySuffix = ''; 1730 1731 if ( $GetParam{RestoreDefaultSettings} eq 'ShownResources' && $GetParam{TeamID} ) { 1732 $PreferenceKey = 'ShownResources'; 1733 $PreferenceKeySuffix = "-$GetParam{TeamID}"; 1734 } 1735 1736 # blank user preferences 1737 $Success = $Kernel::OM->Get('Kernel::System::User')->SetPreferences( 1738 Key => 'User' . $GetParam{OverviewScreen} . $PreferenceKey . $PreferenceKeySuffix, 1739 Value => '', 1740 UserID => $Self->{UserID}, 1741 ); 1742 } 1743 1744 # build JSON output 1745 $JSON = $LayoutObject->JSONEncode( 1746 Data => { 1747 Success => $Success, 1748 }, 1749 ); 1750 } 1751 1752 # ------------------------------------------------------------ # 1753 # team list selection update 1754 # ------------------------------------------------------------ # 1755 elsif ( $Self->{Subaction} eq 'TeamUserList' ) { 1756 my @TeamIDs = $ParamObject->GetArray( Param => 'TeamID[]' ); 1757 my %TeamUserListAll; 1758 1759 # Check if team object is registered. 1760 if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Calendar::Team', Silent => 1 ) ) { 1761 my $TeamObject = $Kernel::OM->Get('Kernel::System::Calendar::Team'); 1762 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 1763 1764 TEAMID: 1765 for my $TeamID (@TeamIDs) { 1766 next TEAMID if !$TeamID; 1767 1768 # get list of team members 1769 my %TeamUserList = $TeamObject->TeamUserList( 1770 TeamID => $TeamID, 1771 UserID => $Self->{UserID}, 1772 ); 1773 1774 # get user data 1775 for my $UserID ( sort keys %TeamUserList ) { 1776 my %User = $UserObject->GetUserData( 1777 UserID => $UserID, 1778 ); 1779 $TeamUserList{$UserID} = $User{UserFullname}; 1780 } 1781 1782 %TeamUserListAll = ( %TeamUserListAll, %TeamUserList ); 1783 } 1784 } 1785 1786 # build JSON output 1787 $JSON = $LayoutObject->JSONEncode( 1788 Data => { 1789 TeamUserList => \%TeamUserListAll, 1790 }, 1791 ); 1792 1793 } 1794 1795 # send JSON response 1796 return $LayoutObject->Attachment( 1797 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 1798 Content => $JSON, 1799 Type => 'inline', 1800 NoCache => 1, 1801 ); 1802} 1803 1804sub _DayOffsetGet { 1805 my ( $Self, %Param ) = @_; 1806 1807 # check needed stuff 1808 for my $Needed (qw(Time)) { 1809 if ( !$Param{$Needed} ) { 1810 $Kernel::OM->Get('Kernel::System::Log')->Log( 1811 Priority => 'error', 1812 Message => "Need $Needed!", 1813 ); 1814 return; 1815 } 1816 } 1817 1818 # Get original date components. 1819 my $OriginalTimeObject = $Kernel::OM->Create( 1820 'Kernel::System::DateTime', 1821 ObjectParams => { 1822 String => $Param{Time}, 1823 }, 1824 ); 1825 my $OriginalTimeSettings = $OriginalTimeObject->Get(); 1826 1827 # Get destination time according to user timezone. 1828 my $DestinationTimeObject = $OriginalTimeObject->Clone(); 1829 $DestinationTimeObject->ToTimeZone( TimeZone => $Self->{UserTimeZone} ); 1830 my $DestinationTimeSettings = $DestinationTimeObject->Get(); 1831 1832 # Compare days of two times. 1833 if ( $OriginalTimeSettings->{Day} == $DestinationTimeSettings->{Day} ) { 1834 return 0; # same day 1835 } 1836 elsif ( $OriginalTimeObject > $DestinationTimeObject ) { 1837 return -1; 1838 } 1839 1840 return 1; 1841} 1842 18431; 1844