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::System::Console::Command::Maint::Stats::Generate;
10
11use strict;
12use warnings;
13
14use parent qw(Kernel::System::Console::BaseCommand);
15
16our @ObjectDependencies = (
17    'Kernel::Config',
18    'Kernel::Language',
19    'Kernel::Output::PDF::Statistics',
20    'Kernel::System::CSV',
21    'Kernel::System::CheckItem',
22    'Kernel::System::DateTime',
23    'Kernel::System::Email',
24    'Kernel::System::Main',
25    'Kernel::System::PDF',
26    'Kernel::System::Stats',
27    'Kernel::System::User',
28);
29
30sub Configure {
31    my ( $Self, %Param ) = @_;
32
33    $Self->Description(
34        'Generate (and send, optional) statistics which have been configured previously in the OTRS statistics module.'
35    );
36    $Self->AddOption(
37        Name        => 'number',
38        Description => "Statistic number as shown in the overview of AgentStats.",
39        Required    => 1,
40        HasValue    => 1,
41        ValueRegex  => qr/\d+/smx,
42    );
43    $Self->AddOption(
44        Name => 'params',
45        Description =>
46            "Parameters which should be passed to the statistic (e.g. Year=1977&Month=10, not for dynamic statistics).",
47        Required   => 0,
48        HasValue   => 1,
49        ValueRegex => qr/.*/smx,
50    );
51    $Self->AddOption(
52        Name        => 'target-filename',
53        Description => "Filename for the generated file.",
54        Required    => 0,
55        HasValue    => 1,
56        ValueRegex  => qr/.*/smx,
57    );
58    $Self->AddOption(
59        Name => 'target-directory',
60        Description =>
61            "Directory to which the generated file should be written (e.g. /output/dir/). If a target directory is provided, no email will be sent.",
62        Required   => 0,
63        HasValue   => 1,
64        ValueRegex => qr/.*/smx,
65    );
66    $Self->AddOption(
67        Name        => 'format',
68        Description => "Target format (CSV|Excel|Print) for which the file should be generated (defaults to CSV).",
69        Required    => 0,
70        HasValue    => 1,
71        ValueRegex  => qr/(CSV|Excel|Print|PDF)/smx,
72    );
73    $Self->AddOption(
74        Name        => 'separator',
75        Description => "Define the separator in case of CSV as target format (defaults to ';').",
76        Required    => 0,
77        HasValue    => 1,
78        ValueRegex  => qr/.*/smx,
79    );
80    $Self->AddOption(
81        Name => 'with-header',
82        Description =>
83            "Add a heading line consisting of statistics title and creation date in case of Excel or CSV as output format.",
84        Required   => 0,
85        HasValue   => 0,
86        ValueRegex => qr/.*/smx,
87    );
88    $Self->AddOption(
89        Name => 'timezone',
90        Description =>
91            "Target time zone (e.g. Europe/Berlin) for which the file should be generated.",
92        Required   => 0,
93        HasValue   => 1,
94        ValueRegex => qr/.*/smx,
95    );
96    $Self->AddOption(
97        Name => 'language',
98        Description =>
99            "Target language (e.g. de) for which the file should be generated (will be OTRS default language or english as fallback if left empty).",
100        Required   => 0,
101        HasValue   => 1,
102        ValueRegex => qr/.*/smx,
103    );
104    $Self->AddOption(
105        Name        => 'mail-sender',
106        Description => "Email address which should appear as sender for the generated file.",
107        Required    => 0,
108        HasValue    => 1,
109        ValueRegex  => qr/.*/smx,
110    );
111    $Self->AddOption(
112        Name        => 'mail-recipient',
113        Description => "Recipient email address to which the generated file should be send.",
114        Required    => 0,
115        HasValue    => 1,
116        Multiple    => 1,
117        ValueRegex  => qr/.*/smx,
118    );
119    $Self->AddOption(
120        Name        => 'mail-body',
121        Description => "Body content for the email which has the generated statistics file attached.",
122        Required    => 0,
123        HasValue    => 1,
124        ValueRegex  => qr/.*/smx,
125    );
126
127    return;
128}
129
130sub PreRun {
131    my ( $Self, %Param ) = @_;
132
133    # check if the passed stat number exists
134    $Self->{StatNumber} = $Self->GetOption('number');
135    $Self->{StatID} = $Kernel::OM->Get('Kernel::System::Stats')->StatNumber2StatID( StatNumber => $Self->{StatNumber} );
136    if ( !$Self->{StatID} ) {
137        die "There is no statistic with number '$Self->{StatNumber}'.\n";
138    }
139
140    # either target directory or mail recipient needs to be defined
141    if ( !$Self->GetOption('target-directory') && !$Self->GetOption('mail-recipient') ) {
142        die "Need either --target-directory or at least one --mail-recipient.\n";
143    }
144
145    # if params have been passed, we build up a body containing the configured params
146    # which is then used as default
147    $Self->{Params}   = $Self->GetOption('params');
148    $Self->{MailBody} = $Self->GetOption('mail-body') || '';
149    if ( !$Self->{MailBody} && $Self->{Params} ) {
150        $Self->{MailBody} .= "Stats with following options:\n\n";
151        $Self->{MailBody} .= "StatNumber: " . $Self->GetOption('number') . "\n";
152        my @P = split( /&/, $Self->{Params} );
153        for (@P) {
154            my ( $Key, $Value ) = split( /=/, $_, 2 );
155            $Self->{MailBody} .= "$Key: $Value\n";
156        }
157    }
158
159    # if there is a recipient, we also need a mail body
160    if ( $Self->GetOption('mail-recipient') && !$Self->{MailBody} ) {
161        die
162            "You defined at least one --mail-recipient which means that you also need to define a mail body using --mail-body.\n";
163    }
164
165    # if a target directory has been passed, check if it exists
166    $Self->{TargetDirectory} = $Self->GetOption('target-directory');
167    if ( $Self->{TargetDirectory} && !-e $Self->{TargetDirectory} ) {
168        die "The target directory '$Self->{TargetDirectory}' does not exist.\n";
169    }
170
171    # set up used language
172    $Self->{Language} = $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en';
173    if ( $Self->GetOption('language') ) {
174        $Self->{Language} = $Self->GetOption('language');
175        $Kernel::OM->ObjectParamAdd(
176            'Kernel::Language' => {
177                UserLanguage => $Self->{Language},
178            },
179        );
180    }
181
182    # set up used format & separator
183    $Self->{Format}    = $Self->GetOption('format')    || 'CSV';
184    $Self->{Separator} = $Self->GetOption('separator') || ';';
185
186    return;
187}
188
189sub Run {
190    my ( $Self, %Param ) = @_;
191
192    $Self->Print("<yellow>Generating statistic number $Self->{StatNumber}...</yellow>\n");
193
194    my $CurSysDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
195
196    my %GetParam;
197    my $Stat = $Kernel::OM->Get('Kernel::System::Stats')->StatsGet(
198        StatID => $Self->{StatID},
199        UserID => 1,
200    );
201
202    if ( $Stat->{StatType} eq 'static' ) {
203        $GetParam{Year}  = $CurSysDTObject->Get()->{Year};
204        $GetParam{Month} = $CurSysDTObject->Get()->{Month};
205        $GetParam{Day}   = $CurSysDTObject->Get()->{Day};
206
207        # get params from -p
208        # only for static files
209        my $Params = $Kernel::OM->Get('Kernel::System::Stats')->GetParams(
210            StatID => $Self->{StatID},
211            UserID => 1,
212        );
213        for my $ParamItem ( @{$Params} ) {
214            if ( !$ParamItem->{Multiple} ) {
215                my $Value = $Self->GetParam(
216                    Param  => $ParamItem->{Name},
217                    Params => $Self->{Params}
218                );
219                if ( defined $Value ) {
220                    $GetParam{ $ParamItem->{Name} } =
221                        $Self->GetParam(
222                        Param  => $ParamItem->{Name},
223                        Params => $Self->{Params},
224                        );
225                }
226                elsif ( defined $ParamItem->{SelectedID} ) {
227                    $GetParam{ $ParamItem->{Name} } = $ParamItem->{SelectedID};
228                }
229            }
230            else {
231                my @Value = $Self->GetArray(
232                    Param  => $ParamItem->{Name},
233                    Params => $Self->{Params},
234                );
235                if (@Value) {
236                    $GetParam{ $ParamItem->{Name} } = \@Value;
237                }
238                elsif ( defined $ParamItem->{SelectedID} ) {
239                    $GetParam{ $ParamItem->{Name} } = [ $ParamItem->{SelectedID} ];
240                }
241            }
242        }
243    }
244    elsif ( $Stat->{StatType} eq 'dynamic' ) {
245        %GetParam = %{$Stat};
246
247        # overwrite the default stats timezone with the given timezone
248        my $TimeZone = $Self->GetOption('timezone');
249        if ( defined $TimeZone && length $TimeZone ) {
250            $GetParam{TimeZone} = $TimeZone;
251        }
252    }
253
254    # run stat...
255    my @StatArray = @{
256        $Kernel::OM->Get('Kernel::System::Stats')->StatsRun(
257            StatID   => $Self->{StatID},
258            GetParam => \%GetParam,
259            UserID   => 1,
260        )
261    };
262
263    # generate output
264    my $TitleArrayRef  = shift(@StatArray);
265    my $Title          = $TitleArrayRef->[0];
266    my $HeadArrayRef   = shift(@StatArray);
267    my $CountStatArray = @StatArray;
268    my $Time           = $CurSysDTObject->ToString();
269    my @WithHeader;
270    if ( $Self->GetOption('with-header') ) {
271        @WithHeader = ( "Name: $Title", "Created: $Time" );
272    }
273    if ( !@StatArray ) {
274        push( @StatArray, [ ' ', 0 ] );
275    }
276    my %Attachment;
277
278    if ( $Self->{Format} eq 'Print' || $Self->{Format} eq 'PDF' ) {
279
280        my $PDFString = $Kernel::OM->Get('Kernel::Output::PDF::Statistics')->GeneratePDF(
281            Stat         => $Stat,
282            Title        => $Title,
283            HeadArrayRef => $HeadArrayRef,
284            StatArray    => \@StatArray,
285            TimeZone     => $GetParam{TimeZone},
286        );
287
288        # save the pdf with the title and timestamp as filename, or read it from param
289        my $Filename;
290        if ( $Self->GetOption('target-filename') ) {
291            $Filename = $Self->GetOption('target-filename');
292        }
293        else {
294            $Filename = $Kernel::OM->Get('Kernel::System::Stats')->StringAndTimestamp2Filename(
295                String   => $Stat->{Title} . " Created",
296                TimeZone => $GetParam{TimeZone},
297            );
298        }
299        %Attachment = (
300            Filename    => $Filename . ".pdf",
301            ContentType => "application/pdf",
302            Content     => $PDFString,
303            Encoding    => "base64",
304            Disposition => "attachment",
305        );
306    }
307    elsif ( $Self->{Format} eq 'Excel' ) {
308
309        # Create the Excel data
310        my $Output = $Kernel::OM->Get('Kernel::System::CSV')->Array2CSV(
311            WithHeader => \@WithHeader,
312            Head       => $HeadArrayRef,
313            Data       => \@StatArray,
314            Format     => 'Excel',
315        );
316
317        # save the Excel with the title and timestamp as filename, or read it from param
318        my $Filename;
319        if ( $Self->GetOption('target-filename') ) {
320            $Filename = $Self->GetOption('target-filename');
321        }
322        else {
323            $Filename = $Kernel::OM->Get('Kernel::System::Stats')->StringAndTimestamp2Filename(
324                String   => $Stat->{Title} . " Created",
325                TimeZone => $GetParam{TimeZone},
326            );
327        }
328
329        %Attachment = (
330            Filename    => $Filename . ".xlsx",
331            ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
332            Content     => $Output,
333            Encoding    => "base64",
334            Disposition => "attachment",
335        );
336    }
337    else {
338
339        # Create the CSV data
340        my $Output = $Kernel::OM->Get('Kernel::System::CSV')->Array2CSV(
341            WithHeader => \@WithHeader,
342            Head       => $HeadArrayRef,
343            Data       => \@StatArray,
344            Separator  => $Self->{Separator},
345        );
346
347        # save the csv with the title and timestamp as filename, or read it from param
348        my $Filename;
349        if ( $Self->GetOption('target-filename') ) {
350            $Filename = $Self->GetOption('target-filename');
351        }
352        else {
353            $Filename = $Kernel::OM->Get('Kernel::System::Stats')->StringAndTimestamp2Filename(
354                String   => $Stat->{Title} . " Created",
355                TimeZone => $GetParam{TimeZone},
356            );
357        }
358
359        %Attachment = (
360            Filename    => $Filename . ".csv",
361            ContentType => "text/csv",
362            Content     => $Output,
363            Encoding    => "base64",
364            Disposition => "attachment",
365        );
366    }
367
368    # write output
369    if ( $Self->{TargetDirectory} ) {
370
371        my $Success = $Kernel::OM->Get('Kernel::System::Main')->FileWrite(
372            Location => "$Self->{TargetDirectory}/$Attachment{Filename}",
373            Content  => \$Attachment{Content},
374            Mode     => 'binmode',
375        );
376
377        if ($Success) {
378            $Self->Print("  Writing file <yellow>$Self->{TargetDirectory}/$Attachment{Filename}</yellow>.\n");
379            $Self->Print("<green>Done.</green>\n");
380            return $Self->ExitCodeOk();
381        }
382        else {
383            $Self->PrintError("Can't write $Self->{TargetDirectory}/$Attachment{Filename}!");
384            return $Self->ExitCodeError();
385        }
386    }
387
388    # send email
389    RECIPIENT:
390    for my $Recipient ( @{ $Self->GetOption('mail-recipient') // [] } ) {
391
392        # recipient check
393        if ( !$Kernel::OM->Get('Kernel::System::CheckItem')->CheckEmail( Address => $Recipient ) ) {
394
395            $Self->PrintError(
396                "Email address $Recipient invalid, skipping address."
397                    . $Kernel::OM->Get('Kernel::System::CheckItem')->CheckError()
398            );
399            next RECIPIENT;
400        }
401
402        my $Result = $Kernel::OM->Get('Kernel::System::Email')->Send(
403            From       => $Self->GetOption('mail-sender'),
404            To         => $Recipient,
405            Subject    => "[Stats - $CountStatArray Records] $Title; Created: $Time",
406            Body       => $Kernel::OM->Get('Kernel::Language')->Translate( $Self->{MailBody} ),
407            Charset    => 'utf-8',
408            Attachment => [ {%Attachment}, ],
409        );
410        if ( $Result->{Success} ) {
411            $Self->Print("<yellow>Email sent to '$Recipient'.</yellow>\n");
412        }
413        else {
414            $Self->Print("<red>Email sending to '$Recipient' has failed.</red>\n");
415        }
416
417    }
418
419    $Self->Print("<green>Done.</green>\n");
420    return $Self->ExitCodeOk();
421}
422
423sub GetParam {
424    my ( $Self, %Param ) = @_;
425
426    if ( !$Param{Param} ) {
427        $Self->PrintError("Need 'Param' in GetParam()");
428    }
429    my @P = split( /&/, $Param{Params} || '' );
430    for (@P) {
431        my ( $Key, $Value ) = split( /=/, $_, 2 );
432        if ( $Key eq $Param{Param} ) {
433            return $Value;
434        }
435    }
436    return;
437}
438
439sub GetArray {
440    my ( $Self, %Param ) = @_;
441
442    if ( !$Param{Param} ) {
443        $Self->PrintError("Need 'Param' in GetArray()");
444    }
445    my @P = split( /&/, $Param{Params} || '' );
446    my @Array;
447    for (@P) {
448        my ( $Key, $Value ) = split( /=/, $_, 2 );
449        if ( $Key eq $Param{Param} ) {
450            push( @Array, $Value );
451        }
452    }
453    return @Array;
454}
455
4561;
457