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::Article::MIMEBase;
10
11use strict;
12use warnings;
13
14use parent 'Kernel::Output::HTML::Article::Base';
15
16use Mail::Address;
17
18use Kernel::Language qw(Translatable);
19use Kernel::System::VariableCheck qw(:all);
20
21our @ObjectDependencies = (
22    'Kernel::Config',
23    'Kernel::Output::HTML::Layout',
24    'Kernel::System::CheckItem',
25    'Kernel::System::CustomerUser',
26    'Kernel::System::Encode',
27    'Kernel::System::Log',
28    'Kernel::System::Main',
29    'Kernel::System::Ticket',
30    'Kernel::System::Ticket::Article',
31);
32
33sub new {
34    my ( $Type, %Param ) = @_;
35
36    my $Self = {};
37    bless( $Self, $Type );
38
39    return $Self;
40}
41
42=head2 ArticleFields()
43
44Returns common article fields for a MIMEBase article.
45
46    my %ArticleFields = $LayoutObject->ArticleFields(
47        TicketID  => 123,   # (required)
48        ArticleID => 123,   # (required)
49    );
50
51Returns:
52
53    %ArticleFields = (
54        Sender => {                     # mandatory
55            Label => 'Sender',
56            Value => 'John Doe',
57            Prio  => 100,
58        },
59        Subject => {                    # mandatory
60            Label => 'Subject',
61            Value => 'Article subject',
62            Prio  => 200,
63        },
64        PGP => {
65            Label => 'PGP',             # mandatory
66            Value => 'Value',           # mandatory
67            Class => 'Class',           # optional
68            ...
69        },
70        ...
71    );
72
73=cut
74
75sub ArticleFields {
76    my ( $Self, %Param ) = @_;
77
78    # Check needed stuff.
79    for my $Needed (qw(TicketID ArticleID)) {
80        if ( !$Param{$Needed} ) {
81            $Kernel::OM->Get('Kernel::System::Log')->Log(
82                Priority => 'error',
83                Message  => "Need $Needed!",
84            );
85            return;
86        }
87    }
88
89    my %Result;
90
91    my $ConfigObject         = $Kernel::OM->Get('Kernel::Config');
92    my $LayoutObject         = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
93    my $MainObject           = $Kernel::OM->Get('Kernel::System::Main');
94    my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param);
95
96    my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
97        %Param,
98        DynamicFields => 0,
99    );
100
101    my %Article = $ArticleBackendObject->ArticleGet(
102        %Param,
103        DynamicFields => 1,
104        RealNames     => 1,
105    );
106
107    # cleanup subject
108    $Article{Subject} = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSubjectClean(
109        TicketNumber => $Ticket{TicketNumber},
110        Subject      => $Article{Subject} || '',
111        Size         => 0,
112    );
113
114    $Result{Subject} = {
115        Label => 'Subject',
116        Value => $Article{Subject},
117    };
118
119    # run article view modules
120    my $Config = $ConfigObject->Get('Ticket::Frontend::ArticleViewModule');
121
122    if ( ref $Config eq 'HASH' ) {
123        my %Jobs = %{$Config};
124
125        JOB:
126        for my $Job ( sort keys %Jobs ) {
127
128            # load module
129            next JOB if !$MainObject->Require( $Jobs{$Job}->{Module} );
130
131            my $Object = $Jobs{$Job}->{Module}->new(
132                TicketID  => $Self->{TicketID},
133                ArticleID => $Param{ArticleID},
134                UserID    => $Param{UserID} || 1,
135            );
136
137            # run module
138            my @Data = $Object->Check(
139                Article => \%Article,
140                %Ticket, Config => $Jobs{$Job}
141            );
142            for my $DataRef (@Data) {
143                if ( !$DataRef->{Successful} ) {
144                    $DataRef->{Result} = 'Error';
145                }
146                else {
147                    $DataRef->{Result} = 'Notice';
148                }
149
150                $Result{ $DataRef->{Key} } = {
151                    Label => $DataRef->{Key},
152                    Value => $DataRef->{Value},
153                    Class => $DataRef->{Result},
154                    Type  => 'ArticleOption',
155                };
156
157                for my $Warning ( @{ $DataRef->{Warnings} } ) {
158                    $Result{ $DataRef->{Key} } = {
159                        Label => $Warning->{Key},
160                        Value => $Warning->{Value},
161                        Class => $Warning->{Result},
162                        Type  => 'ArticleOption',
163                    };
164                }
165            }
166
167            # TODO: Check how to implement this.
168            # # filter option
169            # $Object->Filter(
170            #     Article => \%Article,
171            #     %Ticket, Config => $Jobs{$Job}
172            # );
173        }
174    }
175
176    # do some strips && quoting
177    my $RecipientDisplayType = $ConfigObject->Get('Ticket::Frontend::DefaultRecipientDisplayType') || 'Realname';
178    my $SenderDisplayType    = $ConfigObject->Get('Ticket::Frontend::DefaultSenderDisplayType')    || 'Realname';
179    KEY:
180    for my $Key (qw(From To Cc Bcc)) {
181        next KEY if !$Article{$Key};
182
183        my $DisplayType = $Key eq 'From'             ? $SenderDisplayType : $RecipientDisplayType;
184        my $HiddenType  = $DisplayType eq 'Realname' ? 'Value'            : 'Realname';
185        $Result{$Key} = {
186            Label                   => $Key,
187            Value                   => $Article{$Key},
188            Realname                => $Article{ $Key . 'Realname' },
189            ArticleID               => $Article{ArticleID},
190            $HiddenType . Hidden    => 'Hidden',
191            HideInCustomerInterface => $Key eq 'Bcc' ? 1 : undef,
192        };
193        if ( $Key eq 'From' ) {
194            $Result{Sender} = {
195                Label                => Translatable('Sender'),
196                Value                => $Article{From},
197                Realname             => $Article{FromRealname},
198                $HiddenType . Hidden => 'Hidden',
199                HideInTimelineView   => 1,
200                HideInTicketPrint    => 1,
201            };
202        }
203    }
204
205    # Assign priority.
206    my $Priority = 100;
207    for my $Key (qw(From To Cc Bcc)) {
208        if ( $Result{$Key} ) {
209            $Result{$Key}->{Prio} = $Priority;
210            $Priority += 100;
211        }
212    }
213
214    my @FieldsWithoutPrio = grep { !$Result{$_}->{Prio} } sort keys %Result;
215
216    my $BasePrio = 100000;
217    for my $Key (@FieldsWithoutPrio) {
218        $Result{$Key}->{Prio} = $BasePrio++;
219    }
220
221    return %Result;
222}
223
224=head2 ArticlePreview()
225
226Returns article preview for a MIMEBase article.
227
228    $LayoutObject->ArticlePreview(
229        TicketID   => 123,     # (required)
230        ArticleID  => 123,     # (required)
231        ResultType => 'plain', # (optional) plain|HTML. Default HTML.
232        MaxLength  => 50,      # (optional) performs trimming (for plain result only)
233    );
234
235Returns article preview in scalar form:
236
237    $ArticlePreview = 'Hello, world!';
238
239If HTML preview was requested, but HTML content does not exist for an article, this function will return undef.
240
241=cut
242
243sub ArticlePreview {
244    my ( $Self, %Param ) = @_;
245
246    # Check needed stuff.
247    for my $Needed (qw(TicketID ArticleID)) {
248        if ( !$Param{$Needed} ) {
249            $Kernel::OM->Get('Kernel::System::Log')->Log(
250                Priority => 'error',
251                Message  => "Need $Needed!",
252            );
253            return;
254        }
255    }
256
257    if ( $Param{MaxLength} && !IsPositiveInteger( $Param{MaxLength} ) ) {
258        $Kernel::OM->Get('Kernel::System::Log')->Log(
259            Priority => 'error',
260            Message  => "MaxLength must be positive integer!"
261        );
262
263        return;
264    }
265
266    my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param);
267
268    my %Article = $ArticleBackendObject->ArticleGet(
269        %Param,
270        DynamicFields => 0,
271    );
272
273    my $Result;
274
275    if ( $Param{ResultType} && $Param{ResultType} eq 'plain' ) {
276
277        # plain
278        $Result = $Article{Body};
279
280        if ( $Param{MaxLength} ) {
281
282            # trim
283            $Result = substr( $Result, 0, $Param{MaxLength} );
284        }
285    }
286    else {
287        my $HTMLBodyAttachmentID = $Self->HTMLBodyAttachmentIDGet(%Param);
288
289        if ($HTMLBodyAttachmentID) {
290
291            # Preview doesn't include inline images...
292            my %Data = $ArticleBackendObject->ArticleAttachment(
293                ArticleID => $Param{ArticleID},
294                FileID    => $HTMLBodyAttachmentID,
295            );
296
297            # Get the charset directly from the attachment hash and convert content to the internal charset (utf-8).
298            #   Please see bug#13367 for more information.
299            my $Charset;
300            if ( $Data{ContentType} =~ m/.+?charset=("|'|)(?<Charset>.+)/ig ) {
301                $Charset = $+{Charset};
302                $Charset =~ s/"|'//g;
303            }
304            else {
305                $Charset = 'us-ascii';
306            }
307
308            $Result = $Kernel::OM->Get('Kernel::System::Encode')->Convert(
309                Text  => $Data{Content},
310                From  => $Charset,
311                To    => 'utf-8',
312                Check => 1,
313            );
314        }
315    }
316
317    return $Result;
318}
319
320=head2 HTMLBodyAttachmentIDGet()
321
322Returns HTMLBodyAttachmentID.
323
324    my $HTMLBodyAttachmentID = $LayoutObject->HTMLBodyAttachmentIDGet(
325        TicketID  => 123,     # (required)
326        ArticleID => 123,     # (required)
327    );
328
329Returns
330
331    $HTMLBodyAttachmentID = 23;
332
333=cut
334
335sub HTMLBodyAttachmentIDGet {
336    my ( $Self, %Param ) = @_;
337
338    # Check needed stuff.
339    for my $Needed (qw(TicketID ArticleID)) {
340        if ( !$Param{$Needed} ) {
341            $Kernel::OM->Get('Kernel::System::Log')->Log(
342                Priority => 'error',
343                Message  => "Need $Needed!",
344            );
345            return;
346        }
347    }
348
349    my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param);
350
351    # Get a HTML attachment.
352    my %AttachmentIndexHTMLBody = $ArticleBackendObject->ArticleAttachmentIndex(
353        ArticleID    => $Param{ArticleID},
354        OnlyHTMLBody => 1,
355    );
356
357    my ($HTMLBodyAttachmentID) = sort keys %AttachmentIndexHTMLBody;
358
359    return $HTMLBodyAttachmentID;
360}
361
362=head2 ArticleCustomerRecipientsGet()
363
364Get customer users from an article to use as recipients.
365
366    my @CustomerUserIDs = $LayoutObject->ArticleCustomerRecipientsGet(
367        TicketID  => 123,     # (required)
368        ArticleID => 123,     # (required)
369    );
370
371Returns array of customer user IDs who should receive a message:
372
373    @CustomerUserIDs = (
374        'customer-1',
375        'customer-2',
376        ...
377    );
378
379=cut
380
381sub ArticleCustomerRecipientsGet {
382    my ( $Self, %Param ) = @_;
383
384    for my $Needed (qw(TicketID ArticleID)) {
385        if ( !$Param{$Needed} ) {
386            $Kernel::OM->Get('Kernel::System::Log')->Log(
387                Priority => 'error',
388                Message  => "Need $Needed!",
389            );
390            return;
391        }
392    }
393
394    my %Article = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param)->ArticleGet(%Param);
395    return if !%Article;
396
397    my $RecipientEmail = $Article{From};
398
399    # Check ReplyTo.
400    if ( $Article{ReplyTo} ) {
401        $RecipientEmail = $Article{ReplyTo};
402    }
403
404    # Check article type and use To in case sender is not customer.
405    if ( $Article{SenderType} !~ /customer/ ) {
406        $RecipientEmail = $Article{To};
407        $Article{ReplyTo} = '';
408    }
409
410    my $CheckItemObject    = $Kernel::OM->Get('Kernel::System::CheckItem');
411    my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser');
412
413    my @CustomerUserIDs;
414
415    EMAIL:
416    for my $Email ( Mail::Address->parse($RecipientEmail) ) {
417        next EMAIL if !$CheckItemObject->CheckEmail( Address => $Email->address() );
418
419        # Get single customer user from customer backend based on the email address.
420        my %CustomerSearch = $CustomerUserObject->CustomerSearch(
421            PostMasterSearch => $Email->address(),
422            Limit            => 1,
423        );
424        next EMAIL if !%CustomerSearch;
425
426        # Save customer user ID if not already present in the list.
427        for my $CustomerUserID ( sort keys %CustomerSearch ) {
428            push @CustomerUserIDs, $CustomerUserID if !grep { $_ eq $CustomerUserID } @CustomerUserIDs;
429        }
430    }
431
432    return @CustomerUserIDs;
433}
434
4351;
436