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::PDF::Ticket; 10 11use strict; 12use warnings; 13 14our @ObjectDependencies = ( 15 'Kernel::Config', 16 'Kernel::Output::HTML::Layout', 17 'Kernel::System::LinkObject', 18 'Kernel::System::Log', 19 'Kernel::System::PDF', 20 'Kernel::System::JSON', 21 'Kernel::System::User', 22 'Kernel::System::CustomerUser', 23 'Kernel::System::Ticket', 24 'Kernel::System::Ticket::Article', 25 'Kernel::System::DynamicField', 26 'Kernel::System::DynamicField::Backend', 27); 28 29use Kernel::System::VariableCheck qw(IsHashRefWithData); 30 31sub new { 32 my ( $Type, %Param ) = @_; 33 34 # Allocate new hash for object. 35 my $Self = {}; 36 bless( $Self, $Type ); 37 38 return $Self; 39} 40 41sub GeneratePDF { 42 my ( $Self, %Param ) = @_; 43 44 # Check needed params. 45 for my $Needed (qw(TicketID UserID Interface)) { 46 if ( !$Param{$Needed} ) { 47 $Kernel::OM->Get('Kernel::System::Log')->Log( 48 Priority => "error", 49 Message => "Need $Needed!" 50 ); 51 return; 52 } 53 } 54 55 # Get appropriate interface flag. 56 my %Interface; 57 if ( $Param{Interface} eq 'Agent' ) { 58 $Interface{Agent} = 1; 59 } 60 elsif ( $Param{Interface} eq 'Customer' ) { 61 $Interface{Customer} = 1; 62 $Interface{IsVisibleForCustomer} = { 63 IsVisibleForCustomer => 1, 64 }; 65 } 66 67 # Get ticket content. 68 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 69 my %Ticket = $TicketObject->TicketGet( 70 TicketID => $Param{TicketID}, 71 DynamicFields => 0, 72 UserID => $Param{UserID}, 73 ); 74 75 # Get article list. 76 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 77 my @MetaArticles = $ArticleObject->ArticleList( 78 TicketID => $Ticket{TicketID}, 79 UserID => $Param{UserID}, 80 %{ $Interface{IsVisibleForCustomer} }, 81 ); 82 83 # Check if only one article should be printed in agent interface. 84 if ( $Param{ArticleID} ) { 85 @MetaArticles = grep { $_->{ArticleID} == $Param{ArticleID} } @MetaArticles; 86 } 87 88 # Get article content. 89 my @ArticleBox; 90 for my $MetaArticle (@MetaArticles) { 91 my $ArticleBackendObject = $ArticleObject->BackendForArticle( %{$MetaArticle} ); 92 my %Article = $ArticleBackendObject->ArticleGet( 93 %{$MetaArticle}, 94 DynamicFields => 0, 95 ); 96 my %Attachments = $ArticleBackendObject->ArticleAttachmentIndex( 97 %{$MetaArticle}, 98 ExcludePlainText => 1, 99 ExcludeHTMLBody => 1, 100 ExcludeInline => 1, 101 ); 102 $Article{Atms} = \%Attachments; 103 push @ArticleBox, \%Article; 104 } 105 106 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 107 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 108 my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject'); 109 110 # Get necessary data only for agent interface. 111 my %TicketInfo; 112 my %LinkData; 113 if ( $Interface{Agent} ) { 114 115 # Show total accounted time if feature is active. 116 if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 117 $Ticket{TicketTimeUnits} = $TicketObject->TicketAccountedTimeGet( 118 TicketID => $Ticket{TicketID}, 119 ); 120 } 121 122 # Get user data. 123 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 124 my %User = $UserObject->GetUserData( 125 UserID => $Param{UserID}, 126 ); 127 $TicketInfo{User} = \%User; 128 129 # Get owner info. 130 my %Owner = $UserObject->GetUserData( 131 User => $Ticket{Owner}, 132 ); 133 $TicketInfo{Owner} = \%Owner; 134 135 # Get responsible info. 136 if ( $ConfigObject->Get('Ticket::Responsible') && $Ticket{Responsible} ) { 137 my %Responsible = $UserObject->GetUserData( 138 User => $Ticket{Responsible}, 139 ); 140 $TicketInfo{Responsible} = \%Responsible; 141 } 142 143 # Get link ticket data. 144 my $LinkListWithData = $LinkObject->LinkListWithData( 145 Object => 'Ticket', 146 Key => $Param{TicketID}, 147 State => 'Valid', 148 UserID => $Param{UserID}, 149 ObjectParameters => { 150 Ticket => { 151 IgnoreLinkedTicketStateTypes => 1, 152 }, 153 }, 154 ); 155 156 # Get the link data. 157 if ( $LinkListWithData && ref $LinkListWithData eq 'HASH' && %{$LinkListWithData} ) { 158 %LinkData = $LayoutObject->LinkObjectTableCreate( 159 LinkListWithData => $LinkListWithData, 160 ViewMode => 'SimpleRaw', 161 ); 162 } 163 } 164 165 # Get customer info. 166 my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); 167 my %CustomerData; 168 if ( $Ticket{CustomerUserID} ) { 169 %CustomerData = $CustomerUserObject->CustomerUserDataGet( 170 User => $Ticket{CustomerUserID}, 171 ); 172 } 173 elsif ( $Ticket{CustomerID} ) { 174 %CustomerData = $CustomerUserObject->CustomerUserDataGet( 175 CustomerID => $Ticket{CustomerID}, 176 ); 177 } 178 179 # Transform time values into human readable form. 180 $Ticket{Age} = $LayoutObject->CustomerAge( 181 Age => $Ticket{Age}, 182 Space => ' ', 183 ); 184 185 if ( $Ticket{UntilTime} ) { 186 $Ticket{PendingUntil} = $LayoutObject->CustomerAge( 187 Age => $Ticket{UntilTime}, 188 Space => ' ', 189 ); 190 } 191 192 # Get maximum number of pages. 193 my %Page; 194 $Page{MaxPages} = $ConfigObject->Get('PDF::MaxPages'); 195 if ( !$Page{MaxPages} || $Page{MaxPages} < 1 || $Page{MaxPages} > 1000 ) { 196 $Page{MaxPages} = 100; 197 } 198 my $HeaderRight = $ConfigObject->Get('Ticket::Hook') . $Ticket{TicketNumber}; 199 my $HeadlineLeft = $HeaderRight; 200 my $Title = $HeaderRight; 201 if ( $Ticket{Title} ) { 202 $HeadlineLeft = $Ticket{Title}; 203 $Title .= ' / ' . $Ticket{Title}; 204 } 205 206 $Page{MarginTop} = 30; 207 $Page{MarginRight} = 40; 208 $Page{MarginBottom} = 40; 209 $Page{MarginLeft} = 40; 210 $Page{HeaderRight} = $HeaderRight; 211 $Page{FooterLeft} = ''; 212 $Page{PageText} = $LayoutObject->{LanguageObject}->Translate('Page'); 213 $Page{PageCount} = 1; 214 215 # Create new PDF document. 216 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 217 $PDFObject->DocumentNew( 218 Title => $ConfigObject->Get('Product') . ': ' . $Title, 219 Encode => $LayoutObject->{UserCharset}, 220 ); 221 222 # Create first PDF page. 223 $PDFObject->PageNew( 224 %Page, 225 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 226 ); 227 $Page{PageCount}++; 228 229 $PDFObject->PositionSet( 230 Move => 'relativ', 231 Y => -6, 232 ); 233 234 # Output title. 235 $PDFObject->Text( 236 Text => $Ticket{Title}, 237 FontSize => 13, 238 ); 239 240 $PDFObject->PositionSet( 241 Move => 'relativ', 242 Y => -6, 243 ); 244 245 # Output "printed by". 246 my $PrintedBy = $LayoutObject->{LanguageObject}->Translate('printed by'); 247 my $DateTimeString = $Kernel::OM->Create('Kernel::System::DateTime')->ToString(); 248 my $Time = $LayoutObject->{LanguageObject}->FormatTimeString( 249 $DateTimeString, 250 'DateFormat', 251 ); 252 if ( $Interface{Agent} ) { 253 $PrintedBy .= ' ' . $TicketInfo{User}{UserFullname} . ' (' 254 . $TicketInfo{User}{UserEmail} . ')' 255 . ', ' . $Time; 256 } 257 elsif ( $Interface{Customer} ) { 258 $PrintedBy .= ' ' . $CustomerData{UserFullname} . ' (' 259 . $CustomerData{UserEmail} . ')' 260 . ', ' . $Time; 261 } 262 263 $PDFObject->Text( 264 Text => $PrintedBy, 265 FontSize => 9, 266 ); 267 268 $PDFObject->PositionSet( 269 Move => 'relativ', 270 Y => -14, 271 ); 272 273 # Output ticket infos. 274 $Self->_PDFOutputTicketInfos( 275 PageData => \%Page, 276 TicketData => \%Ticket, 277 OwnerData => $TicketInfo{Owner}, 278 ResponsibleData => $TicketInfo{Responsible}, 279 Interface => \%Interface, 280 ); 281 282 $PDFObject->PositionSet( 283 Move => 'relativ', 284 Y => -6, 285 ); 286 287 # Output ticket dynamic fields. 288 $Self->_PDFOutputTicketDynamicFields( 289 PageData => \%Page, 290 TicketData => \%Ticket, 291 Interface => \%Interface, 292 ); 293 294 $PDFObject->PositionSet( 295 Move => 'relativ', 296 Y => -6, 297 ); 298 299 # Output linked objects. 300 if (%LinkData) { 301 302 # Get link type list. 303 my %LinkTypeList = $LinkObject->TypeList( 304 UserID => $Param{UserID}, 305 ); 306 307 $Self->_PDFOutputLinkedObjects( 308 PageData => \%Page, 309 LinkData => \%LinkData, 310 LinkTypeList => \%LinkTypeList, 311 ); 312 } 313 314 # Output customer infos. 315 if (%CustomerData) { 316 $Self->_PDFOutputCustomerInfos( 317 PageData => \%Page, 318 CustomerData => \%CustomerData, 319 ); 320 } 321 322 # Output articles. 323 $Self->_PDFOutputArticles( 324 PageData => \%Page, 325 ArticleData => \@ArticleBox, 326 ArticleNumber => $Param{ArticleNumber}, 327 Interface => \%Interface, 328 ); 329 330 # Return the PDF document. 331 return $PDFObject->DocumentOutput(); 332} 333 334sub _PDFOutputTicketInfos { 335 my ( $Self, %Param ) = @_; 336 337 # Check needed stuff for both interfaces. 338 for my $Needed (qw(PageData TicketData Interface)) { 339 if ( !defined( $Param{$Needed} ) ) { 340 $Kernel::OM->Get('Kernel::System::Log')->Log( 341 Priority => 'error', 342 Message => "Need $Needed!" 343 ); 344 return; 345 } 346 } 347 348 # Check needed param for agent interface. 349 if ( $Param{Interface}{Agent} && !defined( $Param{OwnerData} ) ) { 350 $Kernel::OM->Get('Kernel::System::Log')->Log( 351 Priority => 'error', 352 Message => "Need OwnerData!" 353 ); 354 return; 355 } 356 357 my %Ticket = %{ $Param{TicketData} }; 358 my %Page = %{ $Param{PageData} }; 359 360 # Get needed objects. 361 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 362 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 363 364 # Create left table. 365 my $TableLeft = []; 366 my $CustomerConfig; 367 if ( $Param{Interface}{Customer} ) { 368 $CustomerConfig = $ConfigObject->Get("Ticket::Frontend::CustomerTicketZoom"); 369 } 370 371 # Add ticket data, respecting AttributesView configuration for customer interface. 372 for my $Attribute (qw(State Priority Queue Lock CustomerID)) { 373 if ( $Param{Interface}{Agent} || $CustomerConfig->{AttributesView}->{$Attribute} ) { 374 my $Row = { 375 Key => $LayoutObject->{LanguageObject}->Translate($Attribute), 376 Value => $LayoutObject->{LanguageObject}->Translate( $Ticket{$Attribute} ) 377 || $Ticket{$Attribute} || '-', 378 }; 379 push( @{$TableLeft}, $Row ); 380 } 381 } 382 383 # Add Owner data, different output between interfaces. 384 if ( $Param{Interface}{Agent} ) { 385 my $OwnerRow = { 386 Key => $LayoutObject->{LanguageObject}->Translate('Owner'), 387 Value => $Ticket{Owner} . ' (' . $Param{OwnerData}->{UserFullname} . ')', 388 }; 389 push( @{$TableLeft}, $OwnerRow ); 390 } 391 elsif ( $Param{Interface}{Customer} && $CustomerConfig->{AttributesView}->{Owner} ) { 392 my $OwnerRow = { 393 Key => $LayoutObject->{LanguageObject}->Translate('Owner'), 394 Value => $Ticket{Owner}, 395 }; 396 push( @{$TableLeft}, $OwnerRow ); 397 } 398 399 # Add Responsible row, if feature is enabled. 400 if ( $ConfigObject->Get('Ticket::Responsible') ) { 401 my $Responsible = '-'; 402 if ( $Param{Interface}{Agent} && $Ticket{Responsible} ) { 403 $Responsible = $Ticket{Responsible} . ' (' . $Param{ResponsibleData}->{UserFullname} . ')'; 404 } 405 elsif ( 406 $Param{Interface}{Customer} 407 && $CustomerConfig->{AttributesView}->{Responsible} 408 && $Ticket{Responsible} 409 ) 410 { 411 $Responsible = $Ticket{Responsible}; 412 } 413 my $Row = { 414 Key => $LayoutObject->{LanguageObject}->Translate('Responsible'), 415 Value => $Responsible, 416 }; 417 push( @{$TableLeft}, $Row ); 418 } 419 420 # Add Type row, if feature is enabled. 421 if ( 422 $ConfigObject->Get('Ticket::Type') 423 && ( $Param{Interface}{Agent} || $CustomerConfig->{AttributesView}->{Type} ) 424 ) 425 { 426 my $Row = { 427 Key => $LayoutObject->{LanguageObject}->Translate('Type'), 428 Value => $Ticket{Type}, 429 }; 430 push( @{$TableLeft}, $Row ); 431 } 432 433 # Add Service and SLA row, if feature is enabled. 434 if ( $ConfigObject->Get('Ticket::Service') ) { 435 if ( $Param{Interface}{Agent} || $CustomerConfig->{AttributesView}->{Service} ) { 436 my $RowService = { 437 Key => $LayoutObject->{LanguageObject}->Translate('Service'), 438 Value => $Ticket{Service} || '-', 439 }; 440 push( @{$TableLeft}, $RowService ); 441 } 442 if ( $Param{Interface}{Agent} || $CustomerConfig->{AttributesView}->{SLA} ) { 443 my $RowSLA = { 444 Key => $LayoutObject->{LanguageObject}->Translate('SLA'), 445 Value => $Ticket{SLA} || '-', 446 }; 447 push( @{$TableLeft}, $RowSLA ); 448 } 449 } 450 451 # Create right table. 452 my $TableRight = [ 453 { 454 Key => $LayoutObject->{LanguageObject}->Translate('Age'), 455 Value => $LayoutObject->{LanguageObject}->Translate( $Ticket{Age} ), 456 }, 457 { 458 Key => $LayoutObject->{LanguageObject}->Translate('Created'), 459 Value => $LayoutObject->{LanguageObject}->FormatTimeString( 460 $Ticket{Created}, 461 'DateFormat', 462 ), 463 }, 464 ]; 465 466 if ( $Param{Interface}{Customer} ) { 467 unshift( 468 @{$TableRight}, 469 { 470 Key => $LayoutObject->{LanguageObject}->Translate('CustomerID'), 471 Value => $Ticket{CustomerID}, 472 } 473 ); 474 } 475 elsif ( $Param{Interface}{Agent} ) { 476 477 # Show created by if different then User ID 1. 478 if ( $Ticket{CreateBy} > 1 ) { 479 my $Row = { 480 Key => $LayoutObject->{LanguageObject}->Translate('Created by'), 481 Value => $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $Ticket{CreateBy} ), 482 }; 483 push( @{$TableRight}, $Row ); 484 } 485 486 # Show accounted time. 487 if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 488 my $Row = { 489 Key => $LayoutObject->{LanguageObject}->Translate('Accounted time'), 490 Value => $Ticket{TicketTimeUnits}, 491 }; 492 push( @{$TableRight}, $Row ); 493 } 494 495 # Only show pending until unless it is really pending. 496 if ( $Ticket{PendingUntil} ) { 497 my $Row = { 498 Key => $LayoutObject->{LanguageObject}->Translate('Pending till'), 499 Value => $Ticket{PendingUntil}, 500 }; 501 push( @{$TableRight}, $Row ); 502 } 503 504 # Add first response time row. 505 if ( defined( $Ticket{FirstResponseTime} ) ) { 506 my $Row = { 507 Key => $LayoutObject->{LanguageObject}->Translate('First Response Time'), 508 Value => $LayoutObject->{LanguageObject}->FormatTimeString( 509 $Ticket{FirstResponseTimeDestinationDate}, 510 'DateFormat', 511 'NoSeconds', 512 ), 513 }; 514 push( @{$TableRight}, $Row ); 515 } 516 517 # Add update time row. 518 if ( defined( $Ticket{UpdateTime} ) ) { 519 my $Row = { 520 Key => $LayoutObject->{LanguageObject}->Translate('Update Time'), 521 Value => $LayoutObject->{LanguageObject}->FormatTimeString( 522 $Ticket{UpdateTimeDestinationDate}, 523 'DateFormat', 524 'NoSeconds', 525 ), 526 }; 527 push( @{$TableRight}, $Row ); 528 } 529 530 # Add solution time row. 531 if ( defined( $Ticket{SolutionTime} ) ) { 532 my $Row = { 533 Key => $LayoutObject->{LanguageObject}->Translate('Solution Time'), 534 Value => $LayoutObject->{LanguageObject}->FormatTimeString( 535 $Ticket{SolutionTimeDestinationDate}, 536 'DateFormat', 537 'NoSeconds', 538 ), 539 }; 540 push( @{$TableRight}, $Row ); 541 } 542 } 543 544 my $Rows = @{$TableLeft}; 545 if ( @{$TableRight} > $Rows ) { 546 $Rows = @{$TableRight}; 547 } 548 549 my %TableParam; 550 for my $Row ( 1 .. $Rows ) { 551 $Row--; 552 $TableParam{CellData}[$Row][0]{Content} = $TableLeft->[$Row]->{Key}; 553 $TableParam{CellData}[$Row][0]{Font} = 'ProportionalBold'; 554 $TableParam{CellData}[$Row][1]{Content} = $TableLeft->[$Row]->{Value}; 555 $TableParam{CellData}[$Row][2]{Content} = ' '; 556 $TableParam{CellData}[$Row][2]{BackgroundColor} = '#FFFFFF'; 557 $TableParam{CellData}[$Row][3]{Content} = $TableRight->[$Row]->{Key}; 558 $TableParam{CellData}[$Row][3]{Font} = 'ProportionalBold'; 559 $TableParam{CellData}[$Row][4]{Content} = $TableRight->[$Row]->{Value}; 560 } 561 562 $TableParam{ColumnData}[0]{Width} = 70; 563 $TableParam{ColumnData}[1]{Width} = 156.5; 564 $TableParam{ColumnData}[2]{Width} = 1; 565 $TableParam{ColumnData}[3]{Width} = 70; 566 $TableParam{ColumnData}[4]{Width} = 156.5; 567 568 $TableParam{Type} = 'Cut'; 569 $TableParam{Border} = 0; 570 $TableParam{FontSize} = 7; 571 $TableParam{BackgroundColorEven} = '#F2F2F2'; 572 $TableParam{Padding} = 6; 573 $TableParam{PaddingTop} = 3; 574 $TableParam{PaddingBottom} = 3; 575 576 # Output table. 577 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 578 PAGE: 579 for ( $Page{PageCount} .. $Page{MaxPages} ) { 580 581 # Output table (or a fragment of it). 582 %TableParam = $PDFObject->Table( %TableParam, ); 583 584 # Stop output or output next page. 585 if ( $TableParam{State} ) { 586 last PAGE; 587 } 588 else { 589 $PDFObject->PageNew( 590 %Page, 591 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 592 ); 593 $Page{PageCount}++; 594 } 595 } 596 return 1; 597} 598 599sub _PDFOutputLinkedObjects { 600 my ( $Self, %Param ) = @_; 601 602 # Check needed stuff. 603 for my $Needed (qw(PageData LinkData LinkTypeList)) { 604 if ( !defined( $Param{$Needed} ) ) { 605 $Kernel::OM->Get('Kernel::System::Log')->Log( 606 Priority => 'error', 607 Message => "Need $Needed!" 608 ); 609 return; 610 } 611 } 612 613 my %Page = %{ $Param{PageData} }; 614 my %TypeList = %{ $Param{LinkTypeList} }; 615 my %TableParam; 616 my $Row = 0; 617 618 # Get needed objects. 619 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 620 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 621 622 for my $LinkTypeLinkDirection ( sort { lc $a cmp lc $b } keys %{ $Param{LinkData} } ) { 623 624 # Investigate link type name. 625 my @LinkData = split q{::}, $LinkTypeLinkDirection; 626 my $LinkTypeName = $TypeList{ $LinkData[0] }->{ $LinkData[1] . 'Name' }; 627 $LinkTypeName = $LayoutObject->{LanguageObject}->Translate($LinkTypeName); 628 629 # Define headline. 630 $TableParam{CellData}[$Row][0]{Content} = $LinkTypeName . ':'; 631 $TableParam{CellData}[$Row][0]{Font} = 'ProportionalBold'; 632 $TableParam{CellData}[$Row][1]{Content} = ''; 633 634 # Extract object list. 635 my $ObjectList = $Param{LinkData}->{$LinkTypeLinkDirection}; 636 637 for my $Object ( sort { lc $a cmp lc $b } keys %{$ObjectList} ) { 638 639 for my $Item ( @{ $ObjectList->{$Object} } ) { 640 641 $TableParam{CellData}[$Row][0]{Content} ||= ''; 642 $TableParam{CellData}[$Row][1]{Content} = $Item->{Title} || ''; 643 } 644 continue { 645 $Row++; 646 } 647 } 648 } 649 650 $TableParam{ColumnData}[0]{Width} = 80; 651 $TableParam{ColumnData}[1]{Width} = 431; 652 653 $PDFObject->HLine( 654 Color => '#aaa', 655 LineWidth => 0.5, 656 ); 657 658 # Set new position. 659 $PDFObject->PositionSet( 660 Move => 'relativ', 661 Y => -10, 662 ); 663 664 # Output headline. 665 $PDFObject->Text( 666 Text => $LayoutObject->{LanguageObject}->Translate('Linked Objects'), 667 Height => 10, 668 Type => 'Cut', 669 Font => 'Proportional', 670 FontSize => 9, 671 Color => '#666666', 672 ); 673 674 # Set new position. 675 $PDFObject->PositionSet( 676 Move => 'relativ', 677 Y => -4, 678 ); 679 680 # Table params. 681 $TableParam{Type} = 'Cut'; 682 $TableParam{Border} = 0; 683 $TableParam{FontSize} = 6; 684 $TableParam{Padding} = 1; 685 $TableParam{PaddingTop} = 3; 686 $TableParam{PaddingBottom} = 3; 687 688 # Output table. 689 PAGE: 690 for ( $Page{PageCount} .. $Page{MaxPages} ) { 691 692 # Output table (or a fragment of it). 693 %TableParam = $PDFObject->Table( %TableParam, ); 694 695 # Stop output or output next page. 696 if ( $TableParam{State} ) { 697 last PAGE; 698 } 699 else { 700 $PDFObject->PageNew( 701 %Page, 702 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 703 ); 704 $Page{PageCount}++; 705 } 706 } 707 708 return 1; 709} 710 711sub _PDFOutputTicketDynamicFields { 712 my ( $Self, %Param ) = @_; 713 714 # Check needed stuff. 715 for my $Needed (qw(PageData TicketData)) { 716 if ( !defined( $Param{$Needed} ) ) { 717 $Kernel::OM->Get('Kernel::System::Log')->Log( 718 Priority => 'error', 719 Message => "Need $Needed!" 720 ); 721 return; 722 } 723 } 724 my $Output = 0; 725 my %Ticket = %{ $Param{TicketData} }; 726 my %Page = %{ $Param{PageData} }; 727 728 my %TableParam; 729 my $Row = 0; 730 731 # Get dynamic field config for appropriate frontend module. 732 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 733 my $DynamicFieldFilter; 734 if ( $Param{Interface}{Agent} ) { 735 $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::AgentTicketPrint")->{DynamicField}; 736 } 737 elsif ( $Param{Interface}{Customer} ) { 738 $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::CustomerTicketPrint")->{DynamicField}; 739 } 740 741 # Get the dynamic fields for ticket object. 742 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 743 Valid => 1, 744 ObjectType => ['Ticket'], 745 FieldFilter => $DynamicFieldFilter || {}, 746 ); 747 748 # Get needed objects. 749 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 750 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 751 752 # Generate table, cycle trough the activated Dynamic Fields for ticket object. 753 DYNAMICFIELD: 754 for my $DynamicFieldConfig ( @{$DynamicField} ) { 755 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 756 757 if ( $Param{Interface}{Customer} ) { 758 759 # Skip dynamic field if is not designed for customer interface. 760 my $IsCustomerInterfaceCapable = $DynamicFieldBackendObject->HasBehavior( 761 DynamicFieldConfig => $DynamicFieldConfig, 762 Behavior => 'IsCustomerInterfaceCapable', 763 ); 764 next DYNAMICFIELD if !$IsCustomerInterfaceCapable; 765 } 766 767 my $Value = $DynamicFieldBackendObject->ValueGet( 768 DynamicFieldConfig => $DynamicFieldConfig, 769 ObjectID => $Ticket{TicketID}, 770 ); 771 772 next DYNAMICFIELD if !$Value; 773 next DYNAMICFIELD if $Value eq ""; 774 775 # Get print string for this dynamic field. 776 my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( 777 DynamicFieldConfig => $DynamicFieldConfig, 778 Value => $Value, 779 HTMLOutput => 0, 780 LayoutObject => $LayoutObject, 781 ); 782 783 $TableParam{CellData}[$Row][0]{Content} 784 = $LayoutObject->{LanguageObject}->Translate( $DynamicFieldConfig->{Label} ) 785 . ':'; 786 $TableParam{CellData}[$Row][0]{Font} = 'ProportionalBold'; 787 $TableParam{CellData}[$Row][1]{Content} = $ValueStrg->{Value}; 788 789 $Row++; 790 $Output = 1; 791 } 792 793 $TableParam{ColumnData}[0]{Width} = 80; 794 $TableParam{ColumnData}[1]{Width} = 431; 795 796 # Output ticket dynamic fields. 797 if ($Output) { 798 799 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 800 801 $PDFObject->HLine( 802 Color => '#aaa', 803 LineWidth => 0.5, 804 ); 805 806 # Set new position. 807 $PDFObject->PositionSet( 808 Move => 'relativ', 809 Y => -10, 810 ); 811 812 # Output headline. 813 $PDFObject->Text( 814 Text => $LayoutObject->{LanguageObject}->Translate('Ticket Dynamic Fields'), 815 Height => 10, 816 Type => 'Cut', 817 Font => 'Proportional', 818 FontSize => 8, 819 Color => '#666666', 820 ); 821 822 # Set new position. 823 $PDFObject->PositionSet( 824 Move => 'relativ', 825 Y => -4, 826 ); 827 828 # Table params. 829 $TableParam{Type} = 'Cut'; 830 $TableParam{Border} = 0; 831 $TableParam{FontSize} = 6; 832 $TableParam{Padding} = 1; 833 $TableParam{PaddingTop} = 3; 834 $TableParam{PaddingBottom} = 3; 835 836 # Output table. 837 PAGE: 838 for ( $Page{PageCount} .. $Page{MaxPages} ) { 839 840 # Output table (or a fragment of it). 841 %TableParam = $PDFObject->Table( %TableParam, ); 842 843 # Stop output or output next page. 844 if ( $TableParam{State} ) { 845 last PAGE; 846 } 847 else { 848 $PDFObject->PageNew( 849 %Page, 850 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 851 ); 852 $Page{PageCount}++; 853 } 854 } 855 } 856 return 1; 857} 858 859sub _PDFOutputCustomerInfos { 860 my ( $Self, %Param ) = @_; 861 862 # Check needed stuff. 863 for my $Needed (qw(PageData CustomerData)) { 864 if ( !defined( $Param{$Needed} ) ) { 865 $Kernel::OM->Get('Kernel::System::Log')->Log( 866 Priority => 'error', 867 Message => "Need $Needed!" 868 ); 869 return; 870 } 871 } 872 my $Output = 0; 873 my %CustomerData = %{ $Param{CustomerData} }; 874 my %Page = %{ $Param{PageData} }; 875 my %TableParam; 876 my $Row = 0; 877 my $Map = $CustomerData{Config}->{Map}; 878 879 # Check if customer company support is enabled. 880 if ( $CustomerData{Config}->{CustomerCompanySupport} ) { 881 my $Map2 = $CustomerData{CompanyConfig}->{Map}; 882 if ($Map2) { 883 push( @{$Map}, @{$Map2} ); 884 } 885 } 886 887 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 888 889 for my $Field ( @{$Map} ) { 890 if ( ${$Field}[3] && $CustomerData{ ${$Field}[0] } ) { 891 $TableParam{CellData}[$Row][0]{Content} = $LayoutObject->{LanguageObject}->Translate( ${$Field}[1] ) . ':'; 892 $TableParam{CellData}[$Row][0]{Font} = 'ProportionalBold'; 893 $TableParam{CellData}[$Row][1]{Content} = $CustomerData{ ${$Field}[0] }; 894 895 $Row++; 896 $Output = 1; 897 } 898 } 899 $TableParam{ColumnData}[0]{Width} = 80; 900 $TableParam{ColumnData}[1]{Width} = 431; 901 902 if ($Output) { 903 904 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 905 906 # Set new position. 907 $PDFObject->PositionSet( 908 Move => 'relativ', 909 Y => -10, 910 ); 911 912 $PDFObject->HLine( 913 Color => '#aaa', 914 LineWidth => 0.5, 915 ); 916 917 # Set new position. 918 $PDFObject->PositionSet( 919 Move => 'relativ', 920 Y => -4, 921 ); 922 923 # Output headline. 924 $PDFObject->Text( 925 Text => $LayoutObject->{LanguageObject}->Translate('Customer Information'), 926 Height => 10, 927 Type => 'Cut', 928 Font => 'Proportional', 929 FontSize => 8, 930 Color => '#666666', 931 ); 932 933 # Set new position. 934 $PDFObject->PositionSet( 935 Move => 'relativ', 936 Y => -4, 937 ); 938 939 # Table params. 940 $TableParam{Type} = 'Cut'; 941 $TableParam{Border} = 0; 942 $TableParam{FontSize} = 6; 943 $TableParam{Padding} = 1; 944 $TableParam{PaddingTop} = 3; 945 $TableParam{PaddingBottom} = 3; 946 947 # Output table. 948 PAGE: 949 for ( $Page{PageCount} .. $Page{MaxPages} ) { 950 951 # Output table (or a fragment of it). 952 %TableParam = $PDFObject->Table( %TableParam, ); 953 954 # Stop output or output next page. 955 if ( $TableParam{State} ) { 956 last PAGE; 957 } 958 else { 959 $PDFObject->PageNew( 960 %Page, 961 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 962 ); 963 $Page{PageCount}++; 964 } 965 } 966 } 967 return 1; 968} 969 970sub _PDFOutputArticles { 971 my ( $Self, %Param ) = @_; 972 973 # Check needed stuff. 974 for my $Needed (qw(PageData ArticleData)) { 975 if ( !defined( $Param{$Needed} ) ) { 976 $Kernel::OM->Get('Kernel::System::Log')->Log( 977 Priority => 'error', 978 Message => "Need $Needed!" 979 ); 980 return; 981 } 982 } 983 my %Page = %{ $Param{PageData} }; 984 985 # Get needed objects. 986 my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); 987 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 988 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 989 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 990 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 991 992 my @ArticleData = @{ $Param{ArticleData} }; 993 my $ArticleCount = scalar @ArticleData; 994 995 # Get config settings. 996 my $ZoomExpandSort = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Frontend::ZoomExpandSort'); 997 998 # Resort article order. 999 if ( $Param{Interface}{Agent} && $ZoomExpandSort eq 'reverse' ) { 1000 @ArticleData = reverse(@ArticleData); 1001 } 1002 1003 my $ArticleCounter = 1; 1004 for my $ArticleTmp (@ArticleData) { 1005 1006 my %Article = %{$ArticleTmp}; 1007 1008 # Get attachment string. 1009 my %AtmIndex = (); 1010 if ( $Article{Atms} ) { 1011 %AtmIndex = %{ $Article{Atms} }; 1012 } 1013 my $Attachments; 1014 for my $FileID ( sort keys %AtmIndex ) { 1015 my %File = %{ $AtmIndex{$FileID} }; 1016 my $Filesize = $LayoutObject->HumanReadableDataSize( Size => $File{FilesizeRaw} ); 1017 $Attachments .= $File{Filename} . ' (' . $Filesize . ")\n"; 1018 } 1019 1020 # Show total accounted time if feature is active. 1021 if ( $Param{Interface}{Agent} && $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 1022 $Article{'Accounted time'} = $ArticleObject->ArticleAccountedTimeGet( 1023 ArticleID => $Article{ArticleID}, 1024 ); 1025 } 1026 1027 # Generate article info table. 1028 my %TableParam1; 1029 my $Row = 0; 1030 1031 $PDFObject->PositionSet( 1032 Move => 'relativ', 1033 Y => -6, 1034 ); 1035 1036 # Get article number. 1037 my $ArticleNumber; 1038 if ( $Param{ArticleNumber} ) { 1039 $ArticleNumber = $Param{ArticleNumber}; 1040 } 1041 else { 1042 $ArticleNumber = $ZoomExpandSort eq 'reverse' ? $ArticleCount - $ArticleCounter + 1 : $ArticleCounter; 1043 } 1044 1045 if ( $Param{Interface}{Customer} ) { 1046 $ArticleNumber = $ArticleCounter; 1047 } 1048 1049 # Article number tag. 1050 $PDFObject->Text( 1051 Text => $LayoutObject->{LanguageObject}->Translate('Article') . ' #' . $ArticleNumber, 1052 Height => 10, 1053 Type => 'Cut', 1054 Font => 'Proportional', 1055 FontSize => 8, 1056 Color => '#666666', 1057 ); 1058 1059 $PDFObject->PositionSet( 1060 Move => 'relativ', 1061 Y => 2, 1062 ); 1063 1064 my %ArticleFields = $LayoutObject->ArticleFields(%Article); 1065 1066 # Display article fields. 1067 ARTICLE_FIELD: 1068 for my $ArticleFieldKey ( 1069 sort { $ArticleFields{$a}->{Prio} <=> $ArticleFields{$b}->{Prio} } 1070 keys %ArticleFields 1071 ) 1072 { 1073 my %ArticleField = %{ $ArticleFields{$ArticleFieldKey} // {} }; 1074 1075 next ARTICLE_FIELD if $Param{Interface}->{Customer} && $ArticleField{HideInCustomerInterface}; 1076 next ARTICLE_FIELD if $ArticleField{HideInTicketPrint}; 1077 next ARTICLE_FIELD if !$ArticleField{Value}; 1078 1079 $TableParam1{CellData}[$Row][0]{Content} 1080 = $LayoutObject->{LanguageObject}->Translate( $ArticleField{Label} ) . ':'; 1081 $TableParam1{CellData}[$Row][0]{Font} = 'ProportionalBold'; 1082 $TableParam1{CellData}[$Row][1]{Content} = $ArticleField{Value}; 1083 $Row++; 1084 } 1085 1086 # Display article accounted time. 1087 if ( $Param{Interface}{Agent} ) { 1088 my $ArticleTime = $ArticleObject->ArticleAccountedTimeGet( 1089 ArticleID => $Article{ArticleID}, 1090 ); 1091 if ($ArticleTime) { 1092 $TableParam1{CellData}[$Row][0]{Content} 1093 = $LayoutObject->{LanguageObject}->Translate('Accounted time') . ':'; 1094 $TableParam1{CellData}[$Row][0]{Font} = 'ProportionalBold'; 1095 $TableParam1{CellData}[$Row][1]{Content} = $ArticleTime; 1096 $Row++; 1097 } 1098 } 1099 1100 $TableParam1{CellData}[$Row][0]{Content} = $LayoutObject->{LanguageObject}->Translate('Created') . ':'; 1101 $TableParam1{CellData}[$Row][0]{Font} = 'ProportionalBold'; 1102 $TableParam1{CellData}[$Row][1]{Content} = $LayoutObject->{LanguageObject}->FormatTimeString( 1103 $Article{CreateTime}, 1104 'DateFormat', 1105 ); 1106 $TableParam1{CellData}[$Row][1]{Content} 1107 .= ' ' . $LayoutObject->{LanguageObject}->Translate('by'); 1108 my $SenderType = $ArticleObject->ArticleSenderTypeLookup( 1109 SenderTypeID => $Article{SenderTypeID}, 1110 ); 1111 $TableParam1{CellData}[$Row][1]{Content} 1112 .= ' ' . $LayoutObject->{LanguageObject}->Translate($SenderType); 1113 $Row++; 1114 1115 # Get dynamic field config for appropriate frontend module. 1116 my $DynamicFieldFilter; 1117 if ( $Param{Interface}{Agent} ) { 1118 $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::AgentTicketPrint")->{DynamicField}; 1119 } 1120 elsif ( $Param{Interface}{Customer} ) { 1121 $DynamicFieldFilter = $ConfigObject->Get("Ticket::Frontend::CustomerTicketPrint")->{DynamicField}; 1122 } 1123 1124 # Get the dynamic fields for ticket object. 1125 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1126 Valid => 1, 1127 ObjectType => ['Article'], 1128 FieldFilter => $DynamicFieldFilter || {}, 1129 ); 1130 1131 # Generate table, cycle trough the activated Dynamic Fields for ticket object. 1132 DYNAMICFIELD: 1133 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1134 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1135 1136 if ( $Param{Interface}{Customer} ) { 1137 1138 # Skip the dynamic field if it is not designed for customer interface. 1139 my $IsCustomerInterfaceCapable = $DynamicFieldBackendObject->HasBehavior( 1140 DynamicFieldConfig => $DynamicFieldConfig, 1141 Behavior => 'IsCustomerInterfaceCapable', 1142 ); 1143 next DYNAMICFIELD if !$IsCustomerInterfaceCapable; 1144 } 1145 1146 my $Value = $DynamicFieldBackendObject->ValueGet( 1147 DynamicFieldConfig => $DynamicFieldConfig, 1148 ObjectID => $Article{ArticleID}, 1149 ); 1150 1151 next DYNAMICFIELD if !$Value; 1152 next DYNAMICFIELD if $Value eq ""; 1153 1154 # Get print string for this dynamic field. 1155 my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( 1156 DynamicFieldConfig => $DynamicFieldConfig, 1157 Value => $Value, 1158 HTMLOutput => 0, 1159 LayoutObject => $LayoutObject, 1160 ); 1161 $TableParam1{CellData}[$Row][0]{Content} 1162 = $LayoutObject->{LanguageObject}->Translate( $DynamicFieldConfig->{Label} ) 1163 . ':'; 1164 $TableParam1{CellData}[$Row][0]{Font} = 'ProportionalBold'; 1165 $TableParam1{CellData}[$Row][1]{Content} = $ValueStrg->{Value}; 1166 $Row++; 1167 } 1168 1169 if ($Attachments) { 1170 $TableParam1{CellData}[$Row][0]{Content} = $LayoutObject->{LanguageObject}->Translate('Attachment') . ':'; 1171 $TableParam1{CellData}[$Row][0]{Font} = 'ProportionalBold'; 1172 chomp($Attachments); 1173 $TableParam1{CellData}[$Row][1]{Content} = $Attachments; 1174 } 1175 $TableParam1{ColumnData}[0]{Width} = 80; 1176 $TableParam1{ColumnData}[1]{Width} = 431; 1177 1178 $PDFObject->PositionSet( 1179 Move => 'relativ', 1180 Y => -6, 1181 ); 1182 1183 $PDFObject->HLine( 1184 Color => '#aaa', 1185 LineWidth => 0.5, 1186 ); 1187 1188 $PDFObject->PositionSet( 1189 Move => 'relativ', 1190 Y => -6, 1191 ); 1192 1193 # Table params (article infos). 1194 $TableParam1{Type} = 'Cut'; 1195 $TableParam1{Border} = 0; 1196 $TableParam1{FontSize} = 6; 1197 $TableParam1{Padding} = 1; 1198 $TableParam1{PaddingTop} = 3; 1199 $TableParam1{PaddingBottom} = 3; 1200 1201 # Output table (article infos). 1202 PAGE: 1203 for ( $Page{PageCount} .. $Page{MaxPages} ) { 1204 1205 # Output table (or a fragment of it). 1206 %TableParam1 = $PDFObject->Table( %TableParam1, ); 1207 1208 # Stop output or output next page. 1209 if ( $TableParam1{State} ) { 1210 last PAGE; 1211 } 1212 else { 1213 $PDFObject->PageNew( 1214 %Page, 1215 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 1216 ); 1217 $Page{PageCount}++; 1218 } 1219 } 1220 1221 my $ArticlePreview = $LayoutObject->ArticlePreview( 1222 %Article, 1223 ResultType => 'plain', 1224 ); 1225 1226 # Table params (article body). 1227 my %TableParam2; 1228 $TableParam2{CellData}[0][0]{Content} = $ArticlePreview || ' '; 1229 $TableParam2{Type} = 'Cut'; 1230 $TableParam2{Border} = 0; 1231 $TableParam2{Font} = 'Monospaced'; 1232 $TableParam2{FontSize} = 7; 1233 $TableParam2{BackgroundColor} = '#f2f2f2'; 1234 $TableParam2{Padding} = 4; 1235 $TableParam2{PaddingTop} = 4; 1236 $TableParam2{PaddingBottom} = 4; 1237 1238 # Output table (article body). 1239 PAGE: 1240 for ( $Page{PageCount} .. $Page{MaxPages} ) { 1241 1242 # Output table (or a fragment of it). 1243 %TableParam2 = $PDFObject->Table( %TableParam2, ); 1244 1245 # Stop output or output next page. 1246 if ( $TableParam2{State} ) { 1247 last PAGE; 1248 } 1249 else { 1250 $PDFObject->PageNew( 1251 %Page, 1252 FooterRight => $Page{PageText} . ' ' . $Page{PageCount}, 1253 ); 1254 $Page{PageCount}++; 1255 } 1256 } 1257 $ArticleCounter++; 1258 } 1259 return 1; 1260} 1261 12621; 1263