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