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::Statistics::View; 10 11## nofilter(TidyAll::Plugin::OTRS::Perl::PodChecker) 12 13use strict; 14use warnings; 15 16use List::Util qw( first ); 17 18use Kernel::System::VariableCheck qw(:all); 19use Kernel::System::DateTime; 20 21our @ObjectDependencies = ( 22 'Kernel::Config', 23 'Kernel::Language', 24 'Kernel::Output::HTML::Layout', 25 'Kernel::Output::PDF::Statistics', 26 'Kernel::System::CSV', 27 'Kernel::System::CustomerCompany', 28 'Kernel::System::DateTime', 29 'Kernel::System::Group', 30 'Kernel::System::Log', 31 'Kernel::System::Main', 32 'Kernel::System::PDF', 33 'Kernel::System::Stats', 34 'Kernel::System::Ticket::Article', 35 'Kernel::System::Ticket', 36 'Kernel::System::User', 37 'Kernel::System::Web::Request', 38); 39 40use Kernel::Language qw(Translatable); 41 42=head1 NAME 43 44Kernel::Output::HTML::Statistics::View - View object for statistics 45 46=head1 DESCRIPTION 47 48Provides several functions to generate statistics GUI elements. 49 50=head1 PUBLIC INTERFACE 51 52=cut 53 54sub new { 55 my ( $Type, %Param ) = @_; 56 57 # allocate new hash for object 58 my $Self = {}; 59 bless( $Self, $Type ); 60 61 return $Self; 62} 63 64=head2 StatsParamsWidget() 65 66generate HTML for statistics run widget. 67 68 my $HTML = $StatsViewObject->StatsParamsWidget( 69 StatID => $StatID, 70 71 Formats => { # optional, limit the available formats 72 Print => 'Print', 73 } 74 75 OutputCounter => 1, # optional, counter to append to ElementIDs 76 # This is needed if there is more than one stat on the page. 77 78 AJAX => 0, # optional, keep script tags for AJAX responses 79 ); 80 81=cut 82 83sub StatsParamsWidget { 84 my ( $Self, %Param ) = @_; 85 86 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 87 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 88 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 89 90 for my $Needed (qw(Stat)) { 91 if ( !$Param{$Needed} ) { 92 $Kernel::OM->Get('Kernel::System::Log')->Log( 93 Priority => "error", 94 Message => "Need $Needed!" 95 ); 96 return; 97 } 98 } 99 100 # Don't allow to run an invalid stat. 101 return if !$Param{Stat}->{Valid}; 102 103 $Param{OutputCounter} ||= 1; 104 105 # Check if there are any configuration errors that must be corrected by the stats admin 106 my $StatsConfigurationValid = $Self->StatsConfigurationValidate( 107 Stat => $Param{Stat}, 108 Errors => {}, 109 ); 110 111 if ( !$StatsConfigurationValid ) { 112 return; 113 } 114 115 my $HasUserGetParam = ref $Param{UserGetParam} eq 'HASH'; 116 117 my %UserGetParam = %{ $Param{UserGetParam} // {} }; 118 my $Format = $Param{Formats} || $ConfigObject->Get('Stats::Format'); 119 120 my $LocalGetParam = sub { 121 my (%Param) = @_; 122 my $Param = $Param{Param}; 123 return $HasUserGetParam ? $UserGetParam{$Param} : $ParamObject->GetParam( Param => $Param ); 124 }; 125 126 my $LocalGetArray = sub { 127 my (%Param) = @_; 128 my $Param = $Param{Param}; 129 if ($HasUserGetParam) { 130 if ( $UserGetParam{$Param} && ref $UserGetParam{$Param} eq 'ARRAY' ) { 131 return @{ $UserGetParam{$Param} }; 132 } 133 return; 134 } 135 return $ParamObject->GetArray( Param => $Param ); 136 }; 137 138 my $Stat = $Param{Stat}; 139 my $StatID = $Stat->{StatID}; 140 141 my $Output; 142 143 # get the object name 144 if ( $Stat->{StatType} eq 'static' ) { 145 $Stat->{ObjectName} = $Stat->{File}; 146 } 147 148 # if no object name is defined use an empty string 149 $Stat->{ObjectName} ||= ''; 150 151 # create format select box 152 my %SelectFormat; 153 VALUE: 154 for my $Value ( @{ $Stat->{Format} } ) { 155 next VALUE if !defined $Format->{$Value}; 156 $SelectFormat{$Value} = $Format->{$Value}; 157 } 158 159 if ( keys %SelectFormat > 1 ) { 160 my %Frontend; 161 $Frontend{SelectFormat} = $LayoutObject->BuildSelection( 162 Data => \%SelectFormat, 163 SelectedID => $LocalGetParam->( Param => 'Format' ), 164 Name => 'Format', 165 Class => 'Modernize', 166 ); 167 $LayoutObject->Block( 168 Name => 'Format', 169 Data => \%Frontend, 170 ); 171 } 172 elsif ( keys %SelectFormat == 1 ) { 173 $LayoutObject->Block( 174 Name => 'FormatFixed', 175 Data => { 176 Format => ( values %SelectFormat )[0], 177 FormatKey => ( keys %SelectFormat )[0], 178 }, 179 ); 180 } 181 else { 182 return; # no possible output format 183 } 184 185 # provide the time zone field only for dynamic statistics 186 if ( $Stat->{StatType} eq 'dynamic' ) { 187 my $SelectedTimeZone = $Self->_GetValidTimeZone( TimeZone => $LocalGetParam->( Param => 'TimeZone' ) ) 188 // $Stat->{TimeZone} 189 // Kernel::System::DateTime->OTRSTimeZoneGet(); 190 191 my %TimeZoneBuildSelection = $Self->_TimeZoneBuildSelection(); 192 193 my %Frontend; 194 $Frontend{SelectTimeZone} = $LayoutObject->BuildSelection( 195 %TimeZoneBuildSelection, 196 Name => 'TimeZone', 197 Class => 'Modernize', 198 SelectedID => $SelectedTimeZone, 199 ); 200 201 $LayoutObject->Block( 202 Name => 'TimeZone', 203 Data => \%Frontend, 204 ); 205 } 206 207 if ( $ConfigObject->Get('Stats::ExchangeAxis') ) { 208 my $ExchangeAxis = $LayoutObject->BuildSelection( 209 Data => { 210 1 => Translatable('Yes'), 211 0 => Translatable('No') 212 }, 213 Name => 'ExchangeAxis', 214 SelectedID => $LocalGetParam->( Param => 'ExchangeAxis' ) // 0, 215 Class => 'Modernize', 216 ); 217 218 $LayoutObject->Block( 219 Name => 'ExchangeAxis', 220 Data => { ExchangeAxis => $ExchangeAxis } 221 ); 222 } 223 224 # get static attributes 225 if ( $Stat->{StatType} eq 'static' ) { 226 227 # load static module 228 my $Params = $Kernel::OM->Get('Kernel::System::Stats')->GetParams( StatID => $StatID ); 229 230 return if !$Params; 231 232 $LayoutObject->Block( 233 Name => 'Static', 234 ); 235 236 PARAMITEM: 237 for my $ParamItem ( @{$Params} ) { 238 $LayoutObject->Block( 239 Name => 'ItemParam', 240 Data => { 241 Param => $ParamItem->{Frontend}, 242 Name => $ParamItem->{Name}, 243 Field => $LayoutObject->BuildSelection( 244 Data => $ParamItem->{Data}, 245 Name => $ParamItem->{Name}, 246 SelectedID => $LocalGetParam->( Param => $ParamItem->{Name} ) // $ParamItem->{SelectedID} || '', 247 Multiple => $ParamItem->{Multiple} || 0, 248 Size => $ParamItem->{Size} || '', 249 Class => 'Modernize', 250 ), 251 }, 252 ); 253 } 254 } 255 256 # get dynamic attributes 257 elsif ( $Stat->{StatType} eq 'dynamic' ) { 258 my %Name = ( 259 UseAsXvalue => Translatable('X-axis'), 260 UseAsValueSeries => Translatable('Y-axis'), 261 UseAsRestriction => Translatable('Filter'), 262 ); 263 264 for my $Use (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) { 265 my $Flag = 0; 266 $LayoutObject->Block( 267 Name => 'Dynamic', 268 Data => { Name => $Name{$Use} }, 269 ); 270 OBJECTATTRIBUTE: 271 for my $ObjectAttribute ( @{ $Stat->{$Use} } ) { 272 next OBJECTATTRIBUTE if !$ObjectAttribute->{Selected}; 273 274 my $ElementName = $Use . $ObjectAttribute->{Element}; 275 my %ValueHash; 276 $Flag = 1; 277 278 # Select All function 279 if ( !$ObjectAttribute->{SelectedValues}[0] ) { 280 if ( 281 $ObjectAttribute->{Values} && ref $ObjectAttribute->{Values} ne 'HASH' 282 ) 283 { 284 $Kernel::OM->Get('Kernel::System::Log')->Log( 285 Priority => 'error', 286 Message => 'Values needs to be a hash reference!' 287 ); 288 next OBJECTATTRIBUTE; 289 } 290 my @Values = keys( %{ $ObjectAttribute->{Values} } ); 291 $ObjectAttribute->{SelectedValues} = \@Values; 292 } 293 294 VALUE: 295 for my $Value ( @{ $ObjectAttribute->{SelectedValues} } ) { 296 if ( $ObjectAttribute->{Values} ) { 297 next VALUE if !defined $ObjectAttribute->{Values}->{$Value}; 298 $ValueHash{$Value} = $ObjectAttribute->{Values}->{$Value}; 299 } 300 else { 301 $ValueHash{Value} = $Value; 302 } 303 } 304 305 $LayoutObject->Block( 306 Name => 'Element', 307 Data => { Name => $ObjectAttribute->{Name} }, 308 ); 309 310 # show fixed elements 311 if ( $ObjectAttribute->{Fixed} ) { 312 if ( $ObjectAttribute->{Block} eq 'Time' ) { 313 if ( $Use eq 'UseAsRestriction' ) { 314 delete $ObjectAttribute->{SelectedValues}; 315 } 316 my $TimeScale = $Self->_TimeScale(); 317 if ( $ObjectAttribute->{TimeStart} ) { 318 $LayoutObject->Block( 319 Name => 'TimePeriodFixed', 320 Data => { 321 TimeStart => $ObjectAttribute->{TimeStart}, 322 TimeStop => $ObjectAttribute->{TimeStop}, 323 }, 324 ); 325 } 326 elsif ( $ObjectAttribute->{TimeRelativeUnit} ) { 327 $LayoutObject->Block( 328 Name => 'TimeRelativeFixed', 329 Data => { 330 TimeRelativeUnit => $TimeScale->{ $ObjectAttribute->{TimeRelativeUnit} }->{Value}, 331 TimeRelativeCount => $ObjectAttribute->{TimeRelativeCount}, 332 TimeRelativeUpcomingCount => $ObjectAttribute->{TimeRelativeUpcomingCount}, 333 }, 334 ); 335 } 336 if ( $ObjectAttribute->{SelectedValues}[0] ) { 337 $LayoutObject->Block( 338 Name => 'TimeScaleFixed', 339 Data => { 340 Scale => $TimeScale->{ $ObjectAttribute->{SelectedValues}[0] }->{Value}, 341 Count => $ObjectAttribute->{TimeScaleCount}, 342 }, 343 ); 344 } 345 } 346 else { 347 348 # find out which sort mechanism is used 349 my @Sorted; 350 if ( $ObjectAttribute->{SortIndividual} ) { 351 @Sorted = grep { $ValueHash{$_} } @{ $ObjectAttribute->{SortIndividual} }; 352 } 353 else { 354 @Sorted = sort { $ValueHash{$a} cmp $ValueHash{$b} } keys %ValueHash; 355 } 356 357 my @FixedAttributes; 358 359 ELEMENT: 360 for my $Element (@Sorted) { 361 my $Value = $ValueHash{$Element}; 362 if ( $ObjectAttribute->{Translation} ) { 363 $Value = $LayoutObject->{LanguageObject}->Translate( $ValueHash{$Element} ); 364 } 365 366 next ELEMENT if !defined $Value; 367 368 push @FixedAttributes, $Value; 369 } 370 371 $LayoutObject->Block( 372 Name => 'Fixed', 373 Data => { 374 Value => join( ', ', @FixedAttributes ), 375 Key => $_, 376 Use => $Use, 377 Element => $ObjectAttribute->{Element}, 378 }, 379 ); 380 } 381 } 382 383 # show unfixed elements 384 else { 385 my %BlockData; 386 $BlockData{Name} = $ObjectAttribute->{Name}; 387 $BlockData{Element} = $ObjectAttribute->{Element}; 388 $BlockData{Value} = $ObjectAttribute->{SelectedValues}->[0]; 389 390 my @SelectedIDs = $LocalGetArray->( Param => $ElementName ); 391 392 if ( $ObjectAttribute->{Block} eq 'MultiSelectField' ) { 393 $BlockData{SelectField} = $LayoutObject->BuildSelection( 394 Data => \%ValueHash, 395 Name => $ElementName, 396 Multiple => 1, 397 Size => 5, 398 SelectedID => @SelectedIDs ? [@SelectedIDs] : $ObjectAttribute->{SelectedValues}, 399 Translation => $ObjectAttribute->{Translation}, 400 TreeView => $ObjectAttribute->{TreeView} || 0, 401 Sort => scalar $ObjectAttribute->{Sort}, 402 SortIndividual => scalar $ObjectAttribute->{SortIndividual}, 403 Class => 'Modernize', 404 ); 405 $LayoutObject->Block( 406 Name => 'MultiSelectField', 407 Data => \%BlockData, 408 ); 409 } 410 elsif ( $ObjectAttribute->{Block} eq 'SelectField' ) { 411 412 $BlockData{SelectField} = $LayoutObject->BuildSelection( 413 Data => \%ValueHash, 414 Name => $ElementName, 415 Translation => $ObjectAttribute->{Translation}, 416 TreeView => $ObjectAttribute->{TreeView} || 0, 417 Sort => scalar $ObjectAttribute->{Sort}, 418 SortIndividual => scalar $ObjectAttribute->{SortIndividual}, 419 SelectedID => $LocalGetParam->( Param => $ElementName ), 420 Class => 'Modernize', 421 ); 422 $LayoutObject->Block( 423 Name => 'SelectField', 424 Data => \%BlockData, 425 ); 426 } 427 428 elsif ( $ObjectAttribute->{Block} eq 'InputField' ) { 429 $LayoutObject->Block( 430 Name => 'InputField', 431 Data => { 432 Key => $ElementName, 433 Value => $LocalGetParam->( Param => $ElementName ) 434 // $ObjectAttribute->{SelectedValues}[0], 435 CSSClass => $ObjectAttribute->{CSSClass}, 436 HTMLDataAttributes => $ObjectAttribute->{HTMLDataAttributes}, 437 }, 438 ); 439 } 440 elsif ( $ObjectAttribute->{Block} eq 'Time' ) { 441 $ObjectAttribute->{Element} = $Use . $ObjectAttribute->{Element}; 442 443 my %Time; 444 if ( $ObjectAttribute->{TimeStart} ) { 445 if ( $LocalGetParam->( Param => $ElementName . 'StartYear' ) ) { 446 for my $Limit (qw(Start Stop)) { 447 for my $Unit (qw(Year Month Day Hour Minute Second)) { 448 if ( defined( $LocalGetParam->( Param => "$ElementName$Limit$Unit" ) ) ) { 449 $Time{ $Limit . $Unit } = $LocalGetParam->( 450 Param => $ElementName . "$Limit$Unit", 451 ); 452 } 453 } 454 if ( !defined( $Time{ $Limit . 'Hour' } ) ) { 455 if ( $Limit eq 'Start' ) { 456 $Time{StartHour} = 0; 457 $Time{StartMinute} = 0; 458 $Time{StartSecond} = 0; 459 } 460 elsif ( $Limit eq 'Stop' ) { 461 $Time{StopHour} = 23; 462 $Time{StopMinute} = 59; 463 $Time{StopSecond} = 59; 464 } 465 } 466 elsif ( !defined( $Time{ $Limit . 'Second' } ) ) { 467 if ( $Limit eq 'Start' ) { 468 $Time{StartSecond} = 0; 469 } 470 elsif ( $Limit eq 'Stop' ) { 471 $Time{StopSecond} = 59; 472 } 473 } 474 $Time{"Time$Limit"} = sprintf( 475 "%04d-%02d-%02d %02d:%02d:%02d", 476 $Time{ $Limit . 'Year' }, 477 $Time{ $Limit . 'Month' }, 478 $Time{ $Limit . 'Day' }, 479 $Time{ $Limit . 'Hour' }, 480 $Time{ $Limit . 'Minute' }, 481 $Time{ $Limit . 'Second' }, 482 ); 483 } 484 } 485 } 486 elsif ( $ObjectAttribute->{TimeRelativeUnit} ) { 487 $Time{TimeRelativeCount} = $LocalGetParam->( 488 Param => $ObjectAttribute->{Element} . 'TimeRelativeCount', 489 ) // $ObjectAttribute->{TimeRelativeCount}; 490 491 $Time{TimeRelativeUpcomingCount} = $LocalGetParam->( 492 Param => $ObjectAttribute->{Element} . 'TimeRelativeUpcomingCount', 493 ) // $ObjectAttribute->{TimeRelativeUpcomingCount}; 494 495 $Time{TimeScaleCount} = $LocalGetParam->( 496 Param => $ObjectAttribute->{Element} . 'TimeScaleCount', 497 ) || $ObjectAttribute->{TimeScaleCount}; 498 499 $Time{TimeRelativeUnitLocalSelectedValue} = $LocalGetParam->( 500 Param => $ObjectAttribute->{Element} . 'TimeRelativeUnit' 501 ); 502 } 503 504 if ( $Use ne 'UseAsRestriction' ) { 505 $Time{TimeScaleUnitLocalSelectedValue} = $LocalGetParam->( 506 Param => $ObjectAttribute->{Element}, 507 ); 508 509 # get the selected x axis time scale value for value series 510 if ( $Use eq 'UseAsValueSeries' ) { 511 512 # get the name for the x axis element 513 my $XAxisElementName = $ObjectAttribute->{Element}; 514 $XAxisElementName =~ s{ \A UseAsValueSeries }{UseAsXvalue}xms; 515 516 # get the current x axis value 517 my $XAxisLocalSelectedValue = $LocalGetParam->( 518 Param => $XAxisElementName, 519 ); 520 $Time{SelectedXAxisValue} = $XAxisLocalSelectedValue 521 || $Self->_GetSelectedXAxisTimeScaleValue( Stat => $Stat ); 522 523 # save the x axis time scale element id for the output 524 $BlockData{XAxisTimeScaleElementID} 525 = $XAxisElementName . '-' . $StatID . '-' . $Param{OutputCounter}; 526 } 527 } 528 529 my %TimeData = $Self->_TimeOutput( 530 StatID => $StatID, 531 OutputCounter => $Param{OutputCounter}, 532 Output => 'View', 533 Use => $Use, 534 %{$ObjectAttribute}, 535 %Time, 536 ); 537 %BlockData = ( %BlockData, %TimeData ); 538 539 if ( $ObjectAttribute->{TimeStart} ) { 540 $LayoutObject->Block( 541 Name => 'TimePeriod', 542 Data => \%BlockData, 543 ); 544 } 545 546 elsif ( $ObjectAttribute->{TimeRelativeUnit} ) { 547 $LayoutObject->Block( 548 Name => 'TimePeriodRelative', 549 Data => \%BlockData, 550 ); 551 } 552 553 # build the Timescale output 554 if ( $Use ne 'UseAsRestriction' ) { 555 $LayoutObject->Block( 556 Name => 'TimeScale', 557 Data => { 558 %BlockData, 559 }, 560 ); 561 562 # send data to JS 563 $LayoutObject->AddJSData( 564 Key => 'StatsParamData', 565 Value => { 566 %BlockData 567 }, 568 ); 569 } 570 571 # end of build timescale output 572 } 573 } 574 } 575 576 # Show this Block if no value series or restrictions are selected 577 if ( !$Flag ) { 578 $LayoutObject->Block( 579 Name => 'NoElement', 580 ); 581 } 582 } 583 } 584 my %YesNo = ( 585 0 => Translatable('No'), 586 1 => Translatable('Yes') 587 ); 588 my %ValidInvalid = ( 589 0 => Translatable('invalid'), 590 1 => Translatable('valid') 591 ); 592 $Stat->{SumRowValue} = $YesNo{ $Stat->{SumRow} }; 593 $Stat->{SumColValue} = $YesNo{ $Stat->{SumCol} }; 594 $Stat->{CacheValue} = $YesNo{ $Stat->{Cache} }; 595 $Stat->{ShowAsDashboardWidgetValue} = $YesNo{ $Stat->{ShowAsDashboardWidget} // 0 }; 596 $Stat->{ValidValue} = $ValidInvalid{ $Stat->{Valid} }; 597 598 for my $Field (qw(CreatedBy ChangedBy)) { 599 $Stat->{$Field} = $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $Stat->{$Field} ); 600 } 601 602 if ( $Param{AJAX} ) { 603 604 # send data to JS 605 $LayoutObject->AddJSData( 606 Key => 'StatsWidgetAJAX', 607 Value => $Param{AJAX} 608 ); 609 } 610 611 $Output .= $LayoutObject->Output( 612 TemplateFile => 'Statistics/StatsParamsWidget', 613 Data => { 614 %{$Stat}, 615 AJAX => $Param{AJAX}, 616 }, 617 AJAX => $Param{AJAX}, 618 ); 619 return $Output; 620} 621 622sub GeneralSpecificationsWidget { 623 my ( $Self, %Param ) = @_; 624 625 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 626 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 627 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 628 629 # In case of page reload because of errors 630 my %Errors = %{ $Param{Errors} // {} }; 631 my %GetParam = %{ $Param{GetParam} // {} }; 632 633 my $Stat; 634 if ( $Param{StatID} ) { 635 $Stat = $Kernel::OM->Get('Kernel::System::Stats')->StatsGet( 636 StatID => $Param{StatID}, 637 UserID => $Param{UserID}, 638 ); 639 } 640 else { 641 $Stat->{StatID} = ''; 642 $Stat->{StatNumber} = ''; 643 $Stat->{Valid} = 1; 644 } 645 646 # Check if a time field is selected in the current statistic configuration, because 647 # only in this case the caching can be activated in the general statistic settings. 648 my $TimeFieldSelected; 649 650 USE: 651 for my $Use (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) { 652 653 for my $ObjectAttribute ( @{ $Stat->{$Use} } ) { 654 655 if ( $ObjectAttribute->{Selected} && $ObjectAttribute->{Block} eq 'Time' ) { 656 $TimeFieldSelected = 1; 657 last USE; 658 } 659 } 660 } 661 662 my %Frontend; 663 664 my %YesNo = ( 665 0 => Translatable('No'), 666 1 => Translatable('Yes') 667 ); 668 669 # Create selectboxes for 'Cache', 'SumRow', 'SumCol', and 'Valid'. 670 for my $Key (qw(Cache ShowAsDashboardWidget SumRow SumCol)) { 671 672 my %SelectionData = %YesNo; 673 674 if ( $Key eq 'Cache' && !$TimeFieldSelected ) { 675 delete $SelectionData{1}; 676 } 677 678 $Frontend{ 'Select' . $Key } = $LayoutObject->BuildSelection( 679 Data => \%SelectionData, 680 SelectedID => $GetParam{$Key} // $Stat->{$Key} || 0, 681 Name => $Key, 682 Class => 'Modernize', 683 ); 684 } 685 686 # New statistics don't get this select. 687 if ( !$Stat->{ObjectBehaviours}->{ProvidesDashboardWidget} ) { 688 $Frontend{'SelectShowAsDashboardWidget'} = $LayoutObject->BuildSelection( 689 Data => { 690 0 => Translatable('No (not supported)'), 691 }, 692 SelectedID => 0, 693 Name => 'ShowAsDashboardWidget', 694 Class => 'Modernize', 695 ); 696 } 697 698 $Frontend{SelectValid} = $LayoutObject->BuildSelection( 699 Data => { 700 0 => Translatable('invalid'), 701 1 => Translatable('valid'), 702 }, 703 SelectedID => $GetParam{Valid} // $Stat->{Valid}, 704 Name => 'Valid', 705 Class => 'Modernize', 706 ); 707 708 # get the default selected formats 709 my $DefaultSelectedFormat = $ConfigObject->Get('Stats::DefaultSelectedFormat') || []; 710 711 # Create a new statistic 712 if ( !$Stat->{StatType} ) { 713 my $DynamicFiles = $Kernel::OM->Get('Kernel::System::Stats')->GetDynamicFiles(); 714 715 my %ObjectModules; 716 DYNAMIC_FILE: 717 for my $DynamicFile ( sort keys %{ $DynamicFiles // {} } ) { 718 my $ObjectName = 'Kernel::System::Stats::Dynamic::' . $DynamicFile; 719 720 next DYNAMIC_FILE if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectName); 721 my $Object = $ObjectName->new(); 722 next DYNAMIC_FILE if !$Object; 723 if ( $Object->can('GetStatElement') ) { 724 $ObjectModules{DynamicMatrix}->{$ObjectName} = $DynamicFiles->{$DynamicFile}; 725 } 726 else { 727 $ObjectModules{DynamicList}->{$ObjectName} = $DynamicFiles->{$DynamicFile}; 728 } 729 } 730 731 my $StaticFiles = $Kernel::OM->Get('Kernel::System::Stats')->GetStaticFiles( 732 OnlyUnusedFiles => 1, 733 UserID => $Param{UserID}, 734 ); 735 for my $StaticFile ( sort keys %{ $StaticFiles // {} } ) { 736 $ObjectModules{Static}->{ 'Kernel::System::Stats::Static::' . $StaticFile } = $StaticFiles->{$StaticFile}; 737 } 738 739 $Frontend{StatisticPreselection} = $ParamObject->GetParam( Param => 'StatisticPreselection' ); 740 741 if ( $Frontend{StatisticPreselection} eq 'Static' ) { 742 $Frontend{StatType} = 'static'; 743 $Frontend{SelectObjectType} = $LayoutObject->BuildSelection( 744 Data => $ObjectModules{Static}, 745 Name => 'ObjectModule', 746 Class => 'Modernize Validate_Required' . ( $Errors{ObjectModuleServerError} ? ' ServerError' : '' ), 747 Translation => 0, 748 SelectedID => $GetParam{ObjectModule}, 749 ); 750 } 751 elsif ( $Frontend{StatisticPreselection} eq 'DynamicList' ) { 752 753 # remove the default selected graph formats for the dynamic lists 754 @{$DefaultSelectedFormat} = grep { $_ !~ m{^D3} } @{$DefaultSelectedFormat}; 755 756 $Frontend{StatType} = 'dynamic'; 757 $Frontend{SelectObjectType} = $LayoutObject->BuildSelection( 758 Data => $ObjectModules{DynamicList}, 759 Name => 'ObjectModule', 760 Translation => 1, 761 Class => 'Modernize ' . ( $Errors{ObjectModuleServerError} ? ' ServerError' : '' ), 762 SelectedID => $GetParam{ObjectModule} // $ConfigObject->Get('Stats::DefaultSelectedDynamicObject'), 763 ); 764 } 765 766 # DynamicMatrix 767 else { 768 $Frontend{StatType} = 'dynamic'; 769 $Frontend{SelectObjectType} = $LayoutObject->BuildSelection( 770 Data => $ObjectModules{DynamicMatrix}, 771 Name => 'ObjectModule', 772 Translation => 1, 773 Class => 'Modernize ' . ( $Errors{ObjectModuleServerError} ? ' ServerError' : '' ), 774 SelectedID => $GetParam{ObjectModule} // $ConfigObject->Get('Stats::DefaultSelectedDynamicObject'), 775 ); 776 777 } 778 } 779 780 # get the avaible formats 781 my $AvailableFormats = $ConfigObject->Get('Stats::Format'); 782 783 # create multiselectboxes 'format' 784 $Stat->{SelectFormat} = $LayoutObject->BuildSelection( 785 Data => $AvailableFormats, 786 Name => 'Format', 787 Class => 'Modernize Validate_Required' . ( $Errors{FormatServerError} ? ' ServerError' : '' ), 788 Multiple => 1, 789 Size => 5, 790 SelectedID => $GetParam{Format} // $Stat->{Format} || $DefaultSelectedFormat, 791 ); 792 793 # create multiselectboxes 'permission' 794 my %Permission = ( 795 Data => { $Kernel::OM->Get('Kernel::System::Group')->GroupList( Valid => 1 ) }, 796 Name => 'Permission', 797 Class => 'Modernize Validate_Required' . ( $Errors{PermissionServerError} ? ' ServerError' : '' ), 798 Multiple => 1, 799 Size => 5, 800 Translation => 0, 801 ); 802 if ( $GetParam{Permission} // $Stat->{Permission} ) { 803 $Permission{SelectedID} = $GetParam{Permission} // $Stat->{Permission}; 804 } 805 else { 806 $Permission{SelectedValue} = $ConfigObject->Get('Stats::DefaultSelectedPermissions'); 807 } 808 $Stat->{SelectPermission} = $LayoutObject->BuildSelection(%Permission); 809 810 # provide the timezone field only for dynamic statistics 811 if ( 812 ( $Stat->{StatType} && $Stat->{StatType} eq 'dynamic' ) 813 || ( $Frontend{StatType} && $Frontend{StatType} eq 'dynamic' ) 814 ) 815 { 816 817 my $SelectedTimeZone = $Self->_GetValidTimeZone( TimeZone => $GetParam{TimeZone} ) // $Stat->{TimeZone}; 818 if ( !defined $SelectedTimeZone ) { 819 my %UserPreferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences( 820 UserID => $Param{UserID} 821 ); 822 $SelectedTimeZone = $Self->_GetValidTimeZone( TimeZone => $UserPreferences{UserTimeZone} ) 823 // Kernel::System::DateTime->OTRSTimeZoneGet(); 824 } 825 826 my %TimeZoneBuildSelection = $Self->_TimeZoneBuildSelection(); 827 828 $Stat->{SelectTimeZone} = $LayoutObject->BuildSelection( 829 %TimeZoneBuildSelection, 830 Name => 'TimeZone', 831 Class => 'Modernize ' . ( $Errors{TimeZoneServerError} ? ' ServerError' : '' ), 832 SelectedID => $SelectedTimeZone, 833 ); 834 } 835 836 my $Output = $LayoutObject->Output( 837 TemplateFile => 'Statistics/GeneralSpecificationsWidget', 838 Data => { 839 %Frontend, 840 %{$Stat}, 841 %GetParam, 842 %Errors, 843 }, 844 ); 845 return $Output; 846} 847 848sub XAxisWidget { 849 my ( $Self, %Param ) = @_; 850 851 my $Stat = $Param{Stat}; 852 853 # get needed objects 854 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 855 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 856 857 # if only one value is available select this value 858 if ( !$Stat->{UseAsXvalue}[0]{Selected} && scalar( @{ $Stat->{UseAsXvalue} } ) == 1 ) { 859 $Stat->{UseAsXvalue}[0]{Selected} = 1; 860 $Stat->{UseAsXvalue}[0]{Fixed} = 1; 861 } 862 863 my @XAxisElements; 864 865 for my $ObjectAttribute ( @{ $Stat->{UseAsXvalue} } ) { 866 my %BlockData; 867 $BlockData{Fixed} = 'checked="checked"'; 868 $BlockData{Checked} = ''; 869 $BlockData{Block} = $ObjectAttribute->{Block}; 870 871 # things which should be done if this attribute is selected 872 if ( $ObjectAttribute->{Selected} ) { 873 $BlockData{Checked} = 'checked="checked"'; 874 if ( !$ObjectAttribute->{Fixed} ) { 875 $BlockData{Fixed} = ''; 876 } 877 } 878 879 if ( $ObjectAttribute->{Block} eq 'SelectField' || $ObjectAttribute->{Block} eq 'MultiSelectField' ) { 880 my $DFTreeClass = ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) 881 ? 'DynamicFieldWithTreeView' : ''; 882 $BlockData{SelectField} = $LayoutObject->BuildSelection( 883 Data => $ObjectAttribute->{Values}, 884 Name => 'XAxis' . $ObjectAttribute->{Element}, 885 Multiple => 1, 886 Size => 5, 887 Class => "Modernize $DFTreeClass", 888 SelectedID => $ObjectAttribute->{SelectedValues}, 889 Translation => $ObjectAttribute->{Translation}, 890 TreeView => $ObjectAttribute->{TreeView} || 0, 891 Sort => scalar $ObjectAttribute->{Sort}, 892 SortIndividual => scalar $ObjectAttribute->{SortIndividual}, 893 ); 894 895 if ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) { 896 my $TreeSelectionMessage = $LayoutObject->{LanguageObject}->Translate("Show Tree Selection"); 897 $BlockData{SelectField} 898 .= ' <a href="#" title="' 899 . $TreeSelectionMessage 900 . '" class="ShowTreeSelection"><span>' 901 . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>'; 902 } 903 } 904 905 $BlockData{Name} = $ObjectAttribute->{Name}; 906 $BlockData{Element} = 'XAxis' . $ObjectAttribute->{Element}; 907 908 # show the attribute block 909 $LayoutObject->Block( 910 Name => 'Attribute', 911 Data => \%BlockData, 912 ); 913 914 if ( $ObjectAttribute->{Block} eq 'Time' ) { 915 916 my %TimeData = $Self->_TimeOutput( 917 Output => 'Edit', 918 Use => 'UseAsXvalue', 919 %{$ObjectAttribute}, 920 Element => $BlockData{Element}, 921 ); 922 %BlockData = ( %BlockData, %TimeData ); 923 } 924 925 my $Block = $ObjectAttribute->{Block}; 926 927 if ( $Block eq 'SelectField' ) { 928 $Block = 'MultiSelectField'; 929 } 930 931 # store data, which will be sent to JS 932 push @XAxisElements, $BlockData{Element} if $BlockData{Checked}; 933 934 # show the input element 935 $LayoutObject->Block( 936 Name => $Block, 937 Data => \%BlockData, 938 ); 939 } 940 941 # send data to JS 942 $LayoutObject->AddJSData( 943 Key => 'XAxisElements', 944 Value => \@XAxisElements, 945 ); 946 947 my $Output = $LayoutObject->Output( 948 TemplateFile => 'Statistics/XAxisWidget', 949 Data => { 950 %{$Stat}, 951 }, 952 ); 953 return $Output; 954} 955 956sub YAxisWidget { 957 my ( $Self, %Param ) = @_; 958 959 my $Stat = $Param{Stat}; 960 961 # get needed objects 962 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 963 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 964 965 my @YAxisElements; 966 967 OBJECTATTRIBUTE: 968 for my $ObjectAttribute ( @{ $Stat->{UseAsValueSeries} } ) { 969 my %BlockData; 970 $BlockData{Fixed} = 'checked="checked"'; 971 $BlockData{Checked} = ''; 972 $BlockData{Block} = $ObjectAttribute->{Block}; 973 974 if ( $ObjectAttribute->{Selected} ) { 975 $BlockData{Checked} = 'checked="checked"'; 976 if ( !$ObjectAttribute->{Fixed} ) { 977 $BlockData{Fixed} = ''; 978 } 979 } 980 981 if ( $ObjectAttribute->{Block} eq 'SelectField' || $ObjectAttribute->{Block} eq 'MultiSelectField' ) { 982 my $DFTreeClass = ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) 983 ? 'DynamicFieldWithTreeView' : ''; 984 $BlockData{SelectField} = $LayoutObject->BuildSelection( 985 Data => $ObjectAttribute->{Values}, 986 Name => 'YAxis' . $ObjectAttribute->{Element}, 987 Multiple => 1, 988 Size => 5, 989 Class => "Modernize $DFTreeClass", 990 SelectedID => $ObjectAttribute->{SelectedValues}, 991 Translation => $ObjectAttribute->{Translation}, 992 TreeView => $ObjectAttribute->{TreeView} || 0, 993 Sort => scalar $ObjectAttribute->{Sort}, 994 SortIndividual => scalar $ObjectAttribute->{SortIndividual}, 995 ); 996 997 if ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) { 998 my $TreeSelectionMessage = $LayoutObject->{LanguageObject}->Translate("Show Tree Selection"); 999 $BlockData{SelectField} 1000 .= ' <a href="#" title="' 1001 . $TreeSelectionMessage 1002 . '" class="ShowTreeSelection"><span>' 1003 . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>'; 1004 } 1005 } 1006 1007 $BlockData{Name} = $ObjectAttribute->{Name}; 1008 $BlockData{Element} = 'YAxis' . $ObjectAttribute->{Element}; 1009 1010 # show the attribute block 1011 $LayoutObject->Block( 1012 Name => 'Attribute', 1013 Data => \%BlockData, 1014 ); 1015 1016 if ( $ObjectAttribute->{Block} eq 'Time' ) { 1017 1018 # get the selected x axis time scale value 1019 my $SelectedXAxisTimeScaleValue = $Self->_GetSelectedXAxisTimeScaleValue( Stat => $Stat ); 1020 1021 my %TimeData = $Self->_TimeOutput( 1022 Output => 'Edit', 1023 Use => 'UseAsValueSeries', 1024 %{$ObjectAttribute}, 1025 Element => $BlockData{Element}, 1026 SelectedXAxisValue => $SelectedXAxisTimeScaleValue, 1027 ); 1028 %BlockData = ( %BlockData, %TimeData ); 1029 } 1030 1031 my $Block = $ObjectAttribute->{Block}; 1032 1033 if ( $Block eq 'SelectField' ) { 1034 $Block = 'MultiSelectField'; 1035 } 1036 1037 # store data, which will be sent to JS 1038 push @YAxisElements, $BlockData{Element} if $BlockData{Checked}; 1039 1040 # show the input element 1041 $LayoutObject->Block( 1042 Name => $Block, 1043 Data => \%BlockData, 1044 ); 1045 } 1046 1047 # send data to JS 1048 $LayoutObject->AddJSData( 1049 Key => 'YAxisElements', 1050 Value => \@YAxisElements, 1051 ); 1052 1053 my $Output = $LayoutObject->Output( 1054 TemplateFile => 'Statistics/YAxisWidget', 1055 Data => { 1056 %{$Stat}, 1057 }, 1058 ); 1059 return $Output; 1060} 1061 1062sub RestrictionsWidget { 1063 my ( $Self, %Param ) = @_; 1064 1065 my $Stat = $Param{Stat}; 1066 1067 # get needed objects 1068 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1069 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1070 1071 my @RestrictionElements; 1072 1073 for my $ObjectAttribute ( @{ $Stat->{UseAsRestriction} } ) { 1074 my %BlockData; 1075 $BlockData{Fixed} = 'checked="checked"'; 1076 $BlockData{Checked} = ''; 1077 $BlockData{Block} = $ObjectAttribute->{Block}; 1078 $BlockData{CSSClass} = $ObjectAttribute->{CSSClass}; 1079 $BlockData{HTMLDataAttributes} = $ObjectAttribute->{HTMLDataAttributes}; 1080 1081 if ( $ObjectAttribute->{Selected} ) { 1082 $BlockData{Checked} = 'checked="checked"'; 1083 if ( !$ObjectAttribute->{Fixed} ) { 1084 $BlockData{Fixed} = ""; 1085 } 1086 } 1087 1088 if ( $ObjectAttribute->{SelectedValues} ) { 1089 $BlockData{SelectedValue} = $ObjectAttribute->{SelectedValues}[0]; 1090 } 1091 else { 1092 $BlockData{SelectedValue} = ''; 1093 $ObjectAttribute->{SelectedValues} = undef; 1094 } 1095 1096 if ( 1097 $ObjectAttribute->{Block} eq 'MultiSelectField' 1098 || $ObjectAttribute->{Block} eq 'SelectField' 1099 ) 1100 { 1101 my $DFTreeClass = ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) 1102 ? 'DynamicFieldWithTreeView' : ''; 1103 1104 # Take into account config 'IncludeUnknownTicketCustomers' for CustomerID restriction field. 1105 # See bug#14869 (https://bugs.otrs.org/show_bug.cgi?id=14869). 1106 if ( 1107 $ObjectAttribute->{Element} eq 'CustomerID' 1108 && !$ConfigObject->Get('Ticket::IncludeUnknownTicketCustomers') 1109 ) 1110 { 1111 my %CustomerCompanyList 1112 = $Kernel::OM->Get('Kernel::System::CustomerCompany')->CustomerCompanyList( Valid => 1 ); 1113 %CustomerCompanyList 1114 = map { $_ => $_ } grep { defined $ObjectAttribute->{Values}->{$_} } sort keys %CustomerCompanyList; 1115 1116 $ObjectAttribute->{Values} = \%CustomerCompanyList; 1117 } 1118 1119 $BlockData{SelectField} = $LayoutObject->BuildSelection( 1120 Data => $ObjectAttribute->{Values}, 1121 Name => 'Restrictions' . $ObjectAttribute->{Element}, 1122 Multiple => 1, 1123 Size => 5, 1124 Class => "Modernize $DFTreeClass", 1125 SelectedID => $ObjectAttribute->{SelectedValues}, 1126 Translation => $ObjectAttribute->{Translation}, 1127 TreeView => $ObjectAttribute->{TreeView} || 0, 1128 Sort => scalar $ObjectAttribute->{Sort}, 1129 SortIndividual => scalar $ObjectAttribute->{SortIndividual}, 1130 ); 1131 1132 if ( $ObjectAttribute->{ShowAsTree} && $ObjectAttribute->{IsDynamicField} ) { 1133 my $TreeSelectionMessage = $LayoutObject->{LanguageObject}->Translate("Show Tree Selection"); 1134 $BlockData{SelectField} 1135 .= ' <a href="#" title="' 1136 . $TreeSelectionMessage 1137 . '" class="ShowTreeSelection"><span>' 1138 . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>'; 1139 } 1140 } 1141 1142 $BlockData{Element} = 'Restrictions' . $ObjectAttribute->{Element}; 1143 $BlockData{Name} = $ObjectAttribute->{Name}; 1144 1145 # show the attribute block 1146 $LayoutObject->Block( 1147 Name => 'Attribute', 1148 Data => \%BlockData, 1149 ); 1150 if ( $ObjectAttribute->{Block} eq 'Time' ) { 1151 1152 my %TimeData = $Self->_TimeOutput( 1153 Output => 'Edit', 1154 Use => 'UseAsRestriction', 1155 %{$ObjectAttribute}, 1156 Element => $BlockData{Element}, 1157 ); 1158 %BlockData = ( %BlockData, %TimeData ); 1159 } 1160 1161 # store data, which will be sent to JS 1162 push @RestrictionElements, $BlockData{Element} if $BlockData{Checked}; 1163 1164 # show the input element 1165 $LayoutObject->Block( 1166 Name => $ObjectAttribute->{Block}, 1167 Data => \%BlockData, 1168 ); 1169 } 1170 1171 # send data to JS 1172 $LayoutObject->AddJSData( 1173 Key => 'RestrictionElements', 1174 Value => \@RestrictionElements, 1175 ); 1176 1177 my $Output = $LayoutObject->Output( 1178 TemplateFile => 'Statistics/RestrictionsWidget', 1179 Data => { 1180 %{$Stat}, 1181 }, 1182 ); 1183 return $Output; 1184} 1185 1186sub PreviewWidget { 1187 my ( $Self, %Param ) = @_; 1188 1189 my $Stat = $Param{Stat}; 1190 1191 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1192 1193 my %StatsConfigurationErrors; 1194 1195 $Self->StatsConfigurationValidate( 1196 Stat => $Stat, 1197 Errors => \%StatsConfigurationErrors, 1198 ); 1199 1200 my %Frontend; 1201 1202 if ( !%StatsConfigurationErrors ) { 1203 $Frontend{PreviewResult} = $Kernel::OM->Get('Kernel::System::Stats')->StatsRun( 1204 StatID => $Stat->{StatID}, 1205 GetParam => $Stat, 1206 Preview => 1, 1207 UserID => $Param{UserID}, 1208 ); 1209 } 1210 1211 # send data to JS 1212 $LayoutObject->AddJSData( 1213 Key => 'PreviewResult', 1214 Value => $Frontend{PreviewResult}, 1215 ); 1216 1217 my $Output = $LayoutObject->Output( 1218 TemplateFile => 'Statistics/PreviewWidget', 1219 Data => { 1220 %{$Stat}, 1221 %Frontend, 1222 StatsConfigurationErrors => \%StatsConfigurationErrors, 1223 }, 1224 ); 1225 return $Output; 1226} 1227 1228sub StatsParamsGet { 1229 my ( $Self, %Param ) = @_; 1230 1231 my $Stat = $Param{Stat}; 1232 1233 my $HasUserGetParam = ref $Param{UserGetParam} eq 'HASH'; 1234 1235 my %UserGetParam = %{ $Param{UserGetParam} // {} }; 1236 1237 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1238 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 1239 1240 my $LocalGetParam = sub { 1241 my (%Param) = @_; 1242 my $Param = $Param{Param}; 1243 return $HasUserGetParam ? $UserGetParam{$Param} : $ParamObject->GetParam( Param => $Param ); 1244 }; 1245 1246 my $LocalGetArray = sub { 1247 my (%Param) = @_; 1248 my $Param = $Param{Param}; 1249 if ($HasUserGetParam) { 1250 if ( $UserGetParam{$Param} && ref $UserGetParam{$Param} eq 'ARRAY' ) { 1251 return @{ $UserGetParam{$Param} }; 1252 } 1253 return; 1254 } 1255 return $ParamObject->GetArray( Param => $Param ); 1256 }; 1257 1258 my ( %GetParam, @Errors ); 1259 1260 # get the time zone param 1261 if ( length $LocalGetParam->( Param => 'TimeZone' ) ) { 1262 $GetParam{TimeZone} = $Self->_GetValidTimeZone( TimeZone => $LocalGetParam->( Param => 'TimeZone' ) ) 1263 // $Stat->{TimeZone}; 1264 } 1265 1266 # get ExchangeAxis param 1267 if ( length $LocalGetParam->( Param => 'ExchangeAxis' ) ) { 1268 $GetParam{ExchangeAxis} = $LocalGetParam->( Param => 'ExchangeAxis' ) // $Stat->{ExchangeAxis}; 1269 } 1270 1271 # 1272 # Static statistics 1273 # 1274 if ( $Stat->{StatType} eq 'static' ) { 1275 my $CurSysDTDetails = $Kernel::OM->Create('Kernel::System::DateTime')->Get(); 1276 1277 $GetParam{Year} = $CurSysDTDetails->{Year}; 1278 $GetParam{Month} = $CurSysDTDetails->{Month}; 1279 $GetParam{Day} = $CurSysDTDetails->{Day}; 1280 1281 my $Params = $Kernel::OM->Get('Kernel::System::Stats')->GetParams( 1282 StatID => $Stat->{StatID}, 1283 ); 1284 1285 PARAMITEM: 1286 for my $ParamItem ( @{$Params} ) { 1287 if ( $ParamItem->{Multiple} ) { 1288 $GetParam{ $ParamItem->{Name} } = [ $LocalGetArray->( Param => $ParamItem->{Name} ) ]; 1289 next PARAMITEM; 1290 } 1291 $GetParam{ $ParamItem->{Name} } = $LocalGetParam->( Param => $ParamItem->{Name} ); 1292 } 1293 } 1294 # 1295 # Dynamic statistics 1296 # 1297 else { 1298 1299 my $TimePeriod = 0; 1300 my $TimeUpcomingPeriod = 0; 1301 1302 for my $Use (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) { 1303 $Stat->{$Use} ||= []; 1304 1305 my @Array = @{ $Stat->{$Use} }; 1306 my $Counter = 0; 1307 ELEMENT: 1308 for my $Element (@Array) { 1309 next ELEMENT if !$Element->{Selected}; 1310 1311 my $ElementName = $Use . $Element->{'Element'}; 1312 1313 if ( !$Element->{Fixed} ) { 1314 1315 if ( $LocalGetArray->( Param => $ElementName ) ) { 1316 my @SelectedValues = $LocalGetArray->( 1317 Param => $ElementName 1318 ); 1319 1320 $Element->{SelectedValues} = \@SelectedValues; 1321 } 1322 elsif ( $LocalGetParam->( Param => $ElementName ) ) { 1323 my $SelectedValue = $LocalGetParam->( 1324 Param => $ElementName 1325 ); 1326 1327 $Element->{SelectedValues} = [$SelectedValue]; 1328 } 1329 1330 # set the first value for a single select field, if no selected value is given 1331 if ( 1332 $Element->{Block} eq 'SelectField' 1333 && ( 1334 !IsArrayRefWithData( $Element->{SelectedValues} ) 1335 || scalar @{ $Element->{SelectedValues} } > 1 1336 ) 1337 ) 1338 { 1339 1340 my @Values = sort keys %{ $Element->{Values} }; 1341 1342 if ( 1343 IsArrayRefWithData( $Element->{SelectedValues} ) 1344 && scalar @{ $Element->{SelectedValues} } > 1 1345 ) 1346 { 1347 @Values = @{ $Element->{SelectedValues} }; 1348 } 1349 1350 $Element->{SelectedValues} = [ $Values[0] ]; 1351 } 1352 1353 if ( $Element->{Block} eq 'InputField' ) { 1354 1355 # Show warning if restrictions contain stop words within ticket search. 1356 my %StopWordFields = $Self->_StopWordFieldsGet(); 1357 1358 if ( $StopWordFields{ $Element->{Element} } ) { 1359 my $ErrorMessage = $Self->_StopWordErrorCheck( 1360 $Element->{Element} => $Element->{SelectedValues}[0], 1361 ); 1362 if ($ErrorMessage) { 1363 push @Errors, "$Element->{Name}: $ErrorMessage"; 1364 } 1365 } 1366 1367 } 1368 if ( $Element->{Block} eq 'Time' ) { 1369 my %Time; 1370 1371 # Check if it is an absolute time period 1372 if ( $Element->{TimeStart} ) { 1373 1374 if ( $LocalGetParam->( Param => $ElementName . 'StartYear' ) ) { 1375 for my $Limit (qw(Start Stop)) { 1376 for my $Unit (qw(Year Month Day Hour Minute Second)) { 1377 if ( defined( $LocalGetParam->( Param => "$ElementName$Limit$Unit" ) ) ) { 1378 $Time{ $Limit . $Unit } = $LocalGetParam->( 1379 Param => $ElementName . "$Limit$Unit", 1380 ); 1381 } 1382 } 1383 if ( !defined( $Time{ $Limit . 'Hour' } ) ) { 1384 if ( $Limit eq 'Start' ) { 1385 $Time{StartHour} = 0; 1386 $Time{StartMinute} = 0; 1387 $Time{StartSecond} = 0; 1388 } 1389 elsif ( $Limit eq 'Stop' ) { 1390 $Time{StopHour} = 23; 1391 $Time{StopMinute} = 59; 1392 $Time{StopSecond} = 59; 1393 } 1394 } 1395 elsif ( !defined( $Time{ $Limit . 'Second' } ) ) { 1396 if ( $Limit eq 'Start' ) { 1397 $Time{StartSecond} = 0; 1398 } 1399 elsif ( $Limit eq 'Stop' ) { 1400 $Time{StopSecond} = 59; 1401 } 1402 } 1403 $Time{"Time$Limit"} = sprintf( 1404 "%04d-%02d-%02d %02d:%02d:%02d", 1405 $Time{ $Limit . 'Year' }, 1406 $Time{ $Limit . 'Month' }, 1407 $Time{ $Limit . 'Day' }, 1408 $Time{ $Limit . 'Hour' }, 1409 $Time{ $Limit . 'Minute' }, 1410 $Time{ $Limit . 'Second' }, 1411 ); 1412 } 1413 1414 $Element->{TimeStart} = $Time{TimeStart}; 1415 $Element->{TimeStop} = $Time{TimeStop}; 1416 1417 if ( $Use eq 'UseAsXvalue' ) { 1418 my $TimeStartEpoch = $Kernel::OM->Create( 1419 'Kernel::System::DateTime', 1420 ObjectParams => { 1421 String => $Element->{TimeStart}, 1422 }, 1423 )->ToEpoch(); 1424 1425 my $TimeStopEpoch = $Kernel::OM->Create( 1426 'Kernel::System::DateTime', 1427 ObjectParams => { 1428 String => $Element->{TimeStop}, 1429 }, 1430 )->ToEpoch(); 1431 1432 $TimePeriod = $TimeStopEpoch - $TimeStartEpoch; 1433 } 1434 } 1435 } 1436 else { 1437 1438 if ( $Use ne 'UseAsValueSeries' ) { 1439 $Time{TimeRelativeUnit} 1440 = $LocalGetParam->( Param => $ElementName . 'TimeRelativeUnit' ); 1441 $Time{TimeRelativeCount} 1442 = $LocalGetParam->( Param => $ElementName . 'TimeRelativeCount' ); 1443 $Time{TimeRelativeUpcomingCount} 1444 = $LocalGetParam->( Param => $ElementName . 'TimeRelativeUpcomingCount' ); 1445 1446 # Use Values of the stat as fallback 1447 $Time{TimeRelativeCount} //= $Element->{TimeRelativeCount}; 1448 $Time{TimeRelativeUpcomingCount} //= $Element->{TimeRelativeUpcomingCount}; 1449 $Time{TimeRelativeUnit} ||= $Element->{TimeRelativeUnit}; 1450 1451 if ( !$Time{TimeRelativeCount} && !$Time{TimeRelativeUpcomingCount} ) { 1452 push @Errors, 1453 Translatable( 1454 'No past complete or the current+upcoming complete relative time value selected.' 1455 ); 1456 } 1457 1458 if ( $Use eq 'UseAsXvalue' ) { 1459 $TimePeriod = $Time{TimeRelativeCount} * $Self->_TimeInSeconds( 1460 TimeUnit => $Time{TimeRelativeUnit}, 1461 ); 1462 $TimeUpcomingPeriod = $Time{TimeRelativeUpcomingCount} * $Self->_TimeInSeconds( 1463 TimeUnit => $Time{TimeRelativeUnit}, 1464 ); 1465 } 1466 1467 $Element->{TimeRelativeCount} = $Time{TimeRelativeCount}; 1468 $Element->{TimeRelativeUpcomingCount} = $Time{TimeRelativeUpcomingCount}; 1469 $Element->{TimeRelativeUnit} = $Time{TimeRelativeUnit}; 1470 } 1471 } 1472 1473 if ( $Use ne 'UseAsRestriction' ) { 1474 1475 if ( $LocalGetParam->( Param => $ElementName ) ) { 1476 $Element->{SelectedValues} = [ $LocalGetParam->( Param => $ElementName ) ]; 1477 } 1478 1479 if ( $LocalGetParam->( Param => $ElementName . 'TimeScaleCount' ) ) { 1480 1481 $Time{TimeScaleCount} = $LocalGetParam->( Param => $ElementName . 'TimeScaleCount' ); 1482 1483 # Use Values of the stat as fallback 1484 $Time{TimeScaleCount} ||= $Element->{TimeScaleCount}; 1485 1486 $Element->{TimeScaleCount} = $Time{TimeScaleCount}; 1487 } 1488 } 1489 } 1490 } 1491 1492 $GetParam{$Use}->[$Counter] = $Element; 1493 $Counter++; 1494 1495 } 1496 if ( ref $GetParam{$Use} ne 'ARRAY' ) { 1497 $GetParam{$Use} = []; 1498 } 1499 } 1500 1501 # check if the timeperiod is too big or the time scale too small 1502 if ( 1503 ( $GetParam{UseAsXvalue}[0]{Block} && $GetParam{UseAsXvalue}[0]{Block} eq 'Time' ) 1504 && ( 1505 !$GetParam{UseAsValueSeries}[0] 1506 || ( $GetParam{UseAsValueSeries}[0] && $GetParam{UseAsValueSeries}[0]{Block} ne 'Time' ) 1507 ) 1508 ) 1509 { 1510 1511 my $ScalePeriod = $Self->_TimeInSeconds( 1512 TimeUnit => $GetParam{UseAsXvalue}[0]{SelectedValues}[0] 1513 ); 1514 1515 # integrate this functionality in the completenesscheck 1516 my $MaxAttr = $ConfigObject->Get('Stats::MaxXaxisAttributes') || 1000; 1517 if ( 1518 ( $TimePeriod + $TimeUpcomingPeriod ) / ( $ScalePeriod * $GetParam{UseAsXvalue}[0]{TimeScaleCount} ) 1519 > $MaxAttr 1520 ) 1521 { 1522 push @Errors, Translatable('The selected time period is larger than the allowed time period.'); 1523 } 1524 } 1525 1526 if ( $GetParam{UseAsValueSeries}[0]{Block} && $GetParam{UseAsValueSeries}[0]{Block} eq 'Time' ) { 1527 1528 my $TimeScale = $Self->_TimeScale( 1529 SelectedXAxisValue => $GetParam{UseAsXvalue}[0]{SelectedValues}[0], 1530 ); 1531 1532 if ( !IsHashRefWithData($TimeScale) ) { 1533 push @Errors, 1534 Translatable( 1535 'No time scale value available for the current selected time scale value on the X axis.' 1536 ); 1537 } 1538 } 1539 } 1540 1541 if (@Errors) { 1542 die \@Errors; 1543 } 1544 1545 return %GetParam; 1546} 1547 1548sub StatsResultRender { 1549 my ( $Self, %Param ) = @_; 1550 1551 my @StatArray = @{ $Param{StatArray} // [] }; 1552 my $Stat = $Param{Stat}; 1553 1554 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1555 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1556 1557 my $TitleArrayRef = shift @StatArray; 1558 my $Title = $TitleArrayRef->[0]; 1559 my $HeadArrayRef = shift @StatArray; 1560 1561 # Generate Filename 1562 my $Filename = $Kernel::OM->Get('Kernel::System::Stats')->StringAndTimestamp2Filename( 1563 String => $Stat->{Title} . ' Created', 1564 TimeZone => $Param{TimeZone}, 1565 ); 1566 1567 # get CSV object 1568 my $CSVObject = $Kernel::OM->Get('Kernel::System::CSV'); 1569 1570 # generate D3 output 1571 if ( $Param{Format} =~ m{^D3} ) { 1572 1573 # if array = empty 1574 if ( !@StatArray ) { 1575 push @StatArray, [ ' ', 0 ]; 1576 } 1577 1578 my $Output = $LayoutObject->Header( 1579 Value => $Title, 1580 Type => 'Small', 1581 ); 1582 1583 # send data to JS 1584 $LayoutObject->AddJSData( 1585 Key => 'D3Data', 1586 Value => { 1587 RawData => [ 1588 [$Title], 1589 $HeadArrayRef, 1590 @StatArray, 1591 ], 1592 Format => $Param{Format}, 1593 } 1594 ); 1595 1596 $Output .= $LayoutObject->Output( 1597 Data => { 1598 %{$Stat}, 1599 }, 1600 TemplateFile => 'Statistics/StatsResultRender/D3', 1601 ); 1602 $Output .= $LayoutObject->Footer( 1603 Type => 'Small', 1604 ); 1605 return $Output; 1606 } 1607 1608 # generate csv output 1609 if ( $Param{Format} eq 'CSV' ) { 1610 1611 # get Separator from language file 1612 my $UserCSVSeparator = $LayoutObject->{LanguageObject}->{Separator}; 1613 1614 if ( $ConfigObject->Get('PreferencesGroups')->{CSVSeparator}->{Active} ) { 1615 my %UserData = $Kernel::OM->Get('Kernel::System::User')->GetUserData( 1616 UserID => $Param{UserID} 1617 ); 1618 $UserCSVSeparator = $UserData{UserCSVSeparator} if $UserData{UserCSVSeparator}; 1619 } 1620 my $Output = $CSVObject->Array2CSV( 1621 Head => $HeadArrayRef, 1622 Data => \@StatArray, 1623 Separator => $UserCSVSeparator, 1624 ); 1625 1626 return $LayoutObject->Attachment( 1627 Filename => $Filename . '.csv', 1628 ContentType => "text/csv", 1629 Content => $Output, 1630 ); 1631 } 1632 1633 # generate excel output 1634 elsif ( $Param{Format} eq 'Excel' ) { 1635 my $Output = $CSVObject->Array2CSV( 1636 Head => $HeadArrayRef, 1637 Data => \@StatArray, 1638 Format => 'Excel', 1639 ); 1640 1641 return $LayoutObject->Attachment( 1642 Filename => $Filename . '.xlsx', 1643 ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 1644 Content => $Output, 1645 ); 1646 1647 } 1648 1649 # pdf or html output 1650 elsif ( $Param{Format} eq 'Print' ) { 1651 my $PDFString = $Kernel::OM->Get('Kernel::Output::PDF::Statistics')->GeneratePDF( 1652 Stat => $Stat, 1653 Title => $Title, 1654 HeadArrayRef => $HeadArrayRef, 1655 StatArray => \@StatArray, 1656 TimeZone => $Param{TimeZone}, 1657 UserID => $Param{UserID}, 1658 ); 1659 return $LayoutObject->Attachment( 1660 Filename => $Filename . '.pdf', 1661 ContentType => 'application/pdf', 1662 Content => $PDFString, 1663 Type => 'inline', 1664 ); 1665 } 1666} 1667 1668=head2 StatsConfigurationValidate() 1669 1670 my $StatCorrectlyConfigured = $StatsViewObject->StatsConfigurationValidate( 1671 StatData => \%StatData, 1672 Errors => \%Errors, # Hash to be populated with errors, if any 1673 ); 1674 1675=cut 1676 1677sub StatsConfigurationValidate { 1678 my ( $Self, %Param ) = @_; 1679 1680 for my $Needed (qw(Stat Errors)) { 1681 if ( !$Param{$Needed} ) { 1682 $Kernel::OM->Get('Kernel::System::Log')->Log( 1683 Priority => 'error', 1684 Message => "Need $Needed" 1685 ); 1686 return; 1687 } 1688 } 1689 1690 my %GeneralSpecificationFieldErrors; 1691 my ( %XAxisFieldErrors, @XAxisGeneralErrors ); 1692 my ( %YAxisFieldErrors, @YAxisGeneralErrors ); 1693 my (%RestrictionsFieldErrors); 1694 1695 my %Stat = %{ $Param{Stat} }; 1696 1697 # Specification 1698 { 1699 KEY: 1700 for my $Field (qw(Title Description StatType Permission Format ObjectModule)) { 1701 if ( !$Stat{$Field} ) { 1702 $GeneralSpecificationFieldErrors{$Field} = Translatable('This field is required.'); 1703 } 1704 } 1705 if ( $Stat{StatType} && $Stat{StatType} eq 'static' && !$Stat{File} ) { 1706 $GeneralSpecificationFieldErrors{File} = Translatable('This field is required.'); 1707 } 1708 if ( $Stat{StatType} && $Stat{StatType} eq 'dynamic' && !$Stat{Object} ) { 1709 $GeneralSpecificationFieldErrors{Object} = Translatable('This field is required.'); 1710 } 1711 } 1712 1713 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1714 1715 if ( $Stat{StatType} eq 'dynamic' ) { 1716 1717 # save the selected x axis time scale value for some checks for the y axis 1718 my $SelectedXAxisTimeScaleValue; 1719 1720 # X Axis 1721 { 1722 my $Flag = 0; 1723 XVALUE: 1724 for my $Xvalue ( @{ $Stat{UseAsXvalue} } ) { 1725 next XVALUE if !$Xvalue->{Selected}; 1726 1727 if ( $Xvalue->{Block} eq 'Time' ) { 1728 if ( $Xvalue->{TimeStart} && $Xvalue->{TimeStop} ) { 1729 my $TimeStartDTObject = $Kernel::OM->Create( 1730 'Kernel::System::DateTime', 1731 ObjectParams => { 1732 String => $Xvalue->{TimeStart}, 1733 }, 1734 ); 1735 1736 my $TimeStopDTObject = $Kernel::OM->Create( 1737 'Kernel::System::DateTime', 1738 ObjectParams => { 1739 String => $Xvalue->{TimeStop}, 1740 }, 1741 ); 1742 1743 if ( !$TimeStartDTObject || !$TimeStopDTObject ) { 1744 $XAxisFieldErrors{ $Xvalue->{Element} } = Translatable('The selected date is not valid.'); 1745 } 1746 elsif ( $TimeStartDTObject > $TimeStopDTObject ) { 1747 $XAxisFieldErrors{ $Xvalue->{Element} } 1748 = Translatable('The selected end time is before the start time.'); 1749 } 1750 } 1751 elsif ( 1752 !$Xvalue->{TimeRelativeUnit} 1753 || ( !$Xvalue->{TimeRelativeCount} && !$Xvalue->{TimeRelativeUpcomingCount} ) 1754 ) 1755 { 1756 $XAxisFieldErrors{ $Xvalue->{Element} } 1757 = Translatable('There is something wrong with your time selection.'); 1758 } 1759 1760 if ( !$Xvalue->{SelectedValues}[0] ) { 1761 $XAxisFieldErrors{ $Xvalue->{Element} } 1762 = Translatable('There is something wrong with your time selection.'); 1763 } 1764 elsif ( $Xvalue->{Fixed} && $#{ $Xvalue->{SelectedValues} } > 0 ) { 1765 $XAxisFieldErrors{ $Xvalue->{Element} } 1766 = Translatable('There is something wrong with your time selection.'); 1767 } 1768 else { 1769 $SelectedXAxisTimeScaleValue = $Xvalue->{SelectedValues}[0]; 1770 } 1771 } 1772 elsif ( $Xvalue->{Block} eq 'SelectField' ) { 1773 if ( $Xvalue->{Fixed} && $#{ $Xvalue->{SelectedValues} } > 0 ) { 1774 $XAxisFieldErrors{ $Xvalue->{Element} } = Translatable( 1775 'Please select only one element or allow modification at stat generation time.' 1776 ); 1777 } 1778 elsif ( $Xvalue->{Fixed} && !$Xvalue->{SelectedValues}[0] ) { 1779 $XAxisFieldErrors{ $Xvalue->{Element} } = Translatable( 1780 'Please select at least one value of this field or allow modification at stat generation time.' 1781 ); 1782 } 1783 } 1784 1785 $Flag = 1; 1786 last XVALUE; 1787 } 1788 if ( !$Flag ) { 1789 push @XAxisGeneralErrors, Translatable('Please select one element for the X-axis.'); 1790 } 1791 } 1792 1793 # Y Axis 1794 { 1795 my $Counter = 0; 1796 my $TimeUsed = 0; 1797 VALUESERIES: 1798 for my $ValueSeries ( @{ $Stat{UseAsValueSeries} } ) { 1799 next VALUESERIES if !$ValueSeries->{Selected}; 1800 1801 if ( $ValueSeries->{Block} eq 'Time' || $ValueSeries->{Block} eq 'TimeExtended' ) { 1802 if ( $ValueSeries->{Fixed} && $#{ $ValueSeries->{SelectedValues} } > 0 ) { 1803 $YAxisFieldErrors{ $ValueSeries->{Element} } 1804 = Translatable('There is something wrong with your time selection.'); 1805 } 1806 elsif ( !$ValueSeries->{SelectedValues}[0] ) { 1807 $YAxisFieldErrors{ $ValueSeries->{Element} } 1808 = Translatable('There is something wrong with your time selection.'); 1809 } 1810 1811 my $TimeScale = $Self->_TimeScale( 1812 SelectedXAxisValue => $SelectedXAxisTimeScaleValue, 1813 ); 1814 1815 if ( !IsHashRefWithData($TimeScale) ) { 1816 $YAxisFieldErrors{ $ValueSeries->{Element} } = Translatable( 1817 'No time scale value available for the current selected time scale value on the X axis.' 1818 ); 1819 } 1820 1821 $TimeUsed++; 1822 } 1823 elsif ( $ValueSeries->{Block} eq 'SelectField' ) { 1824 if ( $ValueSeries->{Fixed} && $#{ $ValueSeries->{SelectedValues} } > 0 ) { 1825 $YAxisFieldErrors{ $ValueSeries->{Element} } = Translatable( 1826 'Please select only one element or allow modification at stat generation time.' 1827 ); 1828 } 1829 elsif ( $ValueSeries->{Fixed} && !$ValueSeries->{SelectedValues}[0] ) { 1830 $YAxisFieldErrors{ $ValueSeries->{Element} } = Translatable( 1831 'Please select at least one value of this field or allow modification at stat generation time.' 1832 ); 1833 } 1834 } 1835 1836 $Counter++; 1837 } 1838 1839 if ( $Counter > 1 && $TimeUsed ) { 1840 push @YAxisGeneralErrors, Translatable('You can only use one time element for the Y axis.'); 1841 } 1842 elsif ( $Counter > 2 ) { 1843 push @YAxisGeneralErrors, Translatable('You can only use one or two elements for the Y axis.'); 1844 } 1845 } 1846 1847 # Restrictions 1848 { 1849 RESTRICTION: 1850 for my $Restriction ( @{ $Stat{UseAsRestriction} } ) { 1851 next RESTRICTION if !$Restriction->{Selected}; 1852 1853 if ( $Restriction->{Block} eq 'SelectField' ) { 1854 if ( $Restriction->{Fixed} && $#{ $Restriction->{SelectedValues} } > 0 ) { 1855 $RestrictionsFieldErrors{ $Restriction->{Element} } = Translatable( 1856 'Please select only one element or allow modification at stat generation time.' 1857 ); 1858 } 1859 elsif ( !$Restriction->{SelectedValues}[0] ) { 1860 $RestrictionsFieldErrors{ $Restriction->{Element} } 1861 = Translatable('Please select at least one value of this field.'); 1862 } 1863 } 1864 elsif ( $Restriction->{Block} eq 'InputField' ) { 1865 if ( !$Restriction->{SelectedValues}[0] && $Restriction->{Fixed} ) { 1866 $RestrictionsFieldErrors{ $Restriction->{Element} } 1867 = Translatable('Please provide a value or allow modification at stat generation time.'); 1868 last RESTRICTION; 1869 } 1870 1871 # Show warning if restrictions contain stop words within ticket search. 1872 my %StopWordFields = $Self->_StopWordFieldsGet(); 1873 1874 if ( $StopWordFields{ $Restriction->{Element} } ) { 1875 my $ErrorMessage = $Self->_StopWordErrorCheck( 1876 $Restriction->{Element} => $Restriction->{SelectedValues}[0], 1877 ); 1878 if ($ErrorMessage) { 1879 $RestrictionsFieldErrors{ $Restriction->{Element} } = $ErrorMessage; 1880 } 1881 } 1882 1883 } 1884 elsif ( $Restriction->{Block} eq 'Time' || $Restriction->{Block} eq 'TimeExtended' ) { 1885 if ( $Restriction->{TimeStart} && $Restriction->{TimeStop} ) { 1886 my $TimeStartDTObject = $Kernel::OM->Create( 1887 'Kernel::System::DateTime', 1888 ObjectParams => { 1889 String => $Restriction->{TimeStart}, 1890 }, 1891 ); 1892 1893 my $TimeStopDTObject = $Kernel::OM->Create( 1894 'Kernel::System::DateTime', 1895 ObjectParams => { 1896 String => $Restriction->{TimeStop}, 1897 }, 1898 ); 1899 1900 if ( !$TimeStartDTObject || !$TimeStopDTObject ) { 1901 $RestrictionsFieldErrors{ $Restriction->{Element} } 1902 = Translatable('The selected date is not valid.'); 1903 } 1904 elsif ( $TimeStartDTObject > $TimeStopDTObject ) { 1905 $RestrictionsFieldErrors{ $Restriction->{Element} } 1906 = Translatable('The selected end time is before the start time.'); 1907 } 1908 } 1909 elsif ( 1910 !$Restriction->{TimeRelativeUnit} 1911 || ( !$Restriction->{TimeRelativeCount} && !$Restriction->{TimeRelativeUpcomingCount} ) 1912 ) 1913 { 1914 $RestrictionsFieldErrors{ $Restriction->{Element} } 1915 = Translatable('There is something wrong with your time selection.'); 1916 } 1917 } 1918 } 1919 } 1920 1921 # Check if the timeperiod is too big or the time scale too small. Also execute this check for 1922 # non-fixed values because it is used in preview and cron stats generation mode. 1923 { 1924 XVALUE: 1925 for my $Xvalue ( @{ $Stat{UseAsXvalue} } ) { 1926 1927 last XVALUE if defined $XAxisFieldErrors{ $Xvalue->{Element} }; 1928 next XVALUE if !( $Xvalue->{Selected} && $Xvalue->{Block} eq 'Time' ); 1929 1930 my $Flag = 1; 1931 VALUESERIES: 1932 for my $ValueSeries ( @{ $Stat{UseAsValueSeries} } ) { 1933 if ( $ValueSeries->{Selected} && $ValueSeries->{Block} eq 'Time' ) { 1934 $Flag = 0; 1935 last VALUESERIES; 1936 } 1937 } 1938 1939 last XVALUE if !$Flag; 1940 1941 my $ScalePeriod = 0; 1942 my $TimePeriod = 0; 1943 my $TimeUpcomingPeriod = 0; 1944 1945 my $Count = $Xvalue->{TimeScaleCount} ? $Xvalue->{TimeScaleCount} : 1; 1946 1947 $ScalePeriod = $Self->_TimeInSeconds( 1948 TimeUnit => $Xvalue->{SelectedValues}[0], 1949 ); 1950 1951 if ( !$ScalePeriod ) { 1952 $XAxisFieldErrors{ $Xvalue->{Element} } = Translatable('Please select a time scale.'); 1953 last XVALUE; 1954 } 1955 1956 if ( $Xvalue->{TimeStop} && $Xvalue->{TimeStart} ) { 1957 my $TimeStartEpoch = $Kernel::OM->Create( 1958 'Kernel::System::DateTime', 1959 ObjectParams => { 1960 String => $Xvalue->{TimeStart}, 1961 }, 1962 )->ToEpoch(); 1963 1964 my $TimeStopEpoch = $Kernel::OM->Create( 1965 'Kernel::System::DateTime', 1966 ObjectParams => { 1967 String => $Xvalue->{TimeStop}, 1968 }, 1969 )->ToEpoch(); 1970 1971 $TimePeriod = $TimeStopEpoch - $TimeStartEpoch; 1972 } 1973 else { 1974 $TimePeriod = $Xvalue->{TimeRelativeCount} * $Self->_TimeInSeconds( 1975 TimeUnit => $Xvalue->{TimeRelativeUnit}, 1976 ); 1977 $TimeUpcomingPeriod = $Xvalue->{TimeRelativeUpcomingCount} * $Self->_TimeInSeconds( 1978 TimeUnit => $Xvalue->{TimeRelativeUnit}, 1979 ); 1980 } 1981 1982 my $MaxAttr = $ConfigObject->Get('Stats::MaxXaxisAttributes') || 1000; 1983 if ( ( $TimePeriod + $TimeUpcomingPeriod ) / ( $ScalePeriod * $Count ) > $MaxAttr ) { 1984 $XAxisFieldErrors{ $Xvalue->{Element} } 1985 = Translatable('Your reporting time interval is too small, please use a larger time scale.'); 1986 } 1987 1988 last XVALUE; 1989 } 1990 } 1991 } 1992 1993 if ( 1994 !%GeneralSpecificationFieldErrors 1995 && !%XAxisFieldErrors 1996 && !@XAxisGeneralErrors 1997 && !%YAxisFieldErrors 1998 && !@YAxisGeneralErrors 1999 && !%RestrictionsFieldErrors 2000 ) 2001 { 2002 return 1; 2003 } 2004 2005 %{ $Param{Errors} } = ( 2006 GeneralSpecificationFieldErrors => \%GeneralSpecificationFieldErrors, 2007 XAxisFieldErrors => \%XAxisFieldErrors, 2008 XAxisGeneralErrors => \@XAxisGeneralErrors, 2009 YAxisFieldErrors => \%YAxisFieldErrors, 2010 YAxisGeneralErrors => \@YAxisGeneralErrors, 2011 RestrictionsFieldErrors => \%RestrictionsFieldErrors, 2012 ); 2013 2014 return; 2015} 2016 2017sub _TimeOutput { 2018 my ( $Self, %Param ) = @_; 2019 2020 # diffrent output types 2021 my %AllowedOutput = ( 2022 Edit => 1, 2023 View => 1, 2024 ); 2025 2026 # check if the output type is given and allowed 2027 if ( !$Param{Output} || !$AllowedOutput{ $Param{Output} } ) { 2028 $Kernel::OM->Get('Kernel::System::Log')->Log( 2029 Priority => "error", 2030 Message => '_TimeOutput: Need allowed output type!', 2031 ); 2032 } 2033 2034 # get layout object 2035 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 2036 2037 my %TimeOutput; 2038 2039 my %TimeScaleBuildSelection = $Self->_TimeScaleBuildSelection(); 2040 2041 my $Element = $Param{Element}; 2042 my $ElementID = $Element; 2043 2044 # add the StatID to the ElementID for the view output 2045 if ( $Param{Output} eq 'View' && $Param{StatID} ) { 2046 $ElementID .= '-' . $Param{StatID} . '-' . $Param{OutputCounter}; 2047 } 2048 2049 if ( $Param{Use} ne 'UseAsValueSeries' ) { 2050 2051 if ( $Param{Output} eq 'Edit' || ( $Param{TimeStart} && $Param{TimeStop} ) ) { 2052 2053 # check if need params are available 2054 if ( !$Param{TimePeriodFormat} ) { 2055 $Kernel::OM->Get('Kernel::System::Log')->Log( 2056 Priority => "error", 2057 Message => '_TimeOutput: Need TimePeriodFormat!', 2058 ); 2059 } 2060 2061 # get time 2062 my $CurSysDTDetails = $Kernel::OM->Create('Kernel::System::DateTime')->Get(); 2063 my $Year = $CurSysDTDetails->{Year}; 2064 my %TimeConfig; 2065 2066 # default time configuration 2067 $TimeConfig{Format} = $Param{TimePeriodFormat}; 2068 $TimeConfig{OverrideTimeZone} = 1; 2069 $TimeConfig{ $Element . 'StartYear' } = $Year - 1; 2070 $TimeConfig{ $Element . 'StartMonth' } = 1; 2071 $TimeConfig{ $Element . 'StartDay' } = 1; 2072 $TimeConfig{ $Element . 'StartHour' } = 0; 2073 $TimeConfig{ $Element . 'StartMinute' } = 0; 2074 $TimeConfig{ $Element . 'StartSecond' } = 1; 2075 $TimeConfig{ $Element . 'StopYear' } = $Year; 2076 $TimeConfig{ $Element . 'StopMonth' } = 12; 2077 $TimeConfig{ $Element . 'StopDay' } = 31; 2078 $TimeConfig{ $Element . 'StopHour' } = 23; 2079 $TimeConfig{ $Element . 'StopMinute' } = 59; 2080 $TimeConfig{ $Element . 'StopSecond' } = 59; 2081 2082 for (qw(Start Stop)) { 2083 $TimeConfig{Prefix} = $Element . $_; 2084 2085 # time setting if available 2086 if ( 2087 $Param{ 'Time' . $_ } 2088 && $Param{ 'Time' . $_ } =~ m{^(\d\d\d\d)-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)$}xi 2089 ) 2090 { 2091 $TimeConfig{ $Element . $_ . 'Year' } = $1; 2092 $TimeConfig{ $Element . $_ . 'Month' } = $2; 2093 $TimeConfig{ $Element . $_ . 'Day' } = $3; 2094 $TimeConfig{ $Element . $_ . 'Hour' } = $4; 2095 $TimeConfig{ $Element . $_ . 'Minute' } = $5; 2096 $TimeConfig{ $Element . $_ . 'Second' } = $6; 2097 } 2098 $TimeOutput{ 'Time' . $_ } = $LayoutObject->BuildDateSelection(%TimeConfig); 2099 } 2100 } 2101 2102 my %TimeCountData; 2103 for my $Counter ( 1 .. 60 ) { 2104 $TimeCountData{$Counter} = $Counter; 2105 } 2106 2107 if ( $Param{Use} eq 'UseAsXvalue' ) { 2108 $TimeOutput{TimeScaleCount} = $LayoutObject->BuildSelection( 2109 Data => \%TimeCountData, 2110 Name => $Element . 'TimeScaleCount', 2111 ID => $ElementID . '-TimeScaleCount', 2112 SelectedID => $Param{TimeScaleCount}, 2113 Sort => 'NumericKey', 2114 Class => 'Modernize', 2115 ); 2116 } 2117 2118 if ( $Param{Output} eq 'Edit' || $Param{TimeRelativeUnit} ) { 2119 2120 my @TimeCountList = qw(TimeRelativeCount TimeRelativeUpcomingCount); 2121 2122 # add the zero for the time relative count selections 2123 $TimeCountData{0} = '-'; 2124 2125 for my $TimeCountName (@TimeCountList) { 2126 2127 $TimeOutput{$TimeCountName} = $LayoutObject->BuildSelection( 2128 Data => \%TimeCountData, 2129 Name => $Element . $TimeCountName, 2130 ID => $ElementID . '-' . $TimeCountName, 2131 SelectedID => $Param{$TimeCountName}, 2132 Sort => 'NumericKey', 2133 Class => 'Modernize', 2134 ); 2135 } 2136 2137 $TimeOutput{TimeRelativeUnit} = $LayoutObject->BuildSelection( 2138 %TimeScaleBuildSelection, 2139 Name => $Element . 'TimeRelativeUnit', 2140 ID => $ElementID . '-TimeRelativeUnit', 2141 Class => 'TimeRelativeUnit' . $Param{Output}, 2142 SelectedID => $Param{TimeRelativeUnitLocalSelectedValue} // $Param{TimeRelativeUnit} // 'Day', 2143 Class => 'Modernize', 2144 ); 2145 } 2146 2147 if ( $Param{TimeRelativeUnit} ) { 2148 $TimeOutput{CheckedRelative} = 'checked="checked"'; 2149 } 2150 else { 2151 $TimeOutput{CheckedAbsolut} = 'checked="checked"'; 2152 } 2153 } 2154 2155 if ( $Param{Use} ne 'UseAsRestriction' ) { 2156 2157 if ( $Param{Output} eq 'View' ) { 2158 $TimeOutput{TimeScaleYAxis} = $Self->_TimeScaleYAxis(); 2159 } 2160 2161 %TimeScaleBuildSelection = $Self->_TimeScaleBuildSelection( 2162 SelectedXAxisValue => $Param{SelectedXAxisValue}, 2163 SortReverse => 1, 2164 ); 2165 2166 $TimeOutput{TimeScaleUnit} = $LayoutObject->BuildSelection( 2167 %TimeScaleBuildSelection, 2168 Name => $Element, 2169 ID => $ElementID, 2170 Class => 'Modernize TimeScale' . $Param{Output}, 2171 SelectedID => $Param{TimeScaleUnitLocalSelectedValue} // $Param{SelectedValues}[0] // 'Day', 2172 ); 2173 $TimeOutput{TimeScaleElementID} = $ElementID; 2174 } 2175 2176 return %TimeOutput; 2177} 2178 2179sub _TimeScaleBuildSelection { 2180 my ( $Self, %Param ) = @_; 2181 2182 my %TimeScaleBuildSelection = ( 2183 Data => { 2184 Second => Translatable('second(s)'), 2185 Minute => Translatable('minute(s)'), 2186 Hour => Translatable('hour(s)'), 2187 Day => Translatable('day(s)'), 2188 Week => Translatable('week(s)'), 2189 Month => Translatable('month(s)'), 2190 Quarter => Translatable('quarter(s)'), 2191 HalfYear => Translatable('half-year(s)'), 2192 Year => Translatable('year(s)'), 2193 }, 2194 Sort => 'IndividualKey', 2195 SortIndividual => [ 'Second', 'Minute', 'Hour', 'Day', 'Week', 'Month', 'Quarter', 'HalfYear', 'Year' ], 2196 ); 2197 2198 # special time scale handling 2199 if ( $Param{SelectedValue} || $Param{SelectedXAxisValue} ) { 2200 2201 my $TimeScale = $Self->_TimeScale(%Param); 2202 2203 # sort the time scale with the defined position 2204 my @TimeScaleSorted = sort { $TimeScale->{$a}->{Position} <=> $TimeScale->{$b}->{Position} } keys %{$TimeScale}; 2205 2206 # reverse the sorting 2207 if ( $Param{SortReverse} ) { 2208 @TimeScaleSorted 2209 = sort { $TimeScale->{$b}->{Position} <=> $TimeScale->{$a}->{Position} } keys %{$TimeScale}; 2210 } 2211 2212 my %TimeScaleData; 2213 2214 ITEM: 2215 for my $Item (@TimeScaleSorted) { 2216 $TimeScaleData{$Item} = $TimeScale->{$Item}->{Value}; 2217 last ITEM if $Param{SelectedValue} && $Param{SelectedValue} eq $Item; 2218 } 2219 2220 $TimeScaleBuildSelection{Data} = \%TimeScaleData; 2221 } 2222 2223 return %TimeScaleBuildSelection; 2224} 2225 2226sub _TimeScale { 2227 my ( $Self, %Param ) = @_; 2228 2229 my %TimeScale = ( 2230 'Second' => { 2231 Position => 1, 2232 Value => Translatable('second(s)'), 2233 }, 2234 'Minute' => { 2235 Position => 2, 2236 Value => Translatable('minute(s)'), 2237 }, 2238 'Hour' => { 2239 Position => 3, 2240 Value => Translatable('hour(s)'), 2241 }, 2242 'Day' => { 2243 Position => 4, 2244 Value => Translatable('day(s)'), 2245 }, 2246 'Week' => { 2247 Position => 5, 2248 Value => Translatable('week(s)'), 2249 }, 2250 'Month' => { 2251 Position => 6, 2252 Value => Translatable('month(s)'), 2253 }, 2254 'Quarter' => { 2255 Position => 7, 2256 Value => Translatable('quarter(s)'), 2257 }, 2258 'HalfYear' => { 2259 Position => 8, 2260 Value => Translatable('half-year(s)'), 2261 }, 2262 'Year' => { 2263 Position => 9, 2264 Value => Translatable('year(s)'), 2265 }, 2266 ); 2267 2268 # allowed y axis time scale values for the selected x axis time value 2269 my $TimeScaleYAxis = $Self->_TimeScaleYAxis(); 2270 2271 if ( $Param{SelectedXAxisValue} ) { 2272 2273 if ( IsArrayRefWithData( $TimeScaleYAxis->{ $Param{SelectedXAxisValue} } ) ) { 2274 %TimeScale 2275 = map { $_->{Key} => $TimeScale{ $_->{Key} } } @{ $TimeScaleYAxis->{ $Param{SelectedXAxisValue} } }; 2276 } 2277 else { 2278 %TimeScale = (); 2279 } 2280 } 2281 2282 return \%TimeScale; 2283} 2284 2285sub _TimeScaleYAxis { 2286 my ( $Self, %Param ) = @_; 2287 2288 # get layout object 2289 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 2290 2291 # allowed y axis time scale values for the selected x axis time value 2292 # x axis value => [ y axis values ], 2293 my %TimeScaleYAxis = ( 2294 'Second' => [ 2295 { 2296 Key => 'Minute', 2297 Value => $LayoutObject->{LanguageObject}->Translate('minute(s)'), 2298 }, 2299 ], 2300 'Minute' => [ 2301 { 2302 Key => 'Hour', 2303 Value => $LayoutObject->{LanguageObject}->Translate('hour(s)'), 2304 }, 2305 ], 2306 'Hour' => [ 2307 { 2308 Key => 'Day', 2309 Value => $LayoutObject->{LanguageObject}->Translate('day(s)'), 2310 }, 2311 ], 2312 'Day' => [ 2313 { 2314 Key => 'Month', 2315 Value => $LayoutObject->{LanguageObject}->Translate('month(s)'), 2316 }, 2317 ], 2318 'Week' => [ 2319 { 2320 Key => 'Week', 2321 Value => $LayoutObject->{LanguageObject}->Translate('week(s)'), 2322 }, 2323 ], 2324 'Month' => [ 2325 { 2326 Key => 'Year', 2327 Value => $LayoutObject->{LanguageObject}->Translate('year(s)'), 2328 }, 2329 ], 2330 'Quarter' => [ 2331 { 2332 Key => 'Year', 2333 Value => $LayoutObject->{LanguageObject}->Translate('year(s)'), 2334 }, 2335 ], 2336 'HalfYear' => [ 2337 { 2338 Key => 'Year', 2339 Value => $LayoutObject->{LanguageObject}->Translate('year(s)'), 2340 }, 2341 ], 2342 ); 2343 2344 return \%TimeScaleYAxis; 2345} 2346 2347sub _GetValidTimeZone { 2348 my ( $Self, %Param ) = @_; 2349 2350 return if !$Param{TimeZone}; 2351 2352 # Return passed time zone only if it is valid. It can happen time zone is still an old-style offset. 2353 # Please see bug#13373 for more information. 2354 return $Param{TimeZone} if Kernel::System::DateTime->IsTimeZoneValid( TimeZone => $Param{TimeZone} ); 2355 2356 return; 2357} 2358 2359sub _TimeZoneBuildSelection { 2360 my ( $Self, %Param ) = @_; 2361 2362 my $TimeZones = Kernel::System::DateTime->TimeZoneList(); 2363 2364 my %TimeZoneBuildSelection = ( 2365 Data => { map { $_ => $_ } @{$TimeZones} }, 2366 ); 2367 2368 return %TimeZoneBuildSelection; 2369} 2370 2371# ATTENTION: this function delivers only approximations!!! 2372sub _TimeInSeconds { 2373 my ( $Self, %Param ) = @_; 2374 2375 # check if need params are available 2376 if ( !$Param{TimeUnit} ) { 2377 $Kernel::OM->Get('Kernel::System::Log')->Log( 2378 Priority => "error", 2379 Message => '_TimeInSeconds: Need TimeUnit!', 2380 ); 2381 return; 2382 } 2383 2384 my %TimeInSeconds = ( 2385 Year => 60 * 60 * 24 * 365, 2386 HalfYear => 60 * 60 * 24 * 182, 2387 Quarter => 60 * 60 * 24 * 91, 2388 Month => 60 * 60 * 24 * 30, 2389 Week => 60 * 60 * 24 * 7, 2390 Day => 60 * 60 * 24, 2391 Hour => 60 * 60, 2392 Minute => 60, 2393 Second => 1, 2394 ); 2395 2396 return $TimeInSeconds{ $Param{TimeUnit} }; 2397} 2398 2399sub _GetSelectedXAxisTimeScaleValue { 2400 my ( $Self, %Param ) = @_; 2401 2402 my $SelectedXAxisTimeScaleValue; 2403 2404 for ( @{ $Param{Stat}->{UseAsXvalue} } ) { 2405 2406 if ( $_->{Selected} && $_->{Block} eq 'Time' ) { 2407 $SelectedXAxisTimeScaleValue = $_->{SelectedValues}[0]; 2408 } 2409 } 2410 2411 return $SelectedXAxisTimeScaleValue; 2412} 2413 2414sub _StopWordErrorCheck { 2415 my ( $Self, %Param ) = @_; 2416 2417 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 2418 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 2419 2420 if ( !%Param ) { 2421 $LayoutObject->FatalError( 2422 Message => Translatable('Got no values to check.'), 2423 ); 2424 } 2425 2426 my %StopWordsServerErrors; 2427 if ( !$ArticleObject->SearchStringStopWordsUsageWarningActive() ) { 2428 return %StopWordsServerErrors; 2429 } 2430 2431 my %SearchStrings; 2432 2433 FIELD: 2434 for my $Field ( sort keys %Param ) { 2435 next FIELD if !defined $Param{$Field}; 2436 next FIELD if !length $Param{$Field}; 2437 2438 $SearchStrings{$Field} = $Param{$Field}; 2439 } 2440 2441 my $ErrorMessage; 2442 2443 if (%SearchStrings) { 2444 2445 my $StopWords = $ArticleObject->SearchStringStopWordsFind( 2446 SearchStrings => \%SearchStrings, 2447 ); 2448 2449 FIELD: 2450 for my $Field ( sort keys %{$StopWords} ) { 2451 next FIELD if !defined $StopWords->{$Field}; 2452 next FIELD if ref $StopWords->{$Field} ne 'ARRAY'; 2453 next FIELD if !@{ $StopWords->{$Field} }; 2454 2455 $ErrorMessage = $LayoutObject->{LanguageObject}->Translate( 2456 'Please remove the following words because they cannot be used for the ticket restrictions: %s.', 2457 join( ',', sort @{ $StopWords->{$Field} } ), 2458 ); 2459 } 2460 } 2461 2462 return $ErrorMessage; 2463} 2464 2465sub _StopWordFieldsGet { 2466 my ( $Self, %Param ) = @_; 2467 2468 if ( !$Kernel::OM->Get('Kernel::System::Ticket::Article')->SearchStringStopWordsUsageWarningActive() ) { 2469 return (); 2470 } 2471 2472 my %StopWordFields = ( 2473 'MIME_From' => 1, 2474 'MIME_To' => 1, 2475 'MIME_Cc' => 1, 2476 'MIME_Subject' => 1, 2477 'MIME_Body' => 1, 2478 ); 2479 2480 return %StopWordFields; 2481} 2482 24831; 2484 2485=head1 TERMS AND CONDITIONS 2486 2487This software is part of the OTRS project (L<https://otrs.org/>). 2488 2489This software comes with ABSOLUTELY NO WARRANTY. For details, see 2490the enclosed file COPYING for license information (GPL). If you 2491did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 2492 2493=cut 2494