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