1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Stats;
10
11use strict;
12use warnings;
13
14use MIME::Base64;
15
16use POSIX qw(ceil);
17
18use Kernel::Language qw(Translatable);
19use Kernel::System::VariableCheck qw(:all);
20use Kernel::Output::HTML::Statistics::View;
21
22our @ObjectDependencies = (
23    'Kernel::Config',
24    'Kernel::Language',
25    'Kernel::Output::HTML::Statistics::View',
26    'Kernel::System::Cache',
27    'Kernel::System::DB',
28    'Kernel::System::Encode',
29    'Kernel::System::Group',
30    'Kernel::System::Log',
31    'Kernel::System::Main',
32    'Kernel::System::Storable',
33    'Kernel::System::DateTime',
34    'Kernel::System::User',
35    'Kernel::System::XML',
36);
37
38=head1 NAME
39
40Kernel::System::Stats - stats lib
41
42=head1 DESCRIPTION
43
44All stats functions.
45
46=head2 Explanation for the time zone parameter
47
48The time zone parameter is available, if the statistic is a dynamic statistic. The selected periods in the frontend are time zone neutral and for the
49search parameters, the selection will be converted to the OTRS time zone, because the times
50are stored within this time zone in the database.
51
52This means e.g. if an absolute period of time from 2015-08-01 00:00:00 to 2015-09-10 23:59:59 and a time zone with an offset of +6 hours has been selected,
53the period will be converted from the +6 time zone to the OTRS time zone for the search parameter,
54so that the right time will be used for searching the database. Given that the OTRS time zone is set to UTC, this
55would result in a period of 2015-07-31 18:00:00 to 2015-09-10 17:59:59 UTC.
56
57For a relative time period, e. g. the last 10 full days, and a time zone with an offset of +10 hours, a DateTime object with the +10 time zone will be created
58for the current time. For the period end date, this date will be taken and extended to the end of the day. Then, 10 full days will be subtracted from this.
59This is the start of the period, which will be extended to 00:00:00. Start and end date will be converted to the time zone of OTRS to search the database.
60
61Example for relative time period 'last 10 full days' with selected time zone offset +10 hours, current date/time within this time zone 2015-09-10 16:00:00, OTRS time zone is UTC:
62End date: 2015-09-10 16:00:00 -> extended to 2015-09-10 23:59:59 -> 2015-09-10 13:59:59 OTRS time zone (UTC)
63Start date: 2015-09-10 16:00:00 - 10 days -> 2015-08-31 16:00:00 -> extended to 00:00:00: 2015-09-01 00:00:00 -> 2015-08-31 14:00:00 OTRS time zone (UTC)
64
65=head1 PUBLIC INTERFACE
66
67=head2 new()
68
69Don't use the constructor directly, use the ObjectManager instead:
70
71    my $StatsObject = $Kernel::OM->Get('Kernel::System::Stats');
72
73=cut
74
75sub new {
76    my ( $Type, %Param ) = @_;
77
78    # allocate new hash for object
79    my $Self = {};
80    bless( $Self, $Type );
81
82    # temporary directory
83    $Self->{StatsTempDir} = $Kernel::OM->Get('Kernel::Config')->Get('Home') . '/var/stats/';
84
85    return $Self;
86}
87
88=head2 StatsAdd()
89
90add new empty stats
91
92    my $StatID = $StatsObject->StatsAdd(
93        UserID => $UserID,
94    );
95
96=cut
97
98sub StatsAdd {
99    my ( $Self, %Param ) = @_;
100
101    for my $Needed (qw(UserID)) {
102        if ( !$Param{$Needed} ) {
103            $Kernel::OM->Get('Kernel::System::Log')->Log(
104                Priority => "error",
105                Message  => "Need $Needed.",
106            );
107            return;
108        }
109    }
110
111    # get needed objects
112    my $XMLObject      = $Kernel::OM->Get('Kernel::System::XML');
113    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
114
115    # get new StatID
116    my $StatID = 1;
117    my @Keys   = $XMLObject->XMLHashSearch(
118        Type => 'Stats',
119    );
120    if (@Keys) {
121        my @SortKeys = sort { $a <=> $b } @Keys;
122        $StatID = $SortKeys[-1] + 1;
123    }
124
125    # requesting current time stamp
126    my $TimeStamp = $DateTimeObject->ToString();
127
128    # meta tags
129    my $StatNumber = $StatID + $Kernel::OM->Get('Kernel::Config')->Get('Stats::StatsStartNumber');
130
131    my %MetaData = (
132        Created => [
133            { Content => $TimeStamp },
134        ],
135        CreatedBy => [
136            { Content => $Param{UserID} },
137        ],
138        Changed => [
139            { Content => $TimeStamp },
140        ],
141        ChangedBy => [
142            { Content => $Param{UserID} },
143        ],
144        Valid => [
145            { Content => 1 },
146        ],
147        StatNumber => [
148            { Content => $StatNumber },
149        ],
150    );
151
152    # start new stats record
153    my @XMLHash = (
154        { otrs_stats => [ \%MetaData ] },
155    );
156    my $Success = $XMLObject->XMLHashAdd(
157        Type    => 'Stats',
158        Key     => $StatID,
159        XMLHash => \@XMLHash,
160    );
161    if ( !$Success ) {
162        $Kernel::OM->Get('Kernel::System::Log')->Log(
163            Priority => 'error',
164            Message  => 'Can not add a new Stat!',
165        );
166        return;
167    }
168
169    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
170        Type => 'Stats',
171    );
172
173    return $StatID;
174}
175
176=head2 StatsGet()
177
178get a hash ref of the stats you need
179
180    my $HashRef = $StatsObject->StatsGet(
181        StatID             => '123',
182        NoObjectAttributes => 1,       # optional
183    );
184
185=cut
186
187sub StatsGet {
188    my ( $Self, %Param ) = @_;
189
190    # check necessary data
191    if ( !$Param{StatID} ) {
192        $Kernel::OM->Get('Kernel::System::Log')->Log(
193            Priority => 'error',
194            Message  => 'Need StatID!'
195        );
196    }
197
198    $Param{NoObjectAttributes} = $Param{NoObjectAttributes} ? 1 : 0;
199
200    # get cache object
201    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
202
203    my $CacheKey = "StatsGet::StatID::$Param{StatID}::NoObjectAttributes::$Param{NoObjectAttributes}";
204
205    my $Cache = $CacheObject->Get(
206        Type => 'Stats',
207        Key  => $CacheKey,
208
209        # Don't store complex structure in memory as it will be modified later.
210        CacheInMemory => 0,
211    );
212    return $Cache if ref $Cache eq 'HASH';
213
214    # get hash from storage
215    my @XMLHash = $Kernel::OM->Get('Kernel::System::XML')->XMLHashGet(
216        Type => 'Stats',
217        Key  => $Param{StatID},
218    );
219
220    if ( !$XMLHash[0] ) {
221        $Kernel::OM->Get('Kernel::System::Log')->Log(
222            Priority => 'error',
223            Message  => "Can't get StatsID $Param{StatID}!",
224        );
225        return;
226    }
227
228    my %Stat;
229    my $StatsXML = $XMLHash[0]->{otrs_stats}->[1];
230
231    # process all strings
232    $Stat{StatID} = $Param{StatID};
233    for my $Key (
234        qw(Title Object File Description SumRow SumCol StatNumber
235        Cache StatType Valid ObjectModule CreatedBy ChangedBy Created Changed
236        ShowAsDashboardWidget
237        )
238        )
239    {
240        if ( defined $StatsXML->{$Key}->[1]->{Content} ) {
241            $Stat{$Key} = $StatsXML->{$Key}->[1]->{Content};
242        }
243    }
244
245    # time zone
246    if ( defined $StatsXML->{TimeZone}->[1]->{Content} ) {
247
248        # Check if stored time zone is valid. It can happen stored time zone is still an old-style offset. Otherwise,
249        #   fall back to the system time zone. Please see bug#13373 for more information.
250        if ( Kernel::System::DateTime->IsTimeZoneValid( TimeZone => $StatsXML->{TimeZone}->[1]->{Content} ) ) {
251            $Stat{TimeZone} = $StatsXML->{TimeZone}->[1]->{Content};
252        }
253        else {
254            $Stat{TimeZone} = Kernel::System::DateTime->OTRSTimeZoneGet();
255        }
256    }
257
258    # process all arrays
259    KEY:
260    for my $Key (qw(Permission Format GraphSize)) {
261        next KEY if !$StatsXML->{$Key}->[1]->{Content};
262
263        $Stat{$Key} = ();
264        for my $Index ( 1 .. $#{ $StatsXML->{$Key} } ) {
265            push @{ $Stat{$Key} }, $StatsXML->{$Key}->[$Index]->{Content};
266        }
267    }
268
269    if ( $Stat{ObjectModule} ) {
270        $Stat{ObjectBehaviours} = $Self->GetObjectBehaviours(
271            ObjectModule => $Stat{ObjectModule},
272        );
273    }
274
275    # get the configuration elements of the dynamic stats
276    # %Allowed is used to avoid double selection in different forms
277    my %Allowed;
278    my %TimeAllowed;
279    my $TimeElement = $Kernel::OM->Get('Kernel::Config')->Get('Stats::TimeElement') || 'Time';
280
281    return \%Stat if !$Stat{Object};
282
283    $Stat{ObjectName} = $Self->GetObjectName(
284        ObjectModule => $Stat{ObjectModule},
285    );
286
287    if ( $Param{NoObjectAttributes} ) {
288
289        $CacheObject->Set(
290            Type  => 'Stats',
291            Key   => $CacheKey,
292            Value => \%Stat,
293            TTL   => 24 * 60 * 60,
294
295            # Don't store complex structure in memory as it will be modified later.
296            CacheInMemory => 0,
297        );
298
299        return \%Stat;
300    }
301
302    KEY:
303    for my $Key (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) {
304
305        # @StatAttributesSimplified give you arrays without undef array elements
306        my @StatAttributesSimplified;
307
308        # get the attributes of the object
309        my @ObjectAttributes = $Self->GetStatsObjectAttributes(
310            ObjectModule => $Stat{ObjectModule},
311            Use          => $Key,
312        );
313
314        next KEY if !@ObjectAttributes;
315
316        ATTRIBUTE:
317        for my $Attribute (@ObjectAttributes) {
318            my $Element = $Attribute->{Element};
319            if ( $Attribute->{Block} eq 'Time' ) {
320                if ( $Key eq 'UseAsValueSeries' ) {
321                    if ( $Allowed{$Element} && $Allowed{$Element} == 1 ) {
322                        $Allowed{$Element}     = 0;
323                        $TimeAllowed{$Element} = 1;
324                    }
325                    else {
326                        $Allowed{$Element} = 1;
327                    }
328                }
329                elsif ( $Key eq 'UseAsRestriction' ) {
330                    if ( $TimeAllowed{$Element} && $TimeAllowed{$Element} == 1 ) {
331                        $Allowed{$Element} = 1;
332                    }
333                    else {
334                        $Allowed{$Element} = 0;
335                    }
336                }
337            }
338            next ATTRIBUTE if $Allowed{$Element};
339
340            if ( $StatsXML->{$Key} ) {
341                my @StatAttributes = @{ $StatsXML->{$Key} };
342                if ( !$StatAttributes[0] ) {
343                    shift @StatAttributes;
344                }
345
346                REF:
347                for my $Ref (@StatAttributes) {
348                    if ( !defined $Attribute->{Translation} ) {
349                        $Attribute->{Translation} = 1;
350                    }
351
352                    next REF
353                        if !(
354                        $Element
355                        && $Ref->{Element}
356                        && $Element eq $Ref->{Element}
357                        );
358
359                    # if selected elements exit, add the information to the StatAttributes
360                    $Attribute->{Selected} = 1;
361                    if ( $Ref->{Fixed} ) {
362                        $Attribute->{Fixed} = 1;
363                    }
364
365                    for my $Index ( 1 .. $#{ $Ref->{SelectedValues} } ) {
366                        push @{ $Attribute->{SelectedValues} }, $Ref->{SelectedValues}->[$Index]->{Content};
367                    }
368
369                    if ( $Attribute->{Block} eq 'Time' ) {
370
371                        # settings for working with time elements
372                        for (
373                            qw(TimeStop TimeStart TimeRelativeUnit
374                            TimeRelativeCount TimeRelativeUpcomingCount TimeScaleCount
375                            )
376                            )
377                        {
378                            if ( defined $Ref->{$_} && ( !$Attribute->{$_} || $Ref->{Fixed} ) ) {
379                                $Attribute->{$_} = $Ref->{$_};
380                            }
381                        }
382
383                        # set a default value for the time relative upcoming count field
384                        $Attribute->{TimeRelativeUpcomingCount} //= 0;
385                    }
386
387                    $Allowed{$Element} = 1;
388                }
389            }
390
391            push @StatAttributesSimplified, $Attribute;
392
393        }
394
395        $Stat{$Key} = \@StatAttributesSimplified;
396    }
397
398    $CacheObject->Set(
399        Type  => 'Stats',
400        Key   => $CacheKey,
401        Value => \%Stat,
402        TTL   => 24 * 60 * 60,
403
404        # Don't store complex structure in memory as it will be modified later.
405        CacheInMemory => 0,
406    );
407
408    return \%Stat;
409}
410
411=head2 StatsUpdate()
412
413update a stat
414
415    $StatsObject->StatsUpdate(
416        StatID => '123',
417        Hash   => \%Hash,
418        UserID => $UserID,
419    );
420
421=cut
422
423sub StatsUpdate {
424    my ( $Self, %Param ) = @_;
425
426    for my $Needed (qw(UserID)) {
427        if ( !$Param{$Needed} ) {
428            $Kernel::OM->Get('Kernel::System::Log')->Log(
429                Priority => "error",
430                Message  => "Need $Needed.",
431            );
432            return;
433        }
434    }
435
436    # declaration of the hash
437    my %StatXML;
438
439    # check necessary data
440    if ( !$Param{StatID} ) {
441        $Kernel::OM->Get('Kernel::System::Log')->Log(
442            Priority => 'error',
443            Message  => 'Need StatID!'
444        );
445    }
446
447    # requesting stats reference
448    my $StatOld = $Self->StatsGet( StatID => $Param{StatID} );
449    if ( !$StatOld ) {
450        $Kernel::OM->Get('Kernel::System::Log')->Log(
451            Priority => 'error',
452            Message =>
453                "Can't get stats, perhaps you have an invalid stats id! (StatsID => $Param{StatID})"
454        );
455        return;
456    }
457
458    # declare variable
459    my $StatNew = $Param{Hash};
460
461    # a delete function can be the better solution
462    for my $Key (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) {
463        for ( @{ $StatOld->{$Key} } ) {
464            if ( !$_->{Selected} ) {
465                $_ = undef;
466            }
467        }
468    }
469
470    # adopt changes
471    for my $Key ( sort keys %{$StatNew} ) {
472        $StatOld->{$Key} = $StatNew->{$Key};
473    }
474
475    KEY:
476    for my $Key ( sort keys %{$StatOld} ) {
477
478        # Don't store the behaviour data
479        next KEY if $Key eq 'ObjectBehaviours';
480
481        if ( $Key eq 'UseAsXvalue' || $Key eq 'UseAsValueSeries' || $Key eq 'UseAsRestriction' ) {
482            my $Index = 0;
483            REF:
484            for my $Ref ( @{ $StatOld->{$Key} } ) {
485                next REF if !$Ref;
486
487                $Index++;
488                $StatXML{$Key}->[$Index]->{Element} = $Ref->{Element};
489                $StatXML{$Key}->[$Index]->{Fixed}   = $Ref->{Fixed};
490                my $SubIndex = 0;
491                for my $Value ( @{ $Ref->{SelectedValues} } ) {
492                    $SubIndex++;
493                    $StatXML{$Key}->[$Index]->{SelectedValues}->[$SubIndex]->{Content} = $Value;
494                }
495
496                # stetting for working with time elements
497                for (qw(TimeStop TimeStart TimeRelativeUnit TimeRelativeCount TimeRelativeUpcomingCount TimeScaleCount))
498                {
499                    if ( defined $Ref->{$_} ) {
500                        $StatXML{$Key}->[$Index]->{$_} = $Ref->{$_};
501                    }
502                }
503            }
504        }
505        elsif ( ref $StatOld->{$Key} eq 'ARRAY' ) {
506            for my $Index ( 0 .. $#{ $StatOld->{$Key} } ) {
507                $StatXML{$Key}->[$Index]->{Content} = $StatOld->{$Key}->[$Index];
508            }
509        }
510        else {
511            if ( defined $StatOld->{$Key} ) {
512                $StatXML{$Key}->[1]->{Content} = $StatOld->{$Key};
513            }
514        }
515    }
516
517    # get datetime object
518    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
519
520    # meta tags
521    $StatXML{Changed}->[1]->{Content}   = $DateTimeObject->ToString();
522    $StatXML{ChangedBy}->[1]->{Content} = $Param{UserID};
523
524    # get xml object
525    my $XMLObject = $Kernel::OM->Get('Kernel::System::XML');
526
527    # please don't change the functionality of XMLHashDelete and XMLHashAdd
528    # into the new function XMLHashUpdate, there is an incompatibility.
529    # Perhaps there are intricacies because of the 'Array[0] = undef' definition
530
531    # delete the old record
532    my $Success = $XMLObject->XMLHashDelete(
533        Type => 'Stats',
534        Key  => $Param{StatID},
535    );
536    if ( !$Success ) {
537        $Kernel::OM->Get('Kernel::System::Log')->Log(
538            Priority => 'error',
539            Message  => "Can't delete XMLHash!"
540        );
541        return;
542    }
543
544    # delete cache
545    $Self->_DeleteCache( StatID => $Param{StatID} );
546
547    my @Array = (
548        {
549            otrs_stats => [ \%StatXML ],
550        },
551    );
552
553    # add the revised record
554    $Success = $XMLObject->XMLHashAdd(
555        Type    => 'Stats',
556        Key     => $Param{StatID},
557        XMLHash => \@Array
558    );
559    if ( !$Success ) {
560        $Kernel::OM->Get('Kernel::System::Log')->Log(
561            Priority => 'error',
562            Message  => "Can't add XMLHash!"
563        );
564        return;
565    }
566
567    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
568        Type => 'Stats',
569    );
570
571    return 1;
572}
573
574=head2 StatsDelete()
575
576delete a stats
577
578    $StatsObject->StatsDelete( StatID => '123' );
579
580=cut
581
582sub StatsDelete {
583    my ( $Self, %Param ) = @_;
584
585    # check necessary data
586    if ( !$Param{StatID} ) {
587        $Kernel::OM->Get('Kernel::System::Log')->Log(
588            Priority => 'error',
589            Message  => 'Need StatID!'
590        );
591    }
592
593    # delete the record
594    my $Success = $Kernel::OM->Get('Kernel::System::XML')->XMLHashDelete(
595        Type => 'Stats',
596        Key  => $Param{StatID},
597    );
598
599    # error handling
600    if ( !$Success ) {
601        $Kernel::OM->Get('Kernel::System::Log')->Log(
602            Priority => 'error',
603            Message  => "Can't delete XMLHash!",
604        );
605        return;
606    }
607
608    # delete cache
609    $Self->_DeleteCache( StatID => $Param{StatID} );
610
611    # get main object
612    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
613
614    # get list of installed stats files
615    my @StatsFileList = $MainObject->DirectoryRead(
616        Directory => $Self->{StatsTempDir},
617        Filter    => '*.xml.installed',
618    );
619
620    # delete the .installed file in temp dir
621    FILE:
622    for my $File ( sort @StatsFileList ) {
623
624        # read file content
625        my $StatsIDRef = $MainObject->FileRead(
626            Location => $File,
627        );
628
629        next FILE if !$StatsIDRef;
630        next FILE if ref $StatsIDRef ne 'SCALAR';
631        next FILE if !${$StatsIDRef};
632
633        next FILE if ${$StatsIDRef} ne $Param{StatID};
634
635        # delete .installed file
636        $MainObject->FileDelete(
637            Location => $File,
638        );
639    }
640
641    # add log message
642    $Kernel::OM->Get('Kernel::System::Log')->Log(
643        Priority => 'notice',
644        Message  => "Delete stats (StatsID = $Param{StatID})",
645    );
646
647    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
648        Type => 'Stats',
649    );
650
651    return 1;
652}
653
654=head2 StatsListGet()
655
656fetches all statistics that the current user may see
657
658    my $StatsRef = $StatsObject->StatsListGet(
659        AccessRw => 1, # Optional, indicates that user may see all statistics
660        UserID   => $UserID,
661    );
662
663    Returns
664
665    {
666        6 => {
667            Title => "Title of stat",
668            ...
669        }
670    }
671
672=cut
673
674sub StatsListGet {
675    my ( $Self, %Param ) = @_;
676
677    for my $Needed (qw(UserID)) {
678        if ( !$Param{$Needed} ) {
679            $Kernel::OM->Get('Kernel::System::Log')->Log(
680                Priority => "error",
681                Message  => "Need $Needed.",
682            );
683            return;
684        }
685    }
686
687    my @SearchResult;
688
689    # get cache object
690    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
691
692    # Only cache the XML search as we need to filter based on user permissions later
693    my $CacheKey = 'StatsListGet::XMLSearch';
694    my $Cache    = $CacheObject->Get(
695        Type => 'Stats',
696        Key  => $CacheKey,
697
698        # Don't store complex structure in memory as it will be modified later.
699        CacheInMemory => 0,
700    );
701
702    # Do we have a cache available?
703    if ( ref $Cache eq 'ARRAY' ) {
704        @SearchResult = @{$Cache};
705    }
706    else {
707
708        # get xml object
709        my $XMLObject = $Kernel::OM->Get('Kernel::System::XML');
710
711        # No cache. Is there stats data yet?
712        if ( !( @SearchResult = $XMLObject->XMLHashSearch( Type => 'Stats' ) ) ) {
713
714            # Import sample stats
715            $Self->_AutomaticSampleImport(
716                UserID => $Param{UserID},
717            );
718
719            # Load stats again
720            return if !( @SearchResult = $XMLObject->XMLHashSearch( Type => 'Stats' ) );
721        }
722        $CacheObject->Set(
723            Type  => 'Stats',
724            Key   => $CacheKey,
725            Value => \@SearchResult,
726            TTL   => 24 * 60 * 60,
727
728            # Don't store complex structure in memory as it will be modified later.
729            CacheInMemory => 0,
730        );
731
732    }
733
734    # get user groups
735    my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
736        UserID => $Param{UserID},
737        Type   => 'ro',
738    );
739
740    my %Result;
741
742    for my $StatID (@SearchResult) {
743
744        my $Stat = $Self->StatsGet(
745            StatID             => $StatID,
746            NoObjectAttributes => 1,
747        );
748
749        my $UserPermission = 0;
750        if ( $Param{AccessRw} || $Param{UserID} == 1 ) {
751
752            $UserPermission = 1;
753        }
754        elsif ( $Stat->{Valid} ) {
755
756            GROUPID:
757            for my $GroupID ( @{ $Stat->{Permission} } ) {
758
759                next GROUPID if !$GroupID;
760                next GROUPID if !$GroupList{$GroupID};
761
762                $UserPermission = 1;
763
764                last GROUPID;
765            }
766        }
767
768        if ( $UserPermission == 1 ) {
769            $Result{$StatID} = $Stat;
770        }
771    }
772
773    return \%Result;
774}
775
776=head2 GetStatsList()
777
778lists all stats id's
779
780    my $ArrayRef = $StatsObject->GetStatsList(
781        AccessRw  => 1, # Optional, indicates that user may see all statistics
782        OrderBy   => 'ID' || 'Title' || 'Object', # optional
783        Direction => 'ASC' || 'DESC',             # optional
784        UserID    => $UserID,
785    );
786
787=cut
788
789sub GetStatsList {
790    my ( $Self, %Param ) = @_;
791
792    for my $Needed (qw(UserID)) {
793        if ( !$Param{$Needed} ) {
794            $Kernel::OM->Get('Kernel::System::Log')->Log(
795                Priority => "error",
796                Message  => "Need $Needed.",
797            );
798            return;
799        }
800    }
801
802    $Param{OrderBy}   ||= 'ID';
803    $Param{Direction} ||= 'ASC';
804
805    my %ResultHash = %{ $Self->StatsListGet(%Param) || {} };
806
807    my @SortArray;
808
809    if ( $Param{OrderBy} eq 'ID' ) {
810        @SortArray = sort { $a <=> $b } keys %ResultHash;
811    }
812    else {
813        @SortArray = sort { $ResultHash{$a}->{ $Param{OrderBy} } cmp $ResultHash{$b}->{ $Param{OrderBy} } }
814            keys %ResultHash;
815    }
816    if ( $Param{Direction} eq 'DESC' ) {
817        @SortArray = reverse @SortArray;
818    }
819
820    return \@SortArray;
821}
822
823=head2 SumBuild()
824
825build sum in x or/and y axis
826
827    $StatArray = $StatsObject->SumBuild(
828        Array  => \@Result,
829        SumRow => 1,
830        SumCol => 0,
831    );
832
833=cut
834
835sub SumBuild {
836    my ( $Self, %Param ) = @_;
837
838    my @Data = @{ $Param{Array} };
839
840    # add sum y
841    if ( $Param{SumCol} ) {
842
843        push @{ $Data[1] }, Translatable('Sum');
844
845        for my $Index1 ( 2 .. $#Data ) {
846
847            my $Sum = 0;
848            INDEX2:
849            for my $Index2 ( 1 .. $#{ $Data[$Index1] } ) {
850
851                next INDEX2 if !$Data[$Index1][$Index2];
852
853                # extract the value
854                my $Value = $Data[$Index1][$Index2];
855
856                # clean the string
857                $Value =~ s{ \A \s+ }{}xms;
858                $Value =~ s{ \s+ \z }{}xms;
859                $Value =~ s{ , }{.}xms;
860
861                # add value to summary
862                if ( $Value =~ m{^-?\d+(\.\d+)?$} ) {
863                    $Sum += $Value;
864                }
865            }
866
867            push @{ $Data[$Index1] }, $Sum;
868        }
869    }
870
871    # add sum x
872    if ( $Param{SumRow} ) {
873
874        my @SumRow = ();
875        $SumRow[0] = 'Sum';
876
877        for my $Index1 ( 2 .. $#Data ) {
878
879            INDEX2:
880            for my $Index2 ( 1 .. $#{ $Data[$Index1] } ) {
881
882                # make sure we have a value to add
883                if ( !defined $Data[$Index1][$Index2] ) {
884                    $Data[$Index1][$Index2] = 0;
885                }
886
887                # extract the value
888                my $Value = $Data[$Index1][$Index2];
889
890                # clean the string
891                $Value =~ s{ \A \s+ }{}xms;
892                $Value =~ s{ \s+ \z }{}xms;
893                $Value =~ s{ , }{.}xms;
894
895                # add value to summary
896                if ( $Value =~ m{^-?\d+(\.\d+)?$} ) {
897                    $SumRow[$Index2] += $Value;
898                }
899            }
900        }
901
902        push @Data, \@SumRow;
903    }
904    return @Data;
905}
906
907=head2 GetStatsObjectAttributes()
908
909Get all attributes from the object in dependence of the use
910
911    my %ObjectAttributes = $StatsObject->GetStatsObjectAttributes(
912        ObjectModule => 'Ticket',
913        Use          => 'UseAsXvalue' || 'UseAsValueSeries' || 'UseAsRestriction',
914    );
915
916=cut
917
918sub GetStatsObjectAttributes {
919    my ( $Self, %Param ) = @_;
920
921    # check needed params
922    for (qw(ObjectModule Use)) {
923        if ( !$Param{$_} ) {
924            $Kernel::OM->Get('Kernel::System::Log')->Log(
925                Priority => 'error',
926                Message  => "Need $_!"
927            );
928            return;
929        }
930    }
931
932    # load module
933    my $ObjectModule = $Param{ObjectModule};
934    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
935    my $StatObject = $ObjectModule->new( %{$Self} );
936    return if !$StatObject;
937    return if !$StatObject->can('GetObjectAttributes');
938
939    # load attributes
940    my @ObjectAttributesRaw = $StatObject->GetObjectAttributes();
941
942    # build the objectattribute array
943    my @ObjectAttributes;
944    for my $HashRef (@ObjectAttributesRaw) {
945        if ( $HashRef->{ $Param{Use} } ) {
946            delete $HashRef->{UseAsXvalue};
947            delete $HashRef->{UseAsValueSeries};
948            delete $HashRef->{UseAsRestriction};
949
950            push @ObjectAttributes, $HashRef;
951        }
952    }
953
954    return @ObjectAttributes;
955}
956
957=head2 GetStaticFiles()
958
959Get all static files
960
961    my $FileHash = $StatsObject->GetStaticFiles(
962        OnlyUnusedFiles => 1 | 0, # optional default 0
963        UserID => $UserID,
964    );
965
966=cut
967
968sub GetStaticFiles {
969    my ( $Self, %Param ) = @_;
970
971    for my $Needed (qw(UserID)) {
972        if ( !$Param{$Needed} ) {
973            $Kernel::OM->Get('Kernel::System::Log')->Log(
974                Priority => "error",
975                Message  => "Need $Needed.",
976            );
977            return;
978        }
979    }
980
981    my $Directory = $Kernel::OM->Get('Kernel::Config')->Get('Home');
982    if ( $Directory !~ m{^.*\/$}x ) {
983        $Directory .= '/';
984    }
985    $Directory .= 'Kernel/System/Stats/Static/';
986
987    if ( !opendir( DIR, $Directory ) ) {
988        $Kernel::OM->Get('Kernel::System::Log')->Log(
989            Priority => 'error',
990            Message  => "Can not open Directory: $Directory",
991        );
992        return ();
993    }
994
995    my %StaticFiles;
996    if ( $Param{OnlyUnusedFiles} ) {
997
998        # get all Stats from the db
999        my $Result = $Self->GetStatsList(
1000            UserID => $Param{UserID},
1001        );
1002
1003        if ( defined $Result ) {
1004            for my $StatID ( @{$Result} ) {
1005                my $Data = $Self->StatsGet(
1006                    StatID             => $StatID,
1007                    UserID             => $Param{UserID},
1008                    NoObjectAttributes => 1,
1009                );
1010
1011                # check witch one are static statistics
1012                if ( $Data->{File} && $Data->{StatType} eq 'static' ) {
1013                    $StaticFiles{ $Data->{File} } = 1;
1014                }
1015            }
1016        }
1017    }
1018
1019    # read files
1020    my %Filelist;
1021
1022    DIRECTORY:
1023    while ( defined( my $Filename = readdir DIR ) ) {
1024        next DIRECTORY if $Filename eq '.';
1025        next DIRECTORY if $Filename eq '..';
1026        if ( $Filename =~ m{^(.*)\.pm$}x ) {
1027            if ( !defined $StaticFiles{$1} ) {
1028                $Filelist{$1} = $1;
1029            }
1030        }
1031    }
1032    closedir(DIR);
1033
1034    return \%Filelist;
1035}
1036
1037=head2 GetDynamicFiles()
1038
1039Get all static objects
1040
1041    my $FileHash = $StatsObject->GetDynamicFiles();
1042
1043=cut
1044
1045sub GetDynamicFiles {
1046    my $Self = shift;
1047
1048    # get config object
1049    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1050
1051    my %Filelist = %{ $ConfigObject->Get('Stats::DynamicObjectRegistration') };
1052    OBJECT:
1053    for my $Object ( sort keys %Filelist ) {
1054        if ( !$Filelist{$Object} ) {
1055            delete $Filelist{$Object};
1056            next OBJECT;
1057        }
1058        $Filelist{$Object} = $Self->GetObjectName(
1059            ObjectModule => $Filelist{$Object}->{Module},
1060        );
1061    }
1062    return if !%Filelist;
1063
1064    return \%Filelist;
1065}
1066
1067=head2 GetObjectName()
1068
1069Get the name of a dynamic object
1070
1071    my $ObjectName = $StatsObject->GetObjectName(
1072        ObjectModule => 'Kernel::System::Stats::Dynamic::TicketList',
1073    );
1074
1075=cut
1076
1077sub GetObjectName {
1078    my ( $Self, %Param ) = @_;
1079    my $Module = $Param{ObjectModule};
1080
1081    # check if it is cached
1082    return $Self->{'Cache::ObjectName'}->{$Module} if $Self->{'Cache::ObjectName'}->{$Module};
1083
1084    # load module, return if module does not exist
1085    # (this is important when stats are uninstalled, see also bug# 4269)
1086    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($Module);
1087
1088    # get name
1089    my $StatObject = $Module->new( %{$Self} );
1090    return if !$StatObject;
1091    return if !$StatObject->can('GetObjectName');
1092
1093    my $Name = $StatObject->GetObjectName();
1094
1095    # cache the result
1096    $Self->{'Cache::ObjectName'}->{$Module} = $Name;
1097
1098    return $Name;
1099}
1100
1101=head2 GetObjectBehaviours()
1102
1103get behaviours that a statistic supports
1104
1105    my $Behaviours = $StatsObject->GetObjectBehaviours(
1106        ObjectModule => 'Kernel::System::Stats::Dynamic::TicketList',
1107    );
1108
1109    returns
1110
1111    {
1112        ProvidesDashboardWidget => 1,
1113        ...
1114    }
1115
1116=cut
1117
1118sub GetObjectBehaviours {
1119    my ( $Self, %Param ) = @_;
1120    my $Module = $Param{ObjectModule};
1121
1122    # check if it is cached
1123    if ( $Self->{'Cache::ObjectBehaviours'}->{$Module} ) {
1124        return $Self->{'Cache::ObjectBehaviours'}->{$Module};
1125    }
1126
1127    # load module, return if module does not exist
1128    # (this is important when stats are uninstalled, see also bug# 4269)
1129    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($Module);
1130
1131    my $StatObject = $Module->new( %{$Self} );
1132    return if !$StatObject;
1133    return if !$StatObject->can('GetObjectBehaviours');
1134
1135    my %ObjectBehaviours = $StatObject->GetObjectBehaviours();
1136
1137    # cache the result
1138    $Self->{'Cache::ObjectBehaviours'}->{$Module} = \%ObjectBehaviours;
1139
1140    return \%ObjectBehaviours;
1141}
1142
1143=head2 ObjectFileCheck()
1144
1145AT THE MOMENT NOT USED
1146
1147check readable object file
1148
1149    my $ObjectFileCheck = $StatsObject->ObjectFileCheck(
1150        Type => 'static',
1151        Name => 'NewTickets',
1152    );
1153
1154=cut
1155
1156sub ObjectFileCheck {
1157    my ( $Self, %Param ) = @_;
1158
1159    my $Directory = $Kernel::OM->Get('Kernel::Config')->Get('Home');
1160    if ( $Directory !~ m{^.*\/$}x ) {
1161        $Directory .= '/';
1162    }
1163    if ( $Param{Type} eq 'static' ) {
1164        $Directory .= 'Kernel/System/Stats/Static/' . $Param{Name} . '.pm';
1165    }
1166    elsif ( $Param{Type} eq 'dynamic' ) {
1167        $Directory .= 'Kernel/System/Stats/Dynamic/' . $Param{Name} . '.pm';
1168    }
1169
1170    return 1 if -r $Directory;
1171    return;
1172}
1173
1174=head2 ObjectModuleCheck()
1175
1176Check the object module.
1177
1178    my $ObjectModuleCheck = $StatsObject->ObjectModuleCheck(
1179        StatType                     => 'static',
1180        ObjectModule                 => 'Kernel::System::Stats::Static::StateAction',
1181        CheckAlreadyUsedStaticObject => 1,                                             # optional
1182    );
1183
1184Returns true on success and false on error.
1185
1186=cut
1187
1188sub ObjectModuleCheck {
1189    my ( $Self, %Param ) = @_;
1190
1191    return if !$Param{StatType} || !$Param{ObjectModule};
1192    return if $Param{StatType} ne 'static' && $Param{StatType} ne 'dynamic';
1193
1194    my $CheckFileLocation = 'Kernel::System::Stats::' . ucfirst $Param{StatType};
1195    my $CheckPackageName  = '[A-Z_a-z][0-9A-Z_a-z]*';
1196    return if $Param{ObjectModule} !~ m{ \A $CheckFileLocation (?: ::$CheckPackageName)+ \z }xms;
1197
1198    my $ObjectName = [ split( m{::}, $Param{ObjectModule} ) ]->[-1];
1199    return if !$ObjectName;
1200
1201    my @RequiredObjectFunctions;
1202
1203    if ( $Param{StatType} eq 'static' ) {
1204
1205        @RequiredObjectFunctions = (
1206            'Param',
1207            'Run',
1208        );
1209
1210        my $StaticFiles = $Self->GetStaticFiles(
1211            OnlyUnusedFiles => $Param{CheckAlreadyUsedStaticObject},
1212            UserID          => 1,
1213        );
1214
1215        if ( $ObjectName && !$StaticFiles->{$ObjectName} ) {
1216            $Kernel::OM->Get('Kernel::System::Log')->Log(
1217                Priority => 'error',
1218                Message  => "Static object $ObjectName doesn't exist or static object already in use!",
1219            );
1220            return;
1221        }
1222    }
1223    else {
1224
1225        @RequiredObjectFunctions = (
1226            'GetObjectName',
1227            'GetObjectAttributes',
1228        );
1229
1230        # Check if the given Object exists in the statistic object registartion.
1231        my $DynamicFiles = $Self->GetDynamicFiles();
1232
1233        if ( !$DynamicFiles->{$ObjectName} ) {
1234            $Kernel::OM->Get('Kernel::System::Log')->Log(
1235                Priority => 'error',
1236                Message  => "Object $ObjectName doesn't exist!"
1237            );
1238            return;
1239        }
1240    }
1241
1242    my $ObjectModule = $Param{ObjectModule};
1243    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
1244
1245    my $StatObject = $ObjectModule->new( %{$Self} );
1246    return if !$StatObject;
1247
1248    # Check for the required object functions.
1249    for my $RequiredObjectFunction (@RequiredObjectFunctions) {
1250        return if !$StatObject->can($RequiredObjectFunction);
1251    }
1252
1253    # Special check for some fucntions in the dynamic statistic object.
1254    if ( $Param{StatType} eq 'dynamic' ) {
1255        return if !$StatObject->can('GetStatTable') && !$StatObject->can('GetStatElement');
1256    }
1257
1258    return 1;
1259}
1260
1261=head2 Export()
1262
1263get content from stats for export
1264
1265    my $ExportFile = $StatsObject->Export(
1266        StatID => '123',
1267        ExportStatNumber => 1 || 0, # optional, only useful move statistics from the test system to the productive system
1268    );
1269
1270=cut
1271
1272sub Export {
1273    my ( $Self, %Param ) = @_;
1274
1275    if ( !$Param{StatID} ) {
1276        $Kernel::OM->Get('Kernel::System::Log')->Log(
1277            Priority => 'error',
1278            Message  => 'Export: Need StatID!'
1279        );
1280        return;
1281    }
1282
1283    # get xml object
1284    my $XMLObject = $Kernel::OM->Get('Kernel::System::XML');
1285
1286    my @XMLHash = $XMLObject->XMLHashGet(
1287        Type => 'Stats',
1288        Key  => $Param{StatID},
1289    );
1290    my $StatsXML = $XMLHash[0]->{otrs_stats}->[1];
1291
1292    my %File;
1293    $File{Filename} = $Self->StringAndTimestamp2Filename(
1294        String => $StatsXML->{Title}->[1]->{Content},
1295    );
1296    $File{Filename} .= '.xml';
1297
1298    # Delete not needed and useful keys from the stats xml.
1299    for my $Key (qw(Changed ChangedBy Created CreatedBy StatID)) {
1300        delete $StatsXML->{$Key};
1301    }
1302    if ( !$Param{ExportStatNumber} ) {
1303        delete $StatsXML->{StatNumber};
1304    }
1305
1306    # wrapper to change ids in used spelling
1307    # wrap permissions
1308    PERMISSION:
1309    for my $ID ( @{ $StatsXML->{Permission} } ) {
1310        next PERMISSION if !$ID;
1311        my $Name = $Kernel::OM->Get('Kernel::System::Group')->GroupLookup( GroupID => $ID->{Content} );
1312        next PERMISSION if !$Name;
1313        $ID->{Content} = $Name;
1314    }
1315
1316    # wrap object dependend ids
1317    if ( $StatsXML->{Object}->[1]->{Content} ) {
1318
1319        # load module
1320        my $ObjectModule = $StatsXML->{ObjectModule}->[1]->{Content};
1321        return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
1322        my $StatObject = $ObjectModule->new( %{$Self} );
1323        return if !$StatObject;
1324
1325        if ( $StatObject->can('ExportWrapper') ) {
1326            $StatsXML = $StatObject->ExportWrapper(
1327                %{$StatsXML},
1328            );
1329        }
1330    }
1331
1332    # convert hash to string
1333    $File{Content} = $XMLObject->XMLHash2XML(
1334        {
1335            otrs_stats => [
1336                undef,
1337                $StatsXML,
1338            ],
1339        },
1340    );
1341
1342    return \%File;
1343}
1344
1345=head2 Import()
1346
1347import a stats from xml file
1348
1349    my $StatID = $StatsObject->Import(
1350        UserID  => $UserID,
1351        Content => $UploadStuff{Content},
1352    );
1353
1354=cut
1355
1356sub Import {
1357    my ( $Self, %Param ) = @_;
1358
1359    for my $Needed (qw(UserID Content)) {
1360        if ( !$Param{$Needed} ) {
1361            $Kernel::OM->Get('Kernel::System::Log')->Log(
1362                Priority => "error",
1363                Message  => "Need $Needed.",
1364            );
1365            return;
1366        }
1367    }
1368
1369    # get xml object
1370    my $XMLObject = $Kernel::OM->Get('Kernel::System::XML');
1371
1372    my @XMLHash = $XMLObject->XMLParse2XMLHash( String => $Param{Content} );
1373
1374    if ( !$XMLHash[0] ) {
1375        shift @XMLHash;
1376    }
1377    my $StatsXML = $XMLHash[0]->{otrs_stats}->[1];
1378
1379    # Get new StatID
1380    my @Keys = $XMLObject->XMLHashSearch(
1381        Type => 'Stats',
1382    );
1383
1384    # check if the required elements are available
1385    for my $Element (qw( Description Format Object ObjectModule Permission StatType SumCol SumRow Title Valid)) {
1386        if ( !defined $StatsXML->{$Element}->[1]->{Content} ) {
1387            $Kernel::OM->Get('Kernel::System::Log')->Log(
1388                Priority => 'error',
1389                Message  => "Can't import Stat, because the required element $Element is not available!"
1390            );
1391            return;
1392        }
1393    }
1394
1395    my $ObjectModuleCheck = $Self->ObjectModuleCheck(
1396        StatType                     => $StatsXML->{StatType}->[1]->{Content},
1397        ObjectModule                 => $StatsXML->{ObjectModule}->[1]->{Content},
1398        CheckAlreadyUsedStaticObject => 1,
1399    );
1400
1401    return if !$ObjectModuleCheck;
1402
1403    my $ObjectModule = $StatsXML->{ObjectModule}->[1]->{Content};
1404    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
1405
1406    my $StatObject = $ObjectModule->new( %{$Self} );
1407    return if !$StatObject;
1408
1409    my $ObjectName = [ split( m{::}, $StatsXML->{ObjectModule}->[1]->{Content} ) ]->[-1];
1410    if ( $StatsXML->{StatType}->[1]->{Content} eq 'static' ) {
1411        $StatsXML->{File}->[1]->{Content} = $ObjectName;
1412    }
1413    else {
1414        $StatsXML->{Object}->[1]->{Content} = $ObjectName;
1415    }
1416
1417    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1418
1419    # if-clause if a stat-xml includes a StatNumber
1420    my $StatID = 1;
1421    if ( $StatsXML->{StatNumber} ) {
1422        my $XMLStatsID = $StatsXML->{StatNumber}->[1]->{Content} - $ConfigObject->Get('Stats::StatsStartNumber');
1423        for my $Key (@Keys) {
1424            if ( $Key eq $XMLStatsID ) {
1425                $Kernel::OM->Get('Kernel::System::Log')->Log(
1426                    Priority => 'error',
1427                    Message  => "Can't import StatNumber $Key, because this StatNumber is already used!"
1428                );
1429                return;
1430            }
1431        }
1432        $StatID = $XMLStatsID;
1433    }
1434
1435    # if no stats number available use this function
1436    else {
1437        my @SortKeys = sort { $a <=> $b } @Keys;
1438        if (@SortKeys) {
1439            $StatID = $SortKeys[-1] + 1;
1440        }
1441    }
1442
1443    # requesting current time stamp
1444    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1445
1446    # meta tags
1447    $StatsXML->{Created}->[1]->{Content}    = $DateTimeObject->ToString();
1448    $StatsXML->{CreatedBy}->[1]->{Content}  = $Param{UserID};
1449    $StatsXML->{Changed}->[1]->{Content}    = $DateTimeObject->ToString();
1450    $StatsXML->{ChangedBy}->[1]->{Content}  = $Param{UserID};
1451    $StatsXML->{StatNumber}->[1]->{Content} = $StatID + $ConfigObject->Get('Stats::StatsStartNumber');
1452
1453    # wrapper to change used spelling in ids
1454    # wrap permissions
1455    my %Groups = $Kernel::OM->Get('Kernel::System::Group')->GroupList( Valid => 1 );
1456
1457    NAME:
1458    for my $Name ( @{ $StatsXML->{Permission} } ) {
1459        next NAME if !$Name;
1460
1461        my $Flag = 1;
1462        ID:
1463        for my $ID ( sort keys %Groups ) {
1464            if ( $Groups{$ID} eq $Name->{Content} ) {
1465                $Name->{Content} = $ID;
1466                $Flag = 0;
1467                last ID;
1468            }
1469        }
1470        if ($Flag) {
1471            $Kernel::OM->Get('Kernel::System::Log')->Log(
1472                Priority => 'error',
1473                Message  => "Can't find the permission (group) $Name->{Content}!"
1474            );
1475            $Name = undef;
1476        }
1477    }
1478
1479    # wrap object dependend ids
1480    if ( $StatObject->can('ImportWrapper') ) {
1481        $StatsXML = $StatObject->ImportWrapper( %{$StatsXML} );
1482    }
1483
1484    return if !$XMLObject->XMLHashAdd(
1485        Type    => 'Stats',
1486        Key     => $StatID,
1487        XMLHash => [
1488            {
1489                otrs_stats => [
1490                    undef,
1491                    $StatsXML,
1492                ],
1493            },
1494        ],
1495    );
1496
1497    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
1498        Type => 'Stats',
1499    );
1500
1501    return $StatID;
1502}
1503
1504=head2 GetParams()
1505
1506    get all edit params from stats for view
1507
1508    my $Params = $StatsObject->GetParams( StatID => '123' );
1509
1510=cut
1511
1512sub GetParams {
1513    my ( $Self, %Param ) = @_;
1514
1515    if ( !$Param{StatID} ) {
1516        $Kernel::OM->Get('Kernel::System::Log')->Log(
1517            Priority => 'error',
1518            Message  => 'Need StatID!'
1519        );
1520        return;
1521    }
1522
1523    my $Stat = $Self->StatsGet( StatID => $Param{StatID} );
1524
1525    # static
1526    # don't remove this if clause, because is required for otrs.GenerateStats.pl
1527    my @Params;
1528    if ( $Stat->{StatType} eq 'static' ) {
1529
1530        # load static modul
1531        my $ObjectModule = $Stat->{ObjectModule};
1532        return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
1533        my $StatObject = $ObjectModule->new( %{$Self} );
1534        return if !$StatObject;
1535        return if !$StatObject->can('Param');
1536
1537        # get params
1538        @Params = $StatObject->Param();
1539    }
1540
1541    return \@Params;
1542}
1543
1544=head2 StatsRun()
1545
1546run a statistic.
1547
1548    my $StatArray = $StatsObject->StatsRun(
1549        StatID     => '123',
1550        GetParam   => \%GetParam,
1551        Preview    => 1,        # optional, return fake data for preview (only for dynamic stats)
1552        UserID     => $UserID,
1553    );
1554
1555=cut
1556
1557sub StatsRun {
1558    my ( $Self, %Param ) = @_;
1559
1560    # check needed params
1561    for my $Needed (qw(StatID GetParam UserID)) {
1562        if ( !$Param{$Needed} ) {
1563            $Kernel::OM->Get('Kernel::System::Log')->Log(
1564                Priority => 'error',
1565                Message  => "Need $Needed!",
1566            );
1567            return;
1568        }
1569    }
1570
1571    my $Stat     = $Self->StatsGet( StatID => $Param{StatID} );
1572    my %GetParam = %{ $Param{GetParam} };
1573    my @Result;
1574
1575    # Perform calculations on the slave DB, if configured.
1576    local $Kernel::System::DB::UseSlaveDB = 1;
1577
1578    # get data if it is a static stats
1579    if ( $Stat->{StatType} eq 'static' ) {
1580
1581        return if $Param{Preview};    # not supported for static stats
1582
1583        @Result = $Self->_GenerateStaticStats(
1584            ObjectModule => $Stat->{ObjectModule},
1585            GetParam     => $Param{GetParam},
1586            Title        => $Stat->{Title},
1587            StatID       => $Stat->{StatID},
1588            Cache        => $Stat->{Cache},
1589            UserID       => $Param{UserID},
1590        );
1591    }
1592
1593    # get data if it is a dynaymic stats
1594    elsif ( $Stat->{StatType} eq 'dynamic' ) {
1595        @Result = $Self->_GenerateDynamicStats(
1596            ObjectModule     => $Stat->{ObjectModule},
1597            Object           => $Stat->{Object},
1598            UseAsXvalue      => $GetParam{UseAsXvalue},
1599            UseAsValueSeries => $GetParam{UseAsValueSeries} || [],
1600            UseAsRestriction => $GetParam{UseAsRestriction} || [],
1601            Title            => $Stat->{Title},
1602            StatID           => $Stat->{StatID},
1603            TimeZone         => $GetParam{TimeZone},
1604            Cache            => $Stat->{Cache},
1605            Preview          => $Param{Preview},
1606            UserID           => $Param{UserID},
1607        );
1608    }
1609
1610    # Build sum in row or col.
1611    if ( @Result && ( $Stat->{SumRow} || $Stat->{SumCol} ) && $Stat->{Format} !~ m{^GD::Graph\.*}x ) {
1612        @Result = $Self->SumBuild(
1613            Array  => \@Result,
1614            SumRow => $Stat->{SumRow},
1615            SumCol => $Stat->{SumCol},
1616        );
1617    }
1618
1619    # Exchange axis if selected.
1620    if ( $GetParam{ExchangeAxis} ) {
1621        my @NewStatArray;
1622        my $Title = $Result[0]->[0];
1623
1624        shift(@Result);
1625        for my $Key1 ( 0 .. $#Result ) {
1626            for my $Key2 ( 0 .. $#{ $Result[0] } ) {
1627                $NewStatArray[$Key2]->[$Key1] = $Result[$Key1]->[$Key2];
1628            }
1629        }
1630        $NewStatArray[0]->[0] = '';
1631        unshift( @NewStatArray, [$Title] );
1632        @Result = @NewStatArray;
1633    }
1634
1635    # Translate the column and row description.
1636    $Self->_ColumnAndRowTranslation(
1637        StatArrayRef => \@Result,
1638        StatRef      => $Stat,
1639        ExchangeAxis => $GetParam{ExchangeAxis},
1640    );
1641
1642    return \@Result;
1643}
1644
1645=head2 StatsResultCacheCompute()
1646
1647computes stats results and adds them to the cache.
1648This can be used to precompute stats data e. g. for dashboard widgets in a cron job.
1649
1650    my $StatArray = $StatsObject->StatsResultCacheCompute(
1651        StatID       => '123',
1652        UserID       => $UserID,        # target UserID
1653        UserGetParam => \%UserGetParam, # user settings of non-fixed fields
1654    );
1655
1656=cut
1657
1658sub StatsResultCacheCompute {
1659    my ( $Self, %Param ) = @_;
1660
1661    for my $Needed (qw(StatID UserGetParam UserID)) {
1662        if ( !$Param{$Needed} ) {
1663            $Kernel::OM->Get('Kernel::System::Log')->Log(
1664                Priority => 'error',
1665                Message  => "Need $Needed!",
1666            );
1667            return;
1668        }
1669    }
1670
1671    my $Stat = $Self->StatsGet(
1672        StatID => $Param{StatID},
1673    );
1674
1675    my $StatsViewObject = $Kernel::OM->Get('Kernel::Output::HTML::Statistics::View');
1676
1677    my $StatConfigurationValid = $StatsViewObject->StatsConfigurationValidate(
1678        Stat   => $Stat,
1679        Errors => {},
1680        UserID => $Param{UserID},
1681    );
1682    if ( !$StatConfigurationValid ) {
1683        $Kernel::OM->Get('Kernel::System::Log')->Log(
1684            Priority => 'error',
1685            Message  => "This statistic contains configuration errors, skipping.",
1686        );
1687        return;
1688    }
1689
1690    my %GetParam = eval {
1691        $StatsViewObject->StatsParamsGet(
1692            Stat         => $Stat,
1693            UserGetParam => $Param{UserGetParam},
1694        );
1695    };
1696
1697    if ( $@ || !%GetParam ) {
1698        my $Errors = ref $@ ? join( "\n", @{$@} ) : $@;
1699        $Kernel::OM->Get('Kernel::System::Log')->Log(
1700            Priority => 'error',
1701            Message  => "The dashboard widget configuration for this user contains errors, skipping: $Errors"
1702        );
1703        return;
1704    }
1705
1706    # get main object
1707    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1708
1709    my $DumpString = $MainObject->Dump( \%GetParam );
1710
1711    my $MD5Sum = $MainObject->MD5sum(
1712        String => \$DumpString,
1713    );
1714
1715    my $CacheKey = "StatsRunCached::$Param{UserID}::$Param{StatID}::$MD5Sum";
1716
1717    my $Result = $Self->StatsRun(
1718        StatID   => $Param{StatID},
1719        GetParam => \%GetParam,
1720        UserID   => $Param{UserID},
1721    );
1722
1723    # Only set/update the cache after computing it, otherwise no cache data
1724    #   would be available in between.
1725    return $Kernel::OM->Get('Kernel::System::Cache')->Set(
1726        Type  => 'StatsRun',
1727        Key   => $CacheKey,
1728        Value => $Result,
1729        TTL   => 24 * 60 * 60,    # cache it for a day, will be overwritten by next function call
1730
1731        # Don't store complex structure in memory as it will be modified later.
1732        CacheInMemory => 0,
1733    );
1734}
1735
1736=head2 StatsResultCacheGet()
1737
1738gets cached statistic results. Will never run the statistic.
1739This can be used to fetch cached stats data e. g. for stats widgets in the dashboard.
1740
1741    my $StatArray = $StatsObject->StatsResultCacheGet(
1742        StatID       => '123',
1743        UserID       => $UserID,    # target UserID
1744        UserGetParam => \%GetParam,
1745    );
1746
1747=cut
1748
1749sub StatsResultCacheGet {
1750    my ( $Self, %Param ) = @_;
1751
1752    for my $Needed (qw(StatID UserGetParam UserID)) {
1753        if ( !$Param{$Needed} ) {
1754            $Kernel::OM->Get('Kernel::System::Log')->Log(
1755                Priority => 'error',
1756                Message  => "Need $Needed!",
1757            );
1758            return;
1759        }
1760    }
1761
1762    my $Stat = $Self->StatsGet(
1763        StatID => $Param{StatID},
1764    );
1765
1766    my $StatsViewObject = $Kernel::OM->Get('Kernel::Output::HTML::Statistics::View');
1767
1768    my $StatConfigurationValid = $StatsViewObject->StatsConfigurationValidate(
1769        Stat   => $Stat,
1770        Errors => {},
1771        UserID => $Param{UserID},
1772    );
1773    if ( !$StatConfigurationValid ) {
1774        $Kernel::OM->Get('Kernel::System::Log')->Log(
1775            Priority => 'error',
1776            Message  => "This statistic contains configuration errors, skipping.",
1777        );
1778        return;
1779    }
1780
1781    my %GetParam = eval {
1782        $StatsViewObject->StatsParamsGet(
1783            Stat         => $Stat,
1784            UserGetParam => $Param{UserGetParam},
1785            UserID       => $Param{UserID},
1786        );
1787    };
1788
1789    if ( $@ || !%GetParam ) {
1790        my $Errors = ref $@ ? join( "\n", @{$@} ) : $@;
1791        $Kernel::OM->Get('Kernel::System::Log')->Log(
1792            Priority => 'error',
1793            Message  => "The dashboard widget configuration for this user contains errors, skipping: $Errors"
1794        );
1795        return;
1796    }
1797
1798    # get main object
1799    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1800
1801    my $DumpString = $MainObject->Dump( \%GetParam );
1802
1803    my $MD5Sum = $MainObject->MD5sum(
1804        String => \$DumpString,
1805    );
1806
1807    my $CacheKey = "StatsRunCached::$Param{UserID}::$Param{StatID}::$MD5Sum";
1808
1809    return $Kernel::OM->Get('Kernel::System::Cache')->Get(
1810        Type => 'StatsRun',
1811        Key  => $CacheKey,
1812
1813        # Don't store complex structure in memory as it will be modified later.
1814        CacheInMemory => 0,
1815    );
1816}
1817
1818=head2 StringAndTimestamp2Filename()
1819
1820builds a filename with a string and a timestamp.
1821(space will be replaced with _ and - e.g. Title-of-File_2006-12-31_11-59)
1822
1823    my $Filename = $StatsObject->StringAndTimestamp2Filename(
1824        String   => 'Title',
1825        TimeZone => 'Europe/Berlin',  # optional
1826    );
1827
1828=cut
1829
1830sub StringAndTimestamp2Filename {
1831    my ( $Self, %Param ) = @_;
1832
1833    if ( !$Param{String} ) {
1834        $Kernel::OM->Get('Kernel::System::Log')->Log(
1835            Priority => 'error',
1836            Message  => 'Need String!'
1837        );
1838        return;
1839    }
1840
1841    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1842    if ( defined $Param{TimeZone} ) {
1843        $DateTimeObject->ToTimeZone( TimeZone => $Param{TimeZone} );
1844    }
1845
1846    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1847    $Param{String} = $MainObject->FilenameCleanUp(
1848        Filename => $Param{String},
1849        Type     => 'Attachment',
1850    );
1851
1852    my $Filename = $Param{String} . '_';
1853    $Filename .= $DateTimeObject->Format( Format => '%Y-%m-%d_%H:%M' );
1854
1855    if ( defined $Param{TimeZone} ) {
1856        my $TimeZone = $MainObject->FilenameCleanUp(
1857            Filename => $Param{TimeZone},
1858            Type     => 'Attachment',
1859        );
1860        $Filename .= '_TimeZone_' . $TimeZone;
1861    }
1862
1863    return $Filename;
1864}
1865
1866=head2 StatNumber2StatID()
1867
1868insert the stat number get the stat id
1869
1870    my $StatID = $StatsObject->StatNumber2StatID(
1871        StatNumber => 11212,
1872    );
1873
1874=cut
1875
1876sub StatNumber2StatID {
1877    my ( $Self, %Param ) = @_;
1878
1879    if ( !$Param{StatNumber} ) {
1880        $Kernel::OM->Get('Kernel::System::Log')->Log(
1881            Priority => 'error',
1882            Message  => 'Need StatNumber!',
1883        );
1884        return;
1885    }
1886
1887    my @Key = $Kernel::OM->Get('Kernel::System::XML')->XMLHashSearch(
1888        Type => 'Stats',
1889        What => [ { "[%]{'otrs_stats'}[%]{'StatNumber'}[%]{'Content'}" => $Param{StatNumber} } ],
1890    );
1891    if ( @Key && $#Key < 1 ) {
1892        return $Key[0];
1893    }
1894
1895    $Kernel::OM->Get('Kernel::System::Log')->Log(
1896        Priority => 'error',
1897        Message  => 'StatNumber invalid!',
1898    );
1899    return;
1900}
1901
1902=head2 StatsInstall()
1903
1904installs stats
1905
1906    my $Result = $StatsObject->StatsInstall(
1907        FilePrefix => 'FAQ',  # (optional)
1908        UserID     => $UserID,
1909    );
1910
1911=cut
1912
1913sub StatsInstall {
1914    my ( $Self, %Param ) = @_;
1915
1916    for my $Needed (qw(UserID)) {
1917        if ( !$Param{$Needed} ) {
1918            $Kernel::OM->Get('Kernel::System::Log')->Log(
1919                Priority => 'error',
1920                Message  => "Need $Needed!",
1921            );
1922            return;
1923        }
1924    }
1925
1926    # prepare prefix
1927    $Param{FilePrefix} = $Param{FilePrefix} ? $Param{FilePrefix} . '-' : '';
1928
1929    # start AutomaticSampleImport if no stats are installed
1930    $Self->GetStatsList(
1931        UserID => $Param{UserID},
1932    );
1933
1934    # get main object
1935    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1936
1937    # get list of stats files
1938    my @StatsFileList = $MainObject->DirectoryRead(
1939        Directory => $Self->{StatsTempDir},
1940        Filter    => $Param{FilePrefix} . '*.xml',
1941    );
1942
1943    # import the stats
1944    my $InstalledPostfix = '.installed';
1945    FILE:
1946    for my $File ( sort @StatsFileList ) {
1947
1948        next FILE if !-f $File;
1949        next FILE if -e $File . $InstalledPostfix;
1950
1951        # read file content
1952        my $XMLContentRef = $MainObject->FileRead(
1953            Location => $File,
1954        );
1955
1956        # import stat
1957        my $StatID = $Self->Import(
1958            Content => ${$XMLContentRef},
1959            UserID  => $Param{UserID},
1960        );
1961
1962        next FILE if !$StatID;
1963
1964        # write installed file with stat id
1965        $MainObject->FileWrite(
1966            Content  => \$StatID,
1967            Location => $File . $InstalledPostfix,
1968        );
1969    }
1970
1971    return 1;
1972}
1973
1974=head2 StatsUninstall()
1975
1976uninstalls stats
1977
1978    my $Result = $StatsObject->StatsUninstall(
1979        FilePrefix => 'FAQ',  # (optional)
1980        UserID     => $UserID,
1981    );
1982
1983=cut
1984
1985sub StatsUninstall {
1986    my ( $Self, %Param ) = @_;
1987
1988    for my $Needed (qw(UserID)) {
1989        if ( !$Param{$Needed} ) {
1990            $Kernel::OM->Get('Kernel::System::Log')->Log(
1991                Priority => 'error',
1992                Message  => "Need $Needed!",
1993            );
1994            return;
1995        }
1996    }
1997
1998    # prepare prefix
1999    $Param{FilePrefix} = $Param{FilePrefix} ? $Param{FilePrefix} . '-' : '';
2000
2001    # get main object
2002    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
2003
2004    # get list of installed stats files
2005    my @StatsFileList = $MainObject->DirectoryRead(
2006        Directory => $Self->{StatsTempDir},
2007        Filter    => $Param{FilePrefix} . '*.xml.installed',
2008    );
2009
2010    my @UninstalledObjectNames;
2011
2012    # delete the stats
2013    for my $File ( sort @StatsFileList ) {
2014
2015        # read file content
2016        my $StatsIDRef = $MainObject->FileRead(
2017            Location => $File,
2018        );
2019
2020        my $Stat = $Self->StatsGet(
2021            StatID             => ${$StatsIDRef},
2022            NoObjectAttributes => 1,
2023        );
2024
2025        # Add object name from the deleted statistic to the uninstalled object names.
2026        if ( $Stat->{ObjectModule} ) {
2027            my $ObjectName = [ split( m{::}, $Stat->{ObjectModule} ) ]->[-1];
2028            push @UninstalledObjectNames, $ObjectName;
2029        }
2030
2031        # delete stats
2032        $Self->StatsDelete(
2033            StatID => ${$StatsIDRef},
2034            UserID => $Param{UserID},
2035        );
2036    }
2037
2038    # Cleanup for all uninstalled object names.
2039    if (@UninstalledObjectNames) {
2040        $Self->StatsCleanUp(
2041            ObjectNames => \@UninstalledObjectNames,
2042            UserID      => $Param{UserID},
2043        );
2044    }
2045
2046    return 1;
2047}
2048
2049=head2 StatsCleanUp()
2050
2051removed stats with not existing backend file
2052
2053    my $Result = $StatsObject->StatsCleanUp(
2054        UserID => 1,
2055
2056        ObjectNames => [ 'Ticket', 'TicketList' ],
2057        or
2058        CheckAllObjects => 1,
2059    );
2060
2061=cut
2062
2063sub StatsCleanUp {
2064    my ( $Self, %Param ) = @_;
2065
2066    for my $Needed (qw(UserID)) {
2067        if ( !$Param{$Needed} ) {
2068            $Kernel::OM->Get('Kernel::System::Log')->Log(
2069                Priority => 'error',
2070                Message  => "Need $Needed!",
2071            );
2072            return;
2073        }
2074    }
2075
2076    if ( !$Param{CheckAllObjects} && !IsArrayRefWithData( $Param{ObjectNames} ) ) {
2077        $Kernel::OM->Get('Kernel::System::Log')->Log(
2078            Priority => 'error',
2079            Message  => "Need ObjectNames or the CheckAllObjects parameter!",
2080        );
2081        return;
2082    }
2083
2084    # get a list of all stats
2085    my $ListRef = $Self->GetStatsList(
2086        UserID => $Param{UserID},
2087    );
2088
2089    return if !$ListRef;
2090    return if ref $ListRef ne 'ARRAY';
2091
2092    # get main object
2093    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
2094
2095    my %LookupObjectNames;
2096    if ( !$Param{CheckAllObjects} ) {
2097        %LookupObjectNames = map { $_ => 1 } @{ $Param{ObjectNames} };
2098    }
2099
2100    STATSID:
2101    for my $StatsID ( @{$ListRef} ) {
2102
2103        # get stats
2104        my $HashRef = $Self->StatsGet(
2105            StatID             => $StatsID,
2106            NoObjectAttributes => 1,
2107        );
2108
2109        # Cleanup only files given in ObjectNames.
2110        if ( !$Param{CheckAllObjects} ) {
2111
2112            my $ObjectName = [ split( m{::}, $HashRef->{ObjectModule} ) ]->[-1];
2113
2114            next STATSID if !$LookupObjectNames{$ObjectName};
2115        }
2116
2117        if (
2118            IsHashRefWithData($HashRef)
2119            && $HashRef->{ObjectModule}
2120            && $MainObject->Require( $HashRef->{ObjectModule} )
2121            )
2122        {
2123            next STATSID;
2124        }
2125
2126        # delete stats
2127        $Self->StatsDelete(
2128            StatID => $StatsID,
2129            UserID => $Param{UserID},
2130        );
2131    }
2132
2133    return 1;
2134}
2135
2136=begin Internal:
2137
2138=head2 _GenerateStaticStats()
2139
2140    take the stat configuration and get the stat table
2141
2142    my @StatArray = $StatsObject->_GenerateStaticStats(
2143        ObjectModule => $Stat->{ObjectModule},
2144        GetParam     => $Param{GetParam},
2145        Title        => $Stat->{Title},
2146        StatID       => $Stat->{StatID},
2147        Cache        => $Stat->{Cache},
2148        UserID       => $UserID,
2149    );
2150
2151=cut
2152
2153sub _GenerateStaticStats {
2154    my ( $Self, %Param ) = @_;
2155
2156    # check needed params
2157    NEED:
2158    for my $Need (qw(ObjectModule GetParam Title StatID UserID)) {
2159        next NEED if $Param{$Need};
2160        $Kernel::OM->Get('Kernel::System::Log')->Log(
2161            Priority => 'error',
2162            Message  => "Need $Need!"
2163        );
2164        return;
2165    }
2166
2167    # load static module
2168    my $ObjectModule = $Param{ObjectModule};
2169    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
2170    my $StatObject = $ObjectModule->new( %{$Self} );
2171    return if !$StatObject;
2172    return if !$StatObject->can('Run');
2173
2174    my @Result;
2175    my %GetParam = %{ $Param{GetParam} };
2176
2177    # use result cache if configured
2178    if ( $Param{Cache} ) {
2179        my $Filename = $Self->_CreateStaticResultCacheFilename(
2180            GetParam => \%GetParam,
2181            StatID   => $Param{StatID},
2182        );
2183
2184        @Result = $Self->_GetResultCache( Filename => $Filename );
2185        if (@Result) {
2186            return @Result;
2187        }
2188    }
2189
2190    # get user object
2191    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
2192
2193    my %User = $UserObject->GetUserData(
2194        UserID => $Param{UserID},
2195    );
2196
2197    # run stats function
2198    @Result = $StatObject->Run(
2199        %GetParam,
2200
2201        # these two lines are requirements of me, perhaps this
2202        # information is needed for former static stats
2203        Format => $Param{Format}->[0],
2204        Module => $Param{ObjectModule},
2205    );
2206
2207    $Result[0]->[0] = $Param{Title} . ' ' . $Result[0]->[0];
2208
2209    # write cache if configured
2210    if ( $Param{Cache} ) {
2211        $Self->_WriteResultCache(
2212            GetParam => \%GetParam,
2213            StatID   => $Param{StatID},
2214            Data     => \@Result,
2215        );
2216    }
2217
2218    return @Result;
2219}
2220
2221=head2 _GenerateDynamicStats()
2222
2223    take the stat configuration and get the stat table
2224
2225    my @StatArray = $StatsObject->_GenerateDynamicStats(
2226        ObjectModule     => 'Kernel::System::Stats::Dynamic::Ticket',
2227        Object           => 'Ticket',
2228        UseAsXvalue      => \UseAsXvalueElements,
2229        UseAsValueSeries => \UseAsValueSeriesElements,
2230        UseAsRestriction => \UseAsRestrictionElements,
2231        Title            => 'TicketStat',
2232        StatID           => 123,
2233        TimeZone         => 'Europe/Berlin',   # optional,
2234        Cache            => 1,      # optional,
2235        Preview          => 1,      # optional, generate fake data
2236        UserID           => $UserID,
2237    );
2238
2239=cut
2240
2241# search for a better way to cache stats (see lines before StatID and Cache)
2242
2243sub _GenerateDynamicStats {
2244    my ( $Self, %Param ) = @_;
2245
2246    my @HeaderLine;
2247    my $TitleTimeStart = '';
2248    my $TitleTimeStop  = '';
2249
2250    my $Preview = $Param{Preview};
2251    my $UserID  = $Param{UserID};
2252
2253    NEED:
2254    for my $Need (qw(ObjectModule UseAsXvalue UseAsValueSeries Title Object StatID UserID)) {
2255        next NEED if $Param{$Need};
2256        $Kernel::OM->Get('Kernel::System::Log')->Log(
2257            Priority => 'error',
2258            Message  => "Need $Need!"
2259        );
2260        return;
2261    }
2262
2263    # include the needed dynamic object
2264    my $ObjectModule = $Param{ObjectModule};
2265    return if !$Kernel::OM->Get('Kernel::System::Main')->Require($ObjectModule);
2266    my $StatObject = $ObjectModule->new( %{$Self} );
2267    return if !$StatObject;
2268    return if !$StatObject->can('GetStatTable') && !$StatObject->can('GetStatElement');
2269
2270    # get time object
2271    # my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
2272
2273    # get the selected values
2274    # perhaps i can split the StatGet function to make this needless
2275    # Problem, i need the block information
2276    my %NewParam;
2277
2278    $NewParam{Title}        = $Param{Title};
2279    $NewParam{Object}       = $Param{Object};
2280    $NewParam{ObjectModule} = $Param{ObjectModule};
2281
2282    if ( $Param{TimeZone} ) {
2283        $NewParam{TimeZone} = $Param{TimeZone};
2284    }
2285
2286    # search for a better way to cache stats (StatID and Cache)
2287    $NewParam{StatID} = $Param{StatID};
2288    $NewParam{Cache}  = $Param{Cache};
2289    for my $Use (qw(UseAsRestriction UseAsXvalue UseAsValueSeries)) {
2290        my @Array = @{ $Param{$Use} };
2291        ELEMENT:
2292        for my $Element (@Array) {
2293            next ELEMENT if !$Element->{Selected};
2294
2295            # Clone the element as we are going to modify it - avoid modifying the original data
2296            $Element = ${ $Kernel::OM->Get('Kernel::System::Storable')->Clone( Data => \$Element ) };
2297
2298            delete $Element->{Selected};
2299            delete $Element->{Fixed};
2300            if ( $Element->{Block} eq 'Time' ) {
2301                delete $Element->{TimePeriodFormat};
2302                if ( $Element->{TimeRelativeUnit} ) {
2303
2304                    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
2305
2306                    # add the selected timezone to the current timestamp
2307                    # to get the real start timestamp for the selected timezone
2308                    if ( $Param{TimeZone} ) {
2309                        $DateTimeObject->ToTimeZone(
2310                            TimeZone => $Param{TimeZone},
2311                        );
2312                    }
2313
2314                    my $DateTimeValues = $DateTimeObject->Get();
2315                    my ( $s, $m, $h, $D, $M, $Y ) = (
2316                        $DateTimeValues->{Second},
2317                        $DateTimeValues->{Minute},
2318                        $DateTimeValues->{Hour},
2319                        $DateTimeValues->{Day},
2320                        $DateTimeValues->{Month},
2321                        $DateTimeValues->{Year},
2322                    );
2323
2324                    # -1 because the current time will be included
2325                    my $CountUpcoming = $Element->{TimeRelativeUpcomingCount} - 1;
2326
2327                    # add the upcoming count to the count past for the calculation
2328                    my $CountPast = $Element->{TimeRelativeCount} + $CountUpcoming;
2329
2330                    if ( $Element->{TimeRelativeUnit} eq 'Year' ) {
2331                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, $CountUpcoming, 0, 0 );
2332                        $Element->{TimeStop} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, 12, 31, 23, 59, 59 );
2333                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, -$CountPast, 0, 0 );
2334                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, 1, 1, 0, 0, 0 );
2335                    }
2336                    elsif ( $Element->{TimeRelativeUnit} eq 'HalfYear' ) {
2337
2338                        # $CountUpcoming was reduced by 1 before, this has to be reverted for half-year
2339                        $CountUpcoming++;
2340
2341                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD(
2342                            $Y, $M, $D, 0, ( $M > 6 ? 1 : 0 ) * 6 - $M + ( 6 * $CountUpcoming ),
2343                            0
2344                        );
2345                        $Element->{TimeStop} = sprintf(
2346                            "%04d-%02d-%02d %02d:%02d:%02d",
2347                            $Y, $M, $Self->_DaysInMonth( $Y, $M ),
2348                            23, 59, 59
2349                        );
2350
2351                        # $CountPast was reduced by 1 before, this has to be reverted for half-year
2352                        #     Examples:
2353                        #     Half-year set to 1, $CountPast will be 0 then - 0 * 6 = 0 (means this half-year)
2354                        #     Half-year set to 2, $CountPast will be 1 then - 1 * 6 = 6 (means last half-year)
2355                        #     With the fix, example 1 will mean last half-year and example 2 will mean
2356                        #     last two half-years
2357                        $CountPast++;
2358                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, -$CountPast * 6 + 1, 0 );
2359                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, 1, 0, 0, 0 );
2360                    }
2361                    elsif ( $Element->{TimeRelativeUnit} eq 'Quarter' ) {
2362
2363                        # $CountUpcoming was reduced by 1 before, this has to be reverted for quarter
2364                        $CountUpcoming++;
2365
2366                        my $LastQuarter = ceil( $M / 3 ) - 1;
2367                        ( $Y, $M, $D )
2368                            = $Self->_AddDeltaYMD( $Y, $M, $D, 0, $LastQuarter * 3 - $M + ( 3 * $CountUpcoming ), 0 );
2369                        $Element->{TimeStop} = sprintf(
2370                            "%04d-%02d-%02d %02d:%02d:%02d",
2371                            $Y, $M, $Self->_DaysInMonth( $Y, $M ),
2372                            23, 59, 59
2373                        );
2374
2375                        # $CountPast was reduced by 1 before, this has to be reverted for quarter
2376                        #     Examples:
2377                        #     Quarter set to 1, $CountPast will be 0 then - 0 * 3 = 0 (means this quarter)
2378                        #     Quarter set to 2, $CountPast will be 1 then - 1 * 3 = 3 (means last quarter)
2379                        #     With the fix, example 1 will mean last quarter and example 2 will mean
2380                        #     last two quarters
2381                        $CountPast++;
2382                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, -$CountPast * 3 + 1, 0 );
2383                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, 1, 0, 0, 0 );
2384                    }
2385                    elsif ( $Element->{TimeRelativeUnit} eq 'Month' ) {
2386                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, $CountUpcoming, 0 );
2387                        $Element->{TimeStop} = sprintf(
2388                            "%04d-%02d-%02d %02d:%02d:%02d",
2389                            $Y, $M, $Self->_DaysInMonth( $Y, $M ),
2390                            23, 59, 59
2391                        );
2392                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, -$CountPast, 0 );
2393                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, 1, 0, 0, 0 );
2394                    }
2395                    elsif ( $Element->{TimeRelativeUnit} eq 'Week' ) {
2396
2397                        # $CountUpcoming was reduced by 1 before, this has to be reverted for week
2398                        $CountUpcoming++;
2399
2400                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, 0, ( $CountUpcoming * 7 ) - 1 );
2401                        $Element->{TimeStop} = sprintf(
2402                            "%04d-%02d-%02d %02d:%02d:%02d",
2403                            $Y, $M, $D, 23, 59, 59
2404                        );
2405
2406                        # $CountPast was reduced by 1 before, this has to be reverted for Week
2407                        #     Examples:
2408                        #     Week set to 1, $CountPast will be 0 then - 0 * 7 = 0 (means today)
2409                        #     Week set to 2, $CountPast will be 1 then - 1 * 7 = 7 (means last week)
2410                        #     With the fix, example 1 will mean last week and example 2 will mean
2411                        #     last two weeks
2412                        $CountPast++;
2413                        ( $Y, $M, $D ) = $Self->_AddDeltaDays( $Y, $M, $D, -$CountPast * 7 + 1 );
2414                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, 0, 0, 0 );
2415                    }
2416                    elsif ( $Element->{TimeRelativeUnit} eq 'Day' ) {
2417                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, 0, $CountUpcoming );
2418                        $Element->{TimeStop} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, 23, 59, 59 );
2419                        ( $Y, $M, $D ) = $Self->_AddDeltaYMD( $Y, $M, $D, 0, 0, -$CountPast );
2420                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, 0, 0, 0 );
2421                    }
2422                    elsif ( $Element->{TimeRelativeUnit} eq 'Hour' ) {
2423                        ( $Y, $M, $D, $h, $m, $s )
2424                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, $CountUpcoming, 0, 0 );
2425                        $Element->{TimeStop} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, 59, 59 );
2426                        ( $Y, $M, $D, $h, $m, $s )
2427                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, -$CountPast, 0, 0 );
2428                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, 0, 0 );
2429                    }
2430                    elsif ( $Element->{TimeRelativeUnit} eq 'Minute' ) {
2431                        ( $Y, $M, $D, $h, $m, $s )
2432                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, 0, $CountUpcoming, 0 );
2433                        $Element->{TimeStop} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, $m, 59 );
2434                        ( $Y, $M, $D, $h, $m, $s )
2435                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, 0, -$CountPast, 0 );
2436                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, $m, 0 );
2437                    }
2438                    elsif ( $Element->{TimeRelativeUnit} eq 'Second' ) {
2439                        ( $Y, $M, $D, $h, $m, $s )
2440                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, 0, 0, $CountUpcoming );
2441                        $Element->{TimeStop} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, $m, $s );
2442                        ( $Y, $M, $D, $h, $m, $s )
2443                            = $Self->_AddDeltaDHMS( $Y, $M, $D, $h, $m, $s, 0, 0, 0, -$CountPast );
2444                        $Element->{TimeStart} = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $Y, $M, $D, $h, $m, $s );
2445                    }
2446                    delete $Element->{TimeRelativeUnit};
2447                    delete $Element->{TimeRelativeCount};
2448                    delete $Element->{TimeRelativeUpcomingCount};
2449                }
2450
2451                $TitleTimeStart = $Element->{TimeStart};
2452                $TitleTimeStop  = $Element->{TimeStop};
2453            }
2454
2455            # Select All function needed from otrs.GenerateStats.pl and fixed values of the frontend
2456            elsif ( !$Element->{SelectedValues}[0] ) {
2457                my @Values = keys( %{ $Element->{Values} } );
2458                $Element->{SelectedValues} = \@Values;
2459            }
2460
2461            push @{ $NewParam{$Use} }, $Element;
2462        }
2463    }
2464
2465    %Param = %NewParam;
2466
2467    # get all restrictions for the search
2468    my %RestrictionAttribute;
2469    for my $RestrictionPart ( @{ $Param{UseAsRestriction} } ) {
2470        my $Element = $RestrictionPart->{Element};
2471        if ( $RestrictionPart->{Block} eq 'InputField' ) {
2472            $RestrictionAttribute{$Element} = $RestrictionPart->{SelectedValues}[0];
2473        }
2474        elsif ( $RestrictionPart->{Block} eq 'SelectField' ) {
2475            $RestrictionAttribute{$Element} = $RestrictionPart->{SelectedValues}[0];
2476        }
2477        elsif ( $RestrictionPart->{Block} eq 'Time' ) {
2478
2479            # convert start and stop time to OTRS time zone
2480            $RestrictionAttribute{ $RestrictionPart->{Values}{TimeStart} } = $Self->_ToOTRSTimeZone(
2481                String   => $RestrictionPart->{TimeStart},
2482                TimeZone => $Param{TimeZone},
2483            );
2484
2485            $RestrictionAttribute{ $RestrictionPart->{Values}{TimeStop} } = $Self->_ToOTRSTimeZone(
2486                String   => $RestrictionPart->{TimeStop},
2487                TimeZone => $Param{TimeZone},
2488            );
2489        }
2490        else {
2491            $RestrictionAttribute{$Element} = $RestrictionPart->{SelectedValues};
2492        }
2493    }
2494
2495    # get needed objects
2496    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
2497    my $UserObject     = $Kernel::OM->Get('Kernel::System::User');
2498
2499    my %User = $UserObject->GetUserData(
2500        UserID => $UserID,
2501    );
2502
2503    # get the selected Xvalue
2504    my $Xvalue = {};
2505    my (
2506        $VSYear,     $VSMonth,     $VSDay,     $VSHour,     $VSMinute,     $VSSecond,
2507        $VSStopYear, $VSStopMonth, $VSStopDay, $VSStopHour, $VSStopMinute, $VSStopSecond
2508    );
2509    my $TimeAbsolutStopUnixTime = 0;
2510    my $Count                   = 0;
2511    my $MonthArrayRef           = _MonthArray();
2512
2513    my $Element = $Param{UseAsXvalue}[0];
2514    if ( $Element->{Block} eq 'Time' ) {
2515        my (
2516            $Year,   $Month,   $Day,   $Hour,   $Minute,   $Second,
2517            $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond
2518        );
2519        if ( $Element->{TimeStart} =~ m{^(\d\d\d\d)-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)$}ix ) {
2520            $Year   = $VSYear   = $1;
2521            $Month  = $VSMonth  = int $2;
2522            $Day    = $VSDay    = int $3;
2523            $Hour   = $VSHour   = int $4;
2524            $Minute = $VSMinute = int $5;
2525            $Second = $VSSecond = int $6;
2526        }
2527
2528        my $DateTimeObject = $Kernel::OM->Create(
2529            'Kernel::System::DateTime',
2530            ObjectParams => {
2531                String => $Element->{TimeStop},
2532            }
2533        );
2534        $TimeAbsolutStopUnixTime = $DateTimeObject->ToEpoch();
2535
2536        my $TimeStart = 0;
2537        my $TimeStop  = 0;
2538
2539        $Count = $Element->{TimeScaleCount} ? $Element->{TimeScaleCount} : 1;
2540
2541        # in these constellation $Count > 1 is not useful!!
2542        if (
2543            $Param{UseAsValueSeries}
2544            && $Param{UseAsValueSeries}[0]{Block}
2545            && $Param{UseAsValueSeries}[0]{Block} eq 'Time'
2546            && $Element->{SelectedValues}[0] eq 'Day'
2547            )
2548        {
2549            $Count = 1;
2550        }
2551
2552        if ( $Element->{SelectedValues}[0] eq 'Minute' ) {
2553            $Second = 0;
2554        }
2555        elsif ( $Element->{SelectedValues}[0] eq 'Hour' ) {
2556            $Second = 0;
2557            $Minute = 0;
2558        }
2559        elsif ( $Element->{SelectedValues}[0] eq 'Day' ) {
2560            $Second = 0;
2561            $Minute = 0;
2562            $Hour   = 0;
2563        }
2564        elsif ( $Element->{SelectedValues}[0] eq 'Week' ) {
2565            $Second = 0;
2566            $Minute = 0;
2567            $Hour   = 0;
2568            ( $Year, $Month, $Day ) = $Self->_MondayOfWeek( $Year, $Month, $Day );
2569        }
2570        elsif ( $Element->{SelectedValues}[0] eq 'Month' ) {
2571            $Second = 0;
2572            $Minute = 0;
2573            $Hour   = 0;
2574            $Day    = 1;
2575        }
2576        elsif ( $Element->{SelectedValues}[0] eq 'Quarter' ) {
2577            $Second = 0;
2578            $Minute = 0;
2579            $Hour   = 0;
2580            $Day    = 1;
2581
2582            # calculate the start month for the quarter
2583            my $QuarterNum = ceil( $Month / 3 );
2584            $Month = ( $QuarterNum * 3 ) - 2;
2585        }
2586        elsif ( $Element->{SelectedValues}[0] eq 'HalfYear' ) {
2587            $Second = 0;
2588            $Minute = 0;
2589            $Hour   = 0;
2590            $Day    = 1;
2591
2592            # calculate the start month for the half-year
2593            my $HalfYearNum = ceil( $Month / 6 );
2594            $Month = ( $HalfYearNum * 6 ) - 5;
2595        }
2596        elsif ( $Element->{SelectedValues}[0] eq 'Year' ) {
2597            $Second = 0;
2598            $Minute = 0;
2599            $Hour   = 0;
2600            $Day    = 1;
2601            $Month  = 1;
2602        }
2603
2604        # FIXME Timeheader zusammenbauen
2605        # my $DateTimeObject = $Kernel::OM->Create('Kernel::System::Datetime');
2606        while (
2607            !$TimeStop
2608            || (
2609                $DateTimeObject->Set( String => $TimeStop )
2610                && $DateTimeObject->ToEpoch()
2611                < $TimeAbsolutStopUnixTime
2612            )
2613            )
2614        {
2615            $TimeStart = sprintf(
2616                "%04d-%02d-%02d %02d:%02d:%02d",
2617                $Year, $Month, $Day, $Hour, $Minute, $Second
2618            );
2619            if ( $Element->{SelectedValues}[0] eq 'Second' ) {
2620                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2621                    $Year, $Month, $Day, $Hour, $Minute, $Second, 0, 0, 0,
2622                    $Count - 1
2623                );
2624                push(
2625                    @HeaderLine,
2626                    sprintf(
2627                        "%02d:%02d:%02d-%02d:%02d:%02d",
2628                        $Hour, $Minute, $Second, $ToHour, $ToMinute, $ToSecond
2629                    )
2630                );
2631            }
2632            elsif ( $Element->{SelectedValues}[0] eq 'Minute' ) {
2633                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2634                    $Year, $Month, $Day, $Hour, $Minute, $Second, 0, 0, $Count,
2635                    -1
2636                );
2637                push(
2638                    @HeaderLine,
2639                    sprintf(
2640                        "%02d:%02d:%02d-%02d:%02d:%02d",
2641                        $Hour, $Minute, $Second, $ToHour, $ToMinute, $ToSecond
2642                    )
2643                );
2644            }
2645            elsif ( $Element->{SelectedValues}[0] eq 'Hour' ) {
2646                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2647                    $Year, $Month, $Day, $Hour, $Minute, $Second, 0, $Count, 0,
2648                    -1
2649                );
2650                push(
2651                    @HeaderLine,
2652                    sprintf(
2653                        "%02d:%02d:%02d-%02d:%02d:%02d",
2654                        $Hour, $Minute, $Second, $ToHour, $ToMinute, $ToSecond
2655                    )
2656                );
2657            }
2658            elsif ( $Element->{SelectedValues}[0] eq 'Day' ) {
2659                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2660                    $Year, $Month, $Day, $Hour, $Minute, $Second, $Count, 0, 0,
2661                    -1
2662                );
2663                my $Dow = $Self->_DayOfWeek( $Year, $Month, $Day );
2664                $Dow = $LanguageObject->Translate( $Self->_DayOfWeekAbbreviation($Dow) );
2665                if ( $ToDay eq $Day ) {
2666                    push @HeaderLine, "$Dow $Day";
2667                }
2668                else {
2669                    push(
2670                        @HeaderLine,
2671                        sprintf(
2672                            "%02d.%02d.%04d - %02d.%02d.%04d",
2673                            $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2674                        )
2675                    );
2676                }
2677            }
2678            elsif ( $Element->{SelectedValues}[0] eq 'Week' ) {
2679                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $Year, $Month, $Day, 0, 0, $Count * 7 );
2680                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2681                    $ToYear, $ToMonth, $ToDay, $Hour, $Minute, $Second, 0, 0, 0,
2682                    -1
2683                );
2684                my %WeekNum;
2685                ( $WeekNum{Week}, $WeekNum{Year} ) = $Self->_WeekOfYear( $Year, $Month, $Day );
2686                my $TranslateWeek = $LanguageObject->Translate('week');
2687                push(
2688                    @HeaderLine,
2689                    sprintf( "$TranslateWeek %02d-%04d - ", $WeekNum{Week}, $WeekNum{Year} ) .
2690                        sprintf(
2691                        "%02d.%02d.%04d - %02d.%02d.%04d",
2692                        $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2693                        )
2694                );
2695            }
2696            elsif ( $Element->{SelectedValues}[0] eq 'Month' ) {
2697                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $Year, $Month, $Day, 0, $Count, 0 );
2698                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2699                    $ToYear, $ToMonth, $ToDay, $Hour, $Minute, $Second, 0, 0, 0,
2700                    -1
2701                );
2702                if ( $ToMonth eq $Month ) {
2703                    my $TranslateMonth = $LanguageObject->Translate( $MonthArrayRef->[$Month] );
2704                    push @HeaderLine, "$TranslateMonth $Month";
2705                }
2706                else {
2707                    push(
2708                        @HeaderLine,
2709                        sprintf(
2710                            "%02d.%02d.%04d - %02d.%02d.%04d",
2711                            $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2712                        )
2713                    );
2714                }
2715            }
2716            elsif ( $Element->{SelectedValues}[0] eq 'Quarter' ) {
2717                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $Year, $Month, $Day, 0, $Count * 3, 0 );
2718                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2719                    $ToYear, $ToMonth, $ToDay, $Hour, $Minute, $Second, 0, 0, 0,
2720                    -1
2721                );
2722
2723                if ( ( $ToMonth - $Month ) == 2 ) {
2724                    my $QuarterNum       = ceil( $Month / 3 );
2725                    my $TranslateQuarter = $LanguageObject->Translate('quarter');
2726                    push(
2727                        @HeaderLine,
2728                        sprintf( "$TranslateQuarter $QuarterNum-%04d", $Year )
2729                    );
2730                }
2731                else {
2732                    push(
2733                        @HeaderLine,
2734                        sprintf(
2735                            "%02d.%02d.%04d - %02d.%02d.%04d",
2736                            $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2737                        )
2738                    );
2739                }
2740            }
2741            elsif ( $Element->{SelectedValues}[0] eq 'HalfYear' ) {
2742                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $Year, $Month, $Day, 0, $Count * 6, 0 );
2743                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2744                    $ToYear, $ToMonth, $ToDay, $Hour, $Minute, $Second, 0, 0, 0,
2745                    -1
2746                );
2747
2748                if ( ( $ToMonth - $Month ) == 5 ) {
2749                    my $HalfYearNum       = ceil( $Month / 6 );
2750                    my $TranslateHalfYear = $LanguageObject->Translate('half-year');
2751                    push(
2752                        @HeaderLine,
2753                        sprintf( "$TranslateHalfYear $HalfYearNum-%04d", $Year )
2754                    );
2755                }
2756                else {
2757                    push(
2758                        @HeaderLine,
2759                        sprintf(
2760                            "%02d.%02d.%04d - %02d.%02d.%04d",
2761                            $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2762                        )
2763                    );
2764                }
2765            }
2766            elsif ( $Element->{SelectedValues}[0] eq 'Year' ) {
2767                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $Year, $Month, $Day, $Count, 0, 0 );
2768                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
2769                    $ToYear, $ToMonth, $ToDay, $Hour, $Minute, $Second, 0, 0, 0,
2770                    -1
2771                );
2772                if ( $ToYear eq $Year ) {
2773                    push @HeaderLine, $Year;
2774                }
2775                else {
2776                    push(
2777                        @HeaderLine,
2778                        sprintf(
2779                            "%02d.%02d.%04d - %02d.%02d.%04d",
2780                            $Day, $Month, $Year, $ToDay, $ToMonth, $ToYear
2781                        )
2782                    );
2783                }
2784            }
2785            ( $Year, $Month, $Day, $Hour, $Minute, $Second ) = $Self->_AddDeltaDHMS(
2786                $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0, 0, 0,
2787                1
2788            );
2789            $TimeStop = sprintf(
2790                "%04d-%02d-%02d %02d:%02d:%02d",
2791                $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond
2792            );
2793
2794            push(
2795                @{ $Xvalue->{SelectedValues} },
2796                {
2797                    # convert to OTRS time zone for correct database search parameter
2798
2799                    TimeStart => $Self->_ToOTRSTimeZone(
2800                        String   => $TimeStart,
2801                        TimeZone => $Param{TimeZone},
2802                    ),
2803
2804                    TimeStop => $Self->_ToOTRSTimeZone(
2805                        String   => $TimeStop,
2806                        TimeZone => $Param{TimeZone},
2807                    ),
2808                }
2809            );
2810        }
2811
2812        $Xvalue->{Block}  = 'Time';
2813        $Xvalue->{Values} = $Element->{Values};
2814    }
2815
2816    # if Block equal MultiSelectField, Selectfield
2817    else {
2818        $Xvalue = $Element;
2819
2820        # build the headerline
2821
2822        for my $Valuename ( @{ $Xvalue->{SelectedValues} } ) {
2823
2824            # Do not translate the values, please see bug#12384 for more information.
2825            push @HeaderLine, $Xvalue->{Values}{$Valuename};
2826        }
2827
2828        # Prevent randomization of x-axis in preview, sort it alphabetically see bug#12714.
2829        if ($Preview) {
2830            @HeaderLine = sort { lc($a) cmp lc($b) } @HeaderLine;
2831        }
2832    }
2833
2834    # get the value series
2835    my %ValueSeries;
2836    my @ArraySelected;
2837    my $ColumnName = '';
2838    my $HeaderLineStart;
2839
2840    # give me all possible elements for Value Series
2841    REF1:
2842    for my $Ref1 ( @{ $Param{UseAsValueSeries} } ) {
2843
2844        # all elements which are shown with multiselectfields
2845        if ( $Ref1->{Block} ne 'Time' ) {
2846            my %SelectedValues;
2847            for my $Ref2 ( @{ $Ref1->{SelectedValues} } ) {
2848
2849                # Do not translate the values, please see bug#12384 for more information.
2850                $SelectedValues{$Ref2} = $Ref1->{Values}{$Ref2};
2851            }
2852            push(
2853                @ArraySelected,
2854                {
2855                    Values  => \%SelectedValues,
2856                    Element => $Ref1->{Element},
2857                    Name    => $Ref1->{Name},
2858                    Block   => $Ref1->{Block},
2859                }
2860            );
2861            next REF1;
2862        }
2863
2864        # timescale elements need a special handling, so we save the start value and reset the HeaderLine
2865        $HeaderLineStart = $HeaderLine[0];
2866        @HeaderLine      = ();
2867
2868        # these all makes only sense, if the count of xaxis is 1
2869        if ( $Ref1->{SelectedValues}[0] eq 'Year' ) {
2870
2871            if ( $Element->{SelectedValues}[0] eq 'Month' ) {
2872
2873                if ( $Count == 1 ) {
2874                    for my $Month ( 1 .. 12 ) {
2875                        push @HeaderLine, "$MonthArrayRef->[$Month] $Month";
2876                    }
2877                }
2878                else {
2879                    for ( my $Month = 1; $Month < 12; $Month += $Count ) {
2880                        push(
2881                            @HeaderLine,
2882                            "$MonthArrayRef->[$Month] - $MonthArrayRef->[$Month + $Count - 1]"
2883                        );
2884                    }
2885                }
2886                $VSSecond = 0;
2887                $VSMinute = 0;
2888                $VSHour   = 0;
2889                $VSDay    = 1;
2890                $VSMonth  = 1;
2891            }
2892            elsif ( $Element->{SelectedValues}[0] eq 'Quarter' ) {
2893
2894                my $TranslateQuarter = $LanguageObject->Translate('quarter');
2895                for my $Quarter ( 1 .. 4 ) {
2896                    push @HeaderLine, "$TranslateQuarter $Quarter";
2897                }
2898                $VSSecond = 0;
2899                $VSMinute = 0;
2900                $VSHour   = 0;
2901                $VSDay    = 1;
2902                $VSMonth  = 1;
2903
2904                # remove the year from the HeaderLineStart value to have the same values as the new generated HeaderLine
2905                $HeaderLineStart =~ s{ -\d\d\d\d }{}xms;
2906            }
2907            elsif ( $Element->{SelectedValues}[0] eq 'HalfYear' ) {
2908
2909                my $TranslateHalfYear = $LanguageObject->Translate('half-year');
2910                for my $HalfYear ( 1 .. 2 ) {
2911                    push @HeaderLine, "$TranslateHalfYear $HalfYear";
2912                }
2913                $VSSecond = 0;
2914                $VSMinute = 0;
2915                $VSHour   = 0;
2916                $VSDay    = 1;
2917                $VSMonth  = 1;
2918
2919                # remove the year from the HeaderLineStart value to have the same values as the new generated HeaderLine
2920                $HeaderLineStart =~ s{ -\d\d\d\d }{}xms;
2921            }
2922
2923            $ColumnName = 'Year';
2924        }
2925        elsif ( $Ref1->{SelectedValues}[0] eq 'Month' ) {
2926
2927            for my $Count ( 1 .. 31 ) {
2928                push @HeaderLine, $Count;
2929            }
2930
2931            $VSSecond   = 0;
2932            $VSMinute   = 0;
2933            $VSHour     = 0;
2934            $VSDay      = 1;
2935            $ColumnName = 'Month';
2936        }
2937        elsif ( $Ref1->{SelectedValues}[0] eq 'Week' ) {
2938
2939            for my $Count ( 1 .. 7 ) {
2940                push @HeaderLine, $Self->_DayOfWeekToText($Count);
2941            }
2942
2943            $VSSecond   = 0;
2944            $VSMinute   = 0;
2945            $VSHour     = 0;
2946            $ColumnName = 'Week';
2947        }
2948        elsif ( $Ref1->{SelectedValues}[0] eq 'Day' ) {
2949            for ( my $Hour = 0; $Hour < 24; $Hour += $Count ) {
2950                push @HeaderLine, sprintf( "%02d:00:00-%02d:59:59", $Hour, $Hour + $Count - 1 );
2951            }
2952            $VSSecond   = 0;
2953            $VSMinute   = 0;
2954            $VSHour     = 0;
2955            $ColumnName = 'Day';
2956        }
2957        elsif ( $Ref1->{SelectedValues}[0] eq 'Hour' ) {
2958            for ( my $Minute = 0; $Minute < 60; $Minute += $Count ) {
2959                my $Time = 'min ' . $Minute . ' - ' . ( $Minute + $Count );
2960                push @HeaderLine, $Time;
2961            }
2962            $VSSecond   = 0;
2963            $VSMinute   = 0;
2964            $ColumnName = 'Hour';
2965        }
2966        elsif ( $Ref1->{SelectedValues}[0] eq 'Minute' ) {
2967            if ( $Count == 1 ) {
2968                for ( 0 .. 59 ) {
2969                    my $Time = 'sec ' . $_;
2970                    push @HeaderLine, $Time;
2971                }
2972            }
2973            else {
2974                for ( my $Second = 0; $Second < 60; $Second += $Count ) {
2975                    my $Time = 'sec ' . $Second . '-' . ( $Second + $Count );
2976                    push @HeaderLine, $Time;
2977                }
2978            }
2979            $VSSecond   = 0;
2980            $ColumnName = 'Minute';
2981        }
2982
2983        my $TimeStart     = 0;
2984        my $TimeStop      = 0;
2985        my $MonthArrayRef = _MonthArray();
2986
2987        $Count = 1;
2988
2989        # Generate the time value series
2990        my ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond );
2991
2992        my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
2993        if ( $Ref1->{SelectedValues}[0] eq 'Year' ) {
2994            while (
2995                !$TimeStop || (
2996                    $DateTimeObject->Set( String => $TimeStop )
2997                    && $DateTimeObject->ToEpoch()
2998                    < $TimeAbsolutStopUnixTime
2999                )
3000                )
3001            {
3002                $TimeStart = sprintf( "%04d-01-01 00:00:00", $VSYear );
3003                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $VSYear, $VSMonth, $VSDay, $Count, 0, 0 );
3004                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3005                    $ToYear, $ToMonth, $ToDay, $VSHour, $VSMinute, $VSSecond, 0,
3006                    0, 0, -1
3007                );
3008                $TimeStop = sprintf( "%04d-12-31 23:59:59", $ToYear );
3009
3010                $ValueSeries{$VSYear} = {
3011                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3012                    $Ref1->{Values}{TimeStart} => $TimeStart
3013                };
3014
3015                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3016                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3017                    0, 0, 1
3018                );
3019            }
3020        }
3021        elsif ( $Ref1->{SelectedValues}[0] eq 'Month' ) {
3022            while (
3023                !$TimeStop || (
3024                    $DateTimeObject->Set( String => $TimeStop )
3025                    && $DateTimeObject->ToEpoch()
3026                    < $TimeAbsolutStopUnixTime
3027                )
3028                )
3029            {
3030                $TimeStart = sprintf( "%04d-%02d-01 00:00:00", $VSYear, $VSMonth );
3031                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaYMD( $VSYear, $VSMonth, $VSDay, 0, $Count, 0 );
3032                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3033                    $ToYear, $ToMonth, $ToDay, $VSHour, $VSMinute, $VSSecond, 0,
3034                    0, 0, -1
3035                );
3036                $TimeStop = sprintf( "%04d-%02d-%02d 23:59:59", $ToYear, $ToMonth, $ToDay );
3037
3038                my $TranslateMonth = $LanguageObject->Translate( $MonthArrayRef->[$VSMonth] );
3039
3040                $ValueSeries{
3041                    $VSYear . '-'
3042                        . sprintf( "%02d", $VSMonth ) . ' '
3043                        . $TranslateMonth
3044                    } = {
3045                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3046                    $Ref1->{Values}{TimeStart} => $TimeStart
3047                    };
3048
3049                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3050                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3051                    0, 0, 1
3052                );
3053            }
3054
3055            # remove the value for this selected value
3056            $HeaderLineStart = '';
3057        }
3058        elsif ( $Ref1->{SelectedValues}[0] eq 'Week' ) {
3059            while (
3060                !$TimeStop || (
3061                    $DateTimeObject->Set( String => $TimeStop )
3062                    && $DateTimeObject->ToEpoch()
3063                    < $TimeAbsolutStopUnixTime
3064                )
3065                )
3066            {
3067                my @Monday = $Self->_MondayOfWeek( $VSYear, $VSMonth, $VSDay );
3068
3069                $TimeStart = sprintf( "%04d-%02d-%02d 00:00:00", @Monday );
3070                ( $ToYear, $ToMonth, $ToDay ) = $Self->_AddDeltaDays( @Monday, $Count * 7 );
3071                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3072                    $ToYear, $ToMonth, $ToDay, $VSHour, $VSMinute, $VSSecond, 0,
3073                    0, 0, -1
3074                );
3075                $TimeStop = sprintf( "%04d-%02d-%02d 23:59:59", $ToYear, $ToMonth, $ToDay );
3076
3077                $ValueSeries{
3078                    sprintf( "%04d-%02d-%02d", @Monday ) . ' - '
3079                        . sprintf( "%04d-%02d-%02d", $ToYear, $ToMonth, $ToDay )
3080                    } = {
3081                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3082                    $Ref1->{Values}{TimeStart} => $TimeStart
3083                    };
3084
3085                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3086                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3087                    0, 0, 1
3088                );
3089            }
3090
3091            # remove the value for this selected value
3092            $HeaderLineStart = '';
3093        }
3094        elsif ( $Ref1->{SelectedValues}[0] eq 'Day' ) {
3095            while (
3096                !$TimeStop || (
3097                    $DateTimeObject->Set( String => $TimeStop )
3098                    && $DateTimeObject->ToEpoch()
3099                    < $TimeAbsolutStopUnixTime
3100                )
3101                )
3102            {
3103                $TimeStart = sprintf( "%04d-%02d-%02d 00:00:00", $VSYear, $VSMonth, $VSDay );
3104                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3105                    $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond,
3106                    $Count, 0, 0, -1
3107                );
3108                $TimeStop = sprintf( "%04d-%02d-%02d 23:59:59", $ToYear, $ToMonth, $ToDay );
3109
3110                $ValueSeries{ sprintf( "%04d-%02d-%02d", $VSYear, $VSMonth, $VSDay ) } = {
3111                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3112                    $Ref1->{Values}{TimeStart} => $TimeStart
3113                };
3114
3115                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3116                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3117                    0, 0, 1
3118                );
3119            }
3120        }
3121        elsif ( $Ref1->{SelectedValues}[0] eq 'Hour' ) {
3122            while (
3123                !$TimeStop || (
3124                    $DateTimeObject->Set( String => $TimeStop )
3125                    && $DateTimeObject->ToEpoch()
3126                    < $TimeAbsolutStopUnixTime
3127                )
3128                )
3129            {
3130                $TimeStart = sprintf( "%04d-%02d-%02d %02d:00:00", $VSYear, $VSMonth, $VSDay, $VSHour );
3131                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3132                    $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond, 0,
3133                    $Count, 0, -1
3134                );
3135                $TimeStop = sprintf( "%04d-%02d-%02d %02d:59:59", $ToYear, $ToMonth, $ToDay, $ToHour );
3136                $ValueSeries{
3137                    sprintf(
3138                        "%04d-%02d-%02d %02d:00:00 - %02d:59:59",
3139                        $VSYear, $VSMonth, $VSDay, $VSHour, $ToHour
3140                    )
3141                    } = {
3142                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3143                    $Ref1->{Values}{TimeStart} => $TimeStart
3144                    };
3145                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3146                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3147                    0, 0, 1
3148                );
3149            }
3150        }
3151        elsif ( $Ref1->{SelectedValues}[0] eq 'Minute' ) {
3152            while (
3153                !$TimeStop || (
3154                    $DateTimeObject->Set( String => $TimeStop )
3155                    && $DateTimeObject->ToEpoch()
3156                    < $TimeAbsolutStopUnixTime
3157                )
3158                )
3159            {
3160                $TimeStart = sprintf(
3161                    "%04d-%02d-%02d %02d:%02d:00",
3162                    $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute
3163                );
3164                ( $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond ) = $Self->_AddDeltaDHMS(
3165                    $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond, 0,
3166                    0, $Count, -1
3167                );
3168                $TimeStop = sprintf(
3169                    "%04d-%02d-%02d %02d:%02d:59",
3170                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute
3171                );
3172                $ValueSeries{
3173                    sprintf(
3174                        "%04d-%02d-%02d %02d:%02d:00 - %02d:%02d:59",
3175                        $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $ToHour, $ToMinute
3176                    )
3177                    } = {
3178                    $Ref1->{Values}{TimeStop}  => $TimeStop,
3179                    $Ref1->{Values}{TimeStart} => $TimeStart
3180                    };
3181                ( $VSYear, $VSMonth, $VSDay, $VSHour, $VSMinute, $VSSecond ) = $Self->_AddDeltaDHMS(
3182                    $ToYear, $ToMonth, $ToDay, $ToHour, $ToMinute, $ToSecond, 0,
3183                    0, 0, 1
3184                );
3185            }
3186        }
3187    }
3188
3189    # merge the array if two elements for the valueseries are avialable
3190    if ( $ArraySelected[0] ) {
3191        KEY:
3192        for my $Key ( sort keys %{ $ArraySelected[0]{Values} } ) {
3193            my $Value0;
3194            if ( $ArraySelected[0]{Block} eq 'SelectField' ) {
3195                $Value0 = $Key;
3196            }
3197            elsif ( $ArraySelected[0]{Block} eq 'MultiSelectField' ) {
3198                $Value0 = [$Key];
3199            }
3200
3201            if ( !$ArraySelected[1] ) {
3202                $ValueSeries{ $ArraySelected[0]{Values}{$Key} } = { $ArraySelected[0]{Element} => $Value0 };
3203                next KEY;
3204            }
3205
3206            for my $SubKey ( sort keys %{ $ArraySelected[1]{Values} } ) {
3207                my $Value1;
3208                if ( $ArraySelected[1]{Block} eq 'SelectField' ) {
3209                    $Value1 = $SubKey;
3210                }
3211                elsif ( $ArraySelected[1]{Block} eq 'MultiSelectField' ) {
3212                    $Value1 = [$SubKey];
3213                }
3214                $ValueSeries{
3215                    $ArraySelected[0]{Values}{$Key} . ' - '
3216                        . $ArraySelected[1]{Values}{$SubKey}
3217                    } = {
3218                    $ArraySelected[0]{Element} => $Value0,
3219                    $ArraySelected[1]{Element} => $Value1
3220                    };
3221            }
3222        }
3223    }
3224
3225    # Use this if no valueseries available
3226    if ( !%ValueSeries ) {
3227        $ValueSeries{ $Param{Object} . 's' } = undef;
3228    }
3229
3230    # get the first column name in the headerline
3231    if ($ColumnName) {
3232        unshift @HeaderLine, $LanguageObject->Translate($ColumnName);
3233    }
3234    elsif ( $ArraySelected[1] ) {
3235        unshift(
3236            @HeaderLine,
3237            $LanguageObject->Translate( $ArraySelected[0]{Name} ) . ' - '
3238                . $LanguageObject->Translate( $ArraySelected[1]{Name} )
3239        );
3240    }
3241    elsif ( $ArraySelected[0] ) {
3242        unshift( @HeaderLine, $LanguageObject->Translate( $ArraySelected[0]{Name} ) || '' );
3243    }
3244    else {
3245
3246        # in cases where there is no value set, then the headers get wrong unless a empty element
3247        #    is added in the header, bug 9796
3248        #
3249        #   e.g. from:
3250        #    Raw    | Misc | PostMaster |    |
3251        #    Ticket | 10   | 20         | 30 |
3252        #
3253        #    to:
3254        #           | Raw | Misc | PostMaster |
3255        #    Ticket | 10  | 20   | 30         |
3256        unshift( @HeaderLine, '' );
3257    }
3258
3259    # push the first array elements in the StatsArray
3260    my $Title = $Param{Title};
3261    if ( $TitleTimeStart && $TitleTimeStop ) {
3262        $Title .= " $TitleTimeStart-$TitleTimeStop";
3263    }
3264
3265    # Extend the title, e.g. to add a fixed time period from the stats object.
3266    if ( $StatObject->can('GetExtendedTitle') ) {
3267        my $ExtendedTitle = $StatObject->GetExtendedTitle(
3268            XValue       => $Xvalue,
3269            Restrictions => \%RestrictionAttribute,
3270        );
3271        if ($ExtendedTitle) {
3272            $Title .= ' ' . $ExtendedTitle;
3273        }
3274    }
3275
3276    # create the cache string
3277    my $CacheString = $Self->_GetCacheString(%Param);
3278
3279    # take the cache value if configured and available
3280    if ( $Param{Cache} && !$Preview ) {
3281        my @StatArray = $Self->_GetResultCache(
3282            Filename => 'Stats' . $Param{StatID} . '-' . $CacheString . '.cache',
3283        );
3284
3285        if (@StatArray) {
3286            return @StatArray;
3287        }
3288    }
3289
3290    # create the table structure
3291    my %TableStructure;
3292    for my $Row ( sort keys %ValueSeries ) {
3293        my @Cells;
3294        CELL:
3295        for my $Cell ( @{ $Xvalue->{SelectedValues} } ) {    # get each cell
3296            $ValueSeries{$Row} ||= {};
3297            my %Attributes = ( %{ $ValueSeries{$Row} }, %RestrictionAttribute );
3298
3299            # the following is necessary if as x-axis and as value-series time is selected
3300            if ( $Xvalue->{Block} eq 'Time' ) {
3301                my $TimeStart = $Xvalue->{Values}{TimeStart};
3302                my $TimeStop  = $Xvalue->{Values}{TimeStop};
3303                if ( $ValueSeries{$Row}{$TimeStop} && $ValueSeries{$Row}{$TimeStart} ) {
3304
3305                    my $CellStartTime = $Self->_TimeStamp2DateTime( TimeStamp => $Cell->{TimeStart} );
3306                    my $CellStopTime  = $Self->_TimeStamp2DateTime( TimeStamp => $Cell->{TimeStop} );
3307                    my $ValueSeriesStartTime
3308                        = $Self->_TimeStamp2DateTime( TimeStamp => $ValueSeries{$Row}{$TimeStart} );
3309                    my $ValueSeriesStopTime = $Self->_TimeStamp2DateTime( TimeStamp => $ValueSeries{$Row}{$TimeStop} );
3310
3311                    if ( $CellStopTime > $ValueSeriesStopTime || $CellStartTime < $ValueSeriesStartTime ) {
3312                        next CELL;
3313                    }
3314
3315                }
3316                $Attributes{$TimeStop}  = $Cell->{TimeStop};
3317                $Attributes{$TimeStart} = $Cell->{TimeStart};
3318            }
3319            elsif ( $Xvalue->{Block} eq 'SelectField' ) {
3320                $Attributes{ $Xvalue->{Element} } = $Cell;
3321            }
3322            else {
3323                $Attributes{ $Xvalue->{Element} } = [$Cell];
3324            }
3325            push @Cells, \%Attributes;
3326        }
3327        $TableStructure{$Row} = \@Cells;
3328    }
3329
3330    my @DataArray;
3331
3332    # Dynamic List Statistic
3333    if ( $StatObject->can('GetStatTable') ) {
3334
3335        if ($Preview) {
3336            return if !$StatObject->can('GetStatTablePreview');
3337
3338            @DataArray = $StatObject->GetStatTablePreview(
3339                ValueSeries    => $Param{UseAsValueSeries},    #\%ValueSeries,
3340                XValue         => $Xvalue,
3341                Restrictions   => \%RestrictionAttribute,
3342                TableStructure => \%TableStructure,
3343                TimeZone       => $Param{TimeZone},
3344            );
3345        }
3346        else {
3347            # get the whole stats table
3348            @DataArray = $StatObject->GetStatTable(
3349                ValueSeries    => $Param{UseAsValueSeries},    #\%ValueSeries,
3350                XValue         => $Xvalue,
3351                Restrictions   => \%RestrictionAttribute,
3352                TableStructure => \%TableStructure,
3353                TimeZone       => $Param{TimeZone},
3354            );
3355        }
3356    }
3357
3358    # Dynamic Matrix Statistic
3359    else {
3360
3361        if ($Preview) {
3362            return if !$StatObject->can('GetStatElementPreview');
3363        }
3364
3365        for my $Row ( sort keys %TableStructure ) {
3366            my @ResultRow = ($Row);
3367            for my $Cell ( @{ $TableStructure{$Row} } ) {
3368                if ($Preview) {
3369                    push @ResultRow, $StatObject->GetStatElementPreview( %{$Cell} );
3370                }
3371                else {
3372                    push @ResultRow, $StatObject->GetStatElement( %{$Cell} ) || 0;
3373                }
3374            }
3375            push @DataArray, \@ResultRow;
3376        }
3377
3378        my $RowCounter = 0;
3379
3380        # fill up empty array elements, e.g month as value series (February has 28 day and Januar 31)
3381        for my $Row (@DataArray) {
3382
3383            $RowCounter++;
3384
3385            if ( $RowCounter == 1 && $HeaderLineStart ) {
3386
3387                # determine the skipping counter
3388                my $SkippingCounter = 0;
3389
3390                INDEX:
3391                for my $Index ( 1 .. $#HeaderLine ) {
3392
3393                    if ( $HeaderLine[$Index] eq $HeaderLineStart ) {
3394                        last INDEX;
3395                    }
3396
3397                    $SkippingCounter++;
3398                }
3399
3400                for my $Index ( 1 .. $SkippingCounter ) {
3401                    splice @{$Row}, $Index, 0, '';
3402                }
3403            }
3404
3405            for my $Index ( 1 .. $#HeaderLine ) {
3406                if ( !defined $Row->[$Index] ) {
3407                    $Row->[$Index] = '';
3408                }
3409            }
3410        }
3411    }
3412
3413    # REMARK: it could be also useful to use the indiviual sort if difined
3414    # so you don't need this function
3415    if ( $StatObject->can('GetHeaderLine') ) {
3416        my $HeaderRef = $StatObject->GetHeaderLine(
3417            XValue       => $Xvalue,
3418            Restrictions => \%RestrictionAttribute,
3419        );
3420
3421        if ($HeaderRef) {
3422            @HeaderLine = @{$HeaderRef};
3423        }
3424    }
3425
3426    my @StatArray = ( [$Title], \@HeaderLine, @DataArray );
3427
3428    if ( !$Param{Cache} || $Preview ) {
3429        return @StatArray;
3430    }
3431
3432    # check if we should cache this result
3433    if ( !$TitleTimeStart || !$TitleTimeStop ) {
3434        $Kernel::OM->Get('Kernel::System::Log')->Log(
3435            Priority => 'notice',
3436            Message  => "Can't cache: StatID $Param{StatID} has no time period, so you can't cache the stat!",
3437        );
3438        return @StatArray;
3439    }
3440
3441    # convert to OTRS time zone to get the correct time for the check
3442    my $CheckTimeStop = $Self->_ToOTRSTimeZone(
3443        String   => $TitleTimeStop,
3444        TimeZone => $Param{TimeZone},
3445    );
3446
3447    my $DateTimeObject      = $Kernel::OM->Create('Kernel::System::DateTime');
3448    my $CheckTimeStopObject = $Self->_TimeStamp2DateTime(
3449        TimeStamp => $CheckTimeStop,
3450    );
3451
3452    if ( $CheckTimeStopObject > $DateTimeObject ) {
3453        $Kernel::OM->Get('Kernel::System::Log')->Log(
3454            Priority => 'notice',
3455            Message  => "Can't cache StatID $Param{StatID}: The selected end time is in the future!",
3456        );
3457        return @StatArray;
3458    }
3459
3460    # write the stats cache
3461    $Self->_SetResultCache(
3462        Filename => 'Stats' . $Param{StatID} . '-' . $CacheString . '.cache',
3463        Result   => \@StatArray,
3464    );
3465    return @StatArray;
3466}
3467
3468=head2 _ColumnAndRowTranslation()
3469
3470Translate the column and row name if needed.
3471
3472    $StatsObject->_ColumnAndRowTranslation(
3473        StatArrayRef => $StatArrayRef,
3474        StatRef      => $StatRef,
3475        ExchangeAxis => 1 | 0,
3476    );
3477
3478=cut
3479
3480sub _ColumnAndRowTranslation {
3481    my ( $Self, %Param ) = @_;
3482
3483    # check if need params are available
3484    for my $NeededParam (qw(StatArrayRef StatRef)) {
3485        if ( !$Param{$NeededParam} ) {
3486            $Kernel::OM->Get('Kernel::System::Log')->Log(
3487                Priority => "error",
3488                Message  => "_ColumnAndRowTranslation: Need $NeededParam!"
3489            );
3490        }
3491    }
3492
3493    # Cut the statistic array in the three pieces, to handle the diffrent values for the translation.
3494    my $TitleArrayRef = shift @{ $Param{StatArrayRef} };
3495    my $HeadArrayRef  = shift @{ $Param{StatArrayRef} };
3496    my $StatArrayRef  = $Param{StatArrayRef};
3497
3498    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
3499
3500    # Find out, if the column or row names should be translated.
3501    my %Translation;
3502    my %Sort;
3503
3504    for my $Use (qw( UseAsXvalue UseAsValueSeries )) {
3505        if (
3506            $Param{StatRef}->{StatType} eq 'dynamic'
3507            && $Param{StatRef}->{$Use}
3508            && ref( $Param{StatRef}->{$Use} ) eq 'ARRAY'
3509            )
3510        {
3511
3512            my @Array = @{ $Param{StatRef}->{$Use} };
3513
3514            ELEMENT:
3515            for my $Element (@Array) {
3516                next ELEMENT if !$Element->{Selected};
3517
3518                if ( $Element->{Translation} && $Element->{Block} eq 'Time' ) {
3519                    $Translation{$Use} = 'Time';
3520                }
3521                elsif ( $Element->{Translation} ) {
3522                    $Translation{$Use} = 'Common';
3523                }
3524                else {
3525                    $Translation{$Use} = '';
3526                }
3527
3528                if ( $Element->{Translation} && $Element->{Block} ne 'Time' && !$Element->{SortIndividual} ) {
3529                    $Sort{$Use} = 1;
3530                }
3531
3532                last ELEMENT;
3533            }
3534        }
3535    }
3536
3537    # Check if the axis are changed.
3538    if ( $Param{ExchangeAxis} ) {
3539        my $UseAsXvalueOld = $Translation{UseAsXvalue};
3540        $Translation{UseAsXvalue}      = $Translation{UseAsValueSeries};
3541        $Translation{UseAsValueSeries} = $UseAsXvalueOld;
3542
3543        my $SortUseAsXvalueOld = $Sort{UseAsXvalue};
3544        $Sort{UseAsXvalue}      = $Sort{UseAsValueSeries};
3545        $Sort{UseAsValueSeries} = $SortUseAsXvalueOld;
3546    }
3547
3548    # Translate the headline array, if all values must be translated and
3549    #   otherwise translate only the first value of the header.
3550    if ( $Translation{UseAsXvalue} && $Translation{UseAsXvalue} ne 'Time' ) {
3551        for my $Word ( @{$HeadArrayRef} ) {
3552            $Word = $LanguageObject->Translate($Word);
3553        }
3554    }
3555    else {
3556        $HeadArrayRef->[0] = $LanguageObject->Translate( $HeadArrayRef->[0] );
3557    }
3558
3559    # Sort the headline array after translation.
3560    if ( $Sort{UseAsXvalue} ) {
3561        my @HeadOld = @{$HeadArrayRef};
3562
3563        # Because the first value is no sortable column name
3564        shift @HeadOld;
3565
3566        # Special handling if the sumfunction is used.
3567        my $SumColRef;
3568        if ( $Param{StatRef}->{SumRow} ) {
3569            $SumColRef = pop @HeadOld;
3570        }
3571
3572        my @SortedHead = sort { $a cmp $b } @HeadOld;
3573
3574        # Special handling if the sumfunction is used.
3575        if ( $Param{StatRef}->{SumCol} ) {
3576            push @SortedHead, $SumColRef;
3577            push @HeadOld,    $SumColRef;
3578        }
3579
3580        # Add the row names to the new StatArray.
3581        my @StatArrayNew;
3582        for my $Row ( @{$StatArrayRef} ) {
3583            push @StatArrayNew, [ $Row->[0] ];
3584        }
3585
3586        for my $ColumnName (@SortedHead) {
3587            my $Counter = 0;
3588            COLUMNNAMEOLD:
3589            for my $ColumnNameOld (@HeadOld) {
3590                $Counter++;
3591                next COLUMNNAMEOLD if $ColumnNameOld ne $ColumnName;
3592
3593                for my $RowLine ( 0 .. $#StatArrayNew ) {
3594                    push @{ $StatArrayNew[$RowLine] }, $StatArrayRef->[$RowLine]->[$Counter];
3595                }
3596                last COLUMNNAMEOLD;
3597            }
3598        }
3599
3600        # Bring the data back to the diffrent references.
3601        unshift @SortedHead, $HeadArrayRef->[0];
3602        @{$HeadArrayRef} = @SortedHead;
3603        @{$StatArrayRef} = @StatArrayNew;
3604    }
3605
3606    # Translate the row description.
3607    if ( $Translation{UseAsValueSeries} && $Translation{UseAsValueSeries} ne 'Time' ) {
3608        for my $Word ( @{$StatArrayRef} ) {
3609            $Word->[0] = $LanguageObject->Translate( $Word->[0] );
3610        }
3611    }
3612
3613    # Sort the row description.
3614    if ( $Sort{UseAsValueSeries} ) {
3615
3616        # Special handling if the sumfunction is used.
3617        my $SumRowArrayRef;
3618        if ( $Param{StatRef}->{SumRow} ) {
3619            $SumRowArrayRef = pop @{$StatArrayRef};
3620        }
3621
3622        my $DisableDefaultResultSort = grep { $_->{DisableDefaultResultSort} && $_->{DisableDefaultResultSort} == 1 }
3623            @{ $Param{StatRef}->{UseAsXvalue} };
3624
3625        if ( !$DisableDefaultResultSort ) {
3626            @{$StatArrayRef} = sort { $a->[0] cmp $b->[0] } @{$StatArrayRef};
3627        }
3628
3629        # Special handling if the sumfunction is used.
3630        if ( $Param{StatRef}->{SumRow} ) {
3631            push @{$StatArrayRef}, $SumRowArrayRef;
3632        }
3633    }
3634
3635    unshift( @{$StatArrayRef}, $TitleArrayRef, $HeadArrayRef );
3636
3637    return 1;
3638}
3639
3640sub _WriteResultCache {
3641    my ( $Self, %Param ) = @_;
3642
3643    my %GetParam = %{ $Param{GetParam} };
3644
3645    if ( $GetParam{Year} && $GetParam{Month} ) {
3646        my $DateTimeObject    = $Kernel::OM->Create('Kernel::System::DateTime');
3647        my $DateTimeNowValues = $DateTimeObject->Get();
3648
3649        my $DateTimeObjectParams = $Kernel::OM->Create(
3650            'Kernel::System::DateTime',
3651            ObjectParams => {
3652                Year   => $GetParam{Year},
3653                Month  => $GetParam{Month},
3654                Day    => $GetParam{Day},
3655                Hour   => $DateTimeNowValues->{Hour},
3656                Minute => $DateTimeNowValues->{Minute},
3657                Second => $DateTimeNowValues->{Second},
3658            }
3659        );
3660
3661        # if get params in future do not cache
3662        return if ( !$DateTimeObject->Compare( DateTimeObject => $DateTimeObjectParams ) );
3663    }
3664
3665    # write cache file
3666    my $Filename = $Self->_CreateStaticResultCacheFilename(
3667        GetParam => $Param{GetParam},
3668        StatID   => $Param{StatID},
3669    );
3670
3671    $Self->_SetResultCache(
3672        Filename => $Filename,
3673        Result   => $Param{Data},
3674    );
3675
3676    return 1;
3677}
3678
3679=head2 _CreateStaticResultCacheFilename()
3680
3681create a filename out of the GetParam information and the stat id
3682
3683    my $Filename = $StatsObject->_CreateStaticResultCacheFilename(
3684        GetParam => {
3685            Year  => 2008,
3686            Month => 3,
3687            Day   => 5
3688        },
3689        StatID   => $Param{StatID},
3690    );
3691
3692=cut
3693
3694sub _CreateStaticResultCacheFilename {
3695    my ( $Self, %Param ) = @_;
3696
3697    # check needed params
3698    for my $NeededParam (qw( StatID GetParam )) {
3699        if ( !$Param{$NeededParam} ) {
3700            $Kernel::OM->Get('Kernel::System::Log')->Log(
3701                Priority => 'error',
3702                Message  => "Need $NeededParam!"
3703            );
3704            return;
3705        }
3706    }
3707
3708    my $GetParamRef = $Param{GetParam};
3709
3710    # format month and day params
3711    for (qw(Month Day)) {
3712        if ( $GetParamRef->{$_} ) {
3713            $GetParamRef->{$_} = sprintf( "%02d", $GetParamRef->{$_} );
3714        }
3715    }
3716
3717    my $Key = '';
3718    if ( $GetParamRef->{Year} ) {
3719        $Key .= $GetParamRef->{Year};
3720    }
3721    if ( $GetParamRef->{Month} ) {
3722        $Key .= "-$GetParamRef->{Month}";
3723    }
3724    if ( $GetParamRef->{Day} ) {
3725        $Key .= "-$GetParamRef->{Day}";
3726    }
3727
3728    my $MD5Key = $Kernel::OM->Get('Kernel::System::Main')->FilenameCleanUp(
3729        Filename => $Key,
3730        Type     => 'md5',
3731    );
3732
3733    return
3734        'Stats'
3735        . $Param{StatID} . '-'
3736        . $Kernel::OM->Get('Kernel::Language')->{UserLanguage} . '-'
3737        . $MD5Key
3738        . '.cache';
3739}
3740
3741=head2 _SetResultCache()
3742
3743cache the stats result with a given cache key (Filename).
3744
3745    $StatsObject->_SetResultCache(
3746        Filename => 'Stats' . $Param{StatID} . '-' . $MD5Key . '.cache',
3747        Result   => $Param{Data},
3748    );
3749
3750=cut
3751
3752sub _SetResultCache {
3753    my ( $Self, %Param ) = @_;
3754
3755    # check needed params
3756    for my $Needed (qw(Filename Result)) {
3757        if ( !$Param{$Needed} ) {
3758            $Kernel::OM->Get('Kernel::System::Log')->Log(
3759                Priority => 'error',
3760                Message  => "Need $Needed!",
3761            );
3762            return;
3763        }
3764    }
3765
3766    $Kernel::OM->Get('Kernel::System::Cache')->Set(
3767        Type  => 'StatsRun',
3768        Key   => $Param{Filename},
3769        Value => $Param{Result},
3770        TTL   => 24 * 60 * 60,
3771
3772        # Don't store complex structure in memory as it will be modified later.
3773        CacheInMemory => 0,
3774    );
3775
3776    return 1;
3777}
3778
3779=head2 _GetResultCache()
3780
3781get stats result from cache, if any
3782
3783    my @Result = $StatsObject->_GetResultCache(
3784        Filename => 'Stats' . $Param{StatID} . '-' . $MD5Key . '.cache',
3785    );
3786
3787=cut
3788
3789sub _GetResultCache {
3790    my ( $Self, %Param ) = @_;
3791
3792    # check needed params
3793    if ( !$Param{Filename} ) {
3794        $Kernel::OM->Get('Kernel::System::Log')->Log(
3795            Priority => 'error',
3796            Message  => '_GetResultCache: Need Filename!',
3797        );
3798        return;
3799    }
3800
3801    my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
3802        Type => 'StatsRun',
3803        Key  => $Param{Filename},
3804
3805        # Don't store complex structure in memory as it will be modified later.
3806        CacheInMemory => 0,
3807    );
3808
3809    if ( ref $Cache ) {
3810        return @{$Cache};
3811    }
3812
3813    return;
3814}
3815
3816=head2 _DeleteCache()
3817
3818clean up stats result cache.
3819
3820=cut
3821
3822sub _DeleteCache {
3823    my ( $Self, %Param ) = @_;
3824
3825    return $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
3826        Type => 'Stats',
3827    );
3828}
3829
3830sub _MonthArray {
3831    my @MonthArray = (
3832        '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
3833    );
3834
3835    return \@MonthArray;
3836}
3837
3838sub _AutomaticSampleImport {
3839    my ( $Self, %Param ) = @_;
3840
3841    # Prevent deep recursions.
3842    local $Self->{InAutomaticSampleImport} = $Self->{InAutomaticSampleImport};
3843    return if $Self->{InAutomaticSampleImport}++;
3844
3845    for my $Needed (qw(UserID)) {
3846        if ( !$Param{$Needed} ) {
3847            $Kernel::OM->Get('Kernel::System::Log')->Log(
3848                Priority => 'error',
3849                Message  => "Need $Needed!",
3850            );
3851            return;
3852        }
3853    }
3854
3855    my $Language  = $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage');
3856    my $Directory = $Self->{StatsTempDir};
3857
3858    if ( !opendir( DIRE, $Directory ) ) {
3859        $Kernel::OM->Get('Kernel::System::Log')->Log(
3860            Priority => 'error',
3861            Message  => "Can not open Directory: $Directory",
3862        );
3863        return;
3864    }
3865
3866    # check if stats in the default language available, if not use en
3867    my $Flag = 0;
3868    while ( defined( my $Filename = readdir DIRE ) ) {
3869        if ( $Filename =~ m{^.*\.$Language\.xml$}x ) {
3870            $Flag = 1;
3871        }
3872    }
3873
3874    rewinddir(DIRE);
3875    if ( !$Flag ) {
3876        $Language = 'en';
3877    }
3878
3879    while ( defined( my $Filename = readdir DIRE ) ) {
3880        if ( $Filename =~ m{^.*\.$Language\.xml$}x ) {
3881
3882            my $Filehandle;
3883            if ( !open $Filehandle, '<', $Directory . $Filename ) {    ## no critic
3884                $Kernel::OM->Get('Kernel::System::Log')->Log(
3885                    Priority => 'error',
3886                    Message  => "Can not open File: " . $Directory . $Filename,
3887                );
3888                closedir(DIRE);
3889                return;
3890            }
3891
3892            my $Content = '';
3893            while (<$Filehandle>) {
3894                $Content .= $_;
3895            }
3896            close $Filehandle;
3897
3898            my $StatID = $Self->Import(
3899                Content => $Content,
3900                UserID  => $Param{UserID},
3901            );
3902        }
3903    }
3904    closedir(DIRE);
3905
3906    return 1;
3907}
3908
3909=head2 _FromOTRSTimeZone()
3910
3911Converts the given date/time string from OTRS time zone to the given time zone.
3912
3913    my $String = $StatsObject->_FromOTRSTimeZone(
3914        String   => '2016-02-20 20:00:00',
3915        TimeZone => 'Europe/Berlin',
3916    );
3917
3918Returns (example for OTRS time zone being set to UTC):
3919
3920    $TimeStamp = '2016-02-20 21:00:00',
3921
3922=cut
3923
3924sub _FromOTRSTimeZone {
3925    my ( $Self, %Param ) = @_;
3926
3927    # check needed params
3928    if ( !$Param{String} ) {
3929        $Kernel::OM->Get('Kernel::System::Log')->Log(
3930            Priority => 'error',
3931            Message  => 'Need String!',
3932        );
3933        return;
3934    }
3935
3936    return $Param{String} if !$Param{TimeZone};
3937
3938    my $DateTimeObject = $Kernel::OM->Create(
3939        'Kernel::System::DateTime',
3940        ObjectParams => {
3941            String => $Param{String},
3942        },
3943    );
3944    $DateTimeObject->ToTimeZone( TimeZone => $Param{TimeZone} );
3945
3946    if ( !$DateTimeObject ) {
3947        $Kernel::OM->Get('Kernel::System::Log')->Log(
3948            Priority => 'error',
3949            Message  => "Error creating DateTime object.",
3950        );
3951
3952        return;
3953    }
3954
3955    return $DateTimeObject->ToString();
3956}
3957
3958=head2 _ToOTRSTimeZone()
3959
3960Converts the given date/time string from the given time zone to OTRS time zone.
3961
3962    my $String = $StatsObject->_ToOTRSTimeZone(
3963        String    => '2016-02-20 18:00:00',
3964        TimeZone  => 'Europe/Berlin',
3965    );
3966
3967Returns (example for OTRS time zone being set to UTC):
3968
3969    $TimeStamp = '2016-02-20 17:00:00',
3970
3971=cut
3972
3973sub _ToOTRSTimeZone {
3974    my ( $Self, %Param ) = @_;
3975
3976    # check needed params
3977    if ( !$Param{String} ) {
3978        $Kernel::OM->Get('Kernel::System::Log')->Log(
3979            Priority => 'error',
3980            Message  => 'Need String!',
3981        );
3982        return;
3983    }
3984
3985    return $Param{String} if !$Param{TimeZone};
3986
3987    my $DateTimeObject = $Kernel::OM->Create(
3988        'Kernel::System::DateTime',
3989        ObjectParams => \%Param,
3990    );
3991
3992    if ( !$DateTimeObject ) {
3993        $Kernel::OM->Get('Kernel::System::Log')->Log(
3994            Priority => 'error',
3995            Message  => "Error creating DateTime object.",
3996        );
3997
3998        return;
3999    }
4000
4001    $DateTimeObject->ToOTRSTimeZone();
4002
4003    return $DateTimeObject->ToString();
4004}
4005
4006=head2 _GetCacheString()
4007
4008returns a string that can be used for caching this particular statistic
4009with the given parameters.
4010
4011    my $Result = $StatsObject->_GetCacheString(
4012        UseAsXvalue      => $UseAsXvalueRef
4013        UseAsValueSeries => $UseAsValueSeriesRef,
4014        UseAsRestriction => $UseAsRestrictionRef,
4015    );
4016
4017=cut
4018
4019sub _GetCacheString {
4020    my ( $Self, %Param ) = @_;
4021
4022    # add the Language to the cache key
4023    my $Result = 'Language:' . $Kernel::OM->Get('Kernel::Language')->{UserLanguage};
4024
4025    if ( $Param{TimeZone} ) {
4026        $Result .= 'TimeZone:' . $Param{TimeZone};
4027    }
4028
4029    for my $Use (qw(UseAsXvalue UseAsValueSeries UseAsRestriction)) {
4030        $Result .= "$Use:";
4031        for my $Element ( @{ $Param{$Use} } ) {
4032            $Result .= "Name:$Element->{Name}:";
4033            if ( $Element->{Block} eq 'Time' ) {
4034                if ( $Element->{SelectedValues}[0] && $Element->{TimeScaleCount} ) {
4035                    $Result .= "TimeScaleUnit:$Element->{SelectedValues}[0]:";
4036                    $Result .= "TimeScaleCount:$Element->{TimeScaleCount}:";
4037                }
4038
4039                if ( $Element->{TimeStart} && $Element->{TimeStop} ) {
4040                    $Result .= "TimeStart:$Element->{TimeStart}:TimeStop:$Element->{TimeStop}:";
4041                }
4042            }
4043            if ( $Element->{SelectedValues} ) {
4044                $Result .= "SelectedValues:" . join( ',', sort @{ $Element->{SelectedValues} } ) . ':';
4045            }
4046        }
4047    }
4048
4049    # Convert to MD5 (not sure if this is needed any more).
4050    $Result = $Kernel::OM->Get('Kernel::System::Main')->FilenameCleanUp(
4051        Filename => $Result,
4052        Type     => 'md5',
4053    );
4054
4055    return $Result;
4056}
4057
4058=head2 _AddDeltaYMD()
4059
4060Substitute for Date::Pcalc::Add_Delta_YMD() which uses Kernel::System::DateTime.
4061
4062=cut
4063
4064sub _AddDeltaYMD {
4065    my ( $Self, $Year, $Month, $Day, $YearsToAdd, $MonthsToAdd, $DaysToAdd ) = @_;
4066
4067    my $DateTimeObject = $Kernel::OM->Create(
4068        'Kernel::System::DateTime',
4069        ObjectParams => {
4070            Year     => $Year,
4071            Month    => $Month,
4072            Day      => $Day,
4073            TimeZone => 'floating',
4074        },
4075    );
4076
4077    if ( !$DateTimeObject ) {
4078        $Kernel::OM->Get('Kernel::System::Log')->Log(
4079            Priority => "error",
4080            Message  => "Error creating DateTime object.",
4081        );
4082
4083        return ( $Year, $Month, $Day, );
4084    }
4085
4086    $DateTimeObject->Add(
4087        Years  => $YearsToAdd  || 0,
4088        Months => $MonthsToAdd || 0,
4089        Days   => $DaysToAdd   || 0,
4090    );
4091    my $DateTimeValues = $DateTimeObject->Get();
4092
4093    return (
4094        $DateTimeValues->{Year},
4095        $DateTimeValues->{Month},
4096        $DateTimeValues->{Day},
4097    );
4098}
4099
4100=head2 _AddDeltaDHMS()
4101
4102Substitute for Date::Pcalc::Add_Delta_DHMS() which uses Kernel::System::DateTime.
4103
4104=cut
4105
4106sub _AddDeltaDHMS {
4107    my ( $Self, $Year, $Month, $Day, $Hour, $Minute, $Second, $DaysToAdd, $HoursToAdd, $MinutesToAdd, $SecondsToAdd )
4108        = @_;
4109
4110    my $DateTimeObject = $Kernel::OM->Create(
4111        'Kernel::System::DateTime',
4112        ObjectParams => {
4113            Year     => $Year,
4114            Month    => $Month,
4115            Day      => $Day,
4116            Hour     => $Hour,
4117            Minute   => $Minute,
4118            Second   => $Second,
4119            TimeZone => 'floating',
4120        },
4121    );
4122
4123    if ( !$DateTimeObject ) {
4124        $Kernel::OM->Get('Kernel::System::Log')->Log(
4125            Priority => "error",
4126            Message  => "Error creating DateTime object.",
4127        );
4128
4129        return ( $Year, $Month, $Day, $Hour, $Minute, $Second, );
4130    }
4131
4132    $DateTimeObject->Add(
4133        Days    => $DaysToAdd    || 0,
4134        Hours   => $HoursToAdd   || 0,
4135        Minutes => $MinutesToAdd || 0,
4136        Seconds => $SecondsToAdd || 0,
4137    );
4138    my $DateTimeValues = $DateTimeObject->Get();
4139
4140    return (
4141        $DateTimeValues->{Year},
4142        $DateTimeValues->{Month},
4143        $DateTimeValues->{Day},
4144        $DateTimeValues->{Hour},
4145        $DateTimeValues->{Minute},
4146        $DateTimeValues->{Second},
4147    );
4148}
4149
4150=head2 _AddDeltaDays()
4151
4152Substitute for Date::Pcalc::Add_Delta_Days() which uses Kernel::System::DateTime.
4153
4154=cut
4155
4156sub _AddDeltaDays {
4157    my ( $Self, $Year, $Month, $Day, $DaysToAdd ) = @_;
4158
4159    my $DateTimeObject = $Kernel::OM->Create(
4160        'Kernel::System::DateTime',
4161        ObjectParams => {
4162            Year     => $Year,
4163            Month    => $Month,
4164            Day      => $Day,
4165            TimeZone => 'floating',
4166        },
4167    );
4168
4169    if ( !$DateTimeObject ) {
4170        $Kernel::OM->Get('Kernel::System::Log')->Log(
4171            Priority => "error",
4172            Message  => "Error creating DateTime object.",
4173        );
4174
4175        return ( $Year, $Month, $Day, );
4176    }
4177
4178    $DateTimeObject->Add(
4179        Days => $DaysToAdd || 0,
4180    );
4181    my $DateTimeValues = $DateTimeObject->Get();
4182
4183    return (
4184        $DateTimeValues->{Year},
4185        $DateTimeValues->{Month},
4186        $DateTimeValues->{Day},
4187    );
4188}
4189
4190=head2 _DaysInMonth()
4191
4192Substitute for Date::Pcalc::Days_in_Month() which uses Kernel::System::DateTime.
4193
4194=cut
4195
4196sub _DaysInMonth {
4197    my ( $Self, $Year, $Month ) = @_;
4198
4199    my $DateTimeObject = $Kernel::OM->Create(
4200        'Kernel::System::DateTime',
4201        ObjectParams => {
4202            Year     => $Year,
4203            Month    => $Month,
4204            Day      => 1,
4205            TimeZone => 'floating',
4206        },
4207    );
4208
4209    if ( !$DateTimeObject ) {
4210        $Kernel::OM->Get('Kernel::System::Log')->Log(
4211            Priority => "error",
4212            Message  => "Error creating DateTime object.",
4213        );
4214
4215        return;
4216    }
4217
4218    my $LastDayOfMonth = $DateTimeObject->LastDayOfMonthGet();
4219
4220    return $LastDayOfMonth->{Day};
4221}
4222
4223=head2 _DayOfWeek()
4224
4225Substitute for Date::Pcalc::Day_of_Week() which uses Kernel::System::DateTime.
4226
4227=cut
4228
4229sub _DayOfWeek {
4230    my ( $Self, $Year, $Month, $Day ) = @_;
4231
4232    my $DateTimeObject = $Kernel::OM->Create(
4233        'Kernel::System::DateTime',
4234        ObjectParams => {
4235            Year     => $Year,
4236            Month    => $Month,
4237            Day      => $Day,
4238            TimeZone => 'floating',
4239        },
4240    );
4241
4242    if ( !$DateTimeObject ) {
4243        $Kernel::OM->Get('Kernel::System::Log')->Log(
4244            Priority => "error",
4245            Message  => "Error creating DateTime object.",
4246        );
4247
4248        return;
4249    }
4250
4251    my $DateTimeValues = $DateTimeObject->Get();
4252
4253    return $DateTimeValues->{DayOfWeek};
4254}
4255
4256=head2 _DayOfWeekAbbreviation()
4257
4258Substitute for Date::Pcalc::Day_of_Week_Abbreviation()
4259
4260=cut
4261
4262sub _DayOfWeekAbbreviation {
4263    my ( $Self, $DayOfWeek ) = @_;
4264
4265    my %DayOfWeekAbbrs = (
4266        1 => 'Mon',
4267        2 => 'Tue',
4268        3 => 'Wed',
4269        4 => 'Thu',
4270        5 => 'Fri',
4271        6 => 'Sat',
4272        7 => 'Sun',
4273    );
4274
4275    return if !$DayOfWeekAbbrs{$DayOfWeek};
4276
4277    return $DayOfWeekAbbrs{$DayOfWeek};
4278}
4279
4280=head2 _DayOfWeekToText()
4281
4282Substitute for Date::Pcalc::Day_of_Week_to_Text()
4283
4284=cut
4285
4286sub _DayOfWeekToText {
4287    my ( $Self, $DayOfWeek ) = @_;
4288
4289    my %DayOfWeekTexts = (
4290        1 => 'Monday',
4291        2 => 'Tuesday',
4292        3 => 'Wednesday',
4293        4 => 'Thursday',
4294        5 => 'Friday',
4295        6 => 'Saturday',
4296        7 => 'Sunday',
4297    );
4298
4299    return if !$DayOfWeekTexts{$DayOfWeek};
4300
4301    return $DayOfWeekTexts{$DayOfWeek};
4302}
4303
4304=head2 _MondayOfWeek()
4305
4306Substitute for Date::Pcalc::Monday_of_Week(), using Kernel::System::DateTime, note different parameters
4307
4308=cut
4309
4310sub _MondayOfWeek {
4311    my ( $Self, $Year, $Month, $Day ) = @_;
4312
4313    my $DateTimeObject = $Kernel::OM->Create(
4314        'Kernel::System::DateTime',
4315        ObjectParams => {
4316            Year     => $Year,
4317            Month    => $Month,
4318            Day      => $Day,
4319            TimeZone => 'floating',
4320        },
4321    );
4322
4323    if ( !$DateTimeObject ) {
4324        $Kernel::OM->Get('Kernel::System::Log')->Log(
4325            Priority => "error",
4326            Message  => "Error creating DateTime object.",
4327        );
4328
4329        return;
4330    }
4331
4332    my $DateTimeValues = $DateTimeObject->Get();
4333    my $DaysToSubtract = $DateTimeValues->{DayOfWeek} - 1;
4334
4335    if ($DaysToSubtract) {
4336        $DateTimeObject->Subtract( Days => $DaysToSubtract );
4337        $DateTimeValues = $DateTimeObject->Get();
4338    }
4339
4340    return (
4341        $DateTimeValues->{Year},
4342        $DateTimeValues->{Month},
4343        $DateTimeValues->{Day},
4344    );
4345}
4346
4347=head2 _WeekOfYear()
4348
4349Substitute for Date::Pcalc::Week_of_Year(), using Kernel::System::DateTime
4350
4351=cut
4352
4353sub _WeekOfYear {
4354    my ( $Self, $Year, $Month, $Day ) = @_;
4355
4356    my $DateTimeObject = $Kernel::OM->Create(
4357        'Kernel::System::DateTime',
4358        ObjectParams => {
4359            Year     => $Year,
4360            Month    => $Month,
4361            Day      => $Day,
4362            TimeZone => 'floating',
4363        },
4364    );
4365
4366    if ( !$DateTimeObject ) {
4367        $Kernel::OM->Get('Kernel::System::Log')->Log(
4368            Priority => "error",
4369            Message  => "Error creating DateTime object.",
4370        );
4371
4372        return;
4373    }
4374
4375    return (
4376        $DateTimeObject->Format( Format => '%{week_number}' ),
4377        $DateTimeObject->Format( Format => '%{week_year}' ),
4378    );
4379}
4380
4381=head2 _HumanReadableAgeGet()
4382
4383Re-implementation of L<CustomerAge()|Kernel::Output::HTML::Layout/CustomerAge()> since this object is inaccessible from
4384the backend.
4385
4386TODO: Currently, there is no support for translation of statistic values, it's planned to be implemented later on. For
4387the time being, this method will return a string in English only.
4388
4389    my $HumanReadableAge = $StatsObject->_HumanReadableAgeGet(
4390        Age   => 360,
4391    );
4392
4393Returns (converted seconds in human readable format, i.e. '1 d 2 h'):
4394
4395    $HumanReadableAge = '6 h',
4396
4397=cut
4398
4399sub _HumanReadableAgeGet {
4400    my ( $Self, %Param ) = @_;
4401
4402    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4403
4404    my $Age       = defined( $Param{Age} ) ? $Param{Age} : return;
4405    my $AgeStrg   = '';
4406    my $DayDsc    = 'd';
4407    my $HourDsc   = 'h';
4408    my $MinuteDsc = 'm';
4409    if ( $ConfigObject->Get('TimeShowCompleteDescription') ) {
4410        $DayDsc    = 'day(s)';
4411        $HourDsc   = 'hour(s)';
4412        $MinuteDsc = 'minute(s)';
4413    }
4414    if ( $Age =~ /^-(.*)/ ) {
4415        $Age     = $1;
4416        $AgeStrg = '-';
4417    }
4418
4419    # get days
4420    if ( $Age >= 86400 ) {
4421        $AgeStrg .= int( ( $Age / 3600 ) / 24 ) . ' ';
4422        $AgeStrg .= $DayDsc . ' ';
4423    }
4424
4425    # get hours
4426    if ( $Age >= 3600 ) {
4427        $AgeStrg .= int( ( $Age / 3600 ) % 24 ) . ' ';
4428        $AgeStrg .= $HourDsc . ' ';
4429    }
4430
4431    # get minutes (just if age < 1 day)
4432    if ( $ConfigObject->Get('TimeShowAlwaysLong') || $Age < 86400 ) {
4433        $AgeStrg .= int( ( $Age / 60 ) % 60 ) . ' ';
4434        $AgeStrg .= $MinuteDsc;
4435    }
4436
4437    $AgeStrg =~ s/\s+$//;
4438
4439    return $AgeStrg;
4440}
4441
4442=head2 _TimeStamp2DateTime
4443
4444Return a datetime object from a timestamp.
4445
4446=cut
4447
4448sub _TimeStamp2DateTime {
4449    my ( $Self, %Param, ) = @_;
4450
4451    my $TimeStamp = $Param{TimeStamp};
4452    return $Kernel::OM->Create(
4453        'Kernel::System::DateTime',
4454        ObjectParams => {
4455            String => $TimeStamp,
4456        },
4457    );
4458}
4459
44601;
4461
4462=end Internal:
4463
4464=head1 TERMS AND CONDITIONS
4465
4466This software is part of the OTRS project (L<https://otrs.org/>).
4467
4468This software comes with ABSOLUTELY NO WARRANTY. For details, see
4469the enclosed file COPYING for license information (GPL). If you
4470did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
4471
4472=cut
4473