# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::Modules::AdminAppointmentCalendarManage; use strict; use warnings; use Kernel::Language qw(Translatable); use Kernel::System::VariableCheck qw(:all); use parent qw(Kernel::System::AsynchronousExecutor); our $ObjectManagerDisabled = 1; sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {%Param}; bless( $Self, $Type ); # Certain search parameters for ticket appointments should be stored as scalars, not array refs. $Self->{SearchParamScalar} = [ 'MIMEBase_From', 'MIMEBase_To', 'MIMEBase_Cc', 'MIMEBase_Subject', 'MIMEBase_Body', 'MIMEBase_AttachmentName', ]; return $Self; } sub Run { my ( $Self, %Param ) = @_; my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); # get names of all parameters my @ParamNames = $ParamObject->GetParamNames(); # get params my %GetParam; PARAMNAME: for my $Key (@ParamNames) { # Queue, OwnerIDs and ResponsibleIDs are multiple selection fields, get array instead. if ( $Key =~ /^QueueID_/ || $Key =~ /^SearchParam_[0-9a-f]+_OwnerIDs$/ || $Key =~ /^SearchParam_[0-9a-f]+_ResponsibleIDs$/ ) { my @ParamArray = $ParamObject->GetArray( Param => $Key ); $GetParam{$Key} = \@ParamArray; next PARAMNAME; } $GetParam{$Key} = $ParamObject->GetParam( Param => $Key ); } my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar'); my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); # Get user's permissions to associated modules which are displayed as links. for my $Module (qw(AgentAppointmentCalendarOverview AdminAppointmentImport)) { my $ModuleGroups = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module') ->{$Module}->{Group} // []; if ( IsArrayRefWithData($ModuleGroups) ) { MODULE_GROUP: for my $ModuleGroup ( @{$ModuleGroups} ) { my $HasPermission = $GroupObject->PermissionCheck( UserID => $Self->{UserID}, GroupName => $ModuleGroup, Type => 'rw', ); if ($HasPermission) { $Param{ModulePermissions}->{$Module} = 1; last MODULE_GROUP; } } } # Always allow links if no groups are specified. else { $Param{ModulePermissions}->{$Module} = 1; } } if ( $Self->{Subaction} eq 'New' ) { my $GroupSelection = $Self->_GroupSelectionGet(); my $ColorPalette = $Self->_ColorPaletteGet(); my $ValidSelection = $Self->_ValidSelectionGet(); my %TicketAppointments = $Self->_TicketAppointments(); $LayoutObject->Block( Name => 'CalendarEdit', Data => { GroupID => $GroupSelection, ValidID => $ValidSelection, Subaction => 'StoreNew', Color => $ColorPalette->[ int rand( scalar @{$ColorPalette} ) ], Title => Translatable('Add new Calendar'), WidgetStatus => 'Collapsed', %TicketAppointments, }, ); $LayoutObject->AddJSData( Key => 'CalendarColorPalette', Value => $ColorPalette, ); $Param{Action} = 'New'; } elsif ( $Self->{Subaction} eq 'StoreNew' ) { my %Error; # check name if ( !$GetParam{CalendarName} ) { $Error{'CalendarNameInvalid'} = 'ServerError'; } else { # check if there is a calendar with same name my %Calendar = $CalendarObject->CalendarGet( CalendarName => $GetParam{CalendarName}, ); if (%Calendar) { $Error{CalendarNameInvalid} = "ServerError"; $Error{CalendarNameExists} = 1; } } $GetParam{TicketAppointments} = $Self->_GetTicketAppointmentParams(%GetParam); # Get queue create permissions for the user. my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( UserID => $Self->{UserID}, Type => 'create', ); my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); # Queue field in ticket appointments is mandatory, check if it's present and valid. for my $Rule ( @{ $GetParam{TicketAppointments} } ) { if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) { QUEUE_ID: for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) { my %QueueData = $QueueObject->QueueGet( ID => $QueueID ); if ( !grep { $_ eq $QueueData{ValidID} } @ValidIDs || !$UserGroups{ $QueueData{GroupID} } ) { $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError'; last QUEUE_ID; } } } else { $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError'; } } if (%Error) { # get selections my $GroupSelection = $Self->_GroupSelectionGet(%GetParam); my $ColorPalette = $Self->_ColorPaletteGet(); my $ValidSelection = $Self->_ValidSelectionGet(%GetParam); my %TicketAppointments = $Self->_TicketAppointments(); # get rule count my $RuleCount = scalar @{ $GetParam{TicketAppointments} || [] }; $LayoutObject->Block( Name => 'CalendarEdit', Data => { %Error, %GetParam, GroupID => $GroupSelection, ValidID => $ValidSelection, Subaction => 'StoreNew', Title => Translatable('Add new Calendar'), WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed', %TicketAppointments, }, ); $LayoutObject->AddJSData( Key => 'CalendarColor', Value => $GetParam{Color}, ); $LayoutObject->AddJSData( Key => 'CalendarColorPalette', Value => $ColorPalette, ); $LayoutObject->AddJSData( Key => 'CalendarRuleCount', Value => $RuleCount, ); # Show all ticket appointment rule blocks. my $RuleNumber = 1; my @RuleIDs; for my $Rule ( @{ $GetParam{TicketAppointments} || [] } ) { $Rule->{Error} = $Error{ $Rule->{RuleID} }; my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} ); $LayoutObject->Block( Name => 'TicketAppointmentRule', Data => { RuleNumber => $RuleNumber++, %{$Rule}, %TicketAppointmentRule, }, ); # Show any search parameter blocks too. $Self->_ShowTicketAppointmentParams( %{$Rule} ); # Save rule ID for button initialization. push @RuleIDs, $Rule->{RuleID}; } $LayoutObject->AddJSData( Key => 'RuleIDs', Value => \@RuleIDs, ); $Param{Action} = 'New'; return $Self->_Mask(%Param); } # create calendar my %Calendar = $CalendarObject->CalendarCreate( %GetParam, UserID => $Self->{UserID}, ); if ( !%Calendar ) { return $LayoutObject->ErrorScreen( Message => Translatable('System was unable to create Calendar!'), Comment => Translatable('Please contact the administrator.'), ); } # Process ticket appointments in async call to core module method. $Self->AsyncCall( ObjectName => 'Kernel::System::Calendar', FunctionName => 'TicketAppointmentProcessCalendar', FunctionParams => { CalendarID => $Calendar{CalendarID}, }, ); # If the user would like to continue editing the calendar, just redirect to the edit screen. if ( defined $ParamObject->GetParam( Param => 'ContinueAfterSave' ) && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' ) ) { return $LayoutObject->Redirect( OP => "Action=$Self->{Action};Subaction=Edit;CalendarID=$GetParam{CalendarID}" ); } else { # Otherwise return to overview. return $LayoutObject->Redirect( OP => "Action=AdminAppointmentCalendarManage" ); } } elsif ( $Self->{Subaction} eq 'Edit' ) { # get data my %GetParam; $GetParam{CalendarID} = $ParamObject->GetParam( Param => 'CalendarID' ) || ''; if ( !$GetParam{CalendarID} ) { return $LayoutObject->ErrorScreen( Message => Translatable('No CalendarID!'), Comment => Translatable('Please contact the administrator.'), ); } # get calendar data my %Calendar = $CalendarObject->CalendarGet( CalendarID => $GetParam{CalendarID}, UserID => $Self->{UserID}, ); if ( !%Calendar ) { # fake message return $LayoutObject->ErrorScreen( Message => Translatable('You have no access to this calendar!'), Comment => Translatable('Please contact the administrator.'), ); } # get selections my $GroupSelection = $Self->_GroupSelectionGet(%Calendar); my $ColorPalette = $Self->_ColorPaletteGet(); my $ValidSelection = $Self->_ValidSelectionGet(%Calendar); my %TicketAppointments = $Self->_TicketAppointments(); my $RuleCount = scalar @{ $Calendar{TicketAppointments} || [] }; $LayoutObject->Block( Name => 'CalendarEdit', Data => { %Calendar, GroupID => $GroupSelection, ValidID => $ValidSelection, Subaction => 'Update', Title => Translatable('Edit Calendar'), WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed', %TicketAppointments, }, ); $LayoutObject->AddJSData( Key => 'CalendarColor', Value => $Calendar{Color}, ); $LayoutObject->AddJSData( Key => 'CalendarColorPalette', Value => $ColorPalette, ); $LayoutObject->AddJSData( Key => 'CalendarRuleCount', Value => $RuleCount, ); # Show all ticket appointment rule blocks. my $RuleNumber = 1; my @RuleIDs; for my $Rule ( @{ $Calendar{TicketAppointments} || [] } ) { my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} ); $LayoutObject->Block( Name => 'TicketAppointmentRule', Data => { RuleNumber => $RuleNumber++, %{$Rule}, %TicketAppointmentRule, }, ); # Show any search parameter blocks too. $Self->_ShowTicketAppointmentParams( %{$Rule} ); # Save rule ID for button initialization. push @RuleIDs, $Rule->{RuleID}; } $LayoutObject->AddJSData( Key => 'RuleIDs', Value => \@RuleIDs, ); $Param{Action} = 'Update'; $Param{Name} = $Calendar{CalendarName}; } elsif ( $Self->{Subaction} eq 'Update' ) { my %Error; # check needed stuff for my $Needed (qw(CalendarID CalendarName Color GroupID)) { if ( !$GetParam{$Needed} ) { $Error{ $Needed . 'Invalid' } = 'ServerError'; return $Self->_Mask( %Param, %GetParam, %Error ); } } # check if there is already a calendar with same name my %Calendar = $CalendarObject->CalendarGet( CalendarName => $GetParam{CalendarName}, UserID => $Self->{UserID}, ); if ( defined $Calendar{CalendarID} && $Calendar{CalendarID} != $GetParam{CalendarID} ) { $Error{CalendarNameInvalid} = "ServerError"; $Error{CalendarNameExists} = 1; } $GetParam{TicketAppointments} = $Self->_GetTicketAppointmentParams(%GetParam); # Get queue create permissions for the user. my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( UserID => $Self->{UserID}, Type => 'create', ); my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); # Queue field in ticket appointments is mandatory, check if it's present and valid. for my $Rule ( @{ $GetParam{TicketAppointments} } ) { if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) { QUEUE_ID: for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) { my %QueueData = $QueueObject->QueueGet( ID => $QueueID ); if ( !grep { $_ eq $QueueData{ValidID} } @ValidIDs || !$UserGroups{ $QueueData{GroupID} } ) { $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError'; last QUEUE_ID; } } } else { $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError'; } } if (%Error) { # get selections my $GroupSelection = $Self->_GroupSelectionGet(%GetParam); my $ColorPalette = $Self->_ColorPaletteGet(); my $ValidSelection = $Self->_ValidSelectionGet(%GetParam); my %TicketAppointments = $Self->_TicketAppointments(); my $RuleCount = scalar @{ $GetParam{TicketAppointments} || [] }; $LayoutObject->Block( Name => 'CalendarEdit', Data => { %Error, %GetParam, GroupID => $GroupSelection, ValidID => $ValidSelection, Subaction => 'Update', Title => Translatable('Edit Calendar'), WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed', %TicketAppointments, }, ); $LayoutObject->AddJSData( Key => 'CalendarColor', Value => $Calendar{Color}, ); $LayoutObject->AddJSData( Key => 'CalendarColorPalette', Value => $ColorPalette, ); $LayoutObject->AddJSData( Key => 'CalendarRuleCount', Value => $RuleCount, ); # Show all ticket appointment rule blocks. my $RuleNumber = 1; my @RuleIDs; for my $Rule ( @{ $GetParam{TicketAppointments} || [] } ) { $Rule->{Error} = $Error{ $Rule->{RuleID} }; my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} ); $LayoutObject->Block( Name => 'TicketAppointmentRule', Data => { RuleNumber => $RuleNumber++, %{$Rule}, %TicketAppointmentRule, }, ); # Show any search parameter blocks too. $Self->_ShowTicketAppointmentParams( %{$Rule} ); # Save rule ID for button initialization. push @RuleIDs, $Rule->{RuleID}; } $LayoutObject->AddJSData( Key => 'RuleIDs', Value => \@RuleIDs, ); $Param{Action} = 'Update'; $Param{Name} = $Calendar{CalendarName}; return $Self->_Mask(%Param); } # update calendar my $Success = $CalendarObject->CalendarUpdate( %GetParam, UserID => $Self->{UserID}, ); if ( !$Success ) { return $LayoutObject->ErrorScreen( Message => Translatable('Error updating the calendar!'), Comment => Translatable('Please contact the administrator.'), ); } # Process ticket appointments in async call to core module method. $Self->AsyncCall( ObjectName => 'Kernel::System::Calendar', FunctionName => 'TicketAppointmentProcessCalendar', FunctionParams => { CalendarID => $GetParam{CalendarID}, }, ); # If the user would like to continue editing the calendar, just redirect to the edit screen. if ( defined $ParamObject->GetParam( Param => 'ContinueAfterSave' ) && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' ) ) { return $LayoutObject->Redirect( OP => "Action=$Self->{Action};Subaction=Edit;CalendarID=$GetParam{CalendarID}" ); } else { # Otherwise return to overview. return $LayoutObject->Redirect( OP => "Action=AdminAppointmentCalendarManage" ); } } elsif ( $Self->{Subaction} eq 'CalendarImport' ) { # challenge token check for write action $LayoutObject->ChallengeTokenCheck(); # get the uploaded file content my $FormID = $ParamObject->GetParam( Param => 'FormID' ) || ''; my %UploadStuff = $ParamObject->GetUploadAll( Param => 'FileUpload', ); my $Content = $UploadStuff{Content}; # check for overwriting option my $OverwriteExistingEntities = $ParamObject->GetParam( Param => 'OverwriteExistingEntities' ) || 0; # extract the team data from the uploaded file my $CalendarData = $Kernel::OM->Get('Kernel::System::YAML')->Load( Data => $Content ); if ( ref $CalendarData ne 'HASH' ) { return $LayoutObject->ErrorScreen( Message => Translatable("Couldn't read calendar configuration file."), Comment => Translatable('Please make sure your file is valid.'), ); } # import the calendar my $Success = $CalendarObject->CalendarImport( Data => $CalendarData, OverwriteExistingEntities => $OverwriteExistingEntities, UserID => $Self->{UserID}, ); if ( !$Success ) { $Param{NotifyMessage} = { Priority => Translatable('Error'), Info => Translatable('Could not import the calendar!'), }; } else { $Param{NotifyMessage} = { Info => Translatable('Calendar imported!'), }; } %Param = $Self->_Overview(%Param); } elsif ( $Self->{Subaction} eq 'CalendarExport' ) { # check for CalendarID my $CalendarID = $ParamObject->GetParam( Param => 'CalendarID' ) || ''; if ( !$CalendarID ) { return $LayoutObject->ErrorScreen( Message => Translatable('Need CalendarID!'), ); } # get calendar data my %CalendarData = $CalendarObject->CalendarExport( CalendarID => $CalendarID, UserID => $Self->{UserID}, ); if ( !IsHashRefWithData( \%CalendarData ) ) { return $LayoutObject->ErrorScreen( Message => Translatable('Could not retrieve data for given CalendarID'), ); } # convert the calendar data hash to string my $CalendarDataYAML = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => \%CalendarData ); # prepare calendar name to be part of the filename my $CalendarName = $CalendarData{CalendarData}->{CalendarName}; $CalendarName =~ s/\s+/_/g; # send the result to the browser return $LayoutObject->Attachment( ContentType => 'text/html; charset=' . $LayoutObject->{Charset}, Content => $CalendarDataYAML, Type => 'attachment', Filename => 'Export_Calendar_' . $CalendarName . '.yml', NoCache => 1, ); } else { if ( $ParamObject->GetParam( Param => 'ImportAppointmentsSuccess' ) || '' ) { $Param{NotifyMessage} = { Info => $LayoutObject->{LanguageObject}->Translate( 'Successfully imported %s appointment(s) to calendar %s.', $ParamObject->GetParam( Param => 'Count' ) || 0, $ParamObject->GetParam( Param => 'Name' ) || '', ), }; } %Param = $Self->_Overview(%Param); } return $Self->_Mask(%Param); } sub _Overview { my ( $Self, %Param ) = @_; my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar'); # get all calendars user has RW access to my @Calendars = $CalendarObject->CalendarList( UserID => $Self->{UserID}, Permission => 'rw', ); my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); $LayoutObject->Block( Name => 'CalendarFilter', ); $LayoutObject->Block( Name => 'Overview', Data => { Title => Translatable('Calendars'), }, ); $Param{ValidCount} = 0; for my $Calendar (@Calendars) { # group name $Calendar->{Group} = $Kernel::OM->Get('Kernel::System::Group')->GroupLookup( GroupID => $Calendar->{GroupID}, ); # valid text $Calendar->{Valid} = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup( ValidID => $Calendar->{ValidID}, ); if ( $Calendar->{ValidID} == 1 ) { $Param{ValidCount}++; } # get access tokens $Calendar->{AccessToken} = $CalendarObject->GetAccessToken( CalendarID => $Calendar->{CalendarID}, UserLogin => $Self->{UserLogin}, ); $LayoutObject->Block( Name => 'Calendar', Data => { %{$Calendar}, }, ); } if ( scalar @Calendars == 0 ) { $LayoutObject->Block( Name => 'CalendarNoDataRow', ); } $Param{Overview} = 1; $LayoutObject->Block( Name => 'MainActions', Data => { %Param, }, ); $LayoutObject->Block( Name => 'ActionImport' ); return %Param; } sub _Mask { my ( $Self, %Param ) = @_; # get needed objects my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # output page my $Output = $LayoutObject->Header(); $Output .= $LayoutObject->NavigationBar(); if ( $Param{NotifyMessage} ) { $Output .= $LayoutObject->Notify( %{ $Param{NotifyMessage} }, ); } $Output .= $LayoutObject->Output( TemplateFile => 'AdminAppointmentCalendarManage', Data => { Title => Translatable('Edit Calendar'), %Param, }, ); $Output .= $LayoutObject->Footer(); return $Output; } sub _GroupSelectionGet { my ( $Self, %Param ) = @_; # get list of groups where user has RW permissions my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( UserID => $Self->{UserID}, Type => 'rw', ); my $GroupSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection( Data => \%GroupList, Name => 'GroupID', SelectedID => $Param{GroupID} || '', Translation => 0, Class => 'Modernize Validate_Required', ); return $GroupSelection; } sub _ColorPaletteGet { my ( $Self, %Param ) = @_; # get color palette my $CalendarColors = $Kernel::OM->Get('Kernel::Config')->Get('AppointmentCalendar::CalendarColors') || [ '#000000', '#1E1E1E', '#3A3A3A', '#545453', '#6E6E6E', '#878687', '#888787', '#A09FA0', '#B8B8B8', '#D0D0D0', '#E8E8E8', '#FFFFFF', '#891100', '#894800', '#888501', '#458401', '#028401', '#018448', '#008688', '#004A88', '#001888', '#491A88', '#891E88', '#891648', '#FF2101', '#FF8802', '#FFFA03', '#83F902', '#05F802', '#03F987', '#00FDFF', '#008CFF', '#002EFF', '#8931FF', '#FF39FF', '#FF2987', '#FF726E', '#FFCE6E', '#FFFB6D', '#CEFA6E', '#68F96E', '#68FDFF', '#68FBD0', '#6ACFFF', '#6E76FF', '#D278FF', '#FF7AFF', '#FF7FD3', ]; return $CalendarColors; } sub _ValidSelectionGet { my ( $Self, %Param ) = @_; my %Valid = $Kernel::OM->Get('Kernel::System::Valid')->ValidList(); my $ValidSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection( Data => \%Valid, Name => 'ValidID', ID => 'ValidID', Class => 'Modernize Validate_Required', SelectedID => $Param{ValidID} || 1, Title => Translatable("Valid"), ); return $ValidSelection; } sub _UserSelectionGet { my ( $Self, %Param ) = @_; my %UserList = $Kernel::OM->Get('Kernel::System::User')->UserList( Type => 'Long' ); my $UserSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection( Data => \%UserList, Name => ( $Param{RuleID} && $Param{ParamName} ) ? 'SearchParam_' . $Param{RuleID} . '_' . $Param{ParamName} : 'SearchParamUser', Multiple => 1, Class => 'SearchParam Modernize Validate_Required', SelectedID => $Param{ParamValue} || [], ); # Add param name as data attribute for later reference in JS. if ( $Param{ParamName} ) { $UserSelection =~ s/