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