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