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