# -- # 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::CustomerTicketOverview; ## nofilter(TidyAll::Plugin::OTRS::Perl::DBObject) use strict; use warnings; our $ObjectManagerDisabled = 1; use Kernel::System::VariableCheck qw(:all); use Kernel::Language qw(Translatable); sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {%Param}; bless( $Self, $Type ); return $Self; } sub Run { my ( $Self, %Param ) = @_; my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); # disable output of customer company tickets my $DisableCompanyTickets = $ConfigObject->Get('Ticket::Frontend::CustomerDisableCompanyTicketAccess'); # check subaction if ( !$Self->{Subaction} ) { return $LayoutObject->Redirect( OP => 'Action=CustomerTicketOverview;Subaction=MyTickets', ); } elsif ( $Self->{Subaction} eq 'CompanyTickets' && $DisableCompanyTickets ) { return $LayoutObject->CustomerNoPermission( WithHeader => 'yes' ); } # check needed CustomerID if ( !$Self->{UserCustomerID} ) { my $Output = $LayoutObject->CustomerHeader( Title => Translatable('Error'), ); $Output .= $LayoutObject->CustomerError( Message => Translatable('Need CustomerID!'), ); $Output .= $LayoutObject->CustomerFooter(); return $Output; } $Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID( SessionID => $Self->{SessionID}, Key => 'LastScreenOverview', Value => $Self->{RequestedURL}, ); my $SortBy = $ParamObject->GetParam( Param => 'SortBy' ) || 'Age'; my $OrderByCurrent = $ParamObject->GetParam( Param => 'OrderBy' ) || 'Down'; # filter definition my %Filters = ( MyTickets => { All => { Name => 'All', Prio => 1000, Search => { CustomerUserLoginRaw => $Self->{UserID}, OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, Open => { Name => 'Open', Prio => 1100, Search => { CustomerUserLoginRaw => $Self->{UserID}, StateType => 'Open', OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, Closed => { Name => 'Closed', Prio => 1200, Search => { CustomerUserLoginRaw => $Self->{UserID}, StateType => 'Closed', OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, }, ); my $UserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); # Add filter for customer company if the company tickets are not disabled. if ( !$DisableCompanyTickets ) { my @CustomerIDs; my %AccessibleCustomers = $Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupContextCustomers( CustomerUserID => $Self->{UserID}, ); # Show customer companies as additional filter selection. if ( $Self->{Subaction} eq 'CompanyTickets' && scalar keys %AccessibleCustomers > 1 ) { @CustomerIDs = $ParamObject->GetArray( Param => 'CustomerIDs' ); $Param{CustomerIDStrg} = $LayoutObject->BuildSelection( Data => \%AccessibleCustomers, Name => 'CustomerIDs', Multiple => 1, Size => 1, SelectedID => \@CustomerIDs, Class => 'Modernize', ); # Remember the active customer ID filter. $Param{CustomerIDs} = ''; for my $CustomerID (@CustomerIDs) { $Param{CustomerIDs} .= ';CustomerIDs=' . $LayoutObject->LinkEncode($CustomerID); } } else { # Default behavior - use all available customer IDs. @CustomerIDs = sort keys %AccessibleCustomers; } $Filters{CompanyTickets} = { All => { Name => 'All', Prio => 1000, Search => { CustomerIDRaw => \@CustomerIDs, OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, Open => { Name => 'Open', Prio => 1100, Search => { CustomerIDRaw => \@CustomerIDs, StateType => 'Open', OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, Closed => { Name => 'Closed', Prio => 1200, Search => { CustomerIDRaw => \@CustomerIDs, StateType => 'Closed', OrderBy => $OrderByCurrent, SortBy => $SortBy, CustomerUserID => $Self->{UserID}, Permission => 'ro', }, }, }; } my $FilterCurrent = $ParamObject->GetParam( Param => 'Filter' ) || 'Open'; # check if filter is valid if ( !$Filters{ $Self->{Subaction} }->{$FilterCurrent} ) { my $Output = $LayoutObject->CustomerHeader( Title => Translatable('Error'), ); $Output .= $LayoutObject->CustomerError( Message => $LayoutObject->{LanguageObject}->Translate( 'Invalid Filter: %s!', $FilterCurrent ), ); $Output .= $LayoutObject->CustomerFooter(); return $Output; } # check if archive search is allowed, otherwise search for all tickets my %SearchInArchive; if ( $ConfigObject->Get('Ticket::ArchiveSystem') && !$ConfigObject->Get('Ticket::CustomerArchiveSystem') ) { $SearchInArchive{ArchiveFlags} = [ 'y', 'n' ]; } my %NavBarFilter; my $Counter = 0; my $AllTickets = 0; my $AllTicketsTotal = 0; my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); for my $Filter ( sort keys %{ $Filters{ $Self->{Subaction} } } ) { $Counter++; my $Count = $TicketObject->TicketSearch( %{ $Filters{ $Self->{Subaction} }->{$Filter}->{Search} }, %SearchInArchive, Result => 'COUNT', ) || 0; my $ClassA = ''; if ( $Filter eq $FilterCurrent ) { $ClassA = 'Selected'; $AllTickets = $Count; } if ( $Filter eq 'All' ) { $AllTicketsTotal = $Count; } $NavBarFilter{ $Filters{ $Self->{Subaction} }->{$Filter}->{Prio} } = { %{ $Filters{ $Self->{Subaction} }->{$Filter} }, Count => $Count, Filter => $Filter, ClassA => $ClassA, }; } my $StartHit = int( $ParamObject->GetParam( Param => 'StartHit' ) || 1 ); my $PageShown = $Self->{UserShowTickets} || 1; if ( !$AllTicketsTotal ) { $LayoutObject->Block( Name => 'Empty', ); my $CustomTexts = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverviewCustomEmptyText'); if ( ref $CustomTexts eq 'HASH' ) { $LayoutObject->Block( Name => 'EmptyCustom', Data => $CustomTexts, ); # only show button, if frontend module for NewTicket is registered # and button text is configured if ( ref $ConfigObject->Get('CustomerFrontend::Module')->{CustomerTicketMessage} eq 'HASH' && defined $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverviewCustomEmptyText') ->{Button} ) { $LayoutObject->Block( Name => 'EmptyCustomButton', Data => $CustomTexts, ); } } else { $LayoutObject->Block( Name => 'EmptyDefault', ); # only show button, if frontend module for NewTicket is registered if ( ref $ConfigObject->Get('CustomerFrontend::Module')->{CustomerTicketMessage} eq 'HASH' ) { $LayoutObject->Block( Name => 'EmptyDefaultButton', ); } } } else { # create & return output my $Link = 'SortBy=' . $LayoutObject->Ascii2Html( Text => $SortBy ) . ';OrderBy=' . $LayoutObject->Ascii2Html( Text => $OrderByCurrent ) . ';Filter=' . $LayoutObject->Ascii2Html( Text => $FilterCurrent ) . ';Subaction=' . $LayoutObject->Ascii2Html( Text => $Self->{Subaction} ) . ';'; my %PageNav = $LayoutObject->PageNavBar( Limit => 10000, StartHit => $StartHit, PageShown => $PageShown, AllHits => $AllTickets, Action => 'Action=CustomerTicketOverview', Link => $Link, IDPrefix => 'CustomerTicketOverview', ); my $OrderBy = 'Down'; if ( $OrderByCurrent eq 'Down' ) { $OrderBy = 'Up'; } my $Sort = ''; my $StateSort = ''; my $TicketSort = ''; my $TitleSort = ''; my $AgeSort = ''; my $QueueSort = ''; my $OwnerSort = ''; # this sets the opposite to the $OrderBy if ( $OrderBy eq 'Down' ) { $Sort = 'SortAscending'; } if ( $OrderBy eq 'Up' ) { $Sort = 'SortDescending'; } if ( $SortBy eq 'State' ) { $StateSort = $Sort; } elsif ( $SortBy eq 'Ticket' ) { $TicketSort = $Sort; } elsif ( $SortBy eq 'Title' ) { $TitleSort = $Sort; } elsif ( $SortBy eq 'Age' ) { $AgeSort = $Sort; } elsif ( $SortBy eq 'Queue' ) { $QueueSort = $Sort; } elsif ( $SortBy eq 'Owner' ) { $OwnerSort = $Sort; } $LayoutObject->Block( Name => 'Filled', Data => { %Param, %PageNav, OrderBy => $OrderBy, StateSort => $StateSort, TicketSort => $TicketSort, TitleSort => $TitleSort, AgeSort => $AgeSort, Filter => $FilterCurrent, }, ); my $Owner = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverview')->{Owner}; my $Queue = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverview')->{Queue}; if ($Owner) { $LayoutObject->Block( Name => 'OverviewNavBarPageOwner', Data => { OrderBy => $OrderBy, OwnerSort => $OwnerSort, Filter => $FilterCurrent, }, ); } if ($Queue) { $LayoutObject->Block( Name => 'OverviewNavBarPageQueue', Data => { OrderBy => $OrderBy, QueueSort => $QueueSort, Filter => $FilterCurrent, }, ); } # show header filter for my $Key ( sort keys %NavBarFilter ) { $LayoutObject->Block( Name => 'FilterHeader', Data => { %Param, %{ $NavBarFilter{$Key} }, }, ); } # show footer filter - show only if more the one page is available if ( $AllTickets > $PageShown ) { $LayoutObject->Block( Name => 'FilterFooter', Data => { %Param, %PageNav, }, ); } for my $Key ( sort keys %NavBarFilter ) { if ( $AllTickets > $PageShown ) { $LayoutObject->Block( Name => 'FilterFooterItem', Data => { %{ $NavBarFilter{$Key} }, }, ); } } # get the dynamic fields for this screen my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); # get dynamic field config for frontend module my $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::CustomerTicketOverview")->{DynamicField}; my $DynamicField = $DynamicFieldObject->DynamicFieldListGet( Valid => 1, ObjectType => ['Ticket'], FieldFilter => $DynamicFieldFilter || {}, ); # reduce the dynamic fields to only the ones that are desinged for customer interface my @CustomerDynamicFields; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $IsCustomerInterfaceCapable = $BackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsCustomerInterfaceCapable', ); next DYNAMICFIELD if !$IsCustomerInterfaceCapable; push @CustomerDynamicFields, $DynamicFieldConfig; } $DynamicField = \@CustomerDynamicFields; # Dynamic fields # cycle trough the activated Dynamic Fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $Label = $DynamicFieldConfig->{Label}; # get field sortable condition my $IsSortable = $BackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsSortable', ); if ($IsSortable) { my $CSS = ''; if ( $SortBy && ( $SortBy eq ( 'DynamicField_' . $DynamicFieldConfig->{Name} ) ) ) { if ( $OrderByCurrent && ( $OrderByCurrent eq 'Up' ) ) { $OrderBy = 'Down'; $CSS .= ' SortDescending'; } else { $OrderBy = 'Up'; $CSS .= ' SortAscending'; } } $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField', Data => { %Param, CSS => $CSS, }, ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldSortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, Filter => $FilterCurrent, }, ); # example of dynamic fields order customization $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name}, Data => { %Param, CSS => $CSS, }, ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name} . '_Sortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, Filter => $FilterCurrent, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField', Data => { %Param, }, ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldNotSortable', Data => { %Param, Label => $Label, }, ); # example of dynamic fields order customization $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name}, Data => { %Param, }, ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name} . '_NotSortable', Data => { %Param, Label => $Label, }, ); } } my @ViewableTickets = $TicketObject->TicketSearch( %{ $Filters{ $Self->{Subaction} }->{$FilterCurrent}->{Search} }, %SearchInArchive, Result => 'ARRAY', ); # show tickets $Counter = 0; for my $TicketID (@ViewableTickets) { $Counter++; if ( $Counter >= $StartHit && $Counter < ( $PageShown + $StartHit ) ) { $Self->ShowTicketStatus( TicketID => $TicketID ); } } } # create & return output my $Title = $Self->{Subaction}; if ( $Title eq 'MyTickets' ) { $Title = Translatable('My Tickets'); } elsif ( $Title eq 'CompanyTickets' ) { $Title = Translatable('Company Tickets'); } my $Refresh = ''; if ( $Self->{UserRefreshTime} ) { $Refresh = 60 * $Self->{UserRefreshTime}; } my $Output = $LayoutObject->CustomerHeader( Title => $Title, Refresh => $Refresh, ); # build NavigationBar $Output .= $LayoutObject->CustomerNavigationBar(); $Output .= $LayoutObject->Output( TemplateFile => 'CustomerTicketOverview', Data => \%Param, ); # get page footer $Output .= $LayoutObject->CustomerFooter(); # return page return $Output; } # ShowTicket sub ShowTicketStatus { my ( $Self, %Param ) = @_; my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); my $CommunicationChannelObject = $Kernel::OM->Get('Kernel::System::CommunicationChannel'); my $TicketID = $Param{TicketID} || return; # Get last customer article. my @ArticleList = $ArticleObject->ArticleList( TicketID => $Param{TicketID}, IsVisibleForCustomer => 1, OnlyLast => 1, ); my %Article; if ( $ArticleList[0] && IsHashRefWithData( $ArticleList[0] ) ) { my $ArticleBackendObject = $ArticleObject->BackendForArticle( %{ $ArticleList[0] } ); %Article = $ArticleBackendObject->ArticleGet( TicketID => $Param{TicketID}, ArticleID => $ArticleList[0]->{ArticleID}, ); } # get ticket info my %Ticket = $TicketObject->TicketGet( TicketID => $TicketID, DynamicFields => 0, ); my $Subject; my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $SmallViewColumnHeader = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverview')->{ColumnHeader}; # Check if the last customer subject or ticket title should be shown. # If ticket title should be shown, check if there are articles, because ticket title # could be related with a subject of an article which does not visible for customer (see bug#13614). # If there is no subject, set to 'Untitled'. if ( $SmallViewColumnHeader eq 'LastCustomerSubject' ) { $Subject = $Article{Subject} || ''; } elsif ( $SmallViewColumnHeader eq 'TicketTitle' && $ArticleList[0] ) { $Subject = $Ticket{Title}; } else { $Subject = Translatable('Untitled!'); } # Condense down the subject. $Subject = $TicketObject->TicketSubjectClean( TicketNumber => $Ticket{TicketNumber}, Subject => $Subject, ); # Age design. $Ticket{CustomerAge} = $LayoutObject->CustomerAge( Age => $Ticket{Age}, Space => ' ' ) || 0; # return ticket information if there is no article if ( !IsHashRefWithData( \%Article ) ) { $Article{State} = $Ticket{State}; $Article{TicketNumber} = $Ticket{TicketNumber}; $Article{Body} = $LayoutObject->{LanguageObject}->Translate('This item has no articles yet.'); } # customer info (customer name) if ( $Article{CustomerUserID} ) { $Param{CustomerName} = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerName( UserLogin => $Article{CustomerUserID}, ); $Param{CustomerName} = '(' . $Param{CustomerName} . ')' if ( $Param{CustomerName} ); } # add block $LayoutObject->Block( Name => 'Record', Data => { %Article, %Ticket, Subject => $Subject, %Param, }, ); my $Owner = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverview')->{Owner}; my $Queue = $ConfigObject->Get('Ticket::Frontend::CustomerTicketOverview')->{Queue}; if ($Owner) { my $OwnerName = $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $Ticket{OwnerID} ); $LayoutObject->Block( Name => 'RecordOwner', Data => { OwnerName => $OwnerName, }, ); } if ($Queue) { $LayoutObject->Block( Name => 'RecordQueue', Data => { %Ticket, }, ); } # get the dynamic fields for this screen my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); # get dynamic field config for frontend module my $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::CustomerTicketOverview")->{DynamicField}; my $DynamicField = $DynamicFieldObject->DynamicFieldListGet( Valid => 1, ObjectType => ['Ticket'], FieldFilter => $DynamicFieldFilter || {}, ); # reduce the dynamic fields to only the ones that are desinged for customer interface my @CustomerDynamicFields; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $IsCustomerInterfaceCapable = $BackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsCustomerInterfaceCapable', ); next DYNAMICFIELD if !$IsCustomerInterfaceCapable; push @CustomerDynamicFields, $DynamicFieldConfig; } $DynamicField = \@CustomerDynamicFields; # Dynamic fields # cycle trough the activated Dynamic Fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # get field value my $Value = $BackendObject->ValueGet( DynamicFieldConfig => $DynamicFieldConfig, ObjectID => $Ticket{TicketID}, ); my $ValueStrg = $BackendObject->DisplayValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $Value, ValueMaxChars => 20, LayoutObject => $LayoutObject, ); $LayoutObject->Block( Name => 'RecordDynamicField', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, }, ); if ( $ValueStrg->{Link} ) { $LayoutObject->Block( Name => 'RecordDynamicFieldLink', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, Link => $ValueStrg->{Link}, $DynamicFieldConfig->{Name} => $ValueStrg->{Title}, }, ); } else { $LayoutObject->Block( Name => 'RecordDynamicFieldPlain', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, }, ); } # example of dynamic fields order customization $LayoutObject->Block( Name => 'RecordDynamicField' . $DynamicFieldConfig->{Name}, Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, }, ); if ( $ValueStrg->{Link} ) { $LayoutObject->Block( Name => 'RecordDynamicField' . $DynamicFieldConfig->{Name} . 'Link', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, Link => $ValueStrg->{Link}, $DynamicFieldConfig->{Name} => $ValueStrg->{Title}, }, ); } else { $LayoutObject->Block( Name => 'RecordDynamicField' . $DynamicFieldConfig->{Name} . 'Plain', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, }, ); } } return; } 1;