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::Modules::AgentTicketMerge;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw(:all);
15use Kernel::Language qw(Translatable);
16use Mail::Address;
17
18our $ObjectManagerDisabled = 1;
19
20sub new {
21    my ( $Type, %Param ) = @_;
22
23    my $Self = {%Param};
24    bless( $Self, $Type );
25
26    return $Self;
27}
28
29sub Run {
30    my ( $Self, %Param ) = @_;
31
32    my $Output;
33    my %Error;
34    my %GetParam;
35
36    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
37
38    if ( !$Self->{TicketID} ) {
39        return $LayoutObject->ErrorScreen(
40            Message => Translatable('No TicketID is given!'),
41            Comment => Translatable('Please contact the administrator.'),
42        );
43    }
44
45    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
46    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
47
48    # get config param
49    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
50
51    # check permissions
52    my $Access = $TicketObject->TicketPermission(
53        Type     => $Config->{Permission},
54        TicketID => $Self->{TicketID},
55        UserID   => $Self->{UserID}
56    );
57
58    # error screen, don't show ticket
59    if ( !$Access ) {
60        return $LayoutObject->NoPermission( WithHeader => 'yes' );
61    }
62
63    # get ACL restrictions
64    my %PossibleActions = ( 1 => $Self->{Action} );
65
66    my $ACL = $TicketObject->TicketAcl(
67        Data          => \%PossibleActions,
68        Action        => $Self->{Action},
69        TicketID      => $Self->{TicketID},
70        ReturnType    => 'Action',
71        ReturnSubType => '-',
72        UserID        => $Self->{UserID},
73    );
74    my %AclAction = $TicketObject->TicketAclActionData();
75
76    # check if ACL restrictions exist
77    if ( $ACL || IsHashRefWithData( \%AclAction ) ) {
78
79        my %AclActionLookup = reverse %AclAction;
80
81        # show error screen if ACL prohibits this action
82        if ( !$AclActionLookup{ $Self->{Action} } ) {
83            return $LayoutObject->NoPermission( WithHeader => 'yes' );
84        }
85    }
86
87    # get ticket data
88    my %Ticket = $TicketObject->TicketGet( TicketID => $Self->{TicketID} );
89
90    # get lock state && write (lock) permissions
91    if ( $Config->{RequiredLock} ) {
92        if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) {
93
94            my $Lock = $TicketObject->TicketLockSet(
95                TicketID => $Self->{TicketID},
96                Lock     => 'lock',
97                UserID   => $Self->{UserID}
98            );
99
100            # Set new owner if ticket owner is different then logged user.
101            if ( $Lock && ( $Ticket{OwnerID} != $Self->{UserID} ) ) {
102
103                # Remember previous owner, which will be used to restore ticket owner on undo action.
104                $Param{PreviousOwner} = $Ticket{OwnerID};
105
106                my $Success = $TicketObject->TicketOwnerSet(
107                    TicketID  => $Self->{TicketID},
108                    UserID    => $Self->{UserID},
109                    NewUserID => $Self->{UserID},
110                );
111
112                # Show lock state.
113                if ($Success) {
114                    $LayoutObject->Block(
115                        Name => 'PropertiesLock',
116                        Data => {
117                            %Param,
118                            TicketID => $Self->{TicketID}
119                        },
120                    );
121                }
122            }
123        }
124        else {
125            my $AccessOk = $TicketObject->OwnerCheck(
126                TicketID => $Self->{TicketID},
127                OwnerID  => $Self->{UserID},
128            );
129            if ( !$AccessOk ) {
130                my $Output = $LayoutObject->Header(
131                    Value     => $Ticket{Number},
132                    Type      => 'Small',
133                    BodyClass => 'Popup',
134                );
135                $Output .= $LayoutObject->Warning(
136                    Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'),
137                    Comment => Translatable('Please change the owner first.'),
138                );
139                $Output .= $LayoutObject->Footer(
140                    Type => 'Small',
141                );
142                return $Output;
143            }
144
145            # show back link
146            $LayoutObject->Block(
147                Name => 'TicketBack',
148                Data => { %Param, TicketID => $Self->{TicketID} },
149            );
150        }
151    }
152    else {
153
154        # show back link
155        $LayoutObject->Block(
156            Name => 'TicketBack',
157            Data => { %Param, TicketID => $Self->{TicketID} },
158        );
159    }
160
161    # merge action
162    if ( $Self->{Subaction} eq 'Merge' ) {
163
164        # challenge token check for write action
165        $LayoutObject->ChallengeTokenCheck();
166
167        # get all parameters
168        for my $Parameter (qw( From To Subject Body InformSender MainTicketNumber )) {
169            $GetParam{$Parameter} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => $Parameter )
170                || '';
171        }
172
173        # rewrap body if no rich text is used
174        if ( $GetParam{Body} && !$LayoutObject->{BrowserRichText} ) {
175            $GetParam{Body} = $LayoutObject->WrapPlainText(
176                MaxCharacters => $ConfigObject->Get('Ticket::Frontend::TextAreaNote'),
177                PlainText     => $GetParam{Body},
178            );
179        }
180
181        # get check item object
182        my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
183
184        # removing blank spaces from the ticket number
185        $CheckItemObject->StringClean(
186            StringRef => \$GetParam{'MainTicketNumber'},
187            TrimLeft  => 1,
188            TrimRight => 1,
189        );
190
191        # check some stuff
192        my $MainTicketID = $TicketObject->TicketIDLookup(
193            TicketNumber => $GetParam{'MainTicketNumber'},
194        );
195
196        # check if source and target TicketID are the same (bug#8667)
197        if ( $MainTicketID && $MainTicketID == $Self->{TicketID} ) {
198            $LayoutObject->FatalError(
199                Message => Translatable('Can\'t merge ticket with itself!'),
200            );
201        }
202
203        # check for errors
204        if ( !$MainTicketID ) {
205            $Error{'MainTicketNumberInvalid'} = 'ServerError';
206        }
207
208        if ( $GetParam{InformSender} ) {
209            for my $Parameter (qw( To Subject Body )) {
210                if ( !$GetParam{$Parameter} ) {
211                    $Error{ $Parameter . 'Invalid' } = 'ServerError';
212                }
213            }
214
215            # check forward email address(es)
216            if ( $GetParam{To} ) {
217                for my $Email ( Mail::Address->parse( $GetParam{To} ) ) {
218                    my $Address = $Email->address();
219                    if (
220                        $Kernel::OM->Get('Kernel::System::SystemAddress')
221                        ->SystemAddressIsLocalAddress( Address => $Address )
222                        )
223                    {
224                        $LayoutObject->Block( Name => 'ToCustomerGenericServerErrorMsg' );
225                        $Error{'ToInvalid'} = 'ServerError';
226                    }
227
228                    # check email address
229                    elsif ( !$CheckItemObject->CheckEmail( Address => $Address ) ) {
230                        my $ToErrorMsg =
231                            'To'
232                            . $CheckItemObject->CheckErrorType()
233                            . 'ServerErrorMsg';
234                        $LayoutObject->Block( Name => $ToErrorMsg );
235                        $Error{'ToInvalid'} = 'ServerError';
236                    }
237                }
238            }
239            else {
240                $LayoutObject->Block( Name => 'ToCustomerGenericServerErrorMsg' );
241            }
242        }
243
244        if (%Error) {
245            my $Output = $LayoutObject->Header(
246                Type      => 'Small',
247                BodyClass => 'Popup',
248            );
249
250            # add rich text editor
251            if ( $LayoutObject->{BrowserRichText} ) {
252
253                # use height/width defined for this screen
254                $Param{RichTextHeight} = $Config->{RichTextHeight} || 0;
255                $Param{RichTextWidth}  = $Config->{RichTextWidth}  || 0;
256
257                # set up rich text editor
258                $LayoutObject->SetRichTextParameters(
259                    Data => \%Param,
260                );
261
262            }
263
264            $Param{InformSenderChecked} = $GetParam{InformSender} ? 'checked="checked"' : '';
265
266            $Output .= $LayoutObject->Output(
267                TemplateFile => 'AgentTicketMerge',
268                Data         => { %Param, %GetParam, %Ticket, %Error },
269            );
270            $Output .= $LayoutObject->Footer(
271                Type => 'Small',
272            );
273            return $Output;
274        }
275
276        # challenge token check for write action
277        $LayoutObject->ChallengeTokenCheck();
278
279        # check permissions
280        my $Access = $TicketObject->TicketPermission(
281            Type     => $Config->{Permission},
282            TicketID => $MainTicketID,
283            UserID   => $Self->{UserID},
284        );
285
286        # error screen, don't show ticket
287        if ( !$Access ) {
288            return $LayoutObject->NoPermission( WithHeader => 'yes' );
289        }
290
291        my $TicketMergeResult = $TicketObject->TicketMerge(
292            MainTicketID  => $MainTicketID,
293            MergeTicketID => $Self->{TicketID},
294            UserID        => $Self->{UserID},
295        );
296
297        # check errors
298        if (
299            $Self->{TicketID} == $MainTicketID
300            || !$TicketMergeResult
301            || $TicketMergeResult eq 'NoValidMergeStates'
302            )
303        {
304            my $Output = $LayoutObject->Header(
305                Type      => 'Small',
306                BodyClass => 'Popup',
307            );
308
309            if ( $TicketMergeResult eq 'NoValidMergeStates' ) {
310                $Output .= $LayoutObject->Notify(
311                    Priority => 'Error',
312                    Info     => 'No merge state found! Please add a valid merge state before merge action.',
313                );
314            }
315
316            # add rich text editor
317            if ( $LayoutObject->{BrowserRichText} ) {
318
319                # use height/width defined for this screen
320                $Param{RichTextHeight} = $Config->{RichTextHeight} || 0;
321                $Param{RichTextWidth}  = $Config->{RichTextWidth}  || 0;
322
323                # set up rich text editor
324                $LayoutObject->SetRichTextParameters(
325                    Data => \%Param,
326                );
327            }
328
329            $Output .= $LayoutObject->Output(
330                TemplateFile => 'AgentTicketMerge',
331                Data         => { %Param, %Ticket },
332            );
333            $Output .= $LayoutObject->Footer(
334                Type => 'Small',
335            );
336            return $Output;
337        }
338        else {
339
340            # send customer info?
341            if ( $GetParam{InformSender} ) {
342                my $MimeType = 'text/plain';
343                if ( $LayoutObject->{BrowserRichText} ) {
344                    $MimeType = 'text/html';
345
346                    # verify html document
347                    $GetParam{Body} = $LayoutObject->RichTextDocumentComplete(
348                        String => $GetParam{Body},
349                    );
350                }
351                my %Ticket = $TicketObject->TicketGet( TicketID => $Self->{TicketID} );
352                $GetParam{Body} =~ s/(&lt;|<)OTRS_TICKET(&gt;|>)/$Ticket{TicketNumber}/g;
353                $GetParam{Body}
354                    =~ s/(&lt;|<)OTRS_MERGE_TO_TICKET(&gt;|>)/$GetParam{'MainTicketNumber'}/g;
355
356                my $EmailArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForChannel(
357                    ChannelName => 'Email',
358                );
359
360                my $ArticleID = $EmailArticleBackendObject->ArticleSend(
361                    TicketID             => $Self->{TicketID},
362                    SenderType           => 'agent',
363                    IsVisibleForCustomer => 1,
364                    HistoryType          => 'SendAnswer',
365                    HistoryComment       => "Merge info to '$GetParam{To}'.",
366                    From                 => $GetParam{From},
367                    Email                => $GetParam{Email},
368                    To                   => $GetParam{To},
369                    Subject              => $GetParam{Subject},
370                    UserID               => $Self->{UserID},
371                    Body                 => $GetParam{Body},
372                    Charset              => $LayoutObject->{UserCharset},
373                    MimeType             => $MimeType,
374                );
375                if ( !$ArticleID ) {
376
377                    # error page
378                    return $LayoutObject->ErrorScreen();
379                }
380            }
381
382            # redirect to merged ticket
383            return $LayoutObject->PopupClose(
384                URL => "Action=AgentTicketZoom;TicketID=$MainTicketID",
385            );
386        }
387    }
388    else {
389        my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
390
391        # Get last customer article.
392        my @Articles = $ArticleObject->ArticleList(
393            TicketID   => $Self->{TicketID},
394            SenderType => 'customer',
395            OnlyLast   => 1,
396        );
397
398        # If the ticket has no customer article, get the last agent article.
399        if ( !@Articles ) {
400            @Articles = $ArticleObject->ArticleList(
401                TicketID   => $Self->{TicketID},
402                SenderType => 'agent',
403                OnlyLast   => 1,
404            );
405        }
406
407        # Finally, if everything failed, get latest article.
408        if ( !@Articles ) {
409            @Articles = $ArticleObject->ArticleList(
410                TicketID => $Self->{TicketID},
411                OnlyLast => 1,
412            );
413        }
414
415        my %Article;
416        for my $Article (@Articles) {
417            %Article = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
418                %{$Article},
419                DynamicFields => 1,
420            );
421        }
422
423        # merge box
424        my $Output = $LayoutObject->Header(
425            Value     => $Ticket{TicketNumber},
426            Type      => 'Small',
427            BodyClass => 'Popup',
428        );
429
430        # prepare salutation
431        my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator');
432        my $Salutation        = $TemplateGenerator->Salutation(
433            TicketID  => $Self->{TicketID},
434            ArticleID => $Article{ArticleID},
435            Data      => {%Article},
436            UserID    => $Self->{UserID},
437        );
438
439        # prepare signature
440        my $Signature = $TemplateGenerator->Signature(
441            TicketID  => $Self->{TicketID},
442            ArticleID => $Article{ArticleID},
443            Data      => {%Article},
444            UserID    => $Self->{UserID},
445        );
446
447        # prepare subject ...
448        $Article{Subject} = $TicketObject->TicketSubjectBuild(
449            TicketNumber => $Ticket{TicketNumber},
450            Subject      => $Article{Subject} || '',
451        );
452
453        # prepare from ...
454        $Article{To} = $Article{From};
455        my %Address = $Kernel::OM->Get('Kernel::System::Queue')->GetSystemAddress( QueueID => $Ticket{QueueID} );
456        $Article{From} = "$Address{RealName} <$Address{Email}>";
457
458        # add salutation and signature to body
459        if ( $LayoutObject->{BrowserRichText} ) {
460            my $Body = $LayoutObject->Ascii2RichText(
461                String => $ConfigObject->Get('Ticket::Frontend::MergeText'),
462            );
463            $Article{Body} = $Salutation
464                . '<br/><br/>'
465                . $Body
466                . '<br/><br/>'
467                . $Signature;
468        }
469        else {
470            $Article{Body} = $Salutation
471                . "\n\n"
472                . $ConfigObject->Get('Ticket::Frontend::MergeText')
473                . "\n\n"
474                . $Signature;
475        }
476
477        # add rich text editor
478        if ( $LayoutObject->{BrowserRichText} ) {
479
480            # use height/width defined for this screen
481            $Param{RichTextHeight} = $Config->{RichTextHeight} || 0;
482            $Param{RichTextWidth}  = $Config->{RichTextWidth}  || 0;
483
484            # set up rich text editor
485            $LayoutObject->SetRichTextParameters(
486                Data => \%Param,
487            );
488        }
489
490        $Output .= $LayoutObject->Output(
491            TemplateFile => 'AgentTicketMerge',
492            Data         => { %Param, %Ticket, %Article, }
493        );
494        $Output .= $LayoutObject->Footer(
495            Type => 'Small',
496        );
497        return $Output;
498    }
499}
500
5011;
502