# -- # 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::Output::HTML::TicketOverview::Small; use strict; use warnings; use Kernel::System::VariableCheck qw(:all); use Kernel::Language qw(Translatable); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::Language', 'Kernel::System::Log', 'Kernel::Output::HTML::Layout', 'Kernel::System::CustomerUser', 'Kernel::System::Group', 'Kernel::System::User', 'Kernel::System::JSON', 'Kernel::System::DynamicField', 'Kernel::System::Ticket::ColumnFilter', 'Kernel::System::DynamicField::Backend', 'Kernel::System::Ticket', 'Kernel::System::Ticket::Article', 'Kernel::System::Main', ); sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = \%Param; bless( $Self, $Type ); # get UserID param $Self->{UserID} = $Param{UserID} || die "Got no UserID!"; # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # set pref for columns key $Self->{PrefKeyColumns} = 'UserFilterColumnsEnabled' . '-' . $Self->{Action}; # load backend config my $BackendConfigKey = 'Ticket::Frontend::' . $Self->{Action}; $Self->{Config} = $ConfigObject->Get($BackendConfigKey); my %Preferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences( UserID => $Self->{UserID}, ); # get JSON object my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON'); # set stored filters if present my $StoredFiltersKey = 'UserStoredFilterColumns-' . $Self->{Action}; if ( $Preferences{$StoredFiltersKey} ) { my $StoredFilters = $JSONObject->Decode( Data => $Preferences{$StoredFiltersKey}, ); $Self->{StoredFilters} = $StoredFilters; } # get the configured dyanmic fields from the Small Overview setting as a basis my %DefaultDynamicFields = %{ $ConfigObject->Get("Ticket::Frontend::OverviewSmall")->{DynamicField} || {} }; my %DefaultColumns = map { 'DynamicField_' . $_ => $DefaultDynamicFields{$_} } sort keys %DefaultDynamicFields; # take general settings (Frontend::Agent) if not defined for the screen $Self->{Config}->{DefaultColumns} //= $ConfigObject->Get('DefaultOverviewColumns'); # check for default settings specific for this screen, should overide the dynamic fields %DefaultColumns = ( %DefaultColumns, %{ $Self->{Config}->{DefaultColumns} || {} } ); # configure columns my @ColumnsAvailable = grep { $DefaultColumns{$_} ne '0' } sort keys %DefaultColumns; my @ColumnsEnabled = grep { $DefaultColumns{$_} eq '2' } sort _DefaultColumnSort keys %DefaultColumns; # if preference settings are available, take them if ( $Preferences{ $Self->{PrefKeyColumns} } ) { my $ColumnsEnabled = $JSONObject->Decode( Data => $Preferences{ $Self->{PrefKeyColumns} }, ); # remove duplicate columns my %UniqueColumns; my @ColumnsEnabledAux; for my $Column ( @{$ColumnsEnabled} ) { if ( !$UniqueColumns{$Column} ) { push @ColumnsEnabledAux, $Column; } $UniqueColumns{$Column} = 1; } # set filtered column list @ColumnsEnabled = @ColumnsEnabledAux; } # always set TicketNumber if ( !grep { $_ eq 'TicketNumber' } @ColumnsEnabled ) { unshift @ColumnsEnabled, 'TicketNumber'; } $Self->{ColumnsEnabled} = \@ColumnsEnabled; $Self->{ColumnsAvailable} = \@ColumnsAvailable; { # loop through all the dynamic fields to get the ones that should be shown DYNAMICFIELDNAME: for my $DynamicFieldName (@ColumnsEnabled) { next DYNAMICFIELDNAME if $DynamicFieldName !~ m{ DynamicField_ }xms; # remove dynamic field prefix my $FieldName = $DynamicFieldName; $FieldName =~ s/DynamicField_//gi; $Self->{DynamicFieldFilter}->{$FieldName} = 1; } } # get the dynamic fields for this screen $Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( Valid => 1, ObjectType => ['Ticket'], FieldFilter => $Self->{DynamicFieldFilter} || {}, ); # hash with all valid sortable columns (taken from TicketSearch) # SortBy => 'Age', # Created|Owner|Responsible|CustomerID|State|TicketNumber|Queue # |Priority|Type|Lock|Title|Service|Changed|SLA|PendingTime|EscalationTime # | EscalationUpdateTime|EscalationResponseTime|EscalationSolutionTime $Self->{ValidSortableColumns} = { 'Age' => 1, 'Created' => 1, 'Owner' => 1, 'Responsible' => 1, 'CustomerID' => 1, 'State' => 1, 'TicketNumber' => 1, 'Queue' => 1, 'Priority' => 1, 'Type' => 1, 'Lock' => 1, 'Title' => 1, 'Service' => 1, 'Changed' => 1, 'SLA' => 1, 'PendingTime' => 1, 'EscalationTime' => 1, 'EscalationUpdateTime' => 1, 'EscalationResponseTime' => 1, 'EscalationSolutionTime' => 1, }; $Self->{AvailableFilterableColumns} = { 'Owner' => 1, 'Responsible' => 1, 'CustomerID' => 1, 'CustomerUserID' => 1, 'State' => 1, 'Queue' => 1, 'Priority' => 1, 'Type' => 1, 'Lock' => 1, 'Service' => 1, 'SLA' => 1, }; # remove queue from filters on AgentTicketQueue if ( $Self->{Action} eq 'AgentTicketQueue' ) { delete $Self->{AvailableFilterableColumns}->{Queue}; } # remove service from filters on AgentTicketService if ( $Self->{Action} eq 'AgentTicketService' ) { delete $Self->{AvailableFilterableColumns}->{Service}; } # get dynamic field backend object my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); # get filterable dynamic fields # cycle trough the activated dynamic fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $IsFiltrable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsFiltrable', ); # if the dynamic field is filterable add it to the AvailableFilterableColumns hash if ($IsFiltrable) { $Self->{AvailableFilterableColumns}->{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = 1; } } # get sortable dynamic fields # cycle trough the activated dynamic fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $IsSortable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsSortable', ); # if the dynamic field is sortable add it to the ValidSortableColumns hash if ($IsSortable) { $Self->{ValidSortableColumns}->{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = 1; } } return $Self; } sub ActionRow { my ( $Self, %Param ) = @_; # get needed objects my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # check if bulk feature is enabled my $BulkFeature = 0; if ( $Param{Bulk} && $ConfigObject->Get('Ticket::Frontend::BulkFeature') ) { my @Groups; if ( $ConfigObject->Get('Ticket::Frontend::BulkFeatureGroup') ) { @Groups = @{ $ConfigObject->Get('Ticket::Frontend::BulkFeatureGroup') }; } if ( !@Groups ) { $BulkFeature = 1; } else { my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); GROUP: for my $Group (@Groups) { my $HasPermission = $GroupObject->PermissionCheck( UserID => $Self->{UserID}, GroupName => $Group, Type => 'rw', ); if ($HasPermission) { $BulkFeature = 1; last GROUP; } } } } $LayoutObject->Block( Name => 'DocumentActionRow', Data => \%Param, ); if ($BulkFeature) { $LayoutObject->Block( Name => 'DocumentActionRowBulk', Data => { %Param, Name => Translatable('Bulk'), }, ); } # check if there was a column filter and no results, and print a link to back if ( scalar @{ $Param{TicketIDs} } == 0 && $Param{LastColumnFilter} ) { $LayoutObject->Block( Name => 'DocumentActionRowLastColumnFilter', Data => { %Param, }, ); } my %ColumnTranslations; my $LanguageObject = $Kernel::OM->Get('Kernel::Language'); # add translations for the allocation lists for regular columns my $Columns = $Self->{Config}->{DefaultColumns} || $ConfigObject->Get('DefaultOverviewColumns') || {}; if ( $Columns && IsHashRefWithData($Columns) ) { COLUMN: for my $Column ( sort keys %{$Columns} ) { # dynamic fields will be translated in the next block next COLUMN if $Column =~ m{ \A DynamicField_ }xms; my $TranslatedWord = $Column; if ( $Column eq 'EscalationTime' ) { $TranslatedWord = Translatable('Service Time'); } elsif ( $Column eq 'EscalationResponseTime' ) { $TranslatedWord = Translatable('First Response Time'); } elsif ( $Column eq 'EscalationSolutionTime' ) { $TranslatedWord = Translatable('Solution Time'); } elsif ( $Column eq 'EscalationUpdateTime' ) { $TranslatedWord = Translatable('Update Time'); } elsif ( $Column eq 'PendingTime' ) { $TranslatedWord = Translatable('Pending till'); } elsif ( $Column eq 'CustomerCompanyName' ) { $TranslatedWord = Translatable('Customer Name'); } elsif ( $Column eq 'CustomerID' ) { $TranslatedWord = Translatable('Customer ID'); } elsif ( $Column eq 'CustomerName' ) { $TranslatedWord = Translatable('Customer User Name'); } elsif ( $Column eq 'CustomerUserID' ) { $TranslatedWord = Translatable('Customer User ID'); } # send data to JS $LayoutObject->AddJSData( Key => 'Column' . $Column, Value => $LanguageObject->Translate($TranslatedWord), ); } } # add translations for the allocation lists for dynamic field columns my $ColumnsDynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( Valid => 0, ObjectType => ['Ticket'], ); if ( $ColumnsDynamicField && IsArrayRefWithData($ColumnsDynamicField) ) { my $Counter = 0; DYNAMICFIELD: for my $DynamicField ( sort @{$ColumnsDynamicField} ) { next DYNAMICFIELD if !$DynamicField; $Counter++; # send data to JS $LayoutObject->AddJSData( Key => 'ColumnDynamicField_' . $DynamicField->{Name}, Value => $LanguageObject->Translate( $DynamicField->{Label} ), ); } } my $Output = $LayoutObject->Output( TemplateFile => 'AgentTicketOverviewSmall', Data => \%Param, ); return $Output; } sub SortOrderBar { my ( $Self, %Param ) = @_; return ''; } sub Run { my ( $Self, %Param ) = @_; # If $Param{EnableColumnFilters} is not sent, we want to disable all filters # for the current screen. We localize the setting for this sub and change it # after that, if needed. The original value will be restored after this function. local $Self->{AvailableFilterableColumns} = $Self->{AvailableFilterableColumns}; if ( !$Param{EnableColumnFilters} ) { $Self->{AvailableFilterableColumns} = {}; # disable all column filters } for my $Item (qw(TicketIDs PageShown StartHit)) { if ( !$Param{$Item} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Item!", ); return; } } # get needed objects my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # check if bulk feature is enabled my $BulkFeature = 0; if ( $Param{Bulk} && $ConfigObject->Get('Ticket::Frontend::BulkFeature') ) { my @Groups; if ( $ConfigObject->Get('Ticket::Frontend::BulkFeatureGroup') ) { @Groups = @{ $ConfigObject->Get('Ticket::Frontend::BulkFeatureGroup') }; } if ( !@Groups ) { $BulkFeature = 1; } else { my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); GROUP: for my $Group (@Groups) { my $HasPermission = $GroupObject->PermissionCheck( UserID => $Self->{UserID}, GroupName => $Group, Type => 'rw', ); if ($HasPermission) { $BulkFeature = 1; last GROUP; } } } } my $Counter = 0; my @ArticleBox; my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); # Get extended ticket data if columns are used, save performance. See bug#14748. my %ExtendedColumnsHash = ( FirstResponse => 1, FirstResponseInMin => 1, FirstResponseDiffInMin => 1, SolutionInMin => 1, SolutionDiffInMin => 1, FirstLock => 1, ); my $Extended = 0; if ( grep { $ExtendedColumnsHash{$_} } @{ $Self->{ColumnsEnabled} } ) { $Extended = 1; } for my $TicketID ( @{ $Param{TicketIDs} } ) { $Counter++; if ( $Counter >= $Param{StartHit} && $Counter < ( $Param{PageShown} + $Param{StartHit} ) ) { # Get last customer article. my @Articles = $ArticleObject->ArticleList( TicketID => $TicketID, SenderType => 'customer', OnlyLast => 1, ); # If the ticket has no customer article, get the last agent article. if ( !@Articles ) { @Articles = $ArticleObject->ArticleList( TicketID => $TicketID, SenderType => 'agent', OnlyLast => 1, ); } # Finally, if everything failed, get latest article. if ( !@Articles ) { @Articles = $ArticleObject->ArticleList( TicketID => $TicketID, OnlyLast => 1, ); } my %Article; for my $Article (@Articles) { %Article = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet( %{$Article}, DynamicFields => 0, ); } # Get ticket data. my %Ticket = $TicketObject->TicketGet( TicketID => $TicketID, Extended => $Extended, DynamicFields => 0, ); %Article = ( %Article, %Ticket ); # Get channel specific fields. if ( $Article{ArticleID} ) { my %ArticleFields = $LayoutObject->ArticleFields( TicketID => $TicketID, ArticleID => $Article{ArticleID}, ); FIELD: for my $FieldKey (qw(Sender Subject)) { next FIELD if !defined $ArticleFields{$FieldKey}->{Value}; $Article{$FieldKey} = $ArticleFields{$FieldKey}->{Realname} // $ArticleFields{$FieldKey}->{Value}; } } # Fallback for tickets without articles: get at least basic ticket data. if ( !%Article ) { %Article = %Ticket; if ( !$Article{Title} ) { $Article{Title} = $LayoutObject->{LanguageObject}->Translate( 'This ticket has no title or subject' ); } $Article{Subject} = $Article{Title}; } # show ticket create time in small view $Article{Created} = $Ticket{Created}; # create human age $Article{Age} = $LayoutObject->CustomerAge( Age => $Article{Age}, Space => ' ', ); # get ACL restrictions my %PossibleActions; my $Counter = 0; # get all registered Actions if ( ref $ConfigObject->Get('Frontend::Module') eq 'HASH' ) { my %Actions = %{ $ConfigObject->Get('Frontend::Module') }; # only use those Actions that stats with AgentTicket %PossibleActions = map { ++$Counter => $_ } grep { substr( $_, 0, length 'AgentTicket' ) eq 'AgentTicket' } sort keys %Actions; } my $ACL = $TicketObject->TicketAcl( Data => \%PossibleActions, Action => $Self->{Action}, TicketID => $Article{TicketID}, ReturnType => 'Action', ReturnSubType => '-', UserID => $Self->{UserID}, ); my %AclAction = %PossibleActions; if ($ACL) { %AclAction = $TicketObject->TicketAclActionData(); } # run ticket pre menu modules my @ActionItems; if ( ref $ConfigObject->Get('Ticket::Frontend::PreMenuModule') eq 'HASH' ) { my %Menus = %{ $ConfigObject->Get('Ticket::Frontend::PreMenuModule') }; my @Items; MENU: for my $Menu ( sort keys %Menus ) { # load module if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Menus{$Menu}->{Module} ) ) { return $LayoutObject->FatalError(); } my $Object = $Menus{$Menu}->{Module}->new( %{$Self}, TicketID => $Article{TicketID}, ); # run module my $Item = $Object->Run( %Param, Ticket => \%Article, ACL => \%AclAction, Config => $Menus{$Menu}, ); next MENU if !$Item; next MENU if ref $Item ne 'HASH'; # add session id if needed if ( !$LayoutObject->{SessionIDCookie} && $Item->{Link} ) { $Item->{Link} .= ';' . $LayoutObject->{SessionName} . '=' . $LayoutObject->{SessionID}; } # create id $Item->{ID} = $Item->{Name}; $Item->{ID} =~ s/(\s|&|;)//ig; my $Output; if ( $Item->{Block} ) { $LayoutObject->Block( Name => $Item->{Block}, Data => $Item, ); $Output = $LayoutObject->Output( TemplateFile => 'AgentTicketOverviewSmall', Data => $Item, ); } else { $Output = '
  • ' . $LayoutObject->{LanguageObject}->Translate( $Item->{Name} ) . '
  • '; } $Output =~ s/\n+//g; $Output =~ s/\s+/ /g; $Output =~ s/<\!--.+?-->//g; push @ActionItems, { HTML => $Output, ID => $Item->{ID}, Link => $LayoutObject->{Baselink} . $Item->{Link}, Target => $Item->{Target}, PopupType => $Item->{PopupType}, Description => $Item->{Description}, }; $Article{ActionItems} = \@ActionItems; } } push @ArticleBox, \%Article; } } # check if sysconfig is a hash reference if ( IsArrayRefWithData( $Self->{ColumnsEnabled} ) ) { # check if column is really filterable COLUMNNAME: for my $ColumnName ( @{ $Self->{ColumnsEnabled} } ) { next COLUMNNAME if !grep { $_ eq $ColumnName } @{ $Self->{ColumnsEnabled} }; next COLUMNNAME if !$Self->{AvailableFilterableColumns}->{$ColumnName}; $Self->{ValidFilterableColumns}->{$ColumnName} = 1; } } my $ColumnValues = $Self->_GetColumnValues( OriginalTicketIDs => $Param{OriginalTicketIDs}, ); # send data to JS $LayoutObject->AddJSData( Key => 'LinkPage', Value => $Param{LinkPage}, ); $LayoutObject->Block( Name => 'DocumentContent', Data => \%Param, ); # array to save the column names to do the query my @Col = @{ $Self->{ColumnsEnabled} }; # define special ticket columns my %SpecialColumns = ( TicketNumber => 1, Owner => 1, Responsible => 1, CustomerID => 1, ); # get dynamic field backend object my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); $Param{OrderBy} = $Param{OrderBy} || 'Up'; my $TicketData = scalar @ArticleBox; if ($TicketData) { $LayoutObject->Block( Name => 'OverviewTable' ); $LayoutObject->Block( Name => 'TableHeader' ); if ($BulkFeature) { $LayoutObject->Block( Name => 'GeneralOverviewHeader', ); $LayoutObject->Block( Name => 'BulkNavBar', Data => \%Param, ); } # meta items my @TicketMetaItems = $LayoutObject->TicketMetaItemsCount(); for my $Item (@TicketMetaItems) { $LayoutObject->Block( Name => 'GeneralOverviewHeader', ); my $CSS = ''; my $OrderBy = $Param{OrderBy}; my $Link; my $Title = $LayoutObject->{LanguageObject}->Translate($Item); if ( $Param{SortBy} && ( $Param{SortBy} eq $Item ) ) { my $TitleDesc; # Change order for sorting column. $OrderBy = $OrderBy eq 'Up' ? 'Down' : 'Up'; if ( $OrderBy eq 'Down' ) { $CSS .= ' SortAscendingLarge'; $TitleDesc = Translatable('sorted ascending'); } else { $CSS .= ' SortDescendingLarge'; $TitleDesc = Translatable('sorted descending'); } $TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc); $Title .= ', ' . $TitleDesc; } $LayoutObject->Block( Name => 'OverviewNavBarPageFlag', Data => { CSS => $CSS, Title => $Title, }, ); if ( $Item eq 'New Article' ) { $LayoutObject->Block( Name => 'OverviewNavBarPageFlagEmpty', Data => { Name => $Item, } ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageFlagLink', Data => { %Param, Name => $Item, CSS => $CSS, OrderBy => $OrderBy, Title => $Title, }, ); } } my $CSS = ''; # show special ticket columns, if needed COLUMN: for my $Column (@Col) { $LayoutObject->Block( Name => 'GeneralOverviewHeader', ); $CSS = $Column; my $Title = $LayoutObject->{LanguageObject}->Translate($Column); my $OrderBy = $Param{OrderBy}; # output overall block so TicketNumber as well as other columns can be ordered $LayoutObject->Block( Name => 'OverviewNavBarPageTicketHeader', Data => {}, ); if ( $SpecialColumns{$Column} ) { if ( $Param{SortBy} && ( $Param{SortBy} eq $Column ) ) { my $TitleDesc; # Change order for sorting column. $OrderBy = $OrderBy eq 'Up' ? 'Down' : 'Up'; if ( $OrderBy eq 'Down' ) { $CSS .= ' SortAscendingLarge'; $TitleDesc = Translatable('sorted ascending'); } else { $CSS .= ' SortDescendingLarge'; $TitleDesc = Translatable('sorted descending'); } $TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc); $Title .= ', ' . $TitleDesc; } # translate the column name to write it in the current language my $TranslatedWord; if ( $Column eq 'CustomerID' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Customer ID'); } else { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate($Column); } my $FilterTitle = $TranslatedWord; my $FilterTitleDesc = Translatable('filter not active'); if ( $Self->{StoredFilters} && ( $Self->{StoredFilters}->{$Column} || $Self->{StoredFilters}->{ $Column . 'IDs' } ) ) { $CSS .= ' FilterActive'; $FilterTitleDesc = Translatable('filter active'); } $FilterTitleDesc = $LayoutObject->{LanguageObject}->Translate($FilterTitleDesc); $FilterTitle .= ', ' . $FilterTitleDesc; $LayoutObject->Block( Name => $Column eq 'TicketNumber' ? 'OverviewNavBarPageTicketNumber' : 'OverviewNavBarPageColumn', Data => { %Param, OrderBy => $OrderBy, ColumnName => $Column || '', CSS => $CSS || '', ColumnNameTranslated => $TranslatedWord || $Column, Title => $Title, }, ); # verify if column is filterable and sortable if ( $Self->{ValidFilterableColumns}->{$Column} && $Self->{ValidSortableColumns}->{$Column} ) { my $Css; if ( $Column eq 'CustomerID' || $Column eq 'Owner' || $Column eq 'Responsible' ) { $Css .= ' Hidden'; } # variable to save the filter's html code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $Column, Label => $Column, ColumnValues => $ColumnValues->{$Column}, SelectedValue => $Param{GetColumnFilter}->{$Column} || '', Css => $Css, ); $LayoutObject->Block( Name => 'OverviewNavBarPageColumnFilterLink', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, ColumnFilterStrg => $ColumnFilterHTML, OrderBy => $OrderBy, Title => $Title, FilterTitle => $FilterTitle, }, ); if ( $Column eq 'CustomerID' ) { $LayoutObject->Block( Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkCustomerIDSearch', Data => {}, ); # send data to JS $LayoutObject->AddJSData( Key => 'CustomerIDAutocomplete', Value => { 'QueryDelay' => 100, 'MaxResultsDisplayed' => 20, 'MinQueryLength' => 2, }, ); } elsif ( $Column eq 'Owner' || $Column eq 'Responsible' ) { $LayoutObject->Block( Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkUserSearch', Data => {}, ); # send data to JS $LayoutObject->AddJSData( Key => 'UserAutocomplete', Value => { 'QueryDelay' => 100, 'MaxResultsDisplayed' => 20, 'MinQueryLength' => 2, }, ); } } # verify if column is filterable elsif ( $Self->{ValidFilterableColumns}->{$Column} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $Column, Label => $Column, ColumnValues => $ColumnValues->{$Column}, SelectedValue => $Param{GetColumnFilter}->{$Column} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageColumnFilter', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, ColumnFilterStrg => $ColumnFilterHTML, OrderBy => $OrderBy, Title => $Title, FilterTitle => $FilterTitle, }, ); } # verify if column is sortable elsif ( $Self->{ValidSortableColumns}->{$Column} ) { $LayoutObject->Block( Name => 'OverviewNavBarPageColumnLink', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, OrderBy => $OrderBy, Title => $Title, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageColumnEmpty', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, Title => $Title, }, ); } next COLUMN; } elsif ( $Column !~ m{\A DynamicField_}xms ) { if ( $Param{SortBy} && ( $Param{SortBy} eq $Column ) ) { my $TitleDesc; # Change order for sorting column. $OrderBy = $OrderBy eq 'Up' ? 'Down' : 'Up'; if ( $OrderBy eq 'Down' ) { $CSS .= ' SortAscendingLarge'; $TitleDesc = Translatable('sorted ascending'); } else { $CSS .= ' SortDescendingLarge'; $TitleDesc = Translatable('sorted descending'); } $TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc); $Title .= ', ' . $TitleDesc; } # translate the column name to write it in the current language my $TranslatedWord; if ( $Column eq 'EscalationTime' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Service Time'); } elsif ( $Column eq 'EscalationResponseTime' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('First Response Time'); } elsif ( $Column eq 'EscalationSolutionTime' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Solution Time'); } elsif ( $Column eq 'EscalationUpdateTime' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Update Time'); } elsif ( $Column eq 'PendingTime' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Pending till'); } elsif ( $Column eq 'CustomerCompanyName' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Customer Name'); } elsif ( $Column eq 'CustomerName' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Customer User Name'); } elsif ( $Column eq 'CustomerUserID' ) { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Customer User ID'); } else { $TranslatedWord = $LayoutObject->{LanguageObject}->Translate($Column); } my $FilterTitle = $TranslatedWord; my $FilterTitleDesc = Translatable('filter not active'); if ( $Self->{StoredFilters} && $Self->{StoredFilters}->{ $Column . 'IDs' } ) { $CSS .= ' FilterActive'; $FilterTitleDesc = Translatable('filter active'); } $FilterTitleDesc = $LayoutObject->{LanguageObject}->Translate($FilterTitleDesc); $FilterTitle .= ', ' . $FilterTitleDesc; $LayoutObject->Block( Name => 'OverviewNavBarPageColumn', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, }, ); # verify if column is filterable and sortable if ( $Self->{ValidFilterableColumns}->{$Column} && $Self->{ValidSortableColumns}->{$Column} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $Column, Label => $Column, ColumnValues => $ColumnValues->{$Column}, SelectedValue => $Param{GetColumnFilter}->{$Column} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageColumnFilterLink', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, ColumnFilterStrg => $ColumnFilterHTML, OrderBy => $OrderBy, Title => $Title, FilterTitle => $FilterTitle, }, ); } # verify if column is just filterable elsif ( $Self->{ValidFilterableColumns}->{$Column} ) { my $Css; if ( $Column eq 'CustomerUserID' ) { $Css = 'Hidden'; } # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $Column, Label => $Column, ColumnValues => $ColumnValues->{$Column}, SelectedValue => $Param{GetColumnFilter}->{$Column} || '', Css => $Css, ); $LayoutObject->Block( Name => 'OverviewNavBarPageColumnFilter', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, ColumnFilterStrg => $ColumnFilterHTML, OrderBy => $OrderBy, Title => $Title, FilterTitle => $FilterTitle, }, ); if ( $Column eq 'CustomerUserID' ) { $LayoutObject->Block( Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkCustomerUserSearch', Data => {}, ); # send data to JS $LayoutObject->AddJSData( Key => 'CustomerUserAutocomplete', Value => { 'QueryDelay' => 100, 'MaxResultsDisplayed' => 20, 'MinQueryLength' => 2, }, ); } } # verify if column is sortable elsif ( $Self->{ValidSortableColumns}->{$Column} ) { $LayoutObject->Block( Name => 'OverviewNavBarPageColumnLink', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, OrderBy => $OrderBy, Title => $Title, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageColumnEmpty', Data => { %Param, ColumnName => $Column, CSS => $CSS, ColumnNameTranslated => $TranslatedWord || $Column, Title => $Title, }, ); } } # show the DFs else { my $DynamicFieldConfig; my $DFColumn = $Column; $DFColumn =~ s/DynamicField_//g; DYNAMICFIELD: for my $DFConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DFConfig); next DYNAMICFIELD if $DFConfig->{Name} ne $DFColumn; $DynamicFieldConfig = $DFConfig; last DYNAMICFIELD; } next COLUMN if !IsHashRefWithData($DynamicFieldConfig); my $Label = $DynamicFieldConfig->{Label}; $Title = $Label; my $FilterTitle = $Label; # get field sortable condition my $IsSortable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsSortable', ); if ($IsSortable) { my $CSS = 'DynamicField_' . $DynamicFieldConfig->{Name}; if ( $Param{SortBy} && ( $Param{SortBy} eq ( 'DynamicField_' . $DynamicFieldConfig->{Name} ) ) ) { my $TitleDesc; # Change order for sorting column. $OrderBy = $OrderBy eq 'Up' ? 'Down' : 'Up'; if ( $OrderBy eq 'Down' ) { $CSS .= ' SortAscendingLarge'; $TitleDesc = Translatable('sorted ascending'); } else { $CSS .= ' SortDescendingLarge'; $TitleDesc = Translatable('sorted descending'); } $TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc); $Title .= ', ' . $TitleDesc; } my $FilterTitleDesc = Translatable('filter not active'); if ( $Self->{StoredFilters} && $Self->{StoredFilters}->{$Column} ) { $CSS .= ' FilterActive'; $FilterTitleDesc = Translatable('filter active'); } $FilterTitleDesc = $LayoutObject->{LanguageObject}->Translate($FilterTitleDesc); $FilterTitle .= ', ' . $FilterTitleDesc; $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField', Data => { %Param, CSS => $CSS, }, ); my $DynamicFieldName = 'DynamicField_' . $DynamicFieldConfig->{Name}; if ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $DynamicFieldName, Label => $Label, ColumnValues => $ColumnValues->{$DynamicFieldName}, SelectedValue => $Param{GetColumnFilter}->{$DynamicFieldName} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldFiltrableSortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, ColumnFilterStrg => $ColumnFilterHTML, Title => $Title, FilterTitle => $FilterTitle, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldSortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, Title => $Title, }, ); } # example of dynamic fields order customization $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name}, Data => { %Param, CSS => $CSS, }, ); if ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $DynamicFieldName, Label => $Label, ColumnValues => $ColumnValues->{$DynamicFieldName}, SelectedValue => $Param{GetColumnFilter}->{$DynamicFieldName} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField' . $DynamicFieldConfig->{Name} . '_FiltrableSortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, ColumnFilterStrg => $ColumnFilterHTML, Title => $Title, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name} . '_Sortable', Data => { %Param, OrderBy => $OrderBy, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, Title => $Title, }, ); } } else { my $DynamicFieldName = 'DynamicField_' . $DynamicFieldConfig->{Name}; my $CSS = $DynamicFieldName; $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField', Data => { %Param, CSS => $CSS, }, ); if ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $DynamicFieldName, Label => $Label, ColumnValues => $ColumnValues->{$DynamicFieldName}, SelectedValue => $Param{GetColumnFilter}->{$DynamicFieldName} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldFiltrableNotSortable', Data => { %Param, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, ColumnFilterStrg => $ColumnFilterHTML, Title => $Title, FilterTitle => $FilterTitle, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicFieldNotSortable', Data => { %Param, Label => $Label, Title => $Title, }, ); } # example of dynamic fields order customization $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name}, Data => { %Param, }, ); if ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) { # variable to save the filter's HTML code my $ColumnFilterHTML = $Self->_InitialColumnFilter( ColumnName => $DynamicFieldName, Label => $Label, ColumnValues => $ColumnValues->{$DynamicFieldName}, SelectedValue => $Param{GetColumnFilter}->{$DynamicFieldName} || '', ); $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name} . '_FiltrableNotSortable', Data => { %Param, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, ColumnFilterStrg => $ColumnFilterHTML, Title => $Title, }, ); } else { $LayoutObject->Block( Name => 'OverviewNavBarPageDynamicField_' . $DynamicFieldConfig->{Name} . '_NotSortable', Data => { %Param, Label => $Label, Title => $Title, }, ); } } } } $LayoutObject->Block( Name => 'TableBody' ); } else { $LayoutObject->Block( Name => 'NoTicketFound' ); } my %ActionRowTickets; for my $ArticleRef (@ArticleBox) { # get last customer article my %Article = %{$ArticleRef}; # escalation human times if ( $Article{EscalationTime} ) { $Article{EscalationTimeHuman} = $LayoutObject->CustomerAge( Age => $Article{EscalationTime}, TimeShowAlwaysLong => 1, Space => ' ', ); $Article{EscalationTimeWorkingTime} = $LayoutObject->CustomerAge( Age => $Article{EscalationTimeWorkingTime}, TimeShowAlwaysLong => 1, Space => ' ', ); } # customer info my %CustomerInfo; if ( $Param{Config}->{CustomerInfo} ) { if ( $Article{CustomerUserID} ) { # get customer user object my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); $Article{CustomerName} = $CustomerUserObject->CustomerName( UserLogin => $Article{CustomerUserID}, ); %CustomerInfo = $CustomerUserObject->CustomerUserDataGet( User => $Article{CustomerUserID}, ); INFOKEY: for my $InfoKey ( sort keys %CustomerInfo ) { next INFOKEY if $InfoKey =~ m{\ACustomer}xms; $CustomerInfo{ 'Customer' . $InfoKey } = $CustomerInfo{$InfoKey}; } } } # get user object my $UserObject = $Kernel::OM->Get('Kernel::System::User'); # user info my %UserInfo = $UserObject->GetUserData( UserID => $Article{OwnerID}, ); # Responsible info. my %ResponsibleInfo = $UserObject->GetUserData( UserID => $Article{ResponsibleID}, ); $UserInfo{ResponsibleInfo} = \%ResponsibleInfo; $LayoutObject->Block( Name => 'Record', Data => { %Article, %UserInfo }, ); # check if bulk feature is enabled if ($BulkFeature) { $LayoutObject->Block( Name => 'GeneralOverviewRow', ); $LayoutObject->Block( Name => Translatable('Bulk'), Data => { %Article, %UserInfo }, ); } # show ticket flags my @TicketMetaItems = $LayoutObject->TicketMetaItems( Ticket => \%Article, ); for my $Item (@TicketMetaItems) { $LayoutObject->Block( Name => 'GeneralOverviewRow', ); $LayoutObject->Block( Name => 'ContentLargeTicketGenericRowMeta', Data => $Item, ); if ($Item) { $LayoutObject->Block( Name => 'ContentLargeTicketGenericRowMetaImage', Data => $Item, ); } } # save column content my $DataValue; # show all needed columns TICKETCOLUMN: for my $TicketColumn (@Col) { $LayoutObject->Block( Name => 'GeneralOverviewRow', ); if ( $TicketColumn !~ m{\A DynamicField_}xms ) { $LayoutObject->Block( Name => 'RecordTicketData', Data => {}, ); if ( $SpecialColumns{$TicketColumn} ) { $LayoutObject->Block( Name => 'Record' . $TicketColumn, Data => { %Article, %UserInfo }, ); next TICKETCOLUMN; } if ( $TicketColumn eq 'CreatedBy' ) { my %TicketCreatedByInfo = $UserObject->GetUserData( UserID => $Article{CreateBy}, ); $LayoutObject->Block( Name => 'RecordTicketCreatedBy', Data => \%TicketCreatedByInfo, ); next TICKETCOLUMN; } # escalation column my %EscalationData; if ( $TicketColumn eq 'EscalationTime' ) { $EscalationData{EscalationTime} = $Article{EscalationTime}; $EscalationData{EscalationDestinationDate} = $Article{EscalationDestinationDate}; $EscalationData{EscalationTimeHuman} = $LayoutObject->CustomerAge( Age => $EscalationData{EscalationTime}, TimeShowAlwaysLong => 1, Space => ' ', ) || '-'; $EscalationData{EscalationTimeWorkingTime} = $LayoutObject->CustomerAge( Age => $EscalationData{EscalationTimeWorkingTime}, TimeShowAlwaysLong => 1, Space => ' ', ); if ( defined $Article{EscalationTime} && $Article{EscalationTime} < 60 * 60 * 1 ) { $EscalationData{EscalationClass} = 'Warning'; } $LayoutObject->Block( Name => 'RecordEscalationTime', Data => {%EscalationData}, ); next TICKETCOLUMN; } my $BlockType = ''; my $CSSClass = ''; if ( $TicketColumn eq 'EscalationSolutionTime' ) { $BlockType = 'Escalation'; $DataValue = $LayoutObject->CustomerAge( Age => $Article{SolutionTime} || 0, TimeShowAlwaysLong => 1, Space => ' ', ); if ( defined $Article{SolutionTime} && $Article{SolutionTime} < 60 * 60 * 1 ) { $CSSClass = 'Warning'; } } elsif ( $TicketColumn eq 'EscalationResponseTime' ) { $BlockType = 'Escalation'; $DataValue = $LayoutObject->CustomerAge( Age => $Article{FirstResponseTime} || 0, TimeShowAlwaysLong => 1, Space => ' ', ); if ( defined $Article{FirstResponseTime} && $Article{FirstResponseTime} < 60 * 60 * 1 ) { $CSSClass = 'Warning'; } } elsif ( $TicketColumn eq 'EscalationUpdateTime' ) { $BlockType = 'Escalation'; $DataValue = $LayoutObject->CustomerAge( Age => $Article{UpdateTime} || 0, TimeShowAlwaysLong => 1, Space => ' ', ); if ( defined $Article{UpdateTime} && $Article{UpdateTime} < 60 * 60 * 1 ) { $CSSClass = 'Warning'; } } elsif ( $TicketColumn eq 'PendingTime' ) { $BlockType = 'Escalation'; $DataValue = $LayoutObject->CustomerAge( Age => $Article{'UntilTime'}, Space => ' ' ); if ( defined $Article{UntilTime} && $Article{UntilTime} < -1 ) { $CSSClass = 'Warning'; } } elsif ( $TicketColumn eq 'State' || $TicketColumn eq 'Lock' || $TicketColumn eq 'Priority' ) { $BlockType = 'Translatable'; $DataValue = $Article{$TicketColumn} || $UserInfo{$TicketColumn}; } elsif ( $TicketColumn eq 'Created' || $TicketColumn eq 'Changed' ) { $BlockType = 'Time'; $DataValue = $Article{$TicketColumn} || $UserInfo{$TicketColumn}; } elsif ( $TicketColumn eq 'Responsible' ) { my %ResponsibleInfo = $UserObject->GetUserData( UserID => $Article{ResponsibleID}, ); $DataValue = $ResponsibleInfo{'UserFullname'}; } else { $DataValue = $Article{$TicketColumn} || $UserInfo{$TicketColumn} || $CustomerInfo{$TicketColumn}; # If value is in date format, change block type to 'Time' so it can be localized. See bug#14542. if ( $DataValue =~ /^\d\d\d\d-(\d|\d\d)-(\d|\d\d)\s(\d|\d\d):(\d|\d\d):(\d|\d\d)/ ) { $BlockType = 'Time'; } } $LayoutObject->Block( Name => "RecordTicketColumn$BlockType", Data => { GenericValue => $DataValue || '-', Class => $CSSClass || '', }, ); } # dynamic fields else { # cycle trough the activated dynamic fields for this screen my $DynamicFieldConfig; my $DFColumn = $TicketColumn; $DFColumn =~ s/DynamicField_//g; DYNAMICFIELD: for my $DFConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DFConfig); next DYNAMICFIELD if $DFConfig->{Name} ne $DFColumn; $DynamicFieldConfig = $DFConfig; last DYNAMICFIELD; } next TICKETCOLUMN if !IsHashRefWithData($DynamicFieldConfig); # get field value my $Value = $DynamicFieldBackendObject->ValueGet( DynamicFieldConfig => $DynamicFieldConfig, ObjectID => $Article{TicketID}, ); my $ValueStrg = $DynamicFieldBackendObject->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}, }, ); } } } # add action items as js if ( $Article{ActionItems} ) { # replace TT directives from string with values for my $ActionItem ( @{ $Article{ActionItems} } ) { $ActionItem->{Link} = $LayoutObject->Output( Template => $ActionItem->{Link}, Data => { TicketID => $Article{TicketID}, }, ); } $ActionRowTickets{ $Article{TicketID} } = $LayoutObject->JSONEncode( Data => $Article{ActionItems} ); } } # send data to JS $LayoutObject->AddJSData( Key => 'ActionRowTickets', Value => \%ActionRowTickets, ); # set column filter form, to correctly fill the column filters is necessary to pass each # overview some information in the AJAX call, for example the fixed Filters or NavBarFilters # and also other values like the Queue in AgentTicketQueue, otherwise the filters will be # filled with default restrictions, resulting in more options than the ones that the # available tickets should provide, see Bug#9902 if ( IsHashRefWithData( $Param{ColumnFilterForm} ) ) { $LayoutObject->Block( Name => 'DocumentColumnFilterForm', Data => {}, ); for my $Element ( sort keys %{ $Param{ColumnFilterForm} } ) { $LayoutObject->Block( Name => 'DocumentColumnFilterFormElement', Data => { ElementName => $Element, ElementValue => $Param{ColumnFilterForm}->{$Element}, }, ); } } # use template my $Output = $LayoutObject->Output( TemplateFile => 'AgentTicketOverviewSmall', Data => { %Param, Type => $Self->{ViewType}, }, ); return $Output; } sub _GetColumnValues { my ( $Self, %Param ) = @_; return if !IsStringWithData( $Param{HeaderColumn} ); my $HeaderColumn = $Param{HeaderColumn}; my %ColumnFilterValues; my $TicketIDs; if ( IsArrayRefWithData( $Param{OriginalTicketIDs} ) ) { $TicketIDs = $Param{OriginalTicketIDs}; } if ( $HeaderColumn !~ m/^DynamicField_/ ) { my $FunctionName = $HeaderColumn . 'FilterValuesGet'; if ( $HeaderColumn eq 'CustomerID' ) { $FunctionName = 'CustomerFilterValuesGet'; } $ColumnFilterValues{$HeaderColumn} = $Kernel::OM->Get('Kernel::System::Ticket::ColumnFilter')->$FunctionName( TicketIDs => $TicketIDs, HeaderColumn => $HeaderColumn, UserID => $Self->{UserID}, ); } else { DYNAMICFIELD: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $FieldName = 'DynamicField_' . $DynamicFieldConfig->{Name}; next DYNAMICFIELD if $FieldName ne $HeaderColumn; # get dynamic field backend object my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); my $IsFiltrable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsFiltrable', ); next DYNAMICFIELD if !$IsFiltrable; $Self->{ValidFilterableColumns}->{$HeaderColumn} = $IsFiltrable; if ( IsArrayRefWithData($TicketIDs) ) { # get the historical values for the field $ColumnFilterValues{$HeaderColumn} = $DynamicFieldBackendObject->ColumnFilterValuesGet( DynamicFieldConfig => $DynamicFieldConfig, LayoutObject => $Kernel::OM->Get('Kernel::Output::HTML::Layout'), TicketIDs => $TicketIDs, ); } else { # get PossibleValues $ColumnFilterValues{$HeaderColumn} = $DynamicFieldBackendObject->PossibleValuesGet( DynamicFieldConfig => $DynamicFieldConfig, ); } last DYNAMICFIELD; } } return \%ColumnFilterValues; } sub _InitialColumnFilter { my ( $Self, %Param ) = @_; return if !$Param{ColumnName}; return if !$Self->{ValidFilterableColumns}->{ $Param{ColumnName} }; # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $Label = $Param{Label} || $Param{ColumnName}; $Label = $LayoutObject->{LanguageObject}->Translate($Label); # set fixed values my $Data = [ { Key => '', Value => uc $Label, }, ]; # define if column filter values should be translatable my $TranslationOption = 0; if ( $Param{ColumnName} eq 'State' || $Param{ColumnName} eq 'Lock' || $Param{ColumnName} eq 'Priority' ) { $TranslationOption = 1; } my $Class = 'ColumnFilter'; if ( $Param{Css} ) { $Class .= ' ' . $Param{Css}; } # build select HTML my $ColumnFilterHTML = $LayoutObject->BuildSelection( Name => 'ColumnFilter' . $Param{ColumnName}, Data => $Data, Class => $Class, Translation => $TranslationOption, SelectedID => '', ); return $ColumnFilterHTML; } sub FilterContent { my ( $Self, %Param ) = @_; return if !$Param{HeaderColumn}; my $HeaderColumn = $Param{HeaderColumn}; # get column values for to build the filters later my $ColumnValues = $Self->_GetColumnValues( OriginalTicketIDs => $Param{OriginalTicketIDs}, HeaderColumn => $HeaderColumn, ); my $SelectedValue = ''; my $SelectedColumn = $HeaderColumn; if ( $HeaderColumn eq 'CustomerUserID' ) { $SelectedColumn = 'CustomerUserLogin'; } if ( $HeaderColumn eq 'CustomerID' ) { $SelectedColumn = 'CustomerID'; } elsif ( $HeaderColumn !~ m{ \A DynamicField_ }xms ) { $SelectedColumn .= 'IDs'; } my $LabelColumn = $HeaderColumn; if ( $LabelColumn =~ m{ \A DynamicField_ }xms ) { my $DynamicFieldConfig; $LabelColumn =~ s{\A DynamicField_ }{}xms; DYNAMICFIELD: for my $DFConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DFConfig); next DYNAMICFIELD if $DFConfig->{Name} ne $LabelColumn; $DynamicFieldConfig = $DFConfig; last DYNAMICFIELD; } if ( IsHashRefWithData($DynamicFieldConfig) ) { $LabelColumn = $DynamicFieldConfig->{Label}; } } if ( $SelectedColumn && $Self->{StoredFilters}->{$SelectedColumn} ) { if ( IsArrayRefWithData( $Self->{StoredFilters}->{$SelectedColumn} ) ) { $SelectedValue = $Self->{StoredFilters}->{$SelectedColumn}->[0]; } elsif ( IsHashRefWithData( $Self->{StoredFilters}->{$SelectedColumn} ) ) { $SelectedValue = $Self->{StoredFilters}->{$SelectedColumn}->{Equals}; } } # variable to save the filter's HTML code my $ColumnFilterJSON = $Self->_ColumnFilterJSON( ColumnName => $HeaderColumn, Label => $LabelColumn, ColumnValues => $ColumnValues->{$HeaderColumn}, SelectedValue => $SelectedValue, ); return $ColumnFilterJSON; } # =head2 _ColumnFilterJSON() # creates a JSON select filter for column header # my $ColumnFilterJSON = $TicketOverviewSmallObject->_ColumnFilterJSON( # ColumnName => 'Queue', # Label => 'Queue', # ColumnValues => { # 1 => 'PostMaster', # 2 => 'Junk', # }, # SelectedValue '1', # ); # =cut sub _ColumnFilterJSON { my ( $Self, %Param ) = @_; if ( !$Self->{AvailableFilterableColumns}->{ $Param{ColumnName} } && !$Self->{AvailableFilterableColumns}->{ $Param{ColumnName} . 'IDs' } ) { return; } # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $Label = $Param{Label}; $Label =~ s{ \A DynamicField_ }{}gxms; $Label = $LayoutObject->{LanguageObject}->Translate($Label); # set fixed values my $Data = [ { Key => 'DeleteFilter', Value => uc $Label, }, { Key => '-', Value => '-', Disabled => 1, }, ]; if ( $Param{ColumnValues} && ref $Param{ColumnValues} eq 'HASH' ) { my %Values = %{ $Param{ColumnValues} }; # Keys must be link encoded for dynamic fields because they are added to URL during filtering # and can contain characters like '&', ';', etc. # See bug#14497 - https://bugs.otrs.org/show_bug.cgi?id=14497. my $Encoding = ( $Param{ColumnName} =~ m/^DynamicField_/ ) ? 1 : 0; # Set possible values. for my $ValueKey ( sort { lc $Values{$a} cmp lc $Values{$b} } keys %Values ) { push @{$Data}, { Key => $Encoding ? $LayoutObject->LinkEncode($ValueKey) : $ValueKey, Value => $Values{$ValueKey}, }; } } # define if column filter values should be translatable my $TranslationOption = 0; if ( $Param{ColumnName} eq 'State' || $Param{ColumnName} eq 'Lock' || $Param{ColumnName} eq 'Priority' ) { $TranslationOption = 1; } # build select HTML my $JSON = $LayoutObject->BuildSelectionJSON( [ { Name => 'ColumnFilter' . $Param{ColumnName}, Data => $Data, Class => 'ColumnFilter', Sort => 'AlphanumericKey', TreeView => 1, SelectedID => $Param{SelectedValue}, Translation => $TranslationOption, AutoComplete => 'off', }, ], ); return $JSON; } sub _DefaultColumnSort { my %DefaultColumns = ( TicketNumber => 100, Age => 110, Changed => 111, PendingTime => 112, EscalationTime => 113, EscalationSolutionTime => 114, EscalationResponseTime => 115, EscalationUpdateTime => 116, Sender => 120, Title => 122, Subject => 124, State => 130, Lock => 140, Queue => 150, Owner => 160, Responsible => 161, CustomerID => 170, CustomerName => 171, CustomerUserID => 172, Type => 180, Service => 191, SLA => 192, Priority => 193, ); # dynamic fields can not be on the DefaultColumns sorting hash # when comparing 2 dynamic fields sorting must be alphabetical if ( !$DefaultColumns{$a} && !$DefaultColumns{$b} ) { return $a cmp $b; } # when a dynamic field is compared to a ticket attribute it must be higher elsif ( !$DefaultColumns{$a} ) { return 1; } # when a ticket attribute is compared to a dynamic field it must be lower elsif ( !$DefaultColumns{$b} ) { return -1; } # otherwise do a numerical comparison with the ticket attributes return $DefaultColumns{$a} <=> $DefaultColumns{$b}; } 1;