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::Output::HTML::Layout::Ticket; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15use Kernel::Language qw(Translatable); 16 17our $ObjectManagerDisabled = 1; 18 19=head1 NAME 20 21Kernel::Output::HTML::Layout::Ticket - all Ticket-related HTML functions 22 23=head1 DESCRIPTION 24 25All Ticket-related HTML functions 26 27=head1 PUBLIC INTERFACE 28 29=head2 AgentCustomerViewTable() 30 31=cut 32 33sub AgentCustomerViewTable { 34 my ( $Self, %Param ) = @_; 35 36 # check customer params 37 if ( ref $Param{Data} ne 'HASH' ) { 38 $Self->FatalError( Message => 'Need Hash ref in Data param' ); 39 } 40 elsif ( ref $Param{Data} eq 'HASH' && !%{ $Param{Data} } ) { 41 return $Self->{LanguageObject}->Translate('none'); 42 } 43 44 # add ticket params if given 45 if ( $Param{Ticket} ) { 46 %{ $Param{Data} } = ( %{ $Param{Data} }, %{ $Param{Ticket} } ); 47 } 48 49 my @MapNew; 50 my $Map = $Param{Data}->{Config}->{Map}; 51 if ($Map) { 52 @MapNew = ( @{$Map} ); 53 } 54 55 # check if customer company support is enabled 56 if ( $Param{Data}->{Config}->{CustomerCompanySupport} ) { 57 my $Map2 = $Param{Data}->{CompanyConfig}->{Map}; 58 if ($Map2) { 59 push( @MapNew, @{$Map2} ); 60 } 61 } 62 63 my $ShownType = 1; 64 if ( $Param{Type} && $Param{Type} eq Translatable('Lite') ) { 65 $ShownType = 2; 66 67 # check if min one lite view item is configured, if not, use 68 # the normal view also 69 my $Used = 0; 70 for my $Field (@MapNew) { 71 if ( $Field->[3] == 2 ) { 72 $Used = 1; 73 } 74 } 75 if ( !$Used ) { 76 $ShownType = 1; 77 } 78 } 79 80 # build html table 81 $Self->Block( 82 Name => 'Customer', 83 Data => $Param{Data}, 84 ); 85 86 # get needed objects 87 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 88 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 89 90 # check Frontend::CustomerUser::Image 91 my $CustomerImage = $ConfigObject->Get('Frontend::CustomerUser::Image'); 92 if ($CustomerImage) { 93 my %Modules = %{$CustomerImage}; 94 95 MODULE: 96 for my $Module ( sort keys %Modules ) { 97 if ( !$MainObject->Require( $Modules{$Module}->{Module} ) ) { 98 $Self->FatalDie(); 99 } 100 101 my $Object = $Modules{$Module}->{Module}->new( 102 %{$Self}, 103 LayoutObject => $Self, 104 ); 105 106 # run module 107 next MODULE if !$Object; 108 109 $Object->Run( 110 Config => $Modules{$Module}, 111 Data => $Param{Data}, 112 ); 113 } 114 } 115 116 my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 117 ObjectType => [ 'CustomerUser', 'CustomerCompany', ], 118 ); 119 120 my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs}; 121 122 # Get dynamic field object. 123 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 124 125 # build table 126 FIELD: 127 for my $Field (@MapNew) { 128 if ( $Field->[3] && $Field->[3] >= $ShownType && $Param{Data}->{ $Field->[0] } ) { 129 my %Record = ( 130 %{ $Param{Data} }, 131 Key => $Field->[1], 132 Value => $Param{Data}->{ $Field->[0] }, 133 ); 134 135 # render dynamic field values 136 if ( $Field->[5] eq 'dynamic_field' ) { 137 if ( !IsArrayRefWithData( $Record{Value} ) ) { 138 $Record{Value} = [ $Record{Value} ]; 139 } 140 141 my $DynamicFieldConfig = $DynamicFieldLookup{ $Field->[2] }; 142 143 next FIELD if !$DynamicFieldConfig; 144 145 my @RenderedValues; 146 VALUE: 147 for my $Value ( @{ $Record{Value} } ) { 148 my $RenderedValue = $DynamicFieldBackendObject->DisplayValueRender( 149 DynamicFieldConfig => $DynamicFieldConfig, 150 Value => $Value, 151 HTMLOutput => 0, 152 LayoutObject => $Self, 153 ); 154 155 next VALUE if !IsHashRefWithData($RenderedValue) || !defined $RenderedValue->{Value}; 156 157 # If there is configured show link in DF, save as map value. 158 $Field->[6] = $RenderedValue->{Link} ? $RenderedValue->{Link} : $Field->[6]; 159 160 push @RenderedValues, $RenderedValue->{Value}; 161 } 162 163 $Record{Value} = join ', ', @RenderedValues; 164 $Record{Key} = $DynamicFieldConfig->{Label}; 165 } 166 167 if ( 168 $Field->[6] 169 && ( 170 $Param{Data}->{TicketID} 171 || $Param{Ticket} 172 || $Field->[6] !~ m{Env\("CGIHandle"\)} 173 ) 174 ) 175 { 176 $Record{LinkStart} = "<a href=\"$Field->[6]\""; 177 if ( !$Param{Ticket} ) { 178 $Record{LinkStart} .= " target=\"_blank\""; 179 } 180 elsif ( $Field->[8] ) { 181 $Record{LinkStart} .= " target=\"$Field->[8]\""; 182 } 183 if ( $Field->[9] ) { 184 $Record{LinkStart} .= " class=\"$Field->[9]\""; 185 } 186 $Record{LinkStart} .= ">"; 187 $Record{LinkStop} = "</a>"; 188 } 189 if ( $Field->[0] ) { 190 $Record{ValueShort} = $Self->Ascii2Html( 191 Text => $Record{Value}, 192 Max => $Param{Max} 193 ); 194 } 195 $Self->Block( 196 Name => 'CustomerRow', 197 Data => \%Record, 198 ); 199 200 if ( 201 $Param{Data}->{Config}->{CustomerCompanySupport} 202 && $Field->[0] eq 'CustomerCompanyName' 203 ) 204 { 205 my $CompanyValidID = $Param{Data}->{CustomerCompanyValidID}; 206 207 if ($CompanyValidID) { 208 my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); 209 my $CompanyIsValid = grep { $CompanyValidID == $_ } @ValidIDs; 210 211 if ( !$CompanyIsValid ) { 212 $Self->Block( 213 Name => 'CustomerRowCustomerCompanyInvalid', 214 ); 215 } 216 } 217 } 218 219 if ( 220 $ConfigObject->Get('ChatEngine::Active') 221 && $Field->[0] eq 'UserLogin' 222 ) 223 { 224 # Check if agent has permission to start chats with the customer users. 225 my $EnableChat = 1; 226 my $ChatStartingAgentsGroup 227 = $ConfigObject->Get('ChatEngine::PermissionGroup::ChatStartingAgents') || 'users'; 228 my $ChatStartingAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck( 229 UserID => $Self->{UserID}, 230 GroupName => $ChatStartingAgentsGroup, 231 Type => 'rw', 232 ); 233 234 if ( !$ChatStartingAgentsGroupPermission ) { 235 $EnableChat = 0; 236 } 237 if ( 238 $EnableChat 239 && !$ConfigObject->Get('ChatEngine::ChatDirection::AgentToCustomer') 240 ) 241 { 242 $EnableChat = 0; 243 } 244 245 if ($EnableChat) { 246 my $VideoChatEnabled = 0; 247 my $VideoChatAgentsGroup 248 = $ConfigObject->Get('ChatEngine::PermissionGroup::VideoChatAgents') || 'users'; 249 my $VideoChatAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck( 250 UserID => $Self->{UserID}, 251 GroupName => $VideoChatAgentsGroup, 252 Type => 'rw', 253 ); 254 255 # Enable the video chat feature if system is entitled and agent is a member of configured group. 256 if ($VideoChatAgentsGroupPermission) { 257 if ( $Kernel::OM->Get('Kernel::System::Main') 258 ->Require( 'Kernel::System::VideoChat', Silent => 1 ) ) 259 { 260 $VideoChatEnabled = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled(); 261 } 262 } 263 264 my $CustomerEnableChat = 0; 265 my $ChatAccess = 0; 266 my $VideoChatAvailable = 0; 267 my $VideoChatSupport = 0; 268 269 # Default status is offline. 270 my $UserState = Translatable('Offline'); 271 my $UserStateDescription = $Self->{LanguageObject}->Translate('User is currently offline.'); 272 273 my $CustomerChatAvailability = $Kernel::OM->Get('Kernel::System::Chat')->CustomerAvailabilityGet( 274 UserID => $Param{Data}->{UserID}, 275 ); 276 277 my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); 278 279 my %CustomerUser = $CustomerUserObject->CustomerUserDataGet( 280 User => $Param{Data}->{UserID}, 281 ); 282 $CustomerUser{UserFullname} = $CustomerUserObject->CustomerName( 283 UserLogin => $Param{Data}->{UserID}, 284 ); 285 $VideoChatSupport = 1 if $CustomerUser{VideoChatHasWebRTC}; 286 287 if ( $CustomerChatAvailability == 3 ) { 288 $UserState = Translatable('Active'); 289 $CustomerEnableChat = 1; 290 $UserStateDescription = $Self->{LanguageObject}->Translate('User is currently active.'); 291 $VideoChatAvailable = 1; 292 } 293 elsif ( $CustomerChatAvailability == 2 ) { 294 $UserState = Translatable('Away'); 295 $CustomerEnableChat = 1; 296 $UserStateDescription = $Self->{LanguageObject}->Translate('User was inactive for a while.'); 297 } 298 299 $Self->Block( 300 Name => 'CustomerRowUserStatus', 301 Data => { 302 %CustomerUser, 303 UserState => $UserState, 304 UserStateDescription => $UserStateDescription, 305 }, 306 ); 307 308 if ($CustomerEnableChat) { 309 $Self->Block( 310 Name => 'CustomerRowChatIcons', 311 Data => { 312 %{ $Param{Data} }, 313 %CustomerUser, 314 VideoChatEnabled => $VideoChatEnabled, 315 VideoChatAvailable => $VideoChatAvailable, 316 VideoChatSupport => $VideoChatSupport, 317 }, 318 ); 319 } 320 } 321 } 322 } 323 } 324 325 # check Frontend::CustomerUser::Item 326 my $CustomerItem = $ConfigObject->Get('Frontend::CustomerUser::Item'); 327 my $CustomerItemCount = 0; 328 if ($CustomerItem) { 329 $Self->Block( 330 Name => 'CustomerItem', 331 ); 332 my %Modules = %{$CustomerItem}; 333 334 MODULE: 335 for my $Module ( sort keys %Modules ) { 336 if ( !$MainObject->Require( $Modules{$Module}->{Module} ) ) { 337 $Self->FatalDie(); 338 } 339 340 my $Object = $Modules{$Module}->{Module}->new( 341 %{$Self}, 342 LayoutObject => $Self, 343 ); 344 345 # run module 346 next MODULE if !$Object; 347 348 my $Run = $Object->Run( 349 Config => $Modules{$Module}, 350 Data => $Param{Data}, 351 ); 352 353 next MODULE if !$Run; 354 355 $CustomerItemCount++; 356 } 357 } 358 359 # create & return output 360 return $Self->Output( 361 TemplateFile => 'AgentCustomerTableView', 362 Data => \%Param 363 ); 364} 365 366sub AgentQueueListOption { 367 my ( $Self, %Param ) = @_; 368 369 my $Size = $Param{Size} ? "size='$Param{Size}'" : ''; 370 my $MaxLevel = defined( $Param{MaxLevel} ) ? $Param{MaxLevel} : 10; 371 my $SelectedID = defined( $Param{SelectedID} ) ? $Param{SelectedID} : ''; 372 my $Selected = defined( $Param{Selected} ) ? $Param{Selected} : ''; 373 my $CurrentQueueID = defined( $Param{CurrentQueueID} ) ? $Param{CurrentQueueID} : ''; 374 my $Class = defined( $Param{Class} ) ? $Param{Class} : ''; 375 my $SelectedIDRefArray = $Param{SelectedIDRefArray} || ''; 376 my $Multiple = $Param{Multiple} ? 'multiple = "multiple"' : ''; 377 my $TreeView = $Param{TreeView} ? $Param{TreeView} : 0; 378 my $OptionTitle = defined( $Param{OptionTitle} ) ? $Param{OptionTitle} : 0; 379 my $OnChangeSubmit = defined( $Param{OnChangeSubmit} ) ? $Param{OnChangeSubmit} : ''; 380 381 if ($OnChangeSubmit) { 382 $OnChangeSubmit = " onchange=\"submit();\""; 383 } 384 else { 385 $OnChangeSubmit = ''; 386 } 387 388 # set OnChange if AJAX is used 389 if ( $Param{Ajax} ) { 390 391 # get log object 392 my $LogObject = $Kernel::OM->Get('Kernel::System::Log'); 393 394 if ( !$Param{Ajax}->{Depend} ) { 395 $LogObject->Log( 396 Priority => 'error', 397 Message => 'Need Depend Param Ajax option!', 398 ); 399 $Self->FatalError(); 400 } 401 if ( !$Param{Ajax}->{Update} ) { 402 $LogObject->Log( 403 Priority => 'error', 404 Message => 'Need Update Param Ajax option()!', 405 ); 406 $Self->FatalError(); 407 } 408 $Param{OnChange} = "Core.AJAX.FormUpdate(\$('#" 409 . $Param{Name} . "'), '" 410 . $Param{Ajax}->{Subaction} . "'," 411 . " '$Param{Name}'," 412 . " ['" 413 . join( "', '", @{ $Param{Ajax}->{Update} } ) . "']);"; 414 } 415 416 if ( $Param{OnChange} ) { 417 $OnChangeSubmit = " onchange=\"$Param{OnChange}\""; 418 } 419 420 # just show a simple list 421 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Frontend::ListType') eq 'list' ) { 422 423 # transform data from Hash in Array because of ordering in frontend by Queue name 424 # it was a problem with name like '(some_queue)' 425 # see bug#10621 http://bugs.otrs.org/show_bug.cgi?id=10621 426 my %QueueDataHash = %{ $Param{Data} || {} }; 427 my @QueueDataArray = map { 428 { 429 Key => $_, 430 Value => $QueueDataHash{$_}, 431 } 432 } sort { $QueueDataHash{$a} cmp $QueueDataHash{$b} } keys %QueueDataHash; 433 434 # find index of first element in array @QueueDataArray for displaying in frontend 435 # at the top should be element with ' $QueueDataArray[$_]->{Key} = 0' like "- Move -" 436 # if such an element is found, it is moved to the top 437 my $MoveStr = $Self->{LanguageObject}->Translate('Move'); 438 my $ValueOfQueueNoKey = '- ' . $MoveStr . ' -'; 439 my ($FirstElementIndex) = grep { 440 $QueueDataArray[$_]->{Value} eq '-' 441 || $QueueDataArray[$_]->{Value} eq $ValueOfQueueNoKey 442 } 0 .. scalar(@QueueDataArray) - 1; 443 if ($FirstElementIndex) { 444 splice( @QueueDataArray, 0, 0, splice( @QueueDataArray, $FirstElementIndex, 1 ) ); 445 } 446 $Param{Data} = \@QueueDataArray; 447 448 $Param{MoveQueuesStrg} = $Self->BuildSelection( 449 %Param, 450 HTMLQuote => 0, 451 SelectedID => $Param{SelectedID} || $Param{SelectedIDRefArray} || '', 452 SelectedValue => $Param{Selected}, 453 Translation => 0, 454 ); 455 return $Param{MoveQueuesStrg}; 456 } 457 458 # build tree list 459 $Param{MoveQueuesStrg} = '<select name="' 460 . $Param{Name} 461 . '" id="' 462 . $Param{Name} 463 . '" class="' 464 . $Class 465 . '" data-tree="true"' 466 . " $Size $Multiple $OnChangeSubmit>\n"; 467 my %UsedData; 468 my %Data; 469 470 if ( $Param{Data} && ref $Param{Data} eq 'HASH' ) { 471 %Data = %{ $Param{Data} }; 472 } 473 else { 474 return 'Need Data Ref in AgentQueueListOption()!'; 475 } 476 477 # add suffix for correct sorting 478 my $KeyNoQueue; 479 my $ValueNoQueue; 480 my $MoveStr = $Self->{LanguageObject}->Translate('Move'); 481 my $ValueOfQueueNoKey = "- " . $MoveStr . " -"; 482 DATA: 483 for ( sort { $Data{$a} cmp $Data{$b} } keys %Data ) { 484 485 # find value for default item in select box 486 # it can be "-" or "Move" 487 if ( 488 $Data{$_} eq "-" 489 || $Data{$_} eq $ValueOfQueueNoKey 490 ) 491 { 492 $KeyNoQueue = $_; 493 $ValueNoQueue = $Data{$_}; 494 next DATA; 495 } 496 $Data{$_} .= '::'; 497 } 498 499 # get HTML utils object 500 my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils'); 501 502 # set default item of select box 503 if ($ValueNoQueue) { 504 $Param{MoveQueuesStrg} .= '<option value="' 505 . $HTMLUtilsObject->ToHTML( 506 String => $KeyNoQueue, 507 ReplaceDoubleSpace => 0, 508 ) 509 . '">' 510 . $ValueNoQueue 511 . "</option>\n"; 512 } 513 514 # build selection string 515 KEY: 516 for ( sort { $Data{$a} cmp $Data{$b} } keys %Data ) { 517 518 # default item of select box has set already 519 next KEY if ( $Data{$_} eq "-" || $Data{$_} eq $ValueOfQueueNoKey ); 520 521 my @Queue = split( /::/, $Param{Data}->{$_} ); 522 $UsedData{ $Param{Data}->{$_} } = 1; 523 my $UpQueue = $Param{Data}->{$_}; 524 $UpQueue =~ s/^(.*)::.+?$/$1/g; 525 if ( !$Queue[$MaxLevel] && $Queue[-1] ne '' ) { 526 $Queue[-1] = $Self->Ascii2Html( 527 Text => $Queue[-1], 528 Max => 50 - $#Queue 529 ); 530 my $Space = ''; 531 for ( my $i = 0; $i < $#Queue; $i++ ) { 532 $Space .= ' '; 533 } 534 535 # check if SelectedIDRefArray exists 536 if ($SelectedIDRefArray) { 537 for my $ID ( @{$SelectedIDRefArray} ) { 538 if ( $ID eq $_ ) { 539 $Param{SelectedIDRefArrayOK}->{$_} = 1; 540 } 541 } 542 } 543 544 if ( !$UsedData{$UpQueue} ) { 545 546 # integrate the not selectable parent and root queues of this queue 547 # useful for ACLs and complex permission settings 548 for my $Index ( 0 .. ( scalar @Queue - 2 ) ) { 549 550 # get the Full Queue Name (with all its parents separated by '::') this will 551 # make a unique name and will be used to set the %DisabledQueueAlreadyUsed 552 # using unique names will prevent erroneous hide of Sub-Queues with the 553 # same name, refer to bug#8148 554 my $FullQueueName; 555 for my $Counter ( 0 .. $Index ) { 556 $FullQueueName .= $Queue[$Counter]; 557 if ( int $Counter < int $Index ) { 558 $FullQueueName .= '::'; 559 } 560 } 561 562 if ( !$UsedData{$FullQueueName} ) { 563 my $DSpace = ' ' x $Index; 564 my $OptionTitleHTMLValue = ''; 565 if ($OptionTitle) { 566 my $HTMLValue = $HTMLUtilsObject->ToHTML( 567 String => $Queue[$Index], 568 ReplaceDoubleSpace => 0, 569 ); 570 $OptionTitleHTMLValue = ' title="' . $HTMLValue . '"'; 571 } 572 $Param{MoveQueuesStrg} 573 .= '<option value="-" disabled="disabled"' 574 . $OptionTitleHTMLValue 575 . '>' 576 . $DSpace 577 . $Queue[$Index] 578 . "</option>\n"; 579 $UsedData{$FullQueueName} = 1; 580 } 581 } 582 } 583 584 # create selectable elements 585 my $String = $Space . $Queue[-1]; 586 my $OptionTitleHTMLValue = ''; 587 if ($OptionTitle) { 588 my $HTMLValue = $HTMLUtilsObject->ToHTML( 589 String => $Queue[-1], 590 ReplaceDoubleSpace => 0, 591 ); 592 $OptionTitleHTMLValue = ' title="' . $HTMLValue . '"'; 593 } 594 my $HTMLValue = $HTMLUtilsObject->ToHTML( 595 String => $_, 596 ReplaceDoubleSpace => 0, 597 ); 598 if ( 599 $SelectedID eq $_ 600 || $Selected eq $Param{Data}->{$_} 601 || $Param{SelectedIDRefArrayOK}->{$_} 602 ) 603 { 604 $Param{MoveQueuesStrg} 605 .= '<option selected="selected" value="' 606 . $HTMLValue . '"' 607 . $OptionTitleHTMLValue . '>' 608 . $String 609 . "</option>\n"; 610 } 611 elsif ( $CurrentQueueID eq $_ ) 612 { 613 $Param{MoveQueuesStrg} 614 .= '<option value="-" disabled="disabled"' 615 . $OptionTitleHTMLValue . '>' 616 . $String 617 . "</option>\n"; 618 } 619 else { 620 $Param{MoveQueuesStrg} 621 .= '<option value="' 622 . $HTMLValue . '"' 623 . $OptionTitleHTMLValue . '>' 624 . $String 625 . "</option>\n"; 626 } 627 } 628 } 629 $Param{MoveQueuesStrg} .= "</select>\n"; 630 631 if ( $Param{TreeView} ) { 632 my $TreeSelectionMessage = $Self->{LanguageObject}->Translate("Show Tree Selection"); 633 $Param{MoveQueuesStrg} 634 .= ' <a href="#" title="' 635 . $TreeSelectionMessage 636 . '" class="ShowTreeSelection"><span>' 637 . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>'; 638 } 639 640 return $Param{MoveQueuesStrg}; 641} 642 643sub TicketListShow { 644 my ( $Self, %Param ) = @_; 645 646 # take object ref to local, remove it from %Param (prevent memory leak) 647 my $Env = $Param{Env}; 648 delete $Param{Env}; 649 650 # lookup latest used view mode 651 if ( !$Param{View} && $Self->{ 'UserTicketOverview' . $Env->{Action} } ) { 652 $Param{View} = $Self->{ 'UserTicketOverview' . $Env->{Action} }; 653 } 654 655 # set default view mode to 'small' 656 my $View = $Param{View} || 'Small'; 657 658 # set default view mode for AgentTicketQueue or AgentTicketService 659 if ( 660 !$Param{View} 661 && ( 662 $Env->{Action} eq 'AgentTicketQueue' 663 || $Env->{Action} eq 'AgentTicketService' 664 ) 665 ) 666 { 667 $View = 'Preview'; 668 } 669 670 # store latest view mode 671 $Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID( 672 SessionID => $Self->{SessionID}, 673 Key => 'UserTicketOverview' . $Env->{Action}, 674 Value => $View, 675 ); 676 677 # get config object 678 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 679 680 # update preferences if needed 681 my $Key = 'UserTicketOverview' . $Env->{Action}; 682 if ( !$ConfigObject->Get('DemoSystem') && ( $Self->{$Key} // '' ) ne $View ) { 683 $Kernel::OM->Get('Kernel::System::User')->SetPreferences( 684 UserID => $Self->{UserID}, 685 Key => $Key, 686 Value => $View, 687 ); 688 } 689 690 # check backends 691 my $Backends = $ConfigObject->Get('Ticket::Frontend::Overview'); 692 if ( !$Backends ) { 693 return $Self->FatalError( 694 Message => 'Need config option Ticket::Frontend::Overview', 695 ); 696 } 697 if ( ref $Backends ne 'HASH' ) { 698 return $Self->FatalError( 699 Message => 'Config option Ticket::Frontend::Overview need to be HASH ref!', 700 ); 701 } 702 703 # check if selected view is available 704 if ( !$Backends->{$View} ) { 705 706 # try to find fallback, take first configured view mode 707 KEY: 708 for my $Key ( sort keys %{$Backends} ) { 709 $Kernel::OM->Get('Kernel::System::Log')->Log( 710 Priority => 'error', 711 Message => "No Config option found for view mode $View, took $Key instead!", 712 ); 713 $View = $Key; 714 last KEY; 715 } 716 } 717 718 # get layout object 719 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 720 721 $LayoutObject->AddJSData( 722 Key => 'View', 723 Value => $View, 724 ); 725 726 # load overview backend module 727 if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Backends->{$View}->{Module} ) ) { 728 return $Env->{LayoutObject}->FatalError(); 729 } 730 my $Object = $Backends->{$View}->{Module}->new( %{$Env} ); 731 return if !$Object; 732 733 # retireve filter values 734 if ( $Param{FilterContentOnly} ) { 735 return $Object->FilterContent( 736 %Param, 737 ); 738 } 739 740 # run action row backend module 741 $Param{ActionRow} = $Object->ActionRow( 742 %Param, 743 Config => $Backends->{$View}, 744 ); 745 746 # run overview backend module 747 $Param{SortOrderBar} = $Object->SortOrderBar( 748 %Param, 749 Config => $Backends->{$View}, 750 ); 751 752 # check start option, if higher then tickets available, set 753 # it to the last ticket page (Thanks to Stefan Schmidt!) 754 my $StartHit = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'StartHit' ) || 1; 755 756 # get personal page shown count 757 my $PageShownPreferencesKey = 'UserTicketOverview' . $View . 'PageShown'; 758 my $PageShown = $Self->{$PageShownPreferencesKey} || 10; 759 my $Group = 'TicketOverview' . $View . 'PageShown'; 760 761 # get data selection 762 my %Data; 763 my $Config = $ConfigObject->Get('PreferencesGroups'); 764 if ( $Config && $Config->{$Group} && $Config->{$Group}->{Data} ) { 765 %Data = %{ $Config->{$Group}->{Data} }; 766 } 767 768 # calculate max. shown per page 769 if ( $StartHit > $Param{Total} ) { 770 my $Pages = int( ( $Param{Total} / $PageShown ) + 0.99999 ); 771 $StartHit = ( ( $Pages - 1 ) * $PageShown ) + 1; 772 } 773 774 # build nav bar 775 my $Limit = $Param{Limit} || 20_000; 776 my %PageNav = $Self->PageNavBar( 777 Limit => $Limit, 778 StartHit => $StartHit, 779 PageShown => $PageShown, 780 AllHits => $Param{Total} || 0, 781 Action => 'Action=' . $Self->{Action}, 782 Link => $Param{LinkPage}, 783 IDPrefix => $Self->{Action}, 784 ); 785 786 # build shown ticket per page 787 $Param{RequestedURL} = $Param{RequestedURL} || "Action=$Self->{Action}"; 788 $Param{Group} = $Group; 789 $Param{PreferencesKey} = $PageShownPreferencesKey; 790 $Param{PageShownString} = $Self->BuildSelection( 791 Name => $PageShownPreferencesKey, 792 SelectedID => $PageShown, 793 Translation => 0, 794 Data => \%Data, 795 Sort => 'NumericValue', 796 Class => 'Modernize', 797 ); 798 799 # nav bar at the beginning of a overview 800 $Param{View} = $View; 801 $Self->Block( 802 Name => 'OverviewNavBar', 803 Data => \%Param, 804 ); 805 806 # back link 807 if ( $Param{LinkBack} ) { 808 $Self->Block( 809 Name => 'OverviewNavBarPageBack', 810 Data => \%Param, 811 ); 812 $LayoutObject->AddJSData( 813 Key => 'Profile', 814 Value => $Param{Profile}, 815 ); 816 } 817 818 # filter selection 819 if ( $Param{Filters} ) { 820 my @NavBarFilters; 821 for my $Prio ( sort keys %{ $Param{Filters} } ) { 822 push @NavBarFilters, $Param{Filters}->{$Prio}; 823 } 824 $Self->Block( 825 Name => 'OverviewNavBarFilter', 826 Data => { 827 %Param, 828 }, 829 ); 830 my $Count = 0; 831 for my $Filter (@NavBarFilters) { 832 $Count++; 833 if ( $Count == scalar @NavBarFilters ) { 834 $Filter->{CSS} = 'Last'; 835 } 836 $Self->Block( 837 Name => 'OverviewNavBarFilterItem', 838 Data => { 839 %Param, 840 %{$Filter}, 841 }, 842 ); 843 if ( $Filter->{Filter} eq $Param{Filter} ) { 844 $Self->Block( 845 Name => 'OverviewNavBarFilterItemSelected', 846 Data => { 847 %Param, 848 %{$Filter}, 849 }, 850 ); 851 } 852 else { 853 $Self->Block( 854 Name => 'OverviewNavBarFilterItemSelectedNot', 855 Data => { 856 %Param, 857 %{$Filter}, 858 }, 859 ); 860 } 861 } 862 } 863 864 # view mode 865 for my $Backend ( 866 sort { $Backends->{$a}->{ModulePriority} <=> $Backends->{$b}->{ModulePriority} } 867 keys %{$Backends} 868 ) 869 { 870 871 $Self->Block( 872 Name => 'OverviewNavBarViewMode', 873 Data => { 874 %Param, 875 %{ $Backends->{$Backend} }, 876 Filter => $Param{Filter}, 877 View => $Backend, 878 }, 879 ); 880 if ( $View eq $Backend ) { 881 $Self->Block( 882 Name => 'OverviewNavBarViewModeSelected', 883 Data => { 884 %Param, 885 %{ $Backends->{$Backend} }, 886 Filter => $Param{Filter}, 887 View => $Backend, 888 }, 889 ); 890 } 891 else { 892 $Self->Block( 893 Name => 'OverviewNavBarViewModeNotSelected', 894 Data => { 895 %Param, 896 %{ $Backends->{$Backend} }, 897 Filter => $Param{Filter}, 898 View => $Backend, 899 }, 900 ); 901 } 902 } 903 904 if (%PageNav) { 905 $Self->Block( 906 Name => 'OverviewNavBarPageNavBar', 907 Data => \%PageNav, 908 ); 909 910 # don't show context settings in AJAX case (e. g. in customer ticket history), 911 # because the submit with page reload will not work there 912 if ( !$Param{AJAX} ) { 913 $Self->Block( 914 Name => 'ContextSettings', 915 Data => { 916 %PageNav, 917 %Param, 918 }, 919 ); 920 921 # show column filter preferences 922 if ( $View eq 'Small' ) { 923 924 # set preferences keys 925 my $PrefKeyColumns = 'UserFilterColumnsEnabled' . '-' . $Env->{Action}; 926 927 # create extra needed objects 928 my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON'); 929 930 # configure columns 931 my @ColumnsEnabled = @{ $Object->{ColumnsEnabled} }; 932 my @ColumnsAvailable; 933 934 # remove duplicate columns 935 my %UniqueColumns; 936 my @ColumnsEnabledAux; 937 938 for my $Column (@ColumnsEnabled) { 939 if ( !$UniqueColumns{$Column} ) { 940 push @ColumnsEnabledAux, $Column; 941 } 942 $UniqueColumns{$Column} = 1; 943 } 944 945 # set filtered column list 946 @ColumnsEnabled = @ColumnsEnabledAux; 947 948 for my $ColumnName ( sort { $a cmp $b } @{ $Object->{ColumnsAvailable} } ) { 949 if ( !grep { $_ eq $ColumnName } @ColumnsEnabled ) { 950 push @ColumnsAvailable, $ColumnName; 951 } 952 } 953 954 my %Columns; 955 for my $ColumnName ( sort @ColumnsAvailable ) { 956 $Columns{Columns}->{$ColumnName} = ( grep { $ColumnName eq $_ } @ColumnsEnabled ) ? 1 : 0; 957 } 958 959 $Self->Block( 960 Name => 'FilterColumnSettings', 961 Data => { 962 Columns => $JSONObject->Encode( Data => \%Columns ), 963 ColumnsEnabled => $JSONObject->Encode( Data => \@ColumnsEnabled ), 964 ColumnsAvailable => $JSONObject->Encode( Data => \@ColumnsAvailable ), 965 NamePref => $PrefKeyColumns, 966 Desc => Translatable('Shown Columns'), 967 Name => $Env->{Action}, 968 View => $View, 969 GroupName => 'TicketOverviewFilterSettings', 970 %Param, 971 }, 972 ); 973 } 974 } # end show column filters preferences 975 976 # check if there was stored filters, and print a link to delete them 977 if ( IsHashRefWithData( $Object->{StoredFilters} ) ) { 978 $Self->Block( 979 Name => 'DocumentActionRowRemoveColumnFilters', 980 Data => { 981 CSS => "ContextSettings RemoveFilters", 982 %Param, 983 }, 984 ); 985 } 986 } 987 988 if ( $Param{NavBar} ) { 989 if ( $Param{NavBar}->{MainName} ) { 990 $Self->Block( 991 Name => 'OverviewNavBarMain', 992 Data => $Param{NavBar}, 993 ); 994 } 995 } 996 997 my $OutputNavBar = $Self->Output( 998 TemplateFile => 'AgentTicketOverviewNavBar', 999 Data => { %Param, }, 1000 ); 1001 my $OutputRaw = ''; 1002 if ( !$Param{Output} ) { 1003 $Self->Print( Output => \$OutputNavBar ); 1004 } 1005 else { 1006 $OutputRaw .= $OutputNavBar; 1007 } 1008 1009 # run overview backend module 1010 my $Output = $Object->Run( 1011 %Param, 1012 Config => $Backends->{$View}, 1013 Limit => $Limit, 1014 StartHit => $StartHit, 1015 PageShown => $PageShown, 1016 AllHits => $Param{Total} || 0, 1017 Output => $Param{Output} || '', 1018 ); 1019 if ( !$Param{Output} ) { 1020 $Self->Print( Output => \$Output ); 1021 } 1022 else { 1023 $OutputRaw .= $Output; 1024 } 1025 1026 return $OutputRaw; 1027} 1028 1029sub TicketMetaItemsCount { 1030 my ( $Self, %Param ) = @_; 1031 return ( 'Priority', 'New Article' ); 1032} 1033 1034sub TicketMetaItems { 1035 my ( $Self, %Param ) = @_; 1036 1037 if ( ref $Param{Ticket} ne 'HASH' ) { 1038 $Self->FatalError( Message => 'Need Hash ref in Ticket param!' ); 1039 } 1040 1041 # return attributes 1042 my @Result; 1043 1044 # show priority 1045 push @Result, { 1046 1047 # Image => $Image, 1048 Title => $Param{Ticket}->{Priority}, 1049 Class => 'Flag', 1050 ClassSpan => 'PriorityID-' . $Param{Ticket}->{PriorityID}, 1051 ClassTable => 'Flags', 1052 }; 1053 1054 # get ticket object 1055 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1056 1057 my %Ticket = $TicketObject->TicketGet( TicketID => $Param{Ticket}->{TicketID} ); 1058 1059 # Show if new message is in there, but show archived tickets as read. 1060 my %TicketFlag; 1061 if ( $Ticket{ArchiveFlag} ne 'y' ) { 1062 %TicketFlag = $TicketObject->TicketFlagGet( 1063 TicketID => $Param{Ticket}->{TicketID}, 1064 UserID => $Self->{UserID}, 1065 ); 1066 } 1067 1068 if ( $Ticket{ArchiveFlag} eq 'y' || $TicketFlag{Seen} ) { 1069 push @Result, undef; 1070 } 1071 else { 1072 1073 # just show ticket flags if agent belongs to the ticket 1074 my $ShowMeta; 1075 if ( 1076 $Self->{UserID} == $Param{Ticket}->{OwnerID} 1077 || $Self->{UserID} == $Param{Ticket}->{ResponsibleID} 1078 ) 1079 { 1080 $ShowMeta = 1; 1081 } 1082 if ( !$ShowMeta && $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Watcher') ) { 1083 my %Watch = $TicketObject->TicketWatchGet( 1084 TicketID => $Param{Ticket}->{TicketID}, 1085 ); 1086 if ( $Watch{ $Self->{UserID} } ) { 1087 $ShowMeta = 1; 1088 } 1089 } 1090 1091 # show ticket flags 1092 my $Image = 'meta-new-inactive.png'; 1093 if ($ShowMeta) { 1094 $Image = 'meta-new.png'; 1095 push @Result, { 1096 Image => $Image, 1097 Title => Translatable('Unread article(s) available'), 1098 Class => 'UnreadArticles', 1099 ClassSpan => 'UnreadArticles Remarkable', 1100 ClassTable => 'UnreadArticles', 1101 }; 1102 } 1103 else { 1104 push @Result, { 1105 Image => $Image, 1106 Title => Translatable('Unread article(s) available'), 1107 Class => 'UnreadArticles', 1108 ClassSpan => 'UnreadArticles Ordinary', 1109 ClassTable => 'UnreadArticles', 1110 }; 1111 } 1112 } 1113 1114 return @Result; 1115} 1116 11171; 1118 1119=head1 TERMS AND CONDITIONS 1120 1121This software is part of the OTRS project (L<https://otrs.org/>). 1122 1123This software comes with ABSOLUTELY NO WARRANTY. For details, see 1124the enclosed file COPYING for license information (GPL). If you 1125did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 1126 1127=cut 1128