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
9use strict;
10use warnings;
11use utf8;
12
13use vars (qw($Self));
14
15my $ConfigObject         = $Kernel::OM->Get('Kernel::Config');
16my $MainObject           = $Kernel::OM->Get('Kernel::System::Main');
17my $ArticleObject        = $Kernel::OM->Get('Kernel::System::Ticket::Article');
18my $ArticleBackendObject = $ArticleObject->BackendForChannel( ChannelName => 'Internal' );
19
20$Kernel::OM->ObjectParamAdd(
21    'Kernel::System::UnitTest::Helper' => {
22        RestoreDatabase  => 1,
23        UseTmpArticleDir => 1,
24    },
25);
26my $Helper = $Kernel::OM->Get('Kernel::System::UnitTest::Helper');
27
28# Create test ticket.
29my $TicketID = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCreate(
30    Title        => 'Some Ticket_Title',
31    QueueID      => 1,
32    Lock         => 'unlock',
33    Priority     => '3 normal',
34    State        => 'closed successful',
35    CustomerNo   => '123465',
36    CustomerUser => 'customer@example.com',
37    OwnerID      => 1,
38    UserID       => 1,
39);
40$Self->True(
41    $TicketID,
42    'TicketCreate()',
43);
44
45my $ArticleID = $ArticleBackendObject->ArticleCreate(
46    TicketID             => $TicketID,
47    SenderType           => 'agent',
48    IsVisibleForCustomer => 0,
49    From                 => 'Some Agent <email@example.com>',
50    To                   => 'Some Customer <customer-a@example.com>',
51    Subject              => 'some short description',
52    Body                 => 'the message text',
53    ContentType          => 'text/plain; charset=ISO-8859-15',
54    HistoryType          => 'OwnerUpdate',
55    HistoryComment       => 'Some free text!',
56    UserID               => 1,
57    NoAgentNotify        => 1,
58);
59
60$Self->True(
61    $ArticleID,
62    'ArticleCreate()',
63);
64
65# article attachment checks
66for my $Backend (qw(DB FS)) {
67
68    # Make sure that the article backend object gets recreated for each loop.
69    $Kernel::OM->ObjectsDiscard( Objects => [ ref $ArticleBackendObject ] );
70
71    $ConfigObject->Set(
72        Key   => 'Ticket::Article::Backend::MIMEBase::ArticleStorage',
73        Value => 'Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorage' . $Backend,
74    );
75
76    $ArticleBackendObject = $ArticleObject->BackendForChannel( ChannelName => 'Internal' );
77
78    $Self->Is(
79        $ArticleBackendObject->{ArticleStorageModule},
80        'Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorage' . $Backend,
81        'Article backend loaded the correct storage module'
82    );
83
84    for my $File (
85        qw(Ticket-Article-Test1.xls Ticket-Article-Test1.txt Ticket-Article-Test1.doc
86        Ticket-Article-Test1.png Ticket-Article-Test1.pdf Ticket-Article-Test-utf8-1.txt Ticket-Article-Test-utf8-1.bin Ticket-Article-Test-empty.txt)
87        )
88    {
89        my $Location   = $ConfigObject->Get('Home') . "/scripts/test/sample/Ticket/$File";
90        my $ContentRef = $MainObject->FileRead(
91            Location => $Location,
92            Mode     => 'binmode',
93        );
94
95        for my $FileName (
96            'SimpleFile',
97            'ÄÖÜカスタマ-',          # Unicode NFC
98            'Второй_файл',    # Unicode NFD
99            )
100        {
101            my $Content                = ${$ContentRef};
102            my $FileNew                = $FileName . $File;
103            my $MD5Orig                = $MainObject->MD5sum( String => $Content );
104            my $ArticleWriteAttachment = $ArticleBackendObject->ArticleWriteAttachment(
105                Content     => $Content,
106                Filename    => $FileNew,
107                ContentType => 'image/png',
108                ArticleID   => $ArticleID,
109                UserID      => 1,
110            );
111            $Self->True(
112                $ArticleWriteAttachment,
113                "$Backend ArticleWriteAttachment() - $FileNew",
114            );
115
116            my %AttachmentIndex = $ArticleBackendObject->ArticleAttachmentIndex(
117                ArticleID => $ArticleID,
118            );
119
120            my $TargetFilename = $FileName . $File;
121
122            $Self->Is(
123                $AttachmentIndex{1}->{Filename},
124                $TargetFilename,
125                "$Backend ArticleAttachmentIndex() Filename - $FileNew"
126            );
127
128            my %Data = $ArticleBackendObject->ArticleAttachment(
129                ArticleID => $ArticleID,
130                FileID    => 1,
131            );
132            $Self->True(
133                $Data{ContentType},
134                "$Backend ArticleAttachment() ContentType - $FileNew",
135            );
136            $Self->True(
137                $Data{Content} eq $Content,
138                "$Backend ArticleWriteAttachment() / ArticleAttachment() - $FileNew",
139            );
140            $Self->True(
141                $Data{ContentType} eq 'image/png',
142                "$Backend ArticleWriteAttachment() / ArticleAttachment() - $File",
143            );
144            my $MD5New = $MainObject->MD5sum( String => $Data{Content} );
145            $Self->Is(
146                $MD5Orig || '1',
147                $MD5New  || '2',
148                "$Backend MD5 - $FileNew",
149            );
150            my $Delete = $ArticleBackendObject->ArticleDeleteAttachment(
151                ArticleID => $ArticleID,
152                UserID    => 1,
153            );
154            $Self->True(
155                $Delete,
156                "$Backend ArticleDeleteAttachment() - $FileNew",
157            );
158
159            %AttachmentIndex = $ArticleBackendObject->ArticleAttachmentIndex(
160                ArticleID => $ArticleID,
161            );
162
163            $Self->IsDeeply(
164                \%AttachmentIndex,
165                {},
166                "$Backend ArticleAttachmentIndex() after delete - $FileNew"
167            );
168        }
169    }
170}
171
172# filename collision checks
173for my $Backend (qw(DB FS)) {
174
175    # Make sure that the article backend object gets recreated for each loop.
176    $Kernel::OM->ObjectsDiscard( Objects => [ ref $ArticleBackendObject ] );
177
178    $ConfigObject->Set(
179        Key   => 'Ticket::Article::Backend::MIMEBase::ArticleStorage',
180        Value => 'Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorage' . $Backend,
181    );
182
183    $ArticleBackendObject = $ArticleObject->BackendForChannel( ChannelName => 'Internal' );
184
185    $Self->Is(
186        $ArticleBackendObject->{ArticleStorageModule},
187        'Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorage' . $Backend,
188        'Article backend loaded the correct storage module',
189    );
190
191    # Store file 2 times
192    my $FileName               = "[Terminology Guide äöß].pdf";
193    my $Content                = '123';
194    my $FileNew                = $FileName;
195    my $ArticleWriteAttachment = $ArticleBackendObject->ArticleWriteAttachment(
196        Content     => $Content,
197        Filename    => $FileNew,
198        ContentType => 'image/png',
199        ArticleID   => $ArticleID,
200        UserID      => 1,
201    );
202    $Self->True(
203        $ArticleWriteAttachment,
204        "$Backend ArticleWriteAttachment() - collision check created $FileNew",
205    );
206
207    $ArticleWriteAttachment = $ArticleBackendObject->ArticleWriteAttachment(
208        Content     => $Content,
209        Filename    => $FileNew,
210        ContentType => 'image/png',
211        ArticleID   => $ArticleID,
212        UserID      => 1,
213    );
214    $Self->True(
215        $ArticleWriteAttachment,
216        "$Backend ArticleWriteAttachment() - collision check created $FileNew second time",
217    );
218
219    my %AttachmentIndex = $ArticleBackendObject->ArticleAttachmentIndex(
220        ArticleID => $ArticleID,
221    );
222
223    my $TargetFilename = '[Terminology Guide äöß]';
224
225    if ( $Backend eq 'FS' ) {
226        $TargetFilename = '_Terminology_Guide_äöß_';
227    }
228
229    $Self->Is(
230        scalar keys %AttachmentIndex,
231        2,
232        "$Backend ArticleWriteAttachment() - collision check number of attachments",
233    );
234
235    my ($Entry1) = grep { $AttachmentIndex{$_}->{Filename} eq "$TargetFilename.pdf" } keys %AttachmentIndex;
236    my ($Entry2) = grep { $AttachmentIndex{$_}->{Filename} eq "$TargetFilename-1.pdf" }
237        keys %AttachmentIndex;
238
239    $Self->IsDeeply(
240        $AttachmentIndex{$Entry1},
241        {
242            'ContentAlternative' => '',
243            'ContentID'          => '',
244            'ContentType'        => 'image/png',
245            'Filename'           => "$TargetFilename.pdf",
246            'FilesizeRaw'        => '3',
247            'Disposition'        => 'attachment',
248        },
249        "$Backend ArticleAttachmentIndex - collision check entry 1",
250    );
251
252    $Self->IsDeeply(
253        $AttachmentIndex{$Entry2},
254        {
255            'ContentAlternative' => '',
256            'ContentID'          => '',
257            'ContentType'        => 'image/png',
258            'Filename'           => "$TargetFilename-1.pdf",
259            'FilesizeRaw'        => '3',
260            'Disposition'        => 'attachment',
261        },
262        "$Backend ArticleAttachmentIndex - collision check entry 2",
263    );
264
265    my $Delete = $ArticleBackendObject->ArticleDeleteAttachment(
266        ArticleID => $ArticleID,
267        UserID    => 1,
268    );
269
270    $Self->True(
271        $Delete,
272        "$Backend ArticleDeleteAttachment()",
273    );
274
275    %AttachmentIndex = $ArticleBackendObject->ArticleAttachmentIndex(
276        ArticleID => $ArticleID,
277    );
278
279    $Self->IsDeeply(
280        \%AttachmentIndex,
281        {},
282        "$Backend ArticleAttachmentIndex() after delete",
283    );
284}
285
286# cleanup is done by RestoreDatabase.
287
2881;
289