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::Package; 10 11use strict; 12use warnings; 13use utf8; 14 15use MIME::Base64; 16use File::Copy; 17 18use Kernel::Config; 19use Kernel::System::SysConfig; 20use Kernel::System::WebUserAgent; 21 22use Kernel::System::VariableCheck qw(:all); 23use Kernel::Language qw(Translatable); 24 25use parent qw(Kernel::System::EventHandler); 26 27our @ObjectDependencies = ( 28 'Kernel::Config', 29 'Kernel::System::Cache', 30 'Kernel::System::CloudService::Backend::Run', 31 'Kernel::System::DateTime', 32 'Kernel::System::DB', 33 'Kernel::System::Encode', 34 'Kernel::System::Environment', 35 'Kernel::System::JSON', 36 'Kernel::System::Loader', 37 'Kernel::System::Log', 38 'Kernel::System::Main', 39 'Kernel::System::OTRSBusiness', 40 'Kernel::System::Scheduler', 41 'Kernel::System::SysConfig::Migration', 42 'Kernel::System::SysConfig::XML', 43 'Kernel::System::SystemData', 44 'Kernel::System::XML', 45); 46 47=head1 NAME 48 49Kernel::System::Package - to manage application packages/modules 50 51=head1 DESCRIPTION 52 53All functions to manage application packages/modules. 54 55=encoding utf-8 56 57=head1 PUBLIC INTERFACE 58 59=head2 new() 60 61create an object 62 63 my $PackageObject = $Kernel::OM->Get('Kernel::System::Package'); 64 65=cut 66 67sub new { 68 my ( $Type, %Param ) = @_; 69 70 # allocate new hash for object 71 my $Self = {}; 72 bless( $Self, $Type ); 73 74 # get needed objects 75 $Self->{ConfigObject} = $Kernel::OM->Get('Kernel::Config'); 76 77 $Self->{PackageMap} = { 78 Name => 'SCALAR', 79 Version => 'SCALAR', 80 Vendor => 'SCALAR', 81 BuildDate => 'SCALAR', 82 BuildHost => 'SCALAR', 83 License => 'SCALAR', 84 URL => 'SCALAR', 85 ChangeLog => 'ARRAY', 86 Description => 'ARRAY', 87 Framework => 'ARRAY', 88 OS => 'ARRAY', 89 PackageRequired => 'ARRAY', 90 ModuleRequired => 'ARRAY', 91 IntroInstall => 'ARRAY', 92 IntroUninstall => 'ARRAY', 93 IntroUpgrade => 'ARRAY', 94 IntroReinstall => 'ARRAY', 95 PackageMerge => 'ARRAY', 96 97 # package flags 98 PackageIsVisible => 'SCALAR', 99 PackageIsDownloadable => 'SCALAR', 100 PackageIsRemovable => 'SCALAR', 101 PackageAllowDirectUpdate => 'SCALAR', 102 103 # *(Pre|Post) - just for compat. to 2.2 104 IntroInstallPre => 'ARRAY', 105 IntroInstallPost => 'ARRAY', 106 IntroUninstallPre => 'ARRAY', 107 IntroUninstallPost => 'ARRAY', 108 IntroUpgradePre => 'ARRAY', 109 IntroUpgradePost => 'ARRAY', 110 IntroReinstallPre => 'ARRAY', 111 IntroReinstallPost => 'ARRAY', 112 113 CodeInstall => 'ARRAY', 114 CodeUpgrade => 'ARRAY', 115 CodeUninstall => 'ARRAY', 116 CodeReinstall => 'ARRAY', 117 }; 118 $Self->{PackageMapFileList} = { 119 File => 'ARRAY', 120 }; 121 122 $Self->{Home} = $Self->{ConfigObject}->Get('Home'); 123 124 # init of event handler 125 $Self->EventHandlerInit( 126 Config => 'Package::EventModulePost', 127 ); 128 129 # reserve space for merged packages 130 $Self->{MergedPackages} = {}; 131 132 # check if cloud services are disabled 133 $Self->{CloudServicesDisabled} = $Self->{ConfigObject}->Get('CloudServices::Disabled') || 0; 134 135 return $Self; 136} 137 138=head2 RepositoryList() 139 140returns a list of repository packages 141 142 my @List = $PackageObject->RepositoryList(); 143 144 my @List = $PackageObject->RepositoryList( 145 Result => 'short', # will only return name, version, install_status md5sum, vendor and build commit ID 146 instead of the structure 147 ); 148 149=cut 150 151sub RepositoryList { 152 my ( $Self, %Param ) = @_; 153 154 my $Result = 'Full'; 155 if ( defined $Param{Result} && lc $Param{Result} eq 'short' ) { 156 $Result = 'Short'; 157 } 158 159 # get cache object 160 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 161 162 # check cache 163 my $Cache = $CacheObject->Get( 164 Type => "RepositoryList", 165 Key => $Result . 'List', 166 ); 167 return @{$Cache} if $Cache; 168 169 # get database object 170 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 171 172 # get repository list 173 $DBObject->Prepare( 174 SQL => 'SELECT name, version, install_status, content, vendor 175 FROM package_repository 176 ORDER BY name, create_time', 177 ); 178 179 # get main object 180 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 181 182 # fetch the data 183 my @Data; 184 while ( my @Row = $DBObject->FetchrowArray() ) { 185 186 my %Package = ( 187 Name => $Row[0], 188 Version => $Row[1], 189 Status => $Row[2], 190 Vendor => $Row[4], 191 ); 192 193 my $Content = $Row[3]; 194 195 if ( $Content && !$DBObject->GetDatabaseFunction('DirectBlob') ) { 196 197 # Backwards compatibility: don't decode existing values that were not yet properly Base64 encoded. 198 if ( $Content =~ m{ \A [a-zA-Z0-9+/\n]+ ={0,2} [\n]? \z }smx ) { # Does it look like Base64? 199 $Content = decode_base64($Content); 200 $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Content ); 201 } 202 } 203 204 # Correct any 'dos-style' line endings that might have been introduced by saving an 205 # opm file from a mail client on Windows (see http://bugs.otrs.org/show_bug.cgi?id=9838). 206 $Content =~ s{\r\n}{\n}xmsg; 207 $Package{MD5sum} = $MainObject->MD5sum( String => \$Content ); 208 209 # Extract and include build commit ID. 210 if ( $Content =~ m{ <BuildCommitID> (.*) </BuildCommitID> }smx ) { 211 $Package{BuildCommitID} = $1; 212 $Package{BuildCommitID} =~ s{ ^\s+|\s+$ }{}gsmx; 213 } 214 215 # get package attributes 216 if ( $Content && $Result eq 'Short' ) { 217 218 push @Data, {%Package}; 219 } 220 elsif ($Content) { 221 222 my %Structure = $Self->PackageParse( String => \$Content ); 223 push @Data, { %Package, %Structure }; 224 } 225 } 226 227 # set cache 228 $CacheObject->Set( 229 Type => 'RepositoryList', 230 Key => $Result . 'List', 231 Value => \@Data, 232 TTL => 30 * 24 * 60 * 60, 233 ); 234 235 return @Data; 236} 237 238=head2 RepositoryGet() 239 240get a package from local repository 241 242 my $Package = $PackageObject->RepositoryGet( 243 Name => 'Application A', 244 Version => '1.0', 245 ); 246 247 my $PackageScalar = $PackageObject->RepositoryGet( 248 Name => 'Application A', 249 Version => '1.0', 250 Result => 'SCALAR', 251 DisableWarnings => 1, # optional 252 ); 253 254=cut 255 256sub RepositoryGet { 257 my ( $Self, %Param ) = @_; 258 259 # check needed stuff 260 for my $Needed (qw(Name Version)) { 261 if ( !defined $Param{$Needed} ) { 262 $Kernel::OM->Get('Kernel::System::Log')->Log( 263 Priority => 'error', 264 Message => "$Needed not defined!", 265 ); 266 return; 267 } 268 } 269 270 # get cache object 271 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 272 273 # check cache 274 my $CacheKey = $Param{Name} . $Param{Version}; 275 my $Cache = $CacheObject->Get( 276 Type => 'RepositoryGet', 277 Key => $CacheKey, 278 ); 279 return $Cache if $Cache && $Param{Result} && $Param{Result} eq 'SCALAR'; 280 return ${$Cache} if $Cache; 281 282 # get database object 283 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 284 285 # get repository 286 $DBObject->Prepare( 287 SQL => 'SELECT content FROM package_repository WHERE name = ? AND version = ?', 288 Bind => [ \$Param{Name}, \$Param{Version} ], 289 Limit => 1, 290 ); 291 292 # fetch data 293 my $Package = ''; 294 ROW: 295 while ( my @Row = $DBObject->FetchrowArray() ) { 296 $Package = $Row[0]; 297 298 next ROW if $DBObject->GetDatabaseFunction('DirectBlob'); 299 300 # Backwards compatibility: don't decode existing values that were not yet properly Base64 encoded. 301 next ROW if $Package !~ m{ \A [a-zA-Z0-9+/\n]+ ={0,2} [\n]? \z }smx; # looks like Base64? 302 $Package = decode_base64($Package); 303 $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Package ); 304 } 305 306 if ( !$Package ) { 307 308 return if $Param{DisableWarnings}; 309 310 $Kernel::OM->Get('Kernel::System::Log')->Log( 311 Priority => 'notice', 312 Message => "No such package: $Param{Name}-$Param{Version}!", 313 ); 314 315 return; 316 } 317 318 # set cache 319 $CacheObject->Set( 320 Type => 'RepositoryGet', 321 Key => $CacheKey, 322 Value => \$Package, 323 TTL => 30 * 24 * 60 * 60, 324 ); 325 326 return \$Package if $Param{Result} && $Param{Result} eq 'SCALAR'; 327 return $Package; 328} 329 330=head2 RepositoryAdd() 331 332add a package to local repository 333 334 $PackageObject->RepositoryAdd( 335 String => $FileString, 336 FromCloud => 0, # optional 1 or 0, it indicates if package came from Cloud or not 337 ); 338 339=cut 340 341sub RepositoryAdd { 342 my ( $Self, %Param ) = @_; 343 344 # check needed stuff 345 if ( !defined $Param{String} ) { 346 $Kernel::OM->Get('Kernel::System::Log')->Log( 347 Priority => 'error', 348 Message => 'String not defined!', 349 ); 350 return; 351 } 352 353 # get from cloud flag 354 $Param{FromCloud} //= 0; 355 356 # get package attributes 357 my %Structure = $Self->PackageParse(%Param); 358 359 if ( !IsHashRefWithData( \%Structure ) ) { 360 $Kernel::OM->Get('Kernel::System::Log')->Log( 361 Priority => 'error', 362 Message => 'Invalid Package!', 363 ); 364 return; 365 } 366 if ( !$Structure{Name} ) { 367 $Kernel::OM->Get('Kernel::System::Log')->Log( 368 Priority => 'error', 369 Message => 'Need Name!', 370 ); 371 return; 372 } 373 if ( !$Structure{Version} ) { 374 $Kernel::OM->Get('Kernel::System::Log')->Log( 375 Priority => 'error', 376 Message => 'Need Version!', 377 ); 378 return; 379 } 380 381 # check if package already exists 382 my $PackageExists = $Self->RepositoryGet( 383 Name => $Structure{Name}->{Content}, 384 Version => $Structure{Version}->{Content}, 385 Result => 'SCALAR', 386 DisableWarnings => 1, 387 ); 388 389 # get database object 390 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 391 392 if ($PackageExists) { 393 $DBObject->Do( 394 SQL => 'DELETE FROM package_repository WHERE name = ? AND version = ?', 395 Bind => [ \$Structure{Name}->{Content}, \$Structure{Version}->{Content} ], 396 ); 397 } 398 399 # add new package 400 my $FileName = $Structure{Name}->{Content} . '-' . $Structure{Version}->{Content} . '.xml'; 401 402 my $Content = $Param{String}; 403 if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) { 404 $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Content ); 405 $Content = encode_base64($Content); 406 } 407 408 return if !$DBObject->Do( 409 SQL => 'INSERT INTO package_repository (name, version, vendor, filename, ' 410 . ' content_type, content, install_status, ' 411 . ' create_time, create_by, change_time, change_by)' 412 . ' VALUES (?, ?, ?, ?, \'text/xml\', ?, \'' 413 . Translatable('not installed') . '\', ' 414 . ' current_timestamp, 1, current_timestamp, 1)', 415 Bind => [ 416 \$Structure{Name}->{Content}, \$Structure{Version}->{Content}, 417 \$Structure{Vendor}->{Content}, \$FileName, \$Content, 418 ], 419 ); 420 421 # cleanup cache 422 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 423 Type => 'RepositoryList', 424 ); 425 426 return 1; 427} 428 429=head2 RepositoryRemove() 430 431remove a package from local repository 432 433 $PackageObject->RepositoryRemove( 434 Name => 'Application A', 435 Version => '1.0', 436 ); 437 438=cut 439 440sub RepositoryRemove { 441 my ( $Self, %Param ) = @_; 442 443 # check needed stuff 444 if ( !defined $Param{Name} ) { 445 $Kernel::OM->Get('Kernel::System::Log')->Log( 446 Priority => 'error', 447 Message => 'Name not defined!', 448 ); 449 return; 450 } 451 452 # create sql 453 my @Bind = ( \$Param{Name} ); 454 my $SQL = 'DELETE FROM package_repository WHERE name = ?'; 455 if ( $Param{Version} ) { 456 $SQL .= ' AND version = ?'; 457 push @Bind, \$Param{Version}; 458 } 459 460 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 461 SQL => $SQL, 462 Bind => \@Bind, 463 ); 464 465 # get cache object 466 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 467 468 # cleanup cache 469 $Self->_RepositoryCacheClear(); 470 471 return 1; 472} 473 474=head2 PackageInstall() 475 476install a package 477 478 $PackageObject->PackageInstall( 479 String => $FileString, 480 Force => 1, # optional 1 or 0, for to install package even if validation fails 481 FromCloud => 1, # optional 1 or 0, it indicates if package's origin is Cloud or not 482 ); 483 484=cut 485 486sub PackageInstall { 487 my ( $Self, %Param ) = @_; 488 489 # check needed stuff 490 if ( !defined $Param{String} ) { 491 $Kernel::OM->Get('Kernel::System::Log')->Log( 492 Priority => 'error', 493 Message => 'String not defined!', 494 ); 495 return; 496 } 497 498 # Cleanup the repository cache before the package installation to have the current state 499 # during the installation. 500 $Self->_RepositoryCacheClear(); 501 502 # get from cloud flag 503 my $FromCloud = $Param{FromCloud} || 0; 504 505 # conflict check 506 my %Structure = $Self->PackageParse(%Param); 507 508 # check if package is already installed 509 if ( $Self->PackageIsInstalled( Name => $Structure{Name}->{Content} ) ) { 510 if ( !$Param{Force} ) { 511 $Kernel::OM->Get('Kernel::System::Log')->Log( 512 Priority => 'notice', 513 Message => 'Package already installed, try upgrade!', 514 ); 515 return $Self->PackageUpgrade(%Param); 516 } 517 } 518 519 # write permission check 520 return if !$Self->_FileSystemCheck(); 521 522 # check OS 523 if ( $Structure{OS} && !$Param{Force} ) { 524 return if !$Self->_OSCheck( OS => $Structure{OS} ); 525 } 526 527 # check framework 528 if ( $Structure{Framework} && !$Param{Force} ) { 529 my %Check = $Self->AnalyzePackageFrameworkRequirements( 530 Framework => $Structure{Framework}, 531 ); 532 return if !$Check{Success}; 533 } 534 535 # check required packages 536 if ( $Structure{PackageRequired} && !$Param{Force} ) { 537 return if !$Self->_CheckPackageRequired( 538 %Param, 539 PackageRequired => $Structure{PackageRequired}, 540 ); 541 } 542 543 # check required modules 544 if ( $Structure{ModuleRequired} && !$Param{Force} ) { 545 return if !$Self->_CheckModuleRequired( 546 %Param, 547 ModuleRequired => $Structure{ModuleRequired}, 548 ); 549 } 550 551 # check merged packages 552 if ( $Structure{PackageMerge} ) { 553 554 # upgrade merged packages (no files) 555 return if !$Self->_MergedPackages( 556 %Param, 557 Structure => \%Structure, 558 ); 559 } 560 561 # check files 562 my $FileCheckOk = 1; 563 if ( !$FileCheckOk && !$Param{Force} ) { 564 $Kernel::OM->Get('Kernel::System::Log')->Log( 565 Priority => 'error', 566 Message => 'File conflict, can\'t install package!', 567 ); 568 return; 569 } 570 571 # check if one of this files is already intalled by an other package 572 if ( %Structure && !$Param{Force} ) { 573 return if !$Self->_PackageFileCheck( 574 Structure => \%Structure, 575 ); 576 } 577 578 # install code (pre) 579 if ( $Structure{CodeInstall} ) { 580 $Self->_Code( 581 Code => $Structure{CodeInstall}, 582 Type => 'pre', 583 Structure => \%Structure, 584 ); 585 } 586 587 # install database (pre) 588 if ( $Structure{DatabaseInstall} && $Structure{DatabaseInstall}->{pre} ) { 589 590 my $DatabaseInstall = $Self->_CheckDBInstalledOrMerged( Database => $Structure{DatabaseInstall}->{pre} ); 591 592 if ( IsArrayRefWithData($DatabaseInstall) ) { 593 $Self->_Database( Database => $DatabaseInstall ); 594 } 595 } 596 597 # install files 598 if ( $Structure{Filelist} && ref $Structure{Filelist} eq 'ARRAY' ) { 599 for my $File ( @{ $Structure{Filelist} } ) { 600 $Self->_FileInstall( File => $File ); 601 } 602 } 603 604 # add package 605 return if !$Self->RepositoryAdd( 606 String => $Param{String}, 607 FromCloud => $FromCloud, 608 ); 609 610 # update package status 611 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 612 SQL => 'UPDATE package_repository SET install_status = \'' 613 . Translatable('installed') . '\'' 614 . ' WHERE name = ? AND version = ?', 615 Bind => [ 616 \$Structure{Name}->{Content}, 617 \$Structure{Version}->{Content}, 618 ], 619 ); 620 621 # install config 622 $Self->_ConfigurationDeploy( 623 Comments => "Package Install $Structure{Name}->{Content} $Structure{Version}->{Content}", 624 Package => $Structure{Name}->{Content}, 625 Action => 'PackageInstall', 626 ); 627 628 # install database (post) 629 if ( $Structure{DatabaseInstall} && $Structure{DatabaseInstall}->{post} ) { 630 631 my $DatabaseInstall = $Self->_CheckDBInstalledOrMerged( Database => $Structure{DatabaseInstall}->{post} ); 632 633 if ( IsArrayRefWithData($DatabaseInstall) ) { 634 $Self->_Database( Database => $DatabaseInstall ); 635 } 636 } 637 638 # install code (post) 639 if ( $Structure{CodeInstall} ) { 640 $Self->_Code( 641 Code => $Structure{CodeInstall}, 642 Type => 'post', 643 Structure => \%Structure, 644 ); 645 } 646 647 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 648 KeepTypes => [ 649 'XMLParse', 650 'SysConfigDefaultListGet', 651 'SysConfigDefaultList', 652 'SysConfigDefault', 653 'SysConfigPersistent', 654 'SysConfigModifiedList', 655 ], 656 ); 657 $Kernel::OM->Get('Kernel::System::Loader')->CacheDelete(); 658 659 # trigger event 660 $Self->EventHandler( 661 Event => 'PackageInstall', 662 Data => { 663 Name => $Structure{Name}->{Content}, 664 Vendor => $Structure{Vendor}->{Content}, 665 Version => $Structure{Version}->{Content}, 666 }, 667 UserID => 1, 668 ); 669 670 return 1; 671} 672 673=head2 PackageReinstall() 674 675reinstall files of a package 676 677 $PackageObject->PackageReinstall( String => $FileString ); 678 679=cut 680 681sub PackageReinstall { 682 my ( $Self, %Param ) = @_; 683 684 # check needed stuff 685 if ( !defined $Param{String} ) { 686 $Kernel::OM->Get('Kernel::System::Log')->Log( 687 Priority => 'error', 688 Message => 'String not defined!', 689 ); 690 return; 691 } 692 693 # Cleanup the repository cache before the package reinstallation to have the current state 694 # during the reinstallation. 695 $Self->_RepositoryCacheClear(); 696 697 # parse source file 698 my %Structure = $Self->PackageParse(%Param); 699 700 # write permission check 701 return if !$Self->_FileSystemCheck(); 702 703 # check OS 704 if ( $Structure{OS} && !$Param{Force} ) { 705 return if !$Self->_OSCheck( OS => $Structure{OS} ); 706 } 707 708 # check framework 709 if ( $Structure{Framework} && !$Param{Force} ) { 710 my %Check = $Self->AnalyzePackageFrameworkRequirements( 711 Framework => $Structure{Framework}, 712 ); 713 return if !$Check{Success}; 714 } 715 716 # reinstall code (pre) 717 if ( $Structure{CodeReinstall} ) { 718 $Self->_Code( 719 Code => $Structure{CodeReinstall}, 720 Type => 'pre', 721 Structure => \%Structure, 722 ); 723 } 724 725 # install files 726 if ( $Structure{Filelist} && ref $Structure{Filelist} eq 'ARRAY' ) { 727 for my $File ( @{ $Structure{Filelist} } ) { 728 729 # install file 730 $Self->_FileInstall( 731 File => $File, 732 Reinstall => 1, 733 ); 734 } 735 } 736 737 # install config 738 $Self->_ConfigurationDeploy( 739 Comments => "Package Reinstall $Structure{Name}->{Content} $Structure{Version}->{Content}", 740 Package => $Structure{Name}->{Content}, 741 Action => 'PackageReinstall', 742 ); 743 744 # reinstall code (post) 745 if ( $Structure{CodeReinstall} ) { 746 $Self->_Code( 747 Code => $Structure{CodeReinstall}, 748 Type => 'post', 749 Structure => \%Structure, 750 ); 751 } 752 753 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 754 KeepTypes => [ 755 'XMLParse', 756 'SysConfigDefaultListGet', 757 'SysConfigDefaultList', 758 'SysConfigDefault', 759 'SysConfigPersistent', 760 'SysConfigModifiedList', 761 ], 762 ); 763 $Kernel::OM->Get('Kernel::System::Loader')->CacheDelete(); 764 765 # trigger event 766 $Self->EventHandler( 767 Event => 'PackageReinstall', 768 Data => { 769 Name => $Structure{Name}->{Content}, 770 Vendor => $Structure{Vendor}->{Content}, 771 Version => $Structure{Version}->{Content}, 772 }, 773 UserID => 1, 774 ); 775 776 return 1; 777} 778 779=head2 PackageUpgrade() 780 781upgrade a package 782 783 $PackageObject->PackageUpgrade( 784 String => $FileString, 785 Force => 1, # optional 1 or 0, for to install package even if validation fails 786 ); 787 788=cut 789 790sub PackageUpgrade { 791 my ( $Self, %Param ) = @_; 792 793 # check needed stuff 794 if ( !defined $Param{String} ) { 795 $Kernel::OM->Get('Kernel::System::Log')->Log( 796 Priority => 'error', 797 Message => 'String not defined!', 798 ); 799 return; 800 } 801 802 # Cleanup the repository cache before the package upgrade to have the current state 803 # during the upgrade. 804 $Self->_RepositoryCacheClear(); 805 806 # conflict check 807 my %Structure = $Self->PackageParse(%Param); 808 809 # check if package is already installed 810 my %InstalledStructure; 811 my $Installed = 0; 812 my $InstalledVersion = 0; 813 for my $Package ( $Self->RepositoryList() ) { 814 815 if ( $Structure{Name}->{Content} eq $Package->{Name}->{Content} ) { 816 817 if ( $Package->{Status} =~ /^installed$/i ) { 818 $Installed = 1; 819 $InstalledVersion = $Package->{Version}->{Content}; 820 %InstalledStructure = %{$Package}; 821 } 822 } 823 } 824 825 if ( !$Installed ) { 826 $Kernel::OM->Get('Kernel::System::Log')->Log( 827 Priority => 'notice', 828 Message => 'Package is not installed, try a installation!', 829 ); 830 return $Self->PackageInstall(%Param); 831 } 832 833 # write permission check 834 return if !$Self->_FileSystemCheck(); 835 836 # check OS 837 if ( $Structure{OS} && !$Param{Force} ) { 838 return if !$Self->_OSCheck( OS => $Structure{OS} ); 839 } 840 841 # check framework 842 if ( $Structure{Framework} && !$Param{Force} ) { 843 my %Check = $Self->AnalyzePackageFrameworkRequirements( 844 Framework => $Structure{Framework}, 845 ); 846 return if !$Check{Success}; 847 } 848 849 # check required packages 850 if ( $Structure{PackageRequired} && !$Param{Force} ) { 851 852 return if !$Self->_CheckPackageRequired( 853 %Param, 854 PackageRequired => $Structure{PackageRequired}, 855 ); 856 } 857 858 # check required modules 859 if ( $Structure{ModuleRequired} && !$Param{Force} ) { 860 861 return if !$Self->_CheckModuleRequired( 862 %Param, 863 ModuleRequired => $Structure{ModuleRequired}, 864 ); 865 } 866 867 # check merged packages 868 if ( $Structure{PackageMerge} ) { 869 870 # upgrade merged packages (no files) 871 return if !$Self->_MergedPackages( 872 %Param, 873 Structure => \%Structure, 874 ); 875 } 876 877 # check version 878 my $CheckVersion = $Self->_CheckVersion( 879 VersionNew => $Structure{Version}->{Content}, 880 VersionInstalled => $InstalledVersion, 881 Type => 'Max', 882 ); 883 884 if ( !$CheckVersion ) { 885 886 if ( $Structure{Version}->{Content} eq $InstalledVersion ) { 887 $Kernel::OM->Get('Kernel::System::Log')->Log( 888 Priority => 'error', 889 Message => 890 "Can't upgrade, package '$Structure{Name}->{Content}-$InstalledVersion' already installed!", 891 ); 892 893 return if !$Param{Force}; 894 } 895 else { 896 $Kernel::OM->Get('Kernel::System::Log')->Log( 897 Priority => 'error', 898 Message => 899 "Can't upgrade, installed package '$InstalledVersion' is newer as '$Structure{Version}->{Content}'!", 900 ); 901 902 return if !$Param{Force}; 903 } 904 } 905 906 # check if one of this files is already installed by an other package 907 if ( %Structure && !$Param{Force} ) { 908 return if !$Self->_PackageFileCheck( 909 Structure => \%Structure, 910 ); 911 } 912 913 # remove old package 914 return if !$Self->RepositoryRemove( Name => $Structure{Name}->{Content} ); 915 916 # add new package 917 return if !$Self->RepositoryAdd( String => $Param{String} ); 918 919 # update package status 920 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 921 SQL => 'UPDATE package_repository SET install_status = \'' 922 . Translatable('installed') . '\'' 923 . ' WHERE name = ? AND version = ?', 924 Bind => [ 925 \$Structure{Name}->{Content}, \$Structure{Version}->{Content}, 926 ], 927 ); 928 929 # upgrade code (pre) 930 if ( $Structure{CodeUpgrade} && ref $Structure{CodeUpgrade} eq 'ARRAY' ) { 931 932 my @Parts; 933 PART: 934 for my $Part ( @{ $Structure{CodeUpgrade} } ) { 935 936 if ( $Part->{Version} ) { 937 938 # skip code upgrade block if its version is bigger than the new package version 939 my $CheckVersion = $Self->_CheckVersion( 940 VersionNew => $Part->{Version}, 941 VersionInstalled => $Structure{Version}->{Content}, 942 Type => 'Max', 943 ); 944 945 next PART if $CheckVersion; 946 947 $CheckVersion = $Self->_CheckVersion( 948 VersionNew => $Part->{Version}, 949 VersionInstalled => $InstalledVersion, 950 Type => 'Min', 951 ); 952 953 if ( !$CheckVersion ) { 954 push @Parts, $Part; 955 } 956 } 957 else { 958 push @Parts, $Part; 959 } 960 } 961 962 $Self->_Code( 963 Code => \@Parts, 964 Type => 'pre', 965 Structure => \%Structure, 966 ); 967 } 968 969 # upgrade database (pre) 970 if ( $Structure{DatabaseUpgrade}->{pre} && ref $Structure{DatabaseUpgrade}->{pre} eq 'ARRAY' ) { 971 972 my @Parts; 973 my $Use = 0; 974 my $UseInstalled; 975 my $NotUseTag; 976 my $NotUseTagLevel; 977 PARTDB: 978 for my $Part ( @{ $Structure{DatabaseUpgrade}->{pre} } ) { 979 980 if ( !$UseInstalled ) { 981 982 if ( 983 $Part->{TagType} eq 'End' 984 && ( defined $NotUseTag && $Part->{Tag} eq $NotUseTag ) 985 && ( defined $NotUseTagLevel && $Part->{TagLevel} eq $NotUseTagLevel ) 986 ) 987 { 988 $UseInstalled = 1; 989 } 990 991 next PARTDB; 992 993 } 994 elsif ( 995 ( 996 defined $Part->{IfPackage} 997 && !$Self->{MergedPackages}->{ $Part->{IfPackage} } 998 ) 999 || ( 1000 defined $Part->{IfNotPackage} 1001 && 1002 ( 1003 defined $Self->{MergedPackages}->{ $Part->{IfNotPackage} } 1004 || $Self->PackageIsInstalled( Name => $Part->{IfNotPackage} ) 1005 ) 1006 ) 1007 ) 1008 { 1009 # store Tag and TagLevel to be used later and found the end of this level 1010 $NotUseTag = $Part->{Tag}; 1011 $NotUseTagLevel = $Part->{TagLevel}; 1012 1013 $UseInstalled = 0; 1014 1015 next PARTDB; 1016 } 1017 1018 if ( $Part->{TagLevel} == 3 && $Part->{Version} ) { 1019 1020 my $CheckVersion = $Self->_CheckVersion( 1021 VersionNew => $Part->{Version}, 1022 VersionInstalled => $InstalledVersion, 1023 Type => 'Min', 1024 ); 1025 1026 if ( !$CheckVersion ) { 1027 $Use = 1; 1028 @Parts = (); 1029 push @Parts, $Part; 1030 } 1031 } 1032 elsif ( $Use && $Part->{TagLevel} == 3 && $Part->{TagType} eq 'End' ) { 1033 $Use = 0; 1034 push @Parts, $Part; 1035 $Self->_Database( Database => \@Parts ); 1036 } 1037 elsif ($Use) { 1038 push @Parts, $Part; 1039 } 1040 } 1041 } 1042 1043 # uninstall old package files 1044 if ( $InstalledStructure{Filelist} && ref $InstalledStructure{Filelist} eq 'ARRAY' ) { 1045 for my $File ( @{ $InstalledStructure{Filelist} } ) { 1046 1047 # remove file 1048 $Self->_FileRemove( File => $File ); 1049 } 1050 } 1051 1052 # install files 1053 if ( $Structure{Filelist} && ref $Structure{Filelist} eq 'ARRAY' ) { 1054 for my $File ( @{ $Structure{Filelist} } ) { 1055 1056 # install file 1057 $Self->_FileInstall( File => $File ); 1058 } 1059 } 1060 1061 # install config 1062 $Self->_ConfigurationDeploy( 1063 Comments => "Package Upgrade $Structure{Name}->{Content} $Structure{Version}->{Content}", 1064 Package => $Structure{Name}->{Content}, 1065 Action => 'PackageUpgrade', 1066 ); 1067 1068 # upgrade database (post) 1069 if ( $Structure{DatabaseUpgrade}->{post} && ref $Structure{DatabaseUpgrade}->{post} eq 'ARRAY' ) 1070 { 1071 1072 my @Parts; 1073 my $Use = 0; 1074 my $UseInstalled = 1; 1075 my $NotUseTag; 1076 my $NotUseTagLevel; 1077 PARTDB: 1078 for my $Part ( @{ $Structure{DatabaseUpgrade}->{post} } ) { 1079 1080 if ( !$UseInstalled ) { 1081 1082 if ( 1083 $Part->{TagType} eq 'End' 1084 && ( defined $NotUseTag && $Part->{Tag} eq $NotUseTag ) 1085 && ( defined $NotUseTagLevel && $Part->{TagLevel} eq $NotUseTagLevel ) 1086 ) 1087 { 1088 $UseInstalled = 1; 1089 } 1090 1091 next PARTDB; 1092 1093 } 1094 elsif ( 1095 ( 1096 defined $Part->{IfPackage} 1097 && !$Self->{MergedPackages}->{ $Part->{IfPackage} } 1098 ) 1099 || ( 1100 defined $Part->{IfNotPackage} 1101 && ( 1102 defined $Self->{MergedPackages}->{ $Part->{IfNotPackage} } 1103 || $Self->PackageIsInstalled( Name => $Part->{IfNotPackage} ) 1104 ) 1105 ) 1106 ) 1107 { 1108 # store Tag and TagLevel to be used later and found the end of this level 1109 $NotUseTag = $Part->{Tag}; 1110 $NotUseTagLevel = $Part->{TagLevel}; 1111 1112 $UseInstalled = 0; 1113 1114 next PARTDB; 1115 } 1116 1117 if ( $Part->{TagLevel} == 3 && $Part->{Version} ) { 1118 1119 my $CheckVersion = $Self->_CheckVersion( 1120 VersionNew => $Part->{Version}, 1121 VersionInstalled => $InstalledVersion, 1122 Type => 'Min', 1123 ); 1124 1125 if ( !$CheckVersion ) { 1126 $Use = 1; 1127 @Parts = (); 1128 push @Parts, $Part; 1129 } 1130 } 1131 elsif ( $Use && $Part->{TagLevel} == 3 && $Part->{TagType} eq 'End' ) { 1132 1133 $Use = 0; 1134 push @Parts, $Part; 1135 $Self->_Database( Database => \@Parts ); 1136 } 1137 elsif ($Use) { 1138 push @Parts, $Part; 1139 } 1140 } 1141 } 1142 1143 # upgrade code (post) 1144 if ( $Structure{CodeUpgrade} && ref $Structure{CodeUpgrade} eq 'ARRAY' ) { 1145 1146 my @Parts; 1147 PART: 1148 for my $Part ( @{ $Structure{CodeUpgrade} } ) { 1149 1150 if ( $Part->{Version} ) { 1151 1152 # skip code upgrade block if its version is bigger than the new package version 1153 my $CheckVersion = $Self->_CheckVersion( 1154 VersionNew => $Part->{Version}, 1155 VersionInstalled => $Structure{Version}->{Content}, 1156 Type => 'Max', 1157 ); 1158 1159 next PART if $CheckVersion; 1160 1161 $CheckVersion = $Self->_CheckVersion( 1162 VersionNew => $Part->{Version}, 1163 VersionInstalled => $InstalledVersion, 1164 Type => 'Min', 1165 ); 1166 1167 if ( !$CheckVersion ) { 1168 push @Parts, $Part; 1169 } 1170 } 1171 else { 1172 push @Parts, $Part; 1173 } 1174 } 1175 1176 $Self->_Code( 1177 Code => \@Parts, 1178 Type => 'post', 1179 Structure => \%Structure, 1180 ); 1181 } 1182 1183 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 1184 KeepTypes => [ 1185 'XMLParse', 1186 'SysConfigDefaultListGet', 1187 'SysConfigDefaultList', 1188 'SysConfigDefault', 1189 'SysConfigPersistent', 1190 'SysConfigModifiedList', 1191 ], 1192 ); 1193 $Kernel::OM->Get('Kernel::System::Loader')->CacheDelete(); 1194 1195 # trigger event 1196 $Self->EventHandler( 1197 Event => 'PackageUpgrade', 1198 Data => { 1199 Name => $Structure{Name}->{Content}, 1200 Vendor => $Structure{Vendor}->{Content}, 1201 Version => $Structure{Version}->{Content}, 1202 }, 1203 UserID => 1, 1204 ); 1205 1206 return 1; 1207} 1208 1209=head2 PackageUninstall() 1210 1211uninstall a package 1212 1213 $PackageObject->PackageUninstall( String => $FileString ); 1214 1215=cut 1216 1217sub PackageUninstall { 1218 my ( $Self, %Param ) = @_; 1219 1220 # check needed stuff 1221 if ( !defined $Param{String} ) { 1222 $Kernel::OM->Get('Kernel::System::Log')->Log( 1223 Priority => 'error', 1224 Message => 'String not defined!' 1225 ); 1226 return; 1227 } 1228 1229 # Cleanup the repository cache before the package uninstallation to have the current state 1230 # during the uninstallation. 1231 $Self->_RepositoryCacheClear(); 1232 1233 # parse source file 1234 my %Structure = $Self->PackageParse(%Param); 1235 1236 # check depends 1237 if ( !$Param{Force} ) { 1238 return if !$Self->_CheckPackageDepends( Name => $Structure{Name}->{Content} ); 1239 } 1240 1241 # write permission check 1242 return if !$Self->_FileSystemCheck(); 1243 1244 # uninstall code (pre) 1245 if ( $Structure{CodeUninstall} ) { 1246 $Self->_Code( 1247 Code => $Structure{CodeUninstall}, 1248 Type => 'pre', 1249 Structure => \%Structure, 1250 ); 1251 } 1252 1253 # uninstall database (pre) 1254 if ( $Structure{DatabaseUninstall} && $Structure{DatabaseUninstall}->{pre} ) { 1255 $Self->_Database( Database => $Structure{DatabaseUninstall}->{pre} ); 1256 } 1257 1258 # files 1259 my $FileCheckOk = 1; 1260 if ( $Structure{Filelist} && ref $Structure{Filelist} eq 'ARRAY' ) { 1261 for my $File ( @{ $Structure{Filelist} } ) { 1262 1263 # remove file 1264 $Self->_FileRemove( File => $File ); 1265 } 1266 } 1267 1268 # remove old packages 1269 $Self->RepositoryRemove( Name => $Structure{Name}->{Content} ); 1270 1271 # install config 1272 $Self->_ConfigurationDeploy( 1273 Comments => "Package Uninstall $Structure{Name}->{Content} $Structure{Version}->{Content}", 1274 Package => $Structure{Name}->{Content}, 1275 Action => 'PackageUninstall', 1276 ); 1277 1278 # uninstall database (post) 1279 if ( $Structure{DatabaseUninstall} && $Structure{DatabaseUninstall}->{post} ) { 1280 $Self->_Database( Database => $Structure{DatabaseUninstall}->{post} ); 1281 } 1282 1283 # uninstall code (post) 1284 if ( $Structure{CodeUninstall} ) { 1285 $Self->_Code( 1286 Code => $Structure{CodeUninstall}, 1287 Type => 'post', 1288 Structure => \%Structure, 1289 ); 1290 } 1291 1292 # install config 1293 $Self->{ConfigObject} = Kernel::Config->new( %{$Self} ); 1294 1295 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 1296 KeepTypes => [ 1297 'XMLParse', 1298 'SysConfigDefaultListGet', 1299 'SysConfigDefaultList', 1300 'SysConfigDefault', 1301 'SysConfigPersistent', 1302 'SysConfigModifiedList', 1303 ], 1304 ); 1305 $Kernel::OM->Get('Kernel::System::Loader')->CacheDelete(); 1306 1307 # trigger event 1308 $Self->EventHandler( 1309 Event => 'PackageUninstall', 1310 Data => { 1311 Name => $Structure{Name}->{Content}, 1312 Vendor => $Structure{Vendor}->{Content}, 1313 Version => $Structure{Version}->{Content}, 1314 }, 1315 UserID => 1, 1316 ); 1317 1318 return 1; 1319} 1320 1321=head2 PackageOnlineRepositories() 1322 1323returns a list of available online repositories 1324 1325 my %List = $PackageObject->PackageOnlineRepositories(); 1326 1327=cut 1328 1329sub PackageOnlineRepositories { 1330 my ( $Self, %Param ) = @_; 1331 1332 # check if online repository should be fetched 1333 return () if !$Self->{ConfigObject}->Get('Package::RepositoryRoot'); 1334 1335 # get repository list 1336 my $XML = ''; 1337 URL: 1338 for my $URL ( @{ $Self->{ConfigObject}->Get('Package::RepositoryRoot') } ) { 1339 1340 $XML = $Self->_Download( URL => $URL ); 1341 1342 last URL if $XML; 1343 } 1344 1345 return if !$XML; 1346 1347 my @XMLARRAY = $Kernel::OM->Get('Kernel::System::XML')->XMLParse( String => $XML ); 1348 1349 my %List; 1350 my $Name = ''; 1351 1352 TAG: 1353 for my $Tag (@XMLARRAY) { 1354 1355 # just use start tags 1356 next TAG if $Tag->{TagType} ne 'Start'; 1357 1358 # reset package data 1359 if ( $Tag->{Tag} eq 'Repository' ) { 1360 $Name = ''; 1361 } 1362 elsif ( $Tag->{Tag} eq 'Name' ) { 1363 $Name = $Tag->{Content}; 1364 } 1365 elsif ( $Tag->{Tag} eq 'URL' ) { 1366 if ($Name) { 1367 $List{ $Tag->{Content} } = $Name; 1368 } 1369 } 1370 } 1371 1372 return %List; 1373} 1374 1375=head2 PackageOnlineList() 1376 1377returns a list of available on-line packages 1378 1379 my @List = $PackageObject->PackageOnlineList( 1380 URL => '', 1381 Lang => 'en', 1382 Cache => 0, # (optional) do not use cached data 1383 FromCloud => 1, # optional 1 or 0, it indicates if a Cloud Service 1384 # should be used for getting the packages list 1385 IncludeSameVersion => 1, # (optional) to also get packages already installed and with the same version 1386 ); 1387 1388=cut 1389 1390sub PackageOnlineList { 1391 my ( $Self, %Param ) = @_; 1392 1393 # check needed stuff 1394 for my $Needed (qw(URL Lang)) { 1395 if ( !defined $Param{$Needed} ) { 1396 $Kernel::OM->Get('Kernel::System::Log')->Log( 1397 Priority => 'error', 1398 Message => "$Needed not defined!", 1399 ); 1400 return; 1401 } 1402 } 1403 if ( !defined $Param{Cache} ) { 1404 1405 if ( $Param{URL} =~ m{ \.otrs\.org\/ }xms ) { 1406 $Param{Cache} = 1; 1407 } 1408 else { 1409 $Param{Cache} = 0; 1410 } 1411 } 1412 1413 $Param{IncludeSameVersion} //= 0; 1414 1415 # get cache object 1416 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1417 1418 # check cache 1419 my $CacheKey = $Param{URL} . '-' . $Param{Lang} . '-' . $Param{IncludeSameVersion}; 1420 if ( $Param{Cache} ) { 1421 my $Cache = $CacheObject->Get( 1422 Type => 'PackageOnlineList', 1423 Key => $CacheKey, 1424 ); 1425 return @{$Cache} if $Cache; 1426 } 1427 1428 my @Packages; 1429 my %Package; 1430 my $Filelist; 1431 if ( !$Param{FromCloud} ) { 1432 1433 my $XML = $Self->_Download( URL => $Param{URL} . '/otrs.xml' ); 1434 return if !$XML; 1435 1436 my @XMLARRAY = $Kernel::OM->Get('Kernel::System::XML')->XMLParse( String => $XML ); 1437 1438 if ( !@XMLARRAY ) { 1439 $Kernel::OM->Get('Kernel::System::Log')->Log( 1440 Priority => 'error', 1441 Message => Translatable('Unable to parse repository index document.'), 1442 ); 1443 return; 1444 } 1445 1446 TAG: 1447 for my $Tag (@XMLARRAY) { 1448 1449 # remember package 1450 if ( $Tag->{TagType} eq 'End' && $Tag->{Tag} eq 'Package' ) { 1451 if (%Package) { 1452 push @Packages, {%Package}; 1453 } 1454 next TAG; 1455 } 1456 1457 # just use start tags 1458 next TAG if $Tag->{TagType} ne 'Start'; 1459 1460 # reset package data 1461 if ( $Tag->{Tag} eq 'Package' ) { 1462 %Package = (); 1463 $Filelist = 0; 1464 } 1465 elsif ( $Tag->{Tag} eq 'Framework' ) { 1466 push @{ $Package{Framework} }, $Tag; 1467 } 1468 elsif ( $Tag->{Tag} eq 'Filelist' ) { 1469 $Filelist = 1; 1470 } 1471 elsif ( $Filelist && $Tag->{Tag} eq 'FileDoc' ) { 1472 push @{ $Package{Filelist} }, $Tag; 1473 } 1474 elsif ( $Tag->{Tag} eq 'Description' ) { 1475 if ( !$Package{Description} ) { 1476 $Package{Description} = $Tag->{Content}; 1477 } 1478 if ( $Tag->{Lang} eq $Param{Lang} ) { 1479 $Package{Description} = $Tag->{Content}; 1480 } 1481 } 1482 elsif ( $Tag->{Tag} eq 'PackageRequired' ) { 1483 push @{ $Package{PackageRequired} }, $Tag; 1484 } 1485 else { 1486 $Package{ $Tag->{Tag} } = $Tag->{Content}; 1487 } 1488 } 1489 1490 } 1491 else { 1492 1493 # On this case a cloud service is used, a URL is not 1494 # needed, instead a operation name, present on the URL 1495 # parameter in order to match with the previous structure 1496 my $Operation = $Param{URL}; 1497 1498 # get list from cloud 1499 my $ListResult = $Self->CloudFileGet( 1500 Operation => $Operation, 1501 Data => { 1502 Language => $Param{Lang}, 1503 PackageRequired => 1, 1504 }, 1505 ); 1506 1507 # check result structure 1508 return if !IsHashRefWithData($ListResult); 1509 1510 my $CurrentFramework = $Kernel::OM->Get('Kernel::Config')->Get('Version'); 1511 FRAMEWORKVERSION: 1512 for my $FrameworkVersion ( sort keys %{$ListResult} ) { 1513 my $FrameworkVersionMatch = $FrameworkVersion; 1514 $FrameworkVersionMatch =~ s/\./\\\./g; 1515 $FrameworkVersionMatch =~ s/x/.+?/gi; 1516 1517 if ( $CurrentFramework =~ m{ \A $FrameworkVersionMatch }xms ) { 1518 1519 @Packages = @{ $ListResult->{$FrameworkVersion} }; 1520 last FRAMEWORKVERSION; 1521 } 1522 } 1523 } 1524 1525 # if not packages found, just return 1526 return if !@Packages; 1527 1528 # just framework packages 1529 my @NewPackages; 1530 my $PackageForRequestedFramework = 0; 1531 for my $Package (@Packages) { 1532 1533 my $FWCheckOk = 0; 1534 1535 if ( $Package->{Framework} ) { 1536 1537 my %Check = $Self->AnalyzePackageFrameworkRequirements( 1538 Framework => $Package->{Framework}, 1539 NoLog => 1 1540 ); 1541 if ( $Check{Success} ) { 1542 $FWCheckOk = 1; 1543 $PackageForRequestedFramework = 1; 1544 } 1545 } 1546 1547 if ($FWCheckOk) { 1548 push @NewPackages, $Package; 1549 } 1550 } 1551 1552 # return if there are packages, just not for this framework version 1553 if ( @Packages && !$PackageForRequestedFramework ) { 1554 $Kernel::OM->Get('Kernel::System::Log')->Log( 1555 Priority => 'error', 1556 Message => 1557 Translatable( 1558 'No packages for your framework version found in this repository, it only contains packages for other framework versions.' 1559 ), 1560 ); 1561 } 1562 @Packages = @NewPackages; 1563 1564 # just the newest packages 1565 my %Newest; 1566 for my $Package (@Packages) { 1567 1568 if ( !$Newest{ $Package->{Name} } ) { 1569 $Newest{ $Package->{Name} } = $Package; 1570 } 1571 else { 1572 1573 my $CheckVersion = $Self->_CheckVersion( 1574 VersionNew => $Package->{Version}, 1575 VersionInstalled => $Newest{ $Package->{Name} }->{Version}, 1576 Type => 'Min', 1577 ); 1578 1579 if ( !$CheckVersion ) { 1580 $Newest{ $Package->{Name} } = $Package; 1581 } 1582 } 1583 } 1584 1585 # get possible actions 1586 @NewPackages = (); 1587 my @LocalList = $Self->RepositoryList(); 1588 1589 for my $Data ( sort keys %Newest ) { 1590 1591 my $InstalledSameVersion = 0; 1592 1593 PACKAGE: 1594 for my $Package (@LocalList) { 1595 1596 next PACKAGE if $Newest{$Data}->{Name} ne $Package->{Name}->{Content}; 1597 1598 $Newest{$Data}->{Local} = 1; 1599 1600 next PACKAGE if $Package->{Status} ne 'installed'; 1601 1602 $Newest{$Data}->{Installed} = 1; 1603 1604 if ( 1605 !$Self->_CheckVersion( 1606 VersionNew => $Newest{$Data}->{Version}, 1607 VersionInstalled => $Package->{Version}->{Content}, 1608 Type => 'Min', 1609 ) 1610 ) 1611 { 1612 $Newest{$Data}->{Upgrade} = 1; 1613 } 1614 1615 # check if version or lower is already installed 1616 elsif ( 1617 !$Self->_CheckVersion( 1618 VersionNew => $Newest{$Data}->{Version}, 1619 VersionInstalled => $Package->{Version}->{Content}, 1620 Type => 'Max', 1621 ) 1622 ) 1623 { 1624 $InstalledSameVersion = 1; 1625 } 1626 } 1627 1628 # add package if not already installed 1629 if ( !$InstalledSameVersion || $Param{IncludeSameVersion} ) { 1630 push @NewPackages, $Newest{$Data}; 1631 } 1632 } 1633 1634 @Packages = @NewPackages; 1635 1636 # set cache 1637 if ( $Param{Cache} ) { 1638 $CacheObject->Set( 1639 Type => 'PackageOnlineList', 1640 Key => $CacheKey, 1641 Value => \@Packages, 1642 TTL => 60 * 60, 1643 ); 1644 } 1645 1646 return @Packages; 1647} 1648 1649=head2 PackageOnlineGet() 1650 1651download of an online package and put it into the local repository 1652 1653 $PackageObject->PackageOnlineGet( 1654 Source => 'http://host.example.com/', 1655 File => 'SomePackage-1.0.opm', 1656 ); 1657 1658=cut 1659 1660sub PackageOnlineGet { 1661 my ( $Self, %Param ) = @_; 1662 1663 # check needed stuff 1664 for my $Needed (qw(File Source)) { 1665 if ( !defined $Param{$Needed} ) { 1666 $Kernel::OM->Get('Kernel::System::Log')->Log( 1667 Priority => 'error', 1668 Message => "$Needed not defined!", 1669 ); 1670 return; 1671 } 1672 } 1673 1674 #check if file might be retrieved from cloud 1675 my $RepositoryCloudList; 1676 if ( !$Self->{CloudServicesDisabled} ) { 1677 $RepositoryCloudList = $Self->RepositoryCloudList(); 1678 } 1679 if ( IsHashRefWithData($RepositoryCloudList) && $RepositoryCloudList->{ $Param{Source} } ) { 1680 1681 my $PackageFromCloud; 1682 1683 # On this case a cloud service is used, Source contains an 1684 # operation name in order to match with the previous structure 1685 my $Operation = $Param{Source} . 'FileGet'; 1686 1687 # download package from cloud 1688 my $PackageResult = $Self->CloudFileGet( 1689 Operation => $Operation, 1690 Data => { 1691 File => $Param{File}, 1692 }, 1693 ); 1694 1695 if ( 1696 IsHashRefWithData($PackageResult) 1697 && $PackageResult->{Package} 1698 ) 1699 { 1700 $PackageFromCloud = $PackageResult->{Package}; 1701 } 1702 elsif ( IsStringWithData($PackageResult) ) { 1703 return 'ErrorMessage:' . $PackageResult; 1704 1705 } 1706 1707 return $PackageFromCloud; 1708 } 1709 1710 return $Self->_Download( URL => $Param{Source} . '/' . $Param{File} ); 1711} 1712 1713=head2 DeployCheck() 1714 1715check if package (files) is deployed, returns true if it's ok 1716 1717 $PackageObject->DeployCheck( 1718 Name => 'Application A', 1719 Version => '1.0', 1720 Log => 1, # Default: 1 1721 ); 1722 1723=cut 1724 1725sub DeployCheck { 1726 my ( $Self, %Param ) = @_; 1727 1728 # check needed stuff 1729 for my $Needed (qw(Name Version)) { 1730 if ( !defined $Param{$Needed} ) { 1731 $Kernel::OM->Get('Kernel::System::Log')->Log( 1732 Priority => 'error', 1733 Message => "$Needed not defined!", 1734 ); 1735 return; 1736 } 1737 } 1738 1739 if ( !defined $Param{Log} ) { 1740 $Param{Log} = 1; 1741 } 1742 1743 my $Package = $Self->RepositoryGet( %Param, Result => 'SCALAR' ); 1744 my %Structure = $Self->PackageParse( String => $Package ); 1745 1746 $Self->{DeployCheckInfo} = undef; 1747 1748 return 1 if !$Structure{Filelist}; 1749 return 1 if ref $Structure{Filelist} ne 'ARRAY'; 1750 1751 # get main object 1752 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 1753 1754 my $Hit = 0; 1755 for my $File ( @{ $Structure{Filelist} } ) { 1756 1757 my $LocalFile = $Self->{Home} . '/' . $File->{Location}; 1758 1759 if ( !-e $LocalFile ) { 1760 1761 if ( $Param{Log} ) { 1762 $Kernel::OM->Get('Kernel::System::Log')->Log( 1763 Priority => 'error', 1764 Message => "$Param{Name}-$Param{Version}: No such file: $LocalFile!", 1765 ); 1766 } 1767 1768 $Self->{DeployCheckInfo}->{File}->{ $File->{Location} } = Translatable('File is not installed!'); 1769 $Hit = 1; 1770 } 1771 elsif ( -e $LocalFile ) { 1772 1773 my $Content = $MainObject->FileRead( 1774 Location => $Self->{Home} . '/' . $File->{Location}, 1775 Mode => 'binmode', 1776 ); 1777 1778 if ($Content) { 1779 1780 if ( ${$Content} ne $File->{Content} ) { 1781 1782 if ( $Param{Log} && !$Kernel::OM->Get('Kernel::Config')->Get('Package::AllowLocalModifications') ) { 1783 $Kernel::OM->Get('Kernel::System::Log')->Log( 1784 Priority => 'error', 1785 Message => "$Param{Name}-$Param{Version}: $LocalFile is different!", 1786 ); 1787 } 1788 1789 $Hit = 1; 1790 $Self->{DeployCheckInfo}->{File}->{ $File->{Location} } = Translatable('File is different!'); 1791 } 1792 } 1793 else { 1794 1795 if ( $Param{Log} ) { 1796 $Kernel::OM->Get('Kernel::System::Log')->Log( 1797 Priority => 'error', 1798 Message => "Can't read $LocalFile!", 1799 ); 1800 } 1801 1802 $Self->{DeployCheckInfo}->{File}->{ $File->{Location} } = Translatable('Can\'t read file!'); 1803 } 1804 } 1805 } 1806 1807 return if $Hit; 1808 return 1; 1809} 1810 1811=head2 DeployCheckInfo() 1812 1813returns the info of the latest DeployCheck(), what's not deployed correctly 1814 1815 my %Hash = $PackageObject->DeployCheckInfo(); 1816 1817=cut 1818 1819sub DeployCheckInfo { 1820 my ( $Self, %Param ) = @_; 1821 1822 return %{ $Self->{DeployCheckInfo} } 1823 if $Self->{DeployCheckInfo}; 1824 1825 return (); 1826} 1827 1828=head2 PackageVerify() 1829 1830check if package is verified by the vendor 1831 1832 $PackageObject->PackageVerify( 1833 Package => $Package, 1834 Structure => \%Structure, 1835 ); 1836 1837or 1838 1839 $PackageObject->PackageVerify( 1840 Package => $Package, 1841 Name => 'FAQ', 1842 ); 1843 1844=cut 1845 1846sub PackageVerify { 1847 my ( $Self, %Param ) = @_; 1848 1849 # check needed stuff 1850 if ( !$Param{Package} ) { 1851 $Kernel::OM->Get('Kernel::System::Log')->Log( 1852 Priority => 'error', 1853 Message => "Need Package!", 1854 ); 1855 1856 return; 1857 } 1858 if ( !$Param{Structure} && !$Param{Name} ) { 1859 $Kernel::OM->Get('Kernel::System::Log')->Log( 1860 Priority => 'error', 1861 Message => 'Need Structure or Name!', 1862 ); 1863 1864 return; 1865 } 1866 1867 # Check if installation of packages, which are not verified by us, is possible. 1868 my $PackageAllowNotVerifiedPackages = $Kernel::OM->Get('Kernel::Config')->Get('Package::AllowNotVerifiedPackages'); 1869 1870 # define package verification info 1871 my $PackageVerifyInfo; 1872 1873 if ($PackageAllowNotVerifiedPackages) { 1874 1875 $PackageVerifyInfo = { 1876 Description => 1877 Translatable( 1878 "<p>If you continue to install this package, the following issues may occur:</p><ul><li>Security problems</li><li>Stability problems</li><li>Performance problems</li></ul><p>Please note that issues that are caused by working with this package are not covered by OTRS service contracts.</p>" 1879 ), 1880 Title => 1881 Translatable('Package not verified by the OTRS Group! It is recommended not to use this package.'), 1882 PackageInstallPossible => 1, 1883 }; 1884 } 1885 else { 1886 1887 $PackageVerifyInfo = { 1888 Description => 1889 Translatable( 1890 '<p>The installation of packages which are not verified by the OTRS Group is not possible by default. You can activate the installation of not verified packages via the "AllowNotVerifiedPackages" system configuration setting.</p>' 1891 ), 1892 Title => 1893 Translatable('Package not verified by the OTRS Group! It is recommended not to use this package.'), 1894 PackageInstallPossible => 0, 1895 }; 1896 } 1897 1898 # return package as verified if cloud services are disabled 1899 if ( $Self->{CloudServicesDisabled} ) { 1900 1901 my $Verify = $PackageAllowNotVerifiedPackages ? 'verified' : 'not_verified'; 1902 1903 if ( $Verify eq 'not_verified' ) { 1904 $PackageVerifyInfo->{VerifyCSSClass} = 'NotVerifiedPackage'; 1905 } 1906 1907 $Self->{PackageVerifyInfo} = $PackageVerifyInfo; 1908 1909 return $Verify; 1910 } 1911 1912 # investigate name 1913 my $Name = $Param{Structure}->{Name}->{Content} || $Param{Name}; 1914 1915 # correct any 'dos-style' line endings - http://bugs.otrs.org/show_bug.cgi?id=9838 1916 $Param{Package} =~ s{\r\n}{\n}xmsg; 1917 1918 # create MD5 sum 1919 my $Sum = $Kernel::OM->Get('Kernel::System::Main')->MD5sum( String => $Param{Package} ); 1920 1921 # get cache object 1922 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 1923 1924 # lookup cache 1925 my $CachedValue = $CacheObject->Get( 1926 Type => 'PackageVerification', 1927 Key => $Sum, 1928 ); 1929 if ($CachedValue) { 1930 1931 if ( $CachedValue eq 'not_verified' ) { 1932 1933 $PackageVerifyInfo->{VerifyCSSClass} = 'NotVerifiedPackage'; 1934 } 1935 1936 $Self->{PackageVerifyInfo} = $PackageVerifyInfo; 1937 1938 return $CachedValue; 1939 } 1940 1941 my $CloudService = 'PackageManagement'; 1942 my $Operation = 'PackageVerify'; 1943 1944 # prepare cloud service request 1945 my %RequestParams = ( 1946 RequestData => { 1947 $CloudService => [ 1948 { 1949 Operation => $Operation, 1950 Data => { 1951 Package => [ 1952 { 1953 Name => $Name, 1954 MD5sum => $Sum, 1955 } 1956 ], 1957 }, 1958 }, 1959 ], 1960 }, 1961 ); 1962 1963 # get cloud service object 1964 my $CloudServiceObject = $Kernel::OM->Get('Kernel::System::CloudService::Backend::Run'); 1965 1966 # dispatch the cloud service request 1967 my $RequestResult = $CloudServiceObject->Request(%RequestParams); 1968 1969 # as this is the only operation an unsuccessful request means that the operation was also 1970 # unsuccessful, in such case set the package as verified 1971 return 'unknown' if !IsHashRefWithData($RequestResult); 1972 1973 my $OperationResult = $CloudServiceObject->OperationResultGet( 1974 RequestResult => $RequestResult, 1975 CloudService => $CloudService, 1976 Operation => $Operation, 1977 ); 1978 1979 # if there was no result for this specific operation or the operation was not success, then 1980 # set the package as verified 1981 return 'unknown' if !IsHashRefWithData($OperationResult); 1982 return 'unknown' if !$OperationResult->{Success}; 1983 1984 my $VerificationData = $OperationResult->{Data}; 1985 1986 # extract response 1987 my $PackageVerify = $VerificationData->{$Name}; 1988 1989 return 'unknown' if !$PackageVerify; 1990 return 'unknown' if $PackageVerify ne 'not_verified' && $PackageVerify ne 'verified'; 1991 1992 # set package verification info 1993 if ( $PackageVerify eq 'not_verified' ) { 1994 1995 $PackageVerifyInfo->{VerifyCSSClass} = 'NotVerifiedPackage'; 1996 1997 $Self->{PackageVerifyInfo} = $PackageVerifyInfo; 1998 } 1999 2000 # set cache 2001 $CacheObject->Set( 2002 Type => 'PackageVerification', 2003 Key => $Sum, 2004 Value => $PackageVerify, 2005 TTL => 30 * 24 * 60 * 60, # 30 days 2006 ); 2007 2008 return $PackageVerify; 2009} 2010 2011=head2 PackageVerifyInfo() 2012 2013returns the info of the latest PackageVerify() 2014 2015 my %Hash = $PackageObject->PackageVerifyInfo(); 2016 2017=cut 2018 2019sub PackageVerifyInfo { 2020 my ( $Self, %Param ) = @_; 2021 2022 return () if !$Self->{PackageVerifyInfo}; 2023 return () if ref $Self->{PackageVerifyInfo} ne 'HASH'; 2024 return () if !%{ $Self->{PackageVerifyInfo} }; 2025 2026 return %{ $Self->{PackageVerifyInfo} }; 2027} 2028 2029=head2 PackageVerifyAll() 2030 2031check if all installed packages are installed by the vendor 2032returns a hash with package names and verification status. 2033 2034 my %VerificationInfo = $PackageObject->PackageVerifyAll(); 2035 2036returns: 2037 2038 %VerificationInfo = ( 2039 FAQ => 'verified', 2040 Support => 'verified', 2041 MyHack => 'not_verified', 2042 ); 2043 2044=cut 2045 2046sub PackageVerifyAll { 2047 my ( $Self, %Param ) = @_; 2048 2049 # get installed package list 2050 my @PackageList = $Self->RepositoryList( 2051 Result => 'Short', 2052 ); 2053 2054 return () if !@PackageList; 2055 2056 # create a mapping of Package Name => md5 pairs 2057 my %PackageList = map { $_->{Name} => $_->{MD5sum} } @PackageList; 2058 2059 # get cache object 2060 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 2061 2062 my %Result; 2063 my @PackagesToVerify; 2064 2065 # first check the cache for each package 2066 for my $Package (@PackageList) { 2067 2068 my $Verification = $CacheObject->Get( 2069 Type => 'PackageVerification', 2070 Key => $Package->{MD5sum}, 2071 ); 2072 2073 # add to result if we have it already 2074 if ($Verification) { 2075 $Result{ $Package->{Name} } = $Verification; 2076 } 2077 else { 2078 $Result{ $Package->{Name} } = 'unknown'; 2079 push @PackagesToVerify, { 2080 Name => $Package->{Name}, 2081 MD5sum => $Package->{MD5sum}, 2082 }; 2083 } 2084 } 2085 2086 return %Result if !@PackagesToVerify; 2087 return %Result if $Self->{CloudServicesDisabled}; 2088 2089 my $CloudService = 'PackageManagement'; 2090 my $Operation = 'PackageVerify'; 2091 2092 # prepare cloud service request 2093 my %RequestParams = ( 2094 RequestData => { 2095 $CloudService => [ 2096 { 2097 Operation => $Operation, 2098 Data => { 2099 Package => \@PackagesToVerify, 2100 }, 2101 }, 2102 ], 2103 }, 2104 ); 2105 2106 # get cloud service object 2107 my $CloudServiceObject = $Kernel::OM->Get('Kernel::System::CloudService::Backend::Run'); 2108 2109 # dispatch the cloud service request 2110 my $RequestResult = $CloudServiceObject->Request(%RequestParams); 2111 2112 # as this is the only operation an unsuccessful request means that the operation was also 2113 # unsuccessful, then return all packages as verified (or cache) 2114 return %Result if !IsHashRefWithData($RequestResult); 2115 2116 my $OperationResult = $CloudServiceObject->OperationResultGet( 2117 RequestResult => $RequestResult, 2118 CloudService => $CloudService, 2119 Operation => $Operation, 2120 ); 2121 2122 # if no operation result found or it was not successful the return all packages as verified 2123 # (or cache) 2124 return %Result if !IsHashRefWithData($OperationResult); 2125 return %Result if !$OperationResult->{Success}; 2126 2127 my $VerificationData = $OperationResult->{Data}; 2128 2129 PACKAGE: 2130 for my $Package ( sort keys %Result ) { 2131 2132 next PACKAGE if !$Package; 2133 next PACKAGE if !$VerificationData->{$Package}; 2134 2135 # extract response 2136 my $PackageVerify = $VerificationData->{$Package}; 2137 2138 next PACKAGE if !$PackageVerify; 2139 next PACKAGE if $PackageVerify ne 'not_verified' && $PackageVerify ne 'verified'; 2140 2141 # process result 2142 $Result{$Package} = $PackageVerify; 2143 2144 # set cache 2145 $CacheObject->Set( 2146 Type => 'PackageVerification', 2147 Key => $PackageList{$Package}, 2148 Value => $PackageVerify, 2149 TTL => 30 * 24 * 60 * 60, # 30 days 2150 ); 2151 } 2152 2153 return %Result; 2154} 2155 2156=head2 PackageBuild() 2157 2158build an opm package 2159 2160 my $Package = $PackageObject->PackageBuild( 2161 Name => { 2162 Content => 'SomePackageName', 2163 }, 2164 Version => { 2165 Content => '1.0', 2166 }, 2167 Vendor => { 2168 Content => 'OTRS AG', 2169 }, 2170 URL => { 2171 Content => 'L<http://otrs.org/>', 2172 }, 2173 License => { 2174 Content => 'GNU GENERAL PUBLIC LICENSE Version 3, November 2007', 2175 } 2176 Description => [ 2177 { 2178 Lang => 'en', 2179 Content => 'english description', 2180 }, 2181 { 2182 Lang => 'de', 2183 Content => 'german description', 2184 }, 2185 ], 2186 Filelist = [ 2187 { 2188 Location => 'Kernel/System/Lala.pm' 2189 Permission => '644', 2190 Content => $FileInString, 2191 }, 2192 { 2193 Location => 'Kernel/System/Lulu.pm' 2194 Permission => '644', 2195 Content => $FileInString, 2196 }, 2197 ], 2198 ); 2199 2200=cut 2201 2202sub PackageBuild { 2203 my ( $Self, %Param ) = @_; 2204 2205 my $XML = ''; 2206 my $Home = $Param{Home} || $Self->{ConfigObject}->Get('Home'); 2207 2208 # check needed stuff 2209 for my $Needed (qw(Name Version Vendor License Description)) { 2210 if ( !defined $Param{$Needed} ) { 2211 $Kernel::OM->Get('Kernel::System::Log')->Log( 2212 Priority => 'error', 2213 Message => "$Needed not defined!", 2214 ); 2215 return; 2216 } 2217 } 2218 2219 # find framework, may we need do some things different to be compat. to 2.2 2220 my $Framework; 2221 if ( $Param{Framework} ) { 2222 2223 FW: 2224 for my $FW ( @{ $Param{Framework} } ) { 2225 2226 next FW if $FW->{Content} !~ /2\.2\./; 2227 2228 $Framework = '2.2'; 2229 2230 last FW; 2231 } 2232 } 2233 2234 # build xml 2235 if ( !$Param{Type} ) { 2236 $XML .= '<?xml version="1.0" encoding="utf-8" ?>'; 2237 $XML .= "\n"; 2238 $XML .= '<otrs_package version="1.1">'; 2239 $XML .= "\n"; 2240 } 2241 2242 TAG: 2243 for my $Tag ( 2244 qw(Name Version Vendor URL License ChangeLog Description Framework OS 2245 IntroInstall IntroUninstall IntroReinstall IntroUpgrade 2246 PackageIsVisible PackageIsDownloadable PackageIsRemovable PackageAllowDirectUpdate PackageMerge 2247 PackageRequired ModuleRequired CodeInstall CodeUpgrade CodeUninstall CodeReinstall) 2248 ) 2249 { 2250 2251 # don't use CodeInstall CodeUpgrade CodeUninstall CodeReinstall in index mode 2252 if ( $Param{Type} && $Tag =~ /(Code|Intro)(Install|Upgrade|Uninstall|Reinstall)/ ) { 2253 next TAG; 2254 } 2255 2256 if ( ref $Param{$Tag} eq 'HASH' ) { 2257 2258 my %OldParam; 2259 for my $Item (qw(Content Encode TagType Tag TagLevel TagCount TagKey TagLastLevel)) { 2260 $OldParam{$Item} = $Param{$Tag}->{$Item} || ''; 2261 delete $Param{$Tag}->{$Item}; 2262 } 2263 2264 $XML .= " <$Tag"; 2265 2266 for my $Item ( sort keys %{ $Param{$Tag} } ) { 2267 $XML .= " $Item=\"" . $Self->_Encode( $Param{$Tag}->{$Item} ) . "\""; 2268 } 2269 2270 $XML .= ">"; 2271 $XML .= $Self->_Encode( $OldParam{Content} ) . "</$Tag>\n"; 2272 } 2273 elsif ( ref $Param{$Tag} eq 'ARRAY' ) { 2274 2275 for my $Item ( @{ $Param{$Tag} } ) { 2276 2277 my $TagSub = $Tag; 2278 my %Hash = %{$Item}; 2279 my %OldParam; 2280 2281 for my $HashParam ( 2282 qw(Content Encode TagType Tag TagLevel TagCount TagKey TagLastLevel) 2283 ) 2284 { 2285 $OldParam{$HashParam} = $Hash{$HashParam} || ''; 2286 delete $Hash{$HashParam}; 2287 } 2288 2289 # compat. to 2.2 2290 if ( $Framework && $Tag =~ /^Intro/ ) { 2291 if ( $Hash{Type} eq 'pre' ) { 2292 $Hash{Type} = 'Pre'; 2293 } 2294 else { 2295 $Hash{Type} = 'Post'; 2296 } 2297 $TagSub = $Tag . $Hash{Type}; 2298 delete $Hash{Type}; 2299 } 2300 2301 $XML .= " <$TagSub"; 2302 2303 for my $Item ( sort keys %Hash ) { 2304 $XML .= " $Item=\"" . $Self->_Encode( $Hash{$Item} ) . "\""; 2305 } 2306 2307 $XML .= ">"; 2308 $XML .= $Self->_Encode( $OldParam{Content} ) . "</$TagSub>\n"; 2309 } 2310 } 2311 } 2312 2313 # don't use Build* in index mode 2314 if ( !$Param{Type} ) { 2315 2316 # get time object 2317 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 2318 2319 $XML .= " <BuildDate>" . $DateTimeObject->ToString() . "</BuildDate>\n"; 2320 $XML .= " <BuildHost>" . $Self->{ConfigObject}->Get('FQDN') . "</BuildHost>\n"; 2321 } 2322 2323 if ( $Param{Filelist} ) { 2324 2325 # get main object 2326 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 2327 2328 $XML .= " <Filelist>\n"; 2329 2330 FILE: 2331 for my $File ( @{ $Param{Filelist} } ) { 2332 2333 my %OldParam; 2334 2335 for my $Item (qw(Content Encode TagType Tag TagLevel TagCount TagKey TagLastLevel)) { 2336 $OldParam{$Item} = $File->{$Item} || ''; 2337 delete $File->{$Item}; 2338 } 2339 2340 # do only use doc/* Filelist in index mode 2341 next FILE if $Param{Type} && $File->{Location} !~ /^doc\//; 2342 2343 if ( !$Param{Type} ) { 2344 $XML .= " <File"; 2345 } 2346 else { 2347 $XML .= " <FileDoc"; 2348 } 2349 for my $Item ( sort keys %{$File} ) { 2350 if ( $Item ne 'Tag' && $Item ne 'Content' && $Item ne 'TagType' && $Item ne 'Size' ) 2351 { 2352 $XML 2353 .= " " 2354 . $Self->_Encode($Item) . "=\"" 2355 . $Self->_Encode( $File->{$Item} ) . "\""; 2356 } 2357 } 2358 2359 # don't use content in in index mode 2360 if ( !$Param{Type} ) { 2361 $XML .= " Encode=\"Base64\">"; 2362 my $FileContent = $MainObject->FileRead( 2363 Location => $Home . '/' . $File->{Location}, 2364 Mode => 'binmode', 2365 ); 2366 2367 return if !defined $FileContent; 2368 2369 $XML .= encode_base64( ${$FileContent}, '' ); 2370 $XML .= "</File>\n"; 2371 } 2372 else { 2373 $XML .= " >"; 2374 $XML .= "</FileDoc>\n"; 2375 } 2376 } 2377 $XML .= " </Filelist>\n"; 2378 } 2379 2380 # don't use Database* in index mode 2381 return $XML if $Param{Type}; 2382 2383 TAG: 2384 for my $Item (qw(DatabaseInstall DatabaseUpgrade DatabaseReinstall DatabaseUninstall)) { 2385 2386 if ( ref $Param{$Item} ne 'HASH' ) { 2387 next TAG; 2388 } 2389 2390 for my $Type ( sort %{ $Param{$Item} } ) { 2391 2392 if ( $Param{$Item}->{$Type} ) { 2393 2394 my $Counter = 1; 2395 for my $Tag ( @{ $Param{$Item}->{$Type} } ) { 2396 2397 if ( $Tag->{TagType} eq 'Start' ) { 2398 2399 my $Space = ''; 2400 for ( 1 .. $Counter ) { 2401 $Space .= ' '; 2402 } 2403 2404 $Counter++; 2405 $XML .= $Space . "<$Tag->{Tag}"; 2406 2407 if ( $Tag->{TagLevel} == 3 ) { 2408 $XML .= " Type=\"$Type\""; 2409 } 2410 2411 KEY: 2412 for my $Key ( sort keys %{$Tag} ) { 2413 2414 next KEY if $Key eq 'Tag'; 2415 next KEY if $Key eq 'Content'; 2416 next KEY if $Key eq 'TagType'; 2417 next KEY if $Key eq 'TagLevel'; 2418 next KEY if $Key eq 'TagCount'; 2419 next KEY if $Key eq 'TagKey'; 2420 next KEY if $Key eq 'TagLastLevel'; 2421 2422 next KEY if !defined $Tag->{$Key}; 2423 2424 next KEY if $Tag->{TagLevel} == 3 && lc $Key eq 'type'; 2425 2426 $XML .= ' ' 2427 . $Self->_Encode($Key) . '="' 2428 . $Self->_Encode( $Tag->{$Key} ) . '"'; 2429 } 2430 2431 $XML .= ">"; 2432 2433 if ( $Tag->{TagLevel} <= 3 || $Tag->{Tag} =~ /(Foreign|Reference|Index)/ ) { 2434 $XML .= "\n"; 2435 } 2436 } 2437 if ( 2438 defined( $Tag->{Content} ) 2439 && $Tag->{TagLevel} >= 4 2440 && $Tag->{Tag} !~ /(Foreign|Reference|Index)/ 2441 ) 2442 { 2443 $XML .= $Self->_Encode( $Tag->{Content} ); 2444 } 2445 if ( $Tag->{TagType} eq 'End' ) { 2446 2447 $Counter = $Counter - 1; 2448 if ( $Tag->{TagLevel} > 3 && $Tag->{Tag} !~ /(Foreign|Reference|Index)/ ) { 2449 $XML .= "</$Tag->{Tag}>\n"; 2450 } 2451 else { 2452 2453 my $Space = ''; 2454 2455 for ( 1 .. $Counter ) { 2456 $Space .= ' '; 2457 } 2458 2459 $XML .= $Space . "</$Tag->{Tag}>\n"; 2460 } 2461 } 2462 } 2463 } 2464 } 2465 } 2466 2467 $XML .= '</otrs_package>'; 2468 2469 return $XML; 2470} 2471 2472=head2 PackageParse() 2473 2474parse a package 2475 2476 my %Structure = $PackageObject->PackageParse( String => $FileString ); 2477 2478=cut 2479 2480sub PackageParse { 2481 my ( $Self, %Param ) = @_; 2482 2483 # check needed stuff 2484 if ( !defined $Param{String} ) { 2485 $Kernel::OM->Get('Kernel::System::Log')->Log( 2486 Priority => 'error', 2487 Message => 'String not defined!', 2488 ); 2489 return; 2490 } 2491 2492 # create checksum 2493 my $CookedString = ref $Param{String} ? ${ $Param{String} } : $Param{String}; 2494 2495 $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$CookedString ); 2496 2497 # create checksum 2498 my $Checksum = $Kernel::OM->Get('Kernel::System::Main')->MD5sum( 2499 String => \$CookedString, 2500 ); 2501 2502 # get cache object 2503 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 2504 2505 # check cache 2506 if ($Checksum) { 2507 my $Cache = $CacheObject->Get( 2508 Type => 'PackageParse', 2509 Key => $Checksum, 2510 2511 # Don't store complex structure in memory as it will be modified later. 2512 CacheInMemory => 0, 2513 ); 2514 return %{$Cache} if $Cache; 2515 } 2516 2517 # get xml object 2518 my $XMLObject = $Kernel::OM->Get('Kernel::System::XML'); 2519 2520 my @XMLARRAY = eval { 2521 $XMLObject->XMLParse(%Param); 2522 }; 2523 2524 if ( !IsArrayRefWithData( \@XMLARRAY ) ) { 2525 $Kernel::OM->Get('Kernel::System::Log')->Log( 2526 Priority => 'error', 2527 Message => "Invalid XMLParse in PackageParse()!", 2528 ); 2529 return; 2530 } 2531 2532 my %Package; 2533 2534 # parse package 2535 my %PackageMap = %{ $Self->{PackageMap} }; 2536 2537 TAG: 2538 for my $Tag (@XMLARRAY) { 2539 2540 next TAG if $Tag->{TagType} ne 'Start'; 2541 2542 if ( $PackageMap{ $Tag->{Tag} } && $PackageMap{ $Tag->{Tag} } eq 'SCALAR' ) { 2543 $Package{ $Tag->{Tag} } = $Tag; 2544 } 2545 elsif ( $PackageMap{ $Tag->{Tag} } && $PackageMap{ $Tag->{Tag} } eq 'ARRAY' ) { 2546 2547 # For compat. to 2.2 - convert Intro(Install|Upgrade|Unintall)(Pre|Post) to 2548 # e. g. <IntroInstall Type="post">. 2549 if ( $Tag->{Tag} =~ /^(Intro(Install|Upgrade|Uninstall))(Pre|Post)/ ) { 2550 $Tag->{Tag} = $1; 2551 $Tag->{Type} = lc $3; 2552 } 2553 2554 # Set default type of Code* and Intro* to post. 2555 elsif ( $Tag->{Tag} =~ /^(Code|Intro)/ && !$Tag->{Type} ) { 2556 $Tag->{Type} = 'post'; 2557 } 2558 2559 push @{ $Package{ $Tag->{Tag} } }, $Tag; 2560 } 2561 } 2562 2563 # define names and locations that are not allowed for files in a package 2564 my $FilesNotAllowed = [ 2565 'Kernel/Config.pm$', 2566 'Kernel/Config/Files/ZZZAuto.pm$', 2567 'Kernel/Config/Files/ZZZAAuto.pm$', 2568 'Kernel/Config/Files/ZZZProcessManagement.pm$', 2569 'var/tmp/Cache', 2570 'var/log/', 2571 '\.\./', 2572 '^/', 2573 ]; 2574 2575 my $Open = 0; 2576 TAG: 2577 for my $Tag (@XMLARRAY) { 2578 2579 if ( $Open && $Tag->{Tag} eq 'Filelist' ) { 2580 $Open = 0; 2581 } 2582 elsif ( !$Open && $Tag->{Tag} eq 'Filelist' ) { 2583 $Open = 1; 2584 next TAG; 2585 } 2586 2587 if ( $Open && $Tag->{TagType} eq 'Start' ) { 2588 2589 # check for allowed file names and locations 2590 FILECHECK: 2591 for my $FileNotAllowed ( @{$FilesNotAllowed} ) { 2592 2593 next FILECHECK if $Tag->{Location} !~ m{ $FileNotAllowed }xms; 2594 2595 $Kernel::OM->Get('Kernel::System::Log')->Log( 2596 Priority => 'error', 2597 Message => "Invalid file/location '$Tag->{Location}' in PackageParse()!", 2598 ); 2599 2600 next TAG; 2601 } 2602 2603 # get attachment size 2604 { 2605 if ( $Tag->{Content} ) { 2606 2607 my $ContentPlain = 0; 2608 2609 if ( $Tag->{Encode} && $Tag->{Encode} eq 'Base64' ) { 2610 $Tag->{Encode} = ''; 2611 $Tag->{Content} = decode_base64( $Tag->{Content} ); 2612 } 2613 2614 $Tag->{Size} = bytes::length( $Tag->{Content} ); 2615 } 2616 } 2617 2618 push @{ $Package{Filelist} }, $Tag; 2619 } 2620 } 2621 2622 for my $Key (qw(DatabaseInstall DatabaseUpgrade DatabaseReinstall DatabaseUninstall)) { 2623 2624 my $Type = 'post'; 2625 2626 TAG: 2627 for my $Tag (@XMLARRAY) { 2628 2629 if ( $Open && $Tag->{Tag} eq $Key ) { 2630 $Open = 0; 2631 push( @{ $Package{$Key}->{$Type} }, $Tag ); 2632 } 2633 elsif ( !$Open && $Tag->{Tag} eq $Key ) { 2634 2635 $Open = 1; 2636 2637 if ( $Tag->{Type} ) { 2638 $Type = $Tag->{Type}; 2639 } 2640 } 2641 2642 next TAG if !$Open; 2643 2644 push @{ $Package{$Key}->{$Type} }, $Tag; 2645 } 2646 } 2647 2648 # check if a structure is present 2649 if ( !%Package ) { 2650 $Kernel::OM->Get('Kernel::System::Log')->Log( 2651 Priority => 'error', 2652 Message => "Invalid package structure in PackageParse()!", 2653 ); 2654 return; 2655 } 2656 2657 # set cache 2658 if ($Checksum) { 2659 $CacheObject->Set( 2660 Type => 'PackageParse', 2661 Key => $Checksum, 2662 Value => \%Package, 2663 TTL => 30 * 24 * 60 * 60, 2664 2665 # Don't store complex structure in memory as it will be modified later. 2666 CacheInMemory => 0, 2667 ); 2668 } 2669 2670 return %Package; 2671} 2672 2673=head2 PackageExport() 2674 2675export files of an package 2676 2677 $PackageObject->PackageExport( 2678 String => $FileString, 2679 Home => '/path/to/export' 2680 ); 2681 2682=cut 2683 2684sub PackageExport { 2685 my ( $Self, %Param ) = @_; 2686 2687 # check needed stuff 2688 for my $Needed (qw(String Home)) { 2689 if ( !defined $Param{$Needed} ) { 2690 $Kernel::OM->Get('Kernel::System::Log')->Log( 2691 Priority => 'error', 2692 Message => "$Needed not defined!", 2693 ); 2694 return; 2695 } 2696 } 2697 2698 # parse source file 2699 my %Structure = $Self->PackageParse(%Param); 2700 2701 return 1 if !$Structure{Filelist}; 2702 return 1 if ref $Structure{Filelist} ne 'ARRAY'; 2703 2704 # install files 2705 for my $File ( @{ $Structure{Filelist} } ) { 2706 2707 $Self->_FileInstall( 2708 File => $File, 2709 Home => $Param{Home}, 2710 ); 2711 } 2712 2713 return 1; 2714} 2715 2716=head2 PackageIsInstalled() 2717 2718returns true if the package is already installed 2719 2720 $PackageObject->PackageIsInstalled( 2721 String => $PackageString, # Attribute String or Name is required 2722 Name => $NameOfThePackage, 2723 ); 2724 2725=cut 2726 2727sub PackageIsInstalled { 2728 my ( $Self, %Param ) = @_; 2729 2730 # check needed stuff 2731 if ( !$Param{String} && !$Param{Name} ) { 2732 $Kernel::OM->Get('Kernel::System::Log')->Log( 2733 Priority => 'error', 2734 Message => 'Need String (PackageString) or Name (Name of the package)!', 2735 ); 2736 return; 2737 } 2738 2739 if ( $Param{String} ) { 2740 my %Structure = $Self->PackageParse(%Param); 2741 $Param{Name} = $Structure{Name}->{Content}; 2742 } 2743 2744 # get database object 2745 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2746 2747 $DBObject->Prepare( 2748 SQL => "SELECT name FROM package_repository " 2749 . "WHERE name = ? AND install_status = 'installed'", 2750 Bind => [ \$Param{Name} ], 2751 Limit => 1, 2752 ); 2753 2754 my $Flag = 0; 2755 while ( my @Row = $DBObject->FetchrowArray() ) { 2756 $Flag = 1; 2757 } 2758 2759 return $Flag; 2760} 2761 2762=head2 PackageInstallDefaultFiles() 2763 2764returns true if the distribution package (located under ) can get installed 2765 2766 $PackageObject->PackageInstallDefaultFiles(); 2767 2768=cut 2769 2770sub PackageInstallDefaultFiles { 2771 my ( $Self, %Param ) = @_; 2772 2773 # write permission check 2774 return if !$Self->_FileSystemCheck(); 2775 2776 # get main object 2777 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 2778 2779 my $Directory = $Self->{ConfigObject}->Get('Home') . '/var/packages'; 2780 my @PackageFiles = $MainObject->DirectoryRead( 2781 Directory => $Directory, 2782 Filter => '*.opm', 2783 ); 2784 2785 # read packages and install 2786 LOCATION: 2787 for my $Location (@PackageFiles) { 2788 2789 # read package 2790 my $ContentSCALARRef = $MainObject->FileRead( 2791 Location => $Location, 2792 Mode => 'binmode', 2793 Type => 'Local', 2794 Result => 'SCALAR', 2795 ); 2796 2797 next LOCATION if !$ContentSCALARRef; 2798 2799 # install package (use eval to be safe) 2800 eval { 2801 $Self->PackageInstall( String => ${$ContentSCALARRef} ); 2802 }; 2803 2804 next LOCATION if !$@; 2805 2806 $Kernel::OM->Get('Kernel::System::Log')->Log( 2807 Priority => 'error', 2808 Message => $@, 2809 ); 2810 } 2811 2812 return 1; 2813} 2814 2815=head2 PackageFileGetMD5Sum() 2816 2817generates a MD5 Sum for all files in a given package 2818 2819 my $MD5Sum = $PackageObject->PackageFileGetMD5Sum( 2820 Name => 'Package Name', 2821 Version => 123.0, 2822 ); 2823 2824returns: 2825 2826 $MD5SumLookup = { 2827 'Direcoty/File1' => 'f3f30bd59afadf542770d43edb280489' 2828 'Direcoty/File2' => 'ccb8a0b86adf125a36392e388eb96778' 2829 }; 2830 2831=cut 2832 2833sub PackageFileGetMD5Sum { 2834 my ( $Self, %Param ) = @_; 2835 2836 for my $Needed (qw(Name Version)) { 2837 if ( !$Param{$Needed} ) { 2838 $Kernel::OM->Get('Kernel::System::Log')->Log( 2839 Priority => 'error', 2840 Message => "Need $Needed!", 2841 ); 2842 } 2843 } 2844 2845 # get cache object 2846 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 2847 2848 # check cache 2849 my $CacheKey = $Param{Name} . $Param{Version}; 2850 my $Cache = $CacheObject->Get( 2851 Type => 'PackageFileGetMD5Sum', 2852 Key => $CacheKey, 2853 ); 2854 return $Cache if IsHashRefWithData($Cache); 2855 2856 # get the package contents 2857 my $Package = $Self->RepositoryGet( 2858 %Param, 2859 Result => 'SCALAR', 2860 ); 2861 my %Structure = $Self->PackageParse( String => $Package ); 2862 2863 return {} if !$Structure{Filelist}; 2864 return {} if ref $Structure{Filelist} ne 'ARRAY'; 2865 2866 # cleanup the Home variable (remove tailing "/") 2867 my $Home = $Self->{Home}; 2868 $Home =~ s{\/\z}{}; 2869 2870 # get main object 2871 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 2872 2873 my %MD5SumLookup; 2874 for my $File ( @{ $Structure{Filelist} } ) { 2875 2876 my $LocalFile = $Home . '/' . $File->{Location}; 2877 2878 # generate the MD5Sum 2879 my $MD5Sum = $MainObject->MD5sum( 2880 String => \$File->{Content}, 2881 ); 2882 2883 $MD5SumLookup{$LocalFile} = $MD5Sum; 2884 } 2885 2886 # set cache 2887 $CacheObject->Set( 2888 Type => 'PackageFileGetMD5Sum', 2889 Key => $CacheKey, 2890 Value => \%MD5SumLookup, 2891 TTL => 6 * 30 * 24 * 60 * 60, # 6 Months (Aprox) 2892 ); 2893 2894 return \%MD5SumLookup; 2895} 2896 2897=head2 AnalyzePackageFrameworkRequirements() 2898 2899Compare a framework array with the current framework. 2900 2901 my %CheckOk = $PackageObject->AnalyzePackageFrameworkRequirements( 2902 Framework => $Structure{Framework}, # [ { 'Content' => '4.0.x', 'Minimum' => '4.0.4'} ] 2903 NoLog => 1, # optional 2904 ); 2905 2906 %CheckOK = ( 2907 Success => 1, # 1 || 0 2908 RequiredFramework => '5.0.x', 2909 RequiredFrameworkMinimum => '5.0.10', 2910 RequiredFrameworkMaximum => '5.0.16', 2911 ); 2912 2913=cut 2914 2915sub AnalyzePackageFrameworkRequirements { 2916 my ( $Self, %Param ) = @_; 2917 2918 # check needed stuff 2919 if ( !defined $Param{Framework} ) { 2920 $Kernel::OM->Get('Kernel::System::Log')->Log( 2921 Priority => 'error', 2922 Message => 'Framework not defined!', 2923 ); 2924 return; 2925 } 2926 2927 # check format 2928 if ( ref $Param{Framework} ne 'ARRAY' ) { 2929 $Kernel::OM->Get('Kernel::System::Log')->Log( 2930 Priority => 'error', 2931 Message => 'Need array ref in Framework param!', 2932 ); 2933 return; 2934 } 2935 2936 my %Response = ( 2937 Success => 0, 2938 ); 2939 2940 my $FWCheck = 0; 2941 my $CurrentFramework = $Self->{ConfigObject}->Get('Version'); 2942 my $PossibleFramework = ''; 2943 2944 if ( ref $Param{Framework} eq 'ARRAY' ) { 2945 2946 FW: 2947 for my $FW ( @{ $Param{Framework} } ) { 2948 2949 next FW if !$FW; 2950 2951 # add framework versions for the log entry 2952 $PossibleFramework .= $FW->{Content} . ';'; 2953 my $Framework = $FW->{Content}; 2954 2955 # add required framework to response hash 2956 $Response{RequiredFramework} = $Framework; 2957 2958 # regexp modify 2959 $Framework =~ s/\./\\\./g; 2960 $Framework =~ s/x/.+?/gi; 2961 2962 # skip to next framework, if we get no positive match 2963 next FW if $CurrentFramework !~ /^$Framework$/i; 2964 2965 # framework is correct 2966 $FWCheck = 1; 2967 2968 if ( !$Param{IgnoreMinimumMaximum} ) { 2969 2970 # get minimum and/or maximum values 2971 # e.g. the opm contains <Framework Minimum="5.0.7" Maximum="5.0.12">5.0.x</Framework> 2972 my $FrameworkMinimum = $FW->{Minimum} || ''; 2973 my $FrameworkMaximum = $FW->{Maximum} || ''; 2974 2975 # check for minimum or maximum required framework, if it was defined 2976 if ( $FrameworkMinimum || $FrameworkMaximum ) { 2977 2978 # prepare hash for framework comparsion 2979 my %FrameworkComparsion; 2980 $FrameworkComparsion{MinimumFrameworkRequired} = $FrameworkMinimum; 2981 $FrameworkComparsion{MaximumFrameworkRequired} = $FrameworkMaximum; 2982 $FrameworkComparsion{CurrentFramework} = $CurrentFramework; 2983 2984 # prepare version parts hash 2985 my %VersionParts; 2986 2987 TYPE: 2988 for my $Type (qw(MinimumFrameworkRequired MaximumFrameworkRequired CurrentFramework)) { 2989 2990 # split version string 2991 my @ThisVersionParts = split /\./, $FrameworkComparsion{$Type}; 2992 $VersionParts{$Type} = \@ThisVersionParts; 2993 } 2994 2995 # check minimum required framework 2996 if ($FrameworkMinimum) { 2997 2998 COUNT: 2999 for my $Count ( 0 .. 2 ) { 3000 3001 $VersionParts{MinimumFrameworkRequired}->[$Count] ||= 0; 3002 $VersionParts{CurrentFramework}->[$Count] ||= 0; 3003 3004 # skip equal version parts 3005 next COUNT 3006 if $VersionParts{MinimumFrameworkRequired}->[$Count] eq 3007 $VersionParts{CurrentFramework}->[$Count]; 3008 3009 # skip current framework verion parts containing "x" 3010 next COUNT if $VersionParts{CurrentFramework}->[$Count] =~ /x/; 3011 3012 if ( 3013 $VersionParts{CurrentFramework}->[$Count] 3014 > $VersionParts{MinimumFrameworkRequired}->[$Count] 3015 ) 3016 { 3017 $FWCheck = 1; 3018 last COUNT; 3019 } 3020 else { 3021 3022 # add required minimum version for the log entry 3023 $PossibleFramework .= 'Minimum Version ' . $FrameworkMinimum . ';'; 3024 3025 # add required minimum version to response hash 3026 $Response{RequiredFrameworkMinimum} = $FrameworkMinimum; 3027 3028 $FWCheck = 0; 3029 } 3030 } 3031 } 3032 3033 # check maximum required framework, if the framework check is still positive so far 3034 if ( $FrameworkMaximum && $FWCheck ) { 3035 3036 COUNT: 3037 for my $Count ( 0 .. 2 ) { 3038 3039 $VersionParts{MaximumFrameworkRequired}->[$Count] ||= 0; 3040 $VersionParts{CurrentFramework}->[$Count] ||= 0; 3041 3042 next COUNT 3043 if $VersionParts{MaximumFrameworkRequired}->[$Count] eq 3044 $VersionParts{CurrentFramework}->[$Count]; 3045 3046 # skip current framework verion parts containing "x" 3047 next COUNT if $VersionParts{CurrentFramework}->[$Count] =~ /x/; 3048 3049 if ( 3050 $VersionParts{CurrentFramework}->[$Count] 3051 < $VersionParts{MaximumFrameworkRequired}->[$Count] 3052 ) 3053 { 3054 3055 $FWCheck = 1; 3056 last COUNT; 3057 } 3058 else { 3059 3060 # add required maximum version for the log entry 3061 $PossibleFramework .= 'Maximum Version ' . $FrameworkMaximum . ';'; 3062 3063 # add required maximum version to response hash 3064 $Response{RequiredFrameworkMaximum} = $FrameworkMaximum; 3065 3066 $FWCheck = 0; 3067 } 3068 3069 } 3070 } 3071 } 3072 } 3073 3074 } 3075 } 3076 3077 if ($FWCheck) { 3078 $Response{Success} = 1; 3079 } 3080 elsif ( !$Param{NoLog} ) { 3081 $Kernel::OM->Get('Kernel::System::Log')->Log( 3082 Priority => 'error', 3083 Message => "Sorry, can't install/upgrade package, because the framework version required" 3084 . " by the package ($PossibleFramework) does not match your Framework ($CurrentFramework)!", 3085 ); 3086 } 3087 3088 return %Response; 3089} 3090 3091=head2 PackageUpgradeAll() 3092 3093Updates installed packages to their latest version. Also updates OTRS Business Solution™ if system 3094 is entitled and there is an update. 3095 3096 my %Result = $PackageObject->PackageUpgradeAll( 3097 Force => 1, # optional 1 or 0, Upgrades packages even if validation fails. 3098 SkipDeployCheck => 1, # optional 1 or 0, If active it does not check file deployment status 3099 # for already updated packages. 3100 ); 3101 3102 %Result = ( 3103 Updated => { # updated packages to the latest on-line repository version 3104 PackageA => 1, 3105 PackageB => 1, 3106 PackageC => 1, 3107 # ... 3108 }, 3109 Installed => { # packages installed as a result of missing dependencies 3110 PackageD => 1, 3111 # ... 3112 }, 3113 AlreadyInstalled { # packages that are already installed with the latest version 3114 PackageE => 1, 3115 # ... 3116 } 3117 Undeployed { # packages not correctly deployed 3118 PackageK => 1, 3119 # ... 3120 } 3121 Failed => { # or {} if no failures 3122 Cyclic => { # packages with cyclic dependencies 3123 PackageF => 1, 3124 # ... 3125 }, 3126 NotFound => { # packages not listed in the on-line repositories 3127 PackageG => 1, 3128 # ... 3129 }, 3130 WrongVersion => { # packages that requires a mayor version that the available in the on-line repositories 3131 PackageH => 1, 3132 # ... 3133 }, 3134 DependencyFail => { # packages with dependencies that fail on any of the above reasons 3135 PackageI => 1, 3136 # ... 3137 }, 3138 }, 3139 ); 3140 3141=cut 3142 3143sub PackageUpgradeAll { 3144 my ( $Self, %Param ) = @_; 3145 3146 # Set system data as communication channel with the GUI 3147 my $SystemDataObject = $Kernel::OM->Get('Kernel::System::SystemData'); 3148 my $DataGroup = 'Package_UpgradeAll'; 3149 my %SystemData = $SystemDataObject->SystemDataGroupGet( 3150 Group => $DataGroup, 3151 ); 3152 if (%SystemData) { 3153 KEY: 3154 for my $Key (qw(StartTime UpdateTime InstalledPackages UpgradeResult Status Success)) 3155 { # remove any existing information 3156 next KEY if !defined $SystemData{$Key}; 3157 3158 my $Success = $SystemDataObject->SystemDataDelete( 3159 Key => "${DataGroup}::${Key}", 3160 UserID => 1, 3161 ); 3162 if ( !$Success ) { 3163 $Kernel::OM->Get('Kernel::System::Log')->Log( 3164 Priority => 'error', 3165 Message => "Could not delete key ${DataGroup}::${Key} from SystemData!", 3166 ); 3167 } 3168 } 3169 } 3170 my $CurrentDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 3171 $SystemDataObject->SystemDataAdd( 3172 Key => "${DataGroup}::StartTime", 3173 Value => $CurrentDateTimeObject->ToString(), 3174 UserID => 1, 3175 ); 3176 $SystemDataObject->SystemDataAdd( 3177 Key => "${DataGroup}::UpdateTime", 3178 Value => $CurrentDateTimeObject->ToString(), 3179 UserID => 1, 3180 ); 3181 $SystemDataObject->SystemDataAdd( 3182 Key => "${DataGroup}::Status", 3183 Value => "Running", 3184 UserID => 1, 3185 ); 3186 3187 my %OnlinePackages = $Self->_PackageOnlineListGet(); 3188 3189 my @PackageOnlineList = @{ $OnlinePackages{PackageList} }; 3190 my %PackageSoruceLookup = %{ $OnlinePackages{PackageLookup} }; 3191 3192 my @PackageInstalledList = $Self->RepositoryList( 3193 Result => 'short', 3194 ); 3195 3196 # Modify @PackageInstalledList if ITSM packages are installed from Bundle (see bug#13778). 3197 if ( grep { $_->{Name} eq 'ITSM' } @PackageInstalledList && grep { $_->{Name} eq 'ITSM' } @PackageOnlineList ) { 3198 my @TmpPackages = ( 3199 'GeneralCatalog', 3200 'ITSMCore', 3201 'ITSMChangeManagement', 3202 'ITSMConfigurationManagement', 3203 'ITSMIncidentProblemManagement', 3204 'ITSMServiceLevelManagement', 3205 'ImportExport' 3206 ); 3207 my %Values = map { $_ => 1 } @TmpPackages; 3208 @PackageInstalledList = grep { !$Values{ $_->{Name} } } @PackageInstalledList; 3209 } 3210 3211 my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON'); 3212 my $JSON = $JSONObject->Encode( 3213 Data => \@PackageInstalledList, 3214 ); 3215 $SystemDataObject->SystemDataAdd( 3216 Key => "${DataGroup}::InstalledPackages", 3217 Value => $JSON, 3218 UserID => 1, 3219 ); 3220 $SystemDataObject->SystemDataAdd( 3221 Key => "${DataGroup}::UpgradeResult", 3222 Value => '{}', 3223 UserID => 1, 3224 ); 3225 3226 my %Result = $Self->PackageInstallOrderListGet( 3227 InstalledPackages => \@PackageInstalledList, 3228 OnlinePackages => \@PackageOnlineList, 3229 ); 3230 3231 my %InstallOrder = %{ $Result{InstallOrder} }; 3232 my $Success = 1; 3233 if ( IsHashRefWithData( $Result{Failed} ) ) { 3234 $Success = 0; 3235 } 3236 3237 my %Failed = %{ $Result{Failed} }; 3238 my %Installed; 3239 my %Updated; 3240 my %AlreadyUpdated; 3241 my %Undeployed; 3242 3243 my %InstalledVersions = map { $_->{Name} => $_->{Version} } @PackageInstalledList; 3244 3245 PACKAGENAME: 3246 for my $PackageName ( sort { $InstallOrder{$b} <=> $InstallOrder{$a} } keys %InstallOrder ) { 3247 3248 if ( $PackageName eq 'OTRSBusiness' ) { 3249 my $UpdateSuccess = $Kernel::OM->Get('Kernel::System::OTRSBusiness')->OTRSBusinessUpdate(); 3250 3251 if ( !$UpdateSuccess ) { 3252 $Success = 0; 3253 $Failed{UpdateError}->{$PackageName} = 1; 3254 next PACKAGENAME; 3255 } 3256 3257 $Updated{'OTRS Business Solution™'} = 1; 3258 next PACKAGENAME; 3259 } 3260 3261 my $MetaPackage = $PackageSoruceLookup{$PackageName}; 3262 next PACKAGENAME if !$MetaPackage; 3263 3264 if ( $MetaPackage->{Version} eq ( $InstalledVersions{$PackageName} || '' ) ) { 3265 3266 if ( $Param{SkipDeployCheck} ) { 3267 $AlreadyUpdated{$PackageName} = 1; 3268 next PACKAGENAME; 3269 } 3270 3271 my $CheckSuccess = $Self->DeployCheck( 3272 Name => $PackageName, 3273 Version => $MetaPackage->{Version}, 3274 Log => 0 3275 ); 3276 if ( !$CheckSuccess ) { 3277 $Undeployed{$PackageName} = 1; 3278 next PACKAGENAME; 3279 } 3280 $AlreadyUpdated{$PackageName} = 1; 3281 next PACKAGENAME; 3282 } 3283 3284 my $Package = $Self->PackageOnlineGet( 3285 Source => $MetaPackage->{URL}, 3286 File => $MetaPackage->{File}, 3287 ); 3288 3289 if ( !$InstalledVersions{$PackageName} ) { 3290 my $InstallSuccess = $Self->PackageInstall( 3291 String => $Package, 3292 FromCloud => $MetaPackage->{FromCloud}, 3293 Force => $Param{Force} || 0, 3294 ); 3295 if ( !$InstallSuccess ) { 3296 $Success = 0; 3297 $Failed{InstallError}->{$PackageName} = 1; 3298 next PACKAGENAME; 3299 } 3300 $Installed{$PackageName} = 1; 3301 next PACKAGENAME; 3302 } 3303 3304 my $UpdateSuccess = $Self->PackageUpgrade( 3305 String => $Package, 3306 Force => $Param{Force} || 0, 3307 ); 3308 if ( !$UpdateSuccess ) { 3309 $Success = 0; 3310 $Failed{UpdateError}->{$PackageName} = 1; 3311 next PACKAGENAME; 3312 } 3313 $Updated{$PackageName} = 1; 3314 next PACKAGENAME; 3315 } 3316 continue { 3317 my $JSON = $JSONObject->Encode( 3318 Data => { 3319 Updated => \%Updated, 3320 Installed => \%Installed, 3321 AlreadyUpdated => \%AlreadyUpdated, 3322 Undeployed => \%Undeployed, 3323 Failed => \%Failed, 3324 }, 3325 ); 3326 $SystemDataObject->SystemDataUpdate( 3327 Key => "${DataGroup}::UpdateTime", 3328 Value => $Kernel::OM->Create('Kernel::System::DateTime')->ToString(), 3329 UserID => 1, 3330 ); 3331 $SystemDataObject->SystemDataUpdate( 3332 Key => "${DataGroup}::UpgradeResult", 3333 Value => $JSON, 3334 UserID => 1, 3335 ); 3336 } 3337 3338 $SystemDataObject->SystemDataAdd( 3339 Key => "${DataGroup}::Success", 3340 Value => $Success, 3341 UserID => 1, 3342 ); 3343 $SystemDataObject->SystemDataUpdate( 3344 Key => "${DataGroup}::Status", 3345 Value => 'Finished', 3346 UserID => 1, 3347 ); 3348 3349 return ( 3350 Success => $Success, 3351 Updated => \%Updated, 3352 Installed => \%Installed, 3353 AlreadyUpdated => \%AlreadyUpdated, 3354 Undeployed => \%Undeployed, 3355 Failed => \%Failed, 3356 ); 3357} 3358 3359=head2 PackageInstallOrderListGet() 3360 3361Gets a list of packages and its corresponding install order including is package dependencies. Higher 3362 install order means to install first. 3363 3364 my %Result = $PackageObject->PackageInstallOrderListGet( 3365 InstalledPackages => \@PakageList, # as returned from RepositoryList(Result => 'short') 3366 OnlinePackages => \@PakageList, # as returned from PackageOnlineList() 3367 ); 3368 3369 %Result = ( 3370 InstallOrder => { 3371 PackageA => 3, 3372 PackageB => 2, 3373 PackageC => 1, 3374 PackageD => 1, 3375 # ... 3376 }, 3377 Failed => { # or {} if no failures 3378 Cyclic => { # packages with cyclic dependencies 3379 PackageE => 1, 3380 # ... 3381 }, 3382 NotFound => { # packages not listed in the on-line repositories 3383 PackageF => 1, 3384 # ... 3385 }, 3386 WrongVersion => { # packages that requires a mayor version that the available in the on-line repositories 3387 PackageG => 1, 3388 # ... 3389 }, 3390 DependencyFail => { # packages with dependencies that fail on any of the above reasons 3391 PackageH => 1, 3392 # ... 3393 } 3394 }, 3395 ); 3396 3397=cut 3398 3399sub PackageInstallOrderListGet { 3400 my ( $Self, %Param ) = @_; 3401 3402 for my $Needed (qw(InstalledPackages OnlinePackages)) { 3403 if ( !$Param{$Needed} || ref $Param{$Needed} ne 'ARRAY' ) { 3404 $Kernel::OM->Get('Kernel::System::Log')->Log( 3405 Priority => 'error', 3406 Message => "$Needed is missing or invalid!", 3407 ); 3408 return; 3409 } 3410 } 3411 3412 my %InstalledVersions = map { $_->{Name} => $_->{Version} } @{ $Param{InstalledPackages} }; 3413 3414 my %OnlinePackageLookup = map { $_->{Name} => $_ } @{ $Param{OnlinePackages} }; 3415 3416 my %InstallOrder; 3417 my %Failed; 3418 3419 my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness'); 3420 3421 if ( $OTRSBusinessObject->OTRSBusinessIsInstalled() && $OTRSBusinessObject->OTRSBusinessIsUpdateable() ) { 3422 $InstallOrder{OTRSBusiness} = 9999; 3423 } 3424 3425 my $DependenciesSuccess = $Self->_PackageInstallOrderListGet( 3426 Callers => {}, 3427 InstalledVersions => \%InstalledVersions, 3428 TargetPackages => \%InstalledVersions, 3429 InstallOrder => \%InstallOrder, 3430 OnlinePackageLookup => \%OnlinePackageLookup, 3431 Failed => \%Failed, 3432 IsDependency => 0, 3433 ); 3434 3435 return ( 3436 InstallOrder => \%InstallOrder, 3437 Failed => \%Failed, 3438 ); 3439} 3440 3441=head2 PackageUpgradeAllDataDelete() 3442 3443Removes all Package Upgrade All data from the database. 3444 3445 my $Success = $PackageObject->PackageUpgradeAllDataDelete(); 3446 3447=cut 3448 3449sub PackageUpgradeAllDataDelete { 3450 my ( $Self, %Param ) = @_; 3451 3452 my $SystemDataObject = $Kernel::OM->Get('Kernel::System::SystemData'); 3453 my $DataGroup = 'Package_UpgradeAll'; 3454 my %SystemData = $SystemDataObject->SystemDataGroupGet( 3455 Group => $DataGroup, 3456 ); 3457 3458 my $Success = 1; 3459 3460 KEY: 3461 for my $Key (qw(StartTime UpdateTime InstalledPackages UpgradeResult Status Success)) { 3462 next KEY if !$SystemData{$Key}; 3463 3464 my $DeleteSuccess = $SystemDataObject->SystemDataDelete( 3465 Key => "${DataGroup}::${Key}", 3466 UserID => 1, 3467 ); 3468 if ( !$DeleteSuccess ) { 3469 $Kernel::OM->Get('Kernel::System::Log')->Log( 3470 Priority => 'error', 3471 Message => "Could not delete key ${DataGroup}::${Key} from SystemData!", 3472 ); 3473 $Success = 0; 3474 } 3475 } 3476 3477 return 1; 3478} 3479 3480=head2 PackageUpgradeAllIsRunning() 3481 3482Check if there is a Package Upgrade All process running by checking the scheduler tasks and the 3483system data. 3484 3485 my %Result = $PackageObject->PackageUpgradeAllIsRunning(); 3486 3487Returns: 3488 %Result = ( 3489 IsRunning => 1, # or 0 if it is not running 3490 UpgradeStatus => 'Running' # (optional) 'Running' or 'Finished' or 'TimedOut', 3491 UpgradeSuccess => 1, # (optional) 1 or 0, 3492 ); 3493 3494=cut 3495 3496sub PackageUpgradeAllIsRunning { 3497 my ( $Self, %Param ) = @_; 3498 3499 my $IsRunning; 3500 3501 # Check if there is a task for the scheduler daemon (process started from GUI). 3502 my @List = $Kernel::OM->Get('Kernel::System::Scheduler')->TaskList( 3503 Type => 'AsynchronousExecutor', 3504 ); 3505 if ( grep { $_->{Name} eq 'Kernel::System::Package-PackageUpgradeAll()' } @List ) { 3506 $IsRunning = 1; 3507 } 3508 3509 my $SystemDataObject = $Kernel::OM->Get('Kernel::System::SystemData'); 3510 my %SystemData = $SystemDataObject->SystemDataGroupGet( 3511 Group => 'Package_UpgradeAll', 3512 ); 3513 3514 # If there is no task running but there is system data it might be that the is a running 3515 # process from the CLI. 3516 if ( 3517 !$IsRunning 3518 && %SystemData 3519 && $SystemData{Status} 3520 && $SystemData{Status} eq 'Running' 3521 ) 3522 { 3523 $IsRunning = 1; 3524 3525 # Check if the last update was more than 5 minutes ago (timed out). 3526 my $CurrentDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 3527 my $TargetDateTimeObject = $Kernel::OM->Create( 3528 'Kernel::System::DateTime', 3529 ObjectParams => { 3530 String => $SystemData{UpdateTime}, 3531 } 3532 ); 3533 $TargetDateTimeObject->Add( Minutes => 5 ); 3534 if ( $CurrentDateTimeObject > $TargetDateTimeObject ) { 3535 $IsRunning = 0; 3536 $SystemData{Status} = 'TimedOut'; 3537 } 3538 } 3539 3540 return ( 3541 IsRunning => $IsRunning // 0, 3542 UpgradeStatus => $SystemData{Status} || '', 3543 UpgradeSuccess => $SystemData{Success} || '', 3544 ); 3545} 3546 3547=begin Internal: 3548 3549=cut 3550 3551sub _Download { 3552 my ( $Self, %Param ) = @_; 3553 3554 # check needed stuff 3555 if ( !defined $Param{URL} ) { 3556 $Kernel::OM->Get('Kernel::System::Log')->Log( 3557 Priority => 'error', 3558 Message => 'URL not defined!', 3559 ); 3560 return; 3561 } 3562 3563 my $WebUserAgentObject = Kernel::System::WebUserAgent->new( 3564 Timeout => $Self->{ConfigObject}->Get('Package::Timeout'), 3565 Proxy => $Self->{ConfigObject}->Get('Package::Proxy'), 3566 ); 3567 3568 my %Response = $WebUserAgentObject->Request( 3569 URL => $Param{URL}, 3570 ); 3571 3572 return if !$Response{Content}; 3573 return ${ $Response{Content} }; 3574} 3575 3576sub _Database { 3577 my ( $Self, %Param ) = @_; 3578 3579 # check needed stuff 3580 if ( !defined $Param{Database} ) { 3581 $Kernel::OM->Get('Kernel::System::Log')->Log( 3582 Priority => 'error', 3583 Message => 'Database not defined!', 3584 ); 3585 return; 3586 } 3587 3588 if ( ref $Param{Database} ne 'ARRAY' ) { 3589 $Kernel::OM->Get('Kernel::System::Log')->Log( 3590 Priority => 'error', 3591 Message => 'Need array ref in Database param!', 3592 ); 3593 return; 3594 } 3595 3596 # get database object 3597 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 3598 3599 my @SQL = $DBObject->SQLProcessor( 3600 Database => $Param{Database}, 3601 ); 3602 3603 for my $SQL (@SQL) { 3604 print STDERR "Notice: $SQL\n"; 3605 $DBObject->Do( SQL => $SQL ); 3606 } 3607 3608 my @SQLPost = $DBObject->SQLProcessorPost(); 3609 3610 for my $SQL (@SQLPost) { 3611 print STDERR "Notice: $SQL\n"; 3612 $DBObject->Do( SQL => $SQL ); 3613 } 3614 3615 return 1; 3616} 3617 3618sub _Code { 3619 my ( $Self, %Param ) = @_; 3620 3621 # check needed stuff 3622 for my $Needed (qw(Code Type Structure)) { 3623 if ( !defined $Param{$Needed} ) { 3624 $Kernel::OM->Get('Kernel::System::Log')->Log( 3625 Priority => 'error', 3626 Message => "$Needed not defined!", 3627 ); 3628 return; 3629 } 3630 } 3631 3632 # check format 3633 if ( ref $Param{Code} ne 'ARRAY' ) { 3634 $Kernel::OM->Get('Kernel::System::Log')->Log( 3635 Priority => 'error', 3636 Message => 'Need array ref in Code param!', 3637 ); 3638 return; 3639 } 3640 3641 # execute code 3642 CODE: 3643 for my $Code ( @{ $Param{Code} } ) { 3644 3645 next CODE if !$Code->{Content}; 3646 next CODE if $Param{Type} !~ /^$Code->{Type}$/i; 3647 3648 # if the merged packages was already installed or not 3649 if ( 3650 ( 3651 defined $Code->{IfPackage} 3652 && !$Self->{MergedPackages}->{ $Code->{IfPackage} } 3653 ) 3654 || ( 3655 defined $Code->{IfNotPackage} 3656 && ( 3657 $Self->{MergedPackages}->{ $Code->{IfNotPackage} } 3658 || $Self->PackageIsInstalled( Name => $Code->{IfNotPackage} ) 3659 ) 3660 ) 3661 ) 3662 { 3663 next CODE; 3664 } 3665 3666 print STDERR "Code: $Code->{Content}\n"; 3667 3668 if ( !eval $Code->{Content} . "\n1;" ) { ## no critic 3669 $Kernel::OM->Get('Kernel::System::Log')->Log( 3670 Priority => 'error', 3671 Message => "Code: $@", 3672 ); 3673 return; 3674 } 3675 } 3676 3677 return 1; 3678} 3679 3680sub _OSCheck { 3681 my ( $Self, %Param ) = @_; 3682 3683 # check needed stuff 3684 if ( !defined $Param{OS} ) { 3685 $Kernel::OM->Get('Kernel::System::Log')->Log( 3686 Priority => 'error', 3687 Message => 'OS not defined!', 3688 ); 3689 return; 3690 } 3691 3692 # check format 3693 if ( ref $Param{OS} ne 'ARRAY' ) { 3694 $Kernel::OM->Get('Kernel::System::Log')->Log( 3695 Priority => 'error', 3696 Message => 'Need array ref in OS param!', 3697 ); 3698 return; 3699 } 3700 3701 # check OS 3702 my $OSCheck = 0; 3703 my $CurrentOS = $^O; 3704 my @TestedOS; 3705 3706 OS: 3707 for my $OS ( @{ $Param{OS} } ) { 3708 next OS if !$OS->{Content}; 3709 push @TestedOS, $OS->{Content}; 3710 next OS if $CurrentOS !~ /^$OS->{Content}$/i; 3711 3712 $OSCheck = 1; 3713 last OS; 3714 } 3715 3716 return 1 if $OSCheck; 3717 return if $Param{NoLog}; 3718 3719 my $PossibleOS = join ', ', @TestedOS; 3720 3721 $Kernel::OM->Get('Kernel::System::Log')->Log( 3722 Priority => 'error', 3723 Message => "Sorry, can't install/upgrade package, because OS of package " 3724 . "($PossibleOS) does not match your OS ($CurrentOS)!", 3725 ); 3726 3727 return; 3728} 3729 3730=head2 _CheckVersion() 3731 3732Compare the two version strings $VersionNew and $VersionInstalled. 3733The type is either 'Min' or 'Max'. 3734'Min' returns a true value if $VersionInstalled >= $VersionNew. 3735'Max' returns a true value if $VersionInstalled < $VersionNew. 3736Otherwise undef is returned in scalar context. 3737 3738 my $CheckOk = $PackageObject->_CheckVersion( 3739 VersionNew => '1.3.92', 3740 VersionInstalled => '1.3.91', 3741 Type => 'Min', # 'Min' or 'Max' 3742 ExternalPackage => 1, # optional 3743 ) 3744 3745=cut 3746 3747sub _CheckVersion { 3748 my ( $Self, %Param ) = @_; 3749 3750 # check needed stuff 3751 for my $Needed (qw(VersionNew VersionInstalled Type)) { 3752 if ( !defined $Param{$Needed} ) { 3753 $Kernel::OM->Get('Kernel::System::Log')->Log( 3754 Priority => 'error', 3755 Message => "$Needed not defined!", 3756 ); 3757 return; 3758 } 3759 } 3760 3761 # check Type 3762 if ( $Param{Type} ne 'Min' && $Param{Type} ne 'Max' ) { 3763 3764 $Kernel::OM->Get('Kernel::System::Log')->Log( 3765 Priority => 'error', 3766 Message => 'Invalid Type!', 3767 ); 3768 return; 3769 } 3770 3771 # prepare parts hash 3772 my %Parts; 3773 TYPE: 3774 for my $Type (qw(VersionNew VersionInstalled)) { 3775 3776 # split version string 3777 my @ThisParts = split /\./, $Param{$Type}; 3778 3779 $Parts{$Type} = \@ThisParts; 3780 $Parts{ $Type . 'Num' } = scalar @ThisParts; 3781 } 3782 3783 # if it is not an external package, and the versions are different 3784 # we want to add a 0 at the end of the shorter version number 3785 # (1.2.3 will be modified to 1.2.3.0) 3786 # This is important to compare with a test-release version number 3787 if ( !$Param{ExternalPackage} && $Parts{VersionNewNum} ne $Parts{VersionInstalledNum} ) { 3788 3789 TYPE: 3790 for my $Type (qw(VersionNew VersionInstalled)) { 3791 3792 next TYPE if $Parts{ $Type . 'Num' } > 3; 3793 3794 # add a zero at the end if number has less than 4 digits 3795 push @{ $Parts{$Type} }, 0; 3796 $Parts{ $Type . 'Num' } = scalar @{ $Parts{$Type} }; 3797 } 3798 } 3799 3800 COUNT: 3801 for my $Count ( 0 .. 5 ) { 3802 3803 $Parts{VersionNew}->[$Count] ||= 0; 3804 $Parts{VersionInstalled}->[$Count] ||= 0; 3805 3806 next COUNT if $Parts{VersionNew}->[$Count] eq $Parts{VersionInstalled}->[$Count]; 3807 3808 # compare versions 3809 if ( $Param{Type} eq 'Min' ) { 3810 return 1 if $Parts{VersionInstalled}->[$Count] >= $Parts{VersionNew}->[$Count]; 3811 return; 3812 } 3813 elsif ( $Param{Type} eq 'Max' ) { 3814 return 1 if $Parts{VersionInstalled}->[$Count] < $Parts{VersionNew}->[$Count]; 3815 return; 3816 } 3817 } 3818 3819 return 1 if $Param{Type} eq 'Min'; 3820 return; 3821} 3822 3823sub _CheckPackageRequired { 3824 my ( $Self, %Param ) = @_; 3825 3826 # check needed stuff 3827 if ( !defined $Param{PackageRequired} ) { 3828 $Kernel::OM->Get('Kernel::System::Log')->Log( 3829 Priority => 'error', 3830 Message => 'PackageRequired not defined!', 3831 ); 3832 return; 3833 } 3834 3835 return 1 if !$Param{PackageRequired}; 3836 return 1 if ref $Param{PackageRequired} ne 'ARRAY'; 3837 3838 # get repository list 3839 my @RepositoryList = $Self->RepositoryList(); 3840 3841 # check required packages 3842 PACKAGE: 3843 for my $Package ( @{ $Param{PackageRequired} } ) { 3844 3845 next PACKAGE if !$Package; 3846 3847 my $Installed = 0; 3848 my $InstalledVersion = 0; 3849 3850 LOCAL: 3851 for my $Local (@RepositoryList) { 3852 3853 next LOCAL if $Local->{Name}->{Content} ne $Package->{Content}; 3854 next LOCAL if $Local->{Status} ne 'installed'; 3855 3856 $Installed = 1; 3857 $InstalledVersion = $Local->{Version}->{Content}; 3858 last LOCAL; 3859 } 3860 3861 if ( !$Installed ) { 3862 $Kernel::OM->Get('Kernel::System::Log')->Log( 3863 Priority => 'error', 3864 Message => "Sorry, can't install package, because package " 3865 . "$Package->{Content} v$Package->{Version} is required!", 3866 ); 3867 return; 3868 } 3869 3870 my $VersionCheck = $Self->_CheckVersion( 3871 VersionNew => $Package->{Version}, 3872 VersionInstalled => $InstalledVersion, 3873 Type => 'Min', 3874 ); 3875 3876 next PACKAGE if $VersionCheck; 3877 3878 $Kernel::OM->Get('Kernel::System::Log')->Log( 3879 Priority => 'error', 3880 Message => "Sorry, can't install package, because " 3881 . "package $Package->{Content} v$Package->{Version} is required!", 3882 ); 3883 return; 3884 } 3885 3886 return 1; 3887} 3888 3889sub _CheckModuleRequired { 3890 my ( $Self, %Param ) = @_; 3891 3892 # check needed stuff 3893 if ( !defined $Param{ModuleRequired} ) { 3894 $Kernel::OM->Get('Kernel::System::Log')->Log( 3895 Priority => 'error', 3896 Message => 'ModuleRequired not defined!', 3897 ); 3898 return; 3899 } 3900 3901 # check required perl modules 3902 if ( $Param{ModuleRequired} && ref $Param{ModuleRequired} eq 'ARRAY' ) { 3903 3904 my $EnvironmentObject = $Kernel::OM->Get('Kernel::System::Environment'); 3905 3906 MODULE: 3907 for my $Module ( @{ $Param{ModuleRequired} } ) { 3908 3909 next MODULE if !$Module; 3910 3911 # Check if module is installed by querying its version number via environment object. 3912 # Some required modules might already be loaded by existing process, and might not support reloading. 3913 # Because of this, opt not to use the main object an its Require() method at this point. 3914 my $Installed = 0; 3915 my $InstalledVersion = $EnvironmentObject->ModuleVersionGet( 3916 Module => $Module->{Content}, 3917 ); 3918 if ($InstalledVersion) { 3919 $Installed = 1; 3920 } 3921 3922 if ( !$Installed ) { 3923 $Kernel::OM->Get('Kernel::System::Log')->Log( 3924 Priority => 'error', 3925 Message => "Sorry, can't install package, because module " 3926 . "$Module->{Content} v$Module->{Version} is required " 3927 . "and not installed!", 3928 ); 3929 return; 3930 } 3931 3932 # return if no version is required 3933 return 1 if !$Module->{Version}; 3934 3935 # return if no module version is available 3936 return 1 if !$InstalledVersion; 3937 3938 # check version 3939 my $Ok = $Self->_CheckVersion( 3940 VersionNew => $Module->{Version}, 3941 VersionInstalled => $InstalledVersion, 3942 Type => 'Min', 3943 ExternalPackage => 1, 3944 ); 3945 3946 if ( !$Ok ) { 3947 $Kernel::OM->Get('Kernel::System::Log')->Log( 3948 Priority => 'error', 3949 Message => "Sorry, can't install package, because module " 3950 . "$Module->{Content} v$Module->{Version} is required and " 3951 . "$InstalledVersion is installed! You need to upgrade " 3952 . "$Module->{Content} to $Module->{Version} or higher first!", 3953 ); 3954 return; 3955 } 3956 } 3957 } 3958 3959 return 1; 3960} 3961 3962sub _CheckPackageDepends { 3963 my ( $Self, %Param ) = @_; 3964 3965 # check needed stuff 3966 if ( !defined $Param{Name} ) { 3967 $Kernel::OM->Get('Kernel::System::Log')->Log( 3968 Priority => 'error', 3969 Message => 'Name not defined!', 3970 ); 3971 return; 3972 } 3973 3974 for my $Local ( $Self->RepositoryList() ) { 3975 3976 if ( 3977 $Local->{PackageRequired} 3978 && ref $Local->{PackageRequired} eq 'ARRAY' 3979 && $Local->{Name}->{Content} ne $Param{Name} 3980 && $Local->{Status} eq 'installed' 3981 ) 3982 { 3983 for my $Module ( @{ $Local->{PackageRequired} } ) { 3984 if ( $Param{Name} eq $Module->{Content} && !$Param{Force} ) { 3985 $Kernel::OM->Get('Kernel::System::Log')->Log( 3986 Priority => 'error', 3987 Message => 3988 "Sorry, can't uninstall package $Param{Name}, " 3989 . "because package $Local->{Name}->{Content} depends on it!", 3990 ); 3991 return; 3992 } 3993 } 3994 } 3995 } 3996 3997 return 1; 3998} 3999 4000sub _PackageFileCheck { 4001 my ( $Self, %Param ) = @_; 4002 4003 # check needed stuff 4004 if ( !defined $Param{Structure} ) { 4005 $Kernel::OM->Get('Kernel::System::Log')->Log( 4006 Priority => 'error', 4007 Message => 'Structure not defined!', 4008 ); 4009 return; 4010 } 4011 4012 # check if one of the files is already installed by another package 4013 PACKAGE: 4014 for my $Package ( $Self->RepositoryList() ) { 4015 4016 next PACKAGE if $Param{Structure}->{Name}->{Content} eq $Package->{Name}->{Content}; 4017 4018 for my $FileNew ( @{ $Param{Structure}->{Filelist} } ) { 4019 4020 FILEOLD: 4021 for my $FileOld ( @{ $Package->{Filelist} } ) { 4022 4023 $FileNew->{Location} =~ s/\/\//\//g; 4024 $FileOld->{Location} =~ s/\/\//\//g; 4025 4026 next FILEOLD if $FileNew->{Location} ne $FileOld->{Location}; 4027 4028 $Kernel::OM->Get('Kernel::System::Log')->Log( 4029 Priority => 'error', 4030 Message => "Can't install/upgrade package, file $FileNew->{Location} already " 4031 . "used in package $Package->{Name}->{Content}-$Package->{Version}->{Content}!", 4032 ); 4033 4034 return; 4035 } 4036 } 4037 } 4038 4039 return 1; 4040} 4041 4042sub _FileInstall { 4043 my ( $Self, %Param ) = @_; 4044 4045 # check needed stuff 4046 for my $Needed (qw(File)) { 4047 if ( !defined $Param{$Needed} ) { 4048 $Kernel::OM->Get('Kernel::System::Log')->Log( 4049 Priority => 'error', 4050 Message => "$Needed not defined!", 4051 ); 4052 return; 4053 } 4054 } 4055 for my $Item (qw(Location Content Permission)) { 4056 if ( !defined $Param{File}->{$Item} ) { 4057 $Kernel::OM->Get('Kernel::System::Log')->Log( 4058 Priority => 'error', 4059 Message => "$Item not defined in File!", 4060 ); 4061 return; 4062 } 4063 } 4064 4065 my $Home = $Param{Home} || $Self->{Home}; 4066 4067 # check Home 4068 if ( !-e $Home ) { 4069 $Kernel::OM->Get('Kernel::System::Log')->Log( 4070 Priority => 'error', 4071 Message => "No such home directory: $Home!", 4072 ); 4073 return; 4074 } 4075 4076 # get real file name in fs 4077 my $RealFile = $Home . '/' . $Param{File}->{Location}; 4078 $RealFile =~ s/\/\//\//g; 4079 4080 # get main object 4081 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 4082 4083 # backup old file (if reinstall, don't overwrite .backup and .save files) 4084 if ( -e $RealFile ) { 4085 if ( $Param{File}->{Type} && $Param{File}->{Type} =~ /^replace$/i ) { 4086 if ( !$Param{Reinstall} || ( $Param{Reinstall} && !-e "$RealFile.backup" ) ) { 4087 move( $RealFile, "$RealFile.backup" ); 4088 } 4089 } 4090 else { 4091 4092 # check if we reinstall the same file, create a .save if it is not the same 4093 my $Save = 0; 4094 if ( $Param{Reinstall} && !-e "$RealFile.save" ) { 4095 4096 # check if it's not the same 4097 my $Content = $MainObject->FileRead( 4098 Location => $RealFile, 4099 Mode => 'binmode', 4100 ); 4101 if ( $Content && ${$Content} ne $Param{File}->{Content} ) { 4102 4103 # check if it's a framework file, create .save file 4104 my %File = $Self->_ReadDistArchive( Home => $Home ); 4105 if ( $File{ $Param{File}->{Location} } ) { 4106 $Save = 1; 4107 } 4108 } 4109 } 4110 4111 # if it's no reinstall or reinstall and framework file but different, back it up 4112 if ( !$Param{Reinstall} || ( $Param{Reinstall} && $Save ) ) { 4113 move( $RealFile, "$RealFile.save" ); 4114 } 4115 } 4116 } 4117 4118 # check directory of location (in case create a directory) 4119 if ( $Param{File}->{Location} =~ /^(.*)\/(.+?|)$/ ) { 4120 4121 my $Directory = $1; 4122 my @Directories = split( /\//, $Directory ); 4123 my $DirectoryCurrent = $Home; 4124 4125 DIRECTORY: 4126 for my $Directory (@Directories) { 4127 4128 $DirectoryCurrent .= '/' . $Directory; 4129 4130 next DIRECTORY if -d $DirectoryCurrent; 4131 4132 if ( mkdir $DirectoryCurrent ) { 4133 print STDERR "Notice: Create Directory $DirectoryCurrent!\n"; 4134 } 4135 else { 4136 $Kernel::OM->Get('Kernel::System::Log')->Log( 4137 Priority => 'error', 4138 Message => "Can't create directory: $DirectoryCurrent: $!", 4139 ); 4140 } 4141 } 4142 } 4143 4144 # write file 4145 return if !$MainObject->FileWrite( 4146 Location => $RealFile, 4147 Content => \$Param{File}->{Content}, 4148 Mode => 'binmode', 4149 Permission => $Param{File}->{Permission}, 4150 ); 4151 4152 print STDERR "Notice: Install $RealFile ($Param{File}->{Permission})!\n"; 4153 4154 return 1; 4155} 4156 4157sub _FileRemove { 4158 my ( $Self, %Param ) = @_; 4159 4160 # check needed stuff 4161 for my $Needed (qw(File)) { 4162 if ( !defined $Param{$Needed} ) { 4163 $Kernel::OM->Get('Kernel::System::Log')->Log( 4164 Priority => 'error', 4165 Message => "$Needed not defined!", 4166 ); 4167 return; 4168 } 4169 } 4170 for my $Item (qw(Location)) { 4171 if ( !defined $Param{File}->{$Item} ) { 4172 $Kernel::OM->Get('Kernel::System::Log')->Log( 4173 Priority => 'error', 4174 Message => "$Item not defined in File!", 4175 ); 4176 return; 4177 } 4178 } 4179 4180 my $Home = $Param{Home} || $Self->{Home}; 4181 4182 # check Home 4183 if ( !-e $Home ) { 4184 $Kernel::OM->Get('Kernel::System::Log')->Log( 4185 Priority => 'error', 4186 Message => "No such home directory: $Home!", 4187 ); 4188 return; 4189 } 4190 4191 # get real file name in fs 4192 my $RealFile = $Home . '/' . $Param{File}->{Location}; 4193 $RealFile =~ s/\/\//\//g; 4194 4195 # check if file exists 4196 if ( !-e $RealFile ) { 4197 $Kernel::OM->Get('Kernel::System::Log')->Log( 4198 Priority => 'debug', 4199 Message => "No such file: $RealFile!", 4200 ); 4201 return; 4202 } 4203 4204 # get main object 4205 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 4206 4207 # check if we should backup this file, if it is touched/different 4208 if ( $Param{File}->{Content} ) { 4209 my $Content = $MainObject->FileRead( 4210 Location => $RealFile, 4211 Mode => 'binmode', 4212 ); 4213 if ( $Content && ${$Content} ne $Param{File}->{Content} ) { 4214 print STDERR "Notice: Backup for changed file: $RealFile.backup\n"; 4215 copy( $RealFile, "$RealFile.custom_backup" ); 4216 } 4217 } 4218 4219 # check if it's a framework file and if $RealFile.(backup|save) exists 4220 # then do not remove it! 4221 my %File = $Self->_ReadDistArchive( Home => $Home ); 4222 if ( $File{ $Param{File}->{Location} } && ( !-e "$RealFile.backup" && !-e "$RealFile.save" ) ) { 4223 $Kernel::OM->Get('Kernel::System::Log')->Log( 4224 Priority => 'error', 4225 Message => "Can't remove file $RealFile, because it a framework file and no " 4226 . "other one exists!", 4227 ); 4228 return; 4229 } 4230 4231 # remove old file 4232 if ( !$MainObject->FileDelete( Location => $RealFile ) ) { 4233 $Kernel::OM->Get('Kernel::System::Log')->Log( 4234 Priority => 'error', 4235 Message => "Can't remove file $RealFile: $!!", 4236 ); 4237 return; 4238 } 4239 4240 print STDERR "Notice: Removed file: $RealFile\n"; 4241 4242 # restore old file (if exists) 4243 if ( -e "$RealFile.backup" ) { 4244 print STDERR "Notice: Recovered: $RealFile.backup\n"; 4245 move( "$RealFile.backup", $RealFile ); 4246 } 4247 4248 # restore old file (if exists) 4249 elsif ( -e "$RealFile.save" ) { 4250 print STDERR "Notice: Recovered: $RealFile.save\n"; 4251 move( "$RealFile.save", $RealFile ); 4252 } 4253 4254 return 1; 4255} 4256 4257sub _ReadDistArchive { 4258 my ( $Self, %Param ) = @_; 4259 4260 my $Home = $Param{Home} || $Self->{Home}; 4261 4262 # check cache 4263 return %{ $Self->{Cache}->{DistArchive}->{$Home} } 4264 if $Self->{Cache}->{DistArchive}->{$Home}; 4265 4266 # check if ARCHIVE exists 4267 if ( !-e "$Home/ARCHIVE" ) { 4268 $Kernel::OM->Get('Kernel::System::Log')->Log( 4269 Priority => 'error', 4270 Message => "No such file: $Home/ARCHIVE!", 4271 ); 4272 return; 4273 } 4274 4275 # read ARCHIVE file 4276 my $Content = $Kernel::OM->Get('Kernel::System::Main')->FileRead( 4277 Directory => $Home, 4278 Filename => 'ARCHIVE', 4279 Result => 'ARRAY', 4280 ); 4281 4282 my %File; 4283 if ($Content) { 4284 4285 for my $ContentRow ( @{$Content} ) { 4286 4287 my @Row = split /::/, $ContentRow; 4288 $Row[1] =~ s/\/\///g; 4289 $Row[1] =~ s/(\n|\r)//g; 4290 4291 $File{ $Row[1] } = $Row[0]; 4292 } 4293 } 4294 else { 4295 $Kernel::OM->Get('Kernel::System::Log')->Log( 4296 Priority => 'error', 4297 Message => "Can't open $Home/ARCHIVE: $!", 4298 ); 4299 } 4300 4301 # set in memory cache 4302 $Self->{Cache}->{DistArchive}->{$Home} = \%File; 4303 4304 return %File; 4305} 4306 4307sub _FileSystemCheck { 4308 my ( $Self, %Param ) = @_; 4309 4310 return 1 if $Self->{FileSystemCheckAlreadyDone}; 4311 4312 my $Home = $Param{Home} || $Self->{Home}; 4313 4314 # check Home 4315 if ( !-e $Home ) { 4316 $Kernel::OM->Get('Kernel::System::Log')->Log( 4317 Priority => 'error', 4318 Message => "No such home directory: $Home!", 4319 ); 4320 return; 4321 } 4322 4323 my @Filepaths = ( 4324 '/bin/', 4325 '/Kernel/', 4326 '/Kernel/System/', 4327 '/Kernel/Output/', 4328 '/Kernel/Output/HTML/', 4329 '/Kernel/Modules/', 4330 ); 4331 4332 # check write permissions 4333 FILEPATH: 4334 for my $Filepath (@Filepaths) { 4335 4336 next FILEPATH if -w $Home . $Filepath; 4337 4338 $Kernel::OM->Get('Kernel::System::Log')->Log( 4339 Priority => 'error', 4340 Message => "ERROR: Need write permissions for directory $Home$Filepath\n" 4341 . " Try: $Home/bin/otrs.SetPermissions.pl!", 4342 ); 4343 4344 return; 4345 } 4346 4347 $Self->{FileSystemCheckAlreadyDone} = 1; 4348 4349 return 1; 4350} 4351 4352sub _Encode { 4353 my ( $Self, $Text ) = @_; 4354 4355 return $Text if !defined $Text; 4356 4357 $Text =~ s/&/&/g; 4358 $Text =~ s/</</g; 4359 $Text =~ s/>/>/g; 4360 $Text =~ s/"/"/g; 4361 4362 return $Text; 4363} 4364 4365=head2 _PackageUninstallMerged() 4366 4367ONLY CALL THIS METHOD FROM A DATABASE UPGRADING SCRIPT DURING FRAMEWORK UPDATES 4368OR FROM A CODEUPGRADE SECTION IN AN SOPM FILE OF A PACKAGE THAT INCLUDES A MERGED FEATURE ADDON. 4369 4370Uninstall an already framework (or module) merged package. 4371 4372Package files that are not in the framework ARCHIVE file will be deleted, DatabaseUninstall() and 4373CodeUninstall are not called. 4374 4375 $Success = $PackageObject->_PackageUninstallMerged( 4376 Name => 'some package name', 4377 Home => 'OTRS Home path', # Optional 4378 DeleteSaved => 1, # or 0, 1 Default, Optional: if set to 1 it also 4379 # delete .save files 4380 ); 4381 4382=cut 4383 4384sub _PackageUninstallMerged { 4385 my ( $Self, %Param ) = @_; 4386 4387 # check needed stuff 4388 if ( !$Param{Name} ) { 4389 $Kernel::OM->Get('Kernel::System::Log')->Log( 4390 Priority => 'error', 4391 Message => 'Need Name (Name of the package)!', 4392 ); 4393 return; 4394 } 4395 4396 my $Home = $Param{Home} || $Self->{Home}; 4397 4398 # check Home 4399 if ( !-e $Home ) { 4400 $Kernel::OM->Get('Kernel::System::Log')->Log( 4401 Priority => 'error', 4402 Message => "No such home directory: $Home!", 4403 ); 4404 return; 4405 } 4406 4407 if ( !defined $Param{DeleteSaved} ) { 4408 $Param{DeleteSaved} = 1; 4409 } 4410 4411 # check if the package is installed, otherwise return success (nothing to do) 4412 my $PackageInstalled = $Self->PackageIsInstalled( 4413 Name => $Param{Name}, 4414 ); 4415 return 1 if !$PackageInstalled; 4416 4417 # get the package details 4418 my @PackageList = $Self->RepositoryList(); 4419 my %PackageListLookup = map { $_->{Name}->{Content} => $_ } @PackageList; 4420 my %PackageDetails = %{ $PackageListLookup{ $Param{Name} } }; 4421 4422 # get the list of framework files 4423 my %FrameworkFiles = $Self->_ReadDistArchive( Home => $Home ); 4424 4425 # can not continue if there are no framework files 4426 return if !%FrameworkFiles; 4427 4428 # remove unneeded files (if exists) 4429 if ( IsArrayRefWithData( $PackageDetails{Filelist} ) ) { 4430 4431 # get main object 4432 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 4433 4434 FILE: 4435 for my $FileHash ( @{ $PackageDetails{Filelist} } ) { 4436 4437 my $File = $FileHash->{Location}; 4438 4439 # get real file name in fs 4440 my $RealFile = $Home . '/' . $File; 4441 $RealFile =~ s/\/\//\//g; 4442 4443 # check if file exists 4444 if ( -e $RealFile ) { 4445 4446 # check framework files (use $File instead of $RealFile) 4447 if ( $FrameworkFiles{$File} ) { 4448 4449 if ( $Param{DeleteSaved} ) { 4450 4451 # check if file was overridden by the package 4452 my $SavedFile = $RealFile . '.save'; 4453 if ( -e $SavedFile ) { 4454 4455 # remove old file 4456 if ( !$MainObject->FileDelete( Location => $SavedFile ) ) { 4457 $Kernel::OM->Get('Kernel::System::Log')->Log( 4458 Priority => 'error', 4459 Message => "Can't remove file $SavedFile: $!!", 4460 ); 4461 return; 4462 } 4463 print STDERR "Notice: Removed old backup file: $SavedFile\n"; 4464 } 4465 } 4466 4467 # skip framework file 4468 print STDERR "Notice: Skiped framework file: $RealFile\n"; 4469 next FILE; 4470 } 4471 4472 # remove old file 4473 if ( !$MainObject->FileDelete( Location => $RealFile ) ) { 4474 $Kernel::OM->Get('Kernel::System::Log')->Log( 4475 Priority => 'error', 4476 Message => "Can't remove file $RealFile: $!!", 4477 ); 4478 return; 4479 } 4480 print STDERR "Notice: Removed file: $RealFile\n"; 4481 } 4482 } 4483 } 4484 4485 # delete package from the database 4486 my $PackageRemove = $Self->RepositoryRemove( 4487 Name => $Param{Name}, 4488 ); 4489 4490 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 4491 KeepTypes => [ 4492 'XMLParse', 4493 'SysConfigDefaultListGet', 4494 'SysConfigDefaultList', 4495 'SysConfigDefault', 4496 'SysConfigPersistent', 4497 'SysConfigModifiedList', 4498 ], 4499 ); 4500 $Kernel::OM->Get('Kernel::System::Loader')->CacheDelete(); 4501 4502 return $PackageRemove; 4503} 4504 4505sub _MergedPackages { 4506 my ( $Self, %Param ) = @_; 4507 4508 # check needed stuff 4509 if ( !defined $Param{Structure}->{PackageMerge} ) { 4510 $Kernel::OM->Get('Kernel::System::Log')->Log( 4511 Priority => 'error', 4512 Message => 'PackageMerge not defined!', 4513 ); 4514 4515 return; 4516 } 4517 4518 return 1 if !$Param{Structure}->{PackageMerge}; 4519 return 1 if ref $Param{Structure}->{PackageMerge} ne 'ARRAY'; 4520 4521 # get repository list 4522 my @RepositoryList = $Self->RepositoryList(); 4523 my %PackageListLookup = map { $_->{Name}->{Content} => $_ } @RepositoryList; 4524 4525 # check required packages 4526 PACKAGE: 4527 for my $Package ( @{ $Param{Structure}->{PackageMerge} } ) { 4528 4529 next PACKAGE if !$Package; 4530 4531 my $Installed = 0; 4532 my $InstalledVersion = 0; 4533 my $TargetVersion = $Package->{TargetVersion}; 4534 my %PackageDetails; 4535 4536 # check if the package is installed, otherwise go next package (nothing to do) 4537 my $PackageInstalled = $Self->PackageIsInstalled( 4538 Name => $Package->{Name}, 4539 ); 4540 4541 # do nothing if package is not installed 4542 next PACKAGE if !$PackageInstalled; 4543 4544 # get complete package info 4545 %PackageDetails = %{ $PackageListLookup{ $Package->{Name} } }; 4546 4547 # verify package version 4548 $InstalledVersion = $PackageDetails{Version}->{Content}; 4549 4550 # store package name and version for 4551 # use it on code and database installation 4552 # for principal package 4553 $Self->{MergedPackages}->{ $Package->{Name} } = $InstalledVersion; 4554 4555 my $CheckTargetVersion = $Self->_CheckVersion( 4556 VersionNew => $TargetVersion, 4557 VersionInstalled => $InstalledVersion, 4558 Type => 'Max', 4559 ); 4560 4561 if ( $TargetVersion eq $InstalledVersion ) { 4562 4563 # do nothing, installed version is the correct one, 4564 # code and database are up to date 4565 } 4566 4567 # merged package shouldn't be newer than the known mergeable target version 4568 elsif ( !$CheckTargetVersion ) { 4569 $Kernel::OM->Get('Kernel::System::Log')->Log( 4570 Priority => 'error', 4571 Message => "Sorry, can't install package, because package " 4572 . "$Package->{Name} v$InstalledVersion newer than required v$TargetVersion!", 4573 ); 4574 4575 return; 4576 } 4577 else { 4578 4579 # upgrade code (merge) 4580 if ( 4581 $Param{Structure}->{CodeUpgrade} 4582 && ref $Param{Structure}->{CodeUpgrade} eq 'ARRAY' 4583 ) 4584 { 4585 4586 my @Parts; 4587 PART: 4588 for my $Part ( @{ $Param{Structure}->{CodeUpgrade} } ) { 4589 4590 if ( $Part->{Version} ) { 4591 4592 # if VersionNew >= VersionInstalled add code for execution 4593 my $CheckVersion = $Self->_CheckVersion( 4594 VersionNew => $Part->{Version}, 4595 VersionInstalled => $TargetVersion, 4596 Type => 'Min', 4597 ); 4598 4599 if ($CheckVersion) { 4600 push @Parts, $Part; 4601 } 4602 } 4603 else { 4604 push @Parts, $Part; 4605 } 4606 } 4607 4608 $Self->_Code( 4609 Code => \@Parts, 4610 Type => 'merge', 4611 Structure => $Param{Structure}, 4612 ); 4613 } 4614 4615 # upgrade database (merge) 4616 if ( 4617 $Param{Structure}->{DatabaseUpgrade}->{merge} 4618 && ref $Param{Structure}->{DatabaseUpgrade}->{merge} eq 'ARRAY' 4619 ) 4620 { 4621 4622 my @Parts; 4623 my $Use = 0; 4624 for my $Part ( @{ $Param{Structure}->{DatabaseUpgrade}->{merge} } ) { 4625 4626 if ( $Part->{TagLevel} == 3 && $Part->{Version} ) { 4627 4628 my $CheckVersion = $Self->_CheckVersion( 4629 VersionNew => $Part->{Version}, 4630 VersionInstalled => $InstalledVersion, 4631 Type => 'Min', 4632 ); 4633 4634 if ( !$CheckVersion ) { 4635 $Use = 1; 4636 @Parts = (); 4637 push @Parts, $Part; 4638 } 4639 } 4640 elsif ( $Use && $Part->{TagLevel} == 3 && $Part->{TagType} eq 'End' ) { 4641 $Use = 0; 4642 push @Parts, $Part; 4643 $Self->_Database( Database => \@Parts ); 4644 } 4645 elsif ($Use) { 4646 push @Parts, $Part; 4647 } 4648 } 4649 } 4650 4651 } 4652 4653 # purge package 4654 if ( IsArrayRefWithData( $PackageDetails{Filelist} ) ) { 4655 for my $File ( @{ $PackageDetails{Filelist} } ) { 4656 4657 # remove file 4658 $Self->_FileRemove( File => $File ); 4659 } 4660 } 4661 4662 # remove merged package from repository 4663 return if !$Self->RepositoryRemove( 4664 Name => $Package->{Name}, 4665 Version => $InstalledVersion, 4666 ); 4667 } 4668 4669 return 1; 4670} 4671 4672sub _CheckDBInstalledOrMerged { 4673 my ( $Self, %Param ) = @_; 4674 4675 # check needed stuff 4676 if ( !defined $Param{Database} ) { 4677 $Kernel::OM->Get('Kernel::System::Log')->Log( 4678 Priority => 'error', 4679 Message => 'Database not defined!', 4680 ); 4681 4682 return; 4683 } 4684 4685 if ( ref $Param{Database} ne 'ARRAY' ) { 4686 $Kernel::OM->Get('Kernel::System::Log')->Log( 4687 Priority => 'error', 4688 Message => 'Need array ref in Database param!', 4689 ); 4690 4691 return; 4692 } 4693 4694 my @Parts; 4695 my $Use = 1; 4696 my $NotUseTag; 4697 my $NotUseTagLevel; 4698 PART: 4699 for my $Part ( @{ $Param{Database} } ) { 4700 4701 if ( $Use eq 0 ) { 4702 4703 if ( 4704 $Part->{TagType} eq 'End' 4705 && ( defined $NotUseTag && $Part->{Tag} eq $NotUseTag ) 4706 && ( defined $NotUseTagLevel && $Part->{TagLevel} eq $NotUseTagLevel ) 4707 ) 4708 { 4709 $Use = 1; 4710 } 4711 4712 next PART; 4713 4714 } 4715 elsif ( 4716 ( 4717 defined $Part->{IfPackage} 4718 && !$Self->{MergedPackages}->{ $Part->{IfPackage} } 4719 ) 4720 || ( 4721 defined $Part->{IfNotPackage} 4722 && 4723 ( 4724 defined $Self->{MergedPackages}->{ $Part->{IfNotPackage} } 4725 || $Self->PackageIsInstalled( Name => $Part->{IfNotPackage} ) 4726 ) 4727 ) 4728 ) 4729 { 4730 # store Tag and TagLevel to be used later and found the end of this level 4731 $NotUseTag = $Part->{Tag}; 4732 $NotUseTagLevel = $Part->{TagLevel}; 4733 4734 $Use = 0; 4735 next PART; 4736 } 4737 4738 push @Parts, $Part; 4739 } 4740 4741 return \@Parts; 4742} 4743 4744=head2 RepositoryCloudList() 4745 4746returns a list of available cloud repositories 4747 4748 my $List = $PackageObject->RepositoryCloudList(); 4749 4750=cut 4751 4752sub RepositoryCloudList { 4753 my ( $Self, %Param ) = @_; 4754 4755 # get cache object 4756 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 4757 4758 # check cache 4759 my $CacheKey = "Repository::List::From::Cloud"; 4760 my $Cache = $CacheObject->Get( 4761 Type => 'RepositoryCloudList', 4762 Key => $CacheKey, 4763 ); 4764 4765 $Param{NoCache} //= 0; 4766 4767 # check if use cache is needed 4768 if ( !$Param{NoCache} ) { 4769 return $Cache if IsHashRefWithData($Cache); 4770 } 4771 4772 my $RepositoryResult = $Self->CloudFileGet( 4773 Operation => 'RepositoryListAvailable', 4774 ); 4775 4776 return if !IsHashRefWithData($RepositoryResult); 4777 4778 # set cache 4779 $CacheObject->Set( 4780 Type => 'RepositoryCloudList', 4781 Key => $CacheKey, 4782 Value => $RepositoryResult, 4783 TTL => 60 * 60, 4784 ); 4785 4786 return $RepositoryResult; 4787} 4788 4789=head2 CloudFileGet() 4790 4791returns a file from cloud 4792 4793 my $List = $PackageObject->CloudFileGet( 4794 Operation => 'OperationName', # used as operation name by the Cloud Service API 4795 # Possible operation names: 4796 # - RepositoryListAvailable 4797 # - FAOListAssigned 4798 # - FAOListAssignedFileGet 4799 ); 4800 4801=cut 4802 4803sub CloudFileGet { 4804 my ( $Self, %Param ) = @_; 4805 4806 return if $Self->{CloudServicesDisabled}; 4807 4808 # check needed stuff 4809 if ( !defined $Param{Operation} ) { 4810 $Kernel::OM->Get('Kernel::System::Log')->Log( 4811 Priority => 'error', 4812 Message => 'Operation not defined!', 4813 ); 4814 return; 4815 } 4816 4817 my %Data; 4818 if ( IsHashRefWithData( $Param{Data} ) ) { 4819 %Data = %{ $Param{Data} }; 4820 } 4821 4822 my $CloudService = 'PackageManagement'; 4823 4824 # prepare cloud service request 4825 my %RequestParams = ( 4826 RequestData => { 4827 $CloudService => [ 4828 { 4829 Operation => $Param{Operation}, 4830 Data => \%Data, 4831 }, 4832 ], 4833 }, 4834 ); 4835 4836 # get cloud service object 4837 my $CloudServiceObject = $Kernel::OM->Get('Kernel::System::CloudService::Backend::Run'); 4838 4839 # dispatch the cloud service request 4840 my $RequestResult = $CloudServiceObject->Request(%RequestParams); 4841 4842 # as this is the only operation an unsuccessful request means that the operation was also 4843 # unsuccessful 4844 if ( !IsHashRefWithData($RequestResult) ) { 4845 my $ErrorMessage = "Can't connect to cloud server!"; 4846 $Kernel::OM->Get('Kernel::System::Log')->Log( 4847 Priority => 'error', 4848 Message => $ErrorMessage, 4849 ); 4850 return $ErrorMessage; 4851 } 4852 4853 my $OperationResult = $CloudServiceObject->OperationResultGet( 4854 RequestResult => $RequestResult, 4855 CloudService => $CloudService, 4856 Operation => $Param{Operation}, 4857 ); 4858 4859 if ( !IsHashRefWithData($OperationResult) ) { 4860 my $ErrorMessage = "Can't get result from server"; 4861 $Kernel::OM->Get('Kernel::System::Log')->Log( 4862 Priority => 'error', 4863 Message => $ErrorMessage, 4864 ); 4865 return $ErrorMessage; 4866 } 4867 elsif ( !$OperationResult->{Success} ) { 4868 my $ErrorMessage = $OperationResult->{ErrorMessage} 4869 || "Can't get list from server!"; 4870 $Kernel::OM->Get('Kernel::System::Log')->Log( 4871 Priority => 'error', 4872 Message => $ErrorMessage, 4873 ); 4874 return $ErrorMessage; 4875 } 4876 4877 # return if not correct structure 4878 return if !IsHashRefWithData( $OperationResult->{Data} ); 4879 4880 # return repo list 4881 return $OperationResult->{Data}; 4882 4883} 4884 4885sub _ConfigurationDeploy { 4886 my ( $Self, %Param ) = @_; 4887 4888 # check needed stuff 4889 for my $Needed (qw(Package Action)) { 4890 if ( !$Param{$Needed} ) { 4891 $Kernel::OM->Get('Kernel::System::Log')->Log( 4892 Priority => 'error', 4893 Message => "Need $Needed!", 4894 ); 4895 return; 4896 } 4897 } 4898 4899 # 4900 # Normally, on package modifications, a configuration settings cleanup needs to happen, 4901 # to prevent old configuration settings from breaking the system. 4902 # 4903 # This does not work in the case of updates: there we can have situations where the packages 4904 # only exist in the DB, but not yet on the file system, and need to be reinstalled. We have 4905 # to prevent the cleanup until all packages are properly installed again. 4906 # 4907 # Please see bug#13754 for more information. 4908 # 4909 4910 my $CleanUp = 1; 4911 4912 PACKAGE: 4913 for my $Package ( $Self->RepositoryList() ) { 4914 4915 # Only check the deployment state of the XML configuration files for performance reasons. 4916 # Otherwise, this would be too slow on systems with many packages. 4917 $CleanUp = $Self->_ConfigurationFilesDeployCheck( 4918 Name => $Package->{Name}->{Content}, 4919 Version => $Package->{Version}->{Content}, 4920 ); 4921 4922 # Stop if any package has its configuration wrong deployed, configuration cleanup should not 4923 # take place in the lines below. Otherwise modified setting values can be lost. 4924 last PACKAGE if !$CleanUp; 4925 } 4926 4927 my $SysConfigObject = Kernel::System::SysConfig->new(); 4928 4929 if ( 4930 !$SysConfigObject->ConfigurationXML2DB( 4931 UserID => 1, 4932 Force => 1, 4933 CleanUp => $CleanUp, 4934 ) 4935 ) 4936 { 4937 $Kernel::OM->Get('Kernel::System::Log')->Log( 4938 Priority => 'error', 4939 Message => "There was a problem writing XML to DB.", 4940 ); 4941 return; 4942 } 4943 4944 # get OTRS home directory 4945 my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home'); 4946 4947 # build file location for OTRS5 config file 4948 my $OTRS5ConfigFile = "$Home/Kernel/Config/Backups/ZZZAutoOTRS5.pm"; 4949 4950 # if this is a Packageupgrade and if there is a ZZZAutoOTRS5.pm file in the backup location 4951 # (this file has been copied there during the migration from OTRS 5 to OTRS 6) 4952 if ( ( IsHashRefWithData( $Self->{MergedPackages} ) || $Param{Action} eq 'PackageUpgrade' ) && -e $OTRS5ConfigFile ) 4953 { 4954 4955 # delete categories cache 4956 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 4957 Type => 'SysConfig', 4958 Key => 'ConfigurationCategoriesGet', 4959 ); 4960 4961 # get all config categories 4962 my %Categories = $SysConfigObject->ConfigurationCategoriesGet(); 4963 4964 # to store all setting names from this package 4965 my @PackageSettings; 4966 4967 # get all config files names for this package 4968 CONFIGXMLFILE: 4969 for my $ConfigXMLFile ( @{ $Categories{ $Param{Package} }->{Files} } ) { 4970 4971 my $FileLocation = "$Home/Kernel/Config/Files/XML/$ConfigXMLFile"; 4972 4973 # get the content of the XML file 4974 my $ContentRef = $Kernel::OM->Get('Kernel::System::Main')->FileRead( 4975 Location => $FileLocation, 4976 Mode => 'utf8', 4977 Result => 'SCALAR', 4978 ); 4979 4980 # check error, but continue 4981 if ( !$ContentRef ) { 4982 $Kernel::OM->Get('Kernel::System::Log')->Log( 4983 Priority => 'error', 4984 Message => "Could not read content of $FileLocation!", 4985 ); 4986 next CONFIGXMLFILE; 4987 } 4988 4989 # get all settings from this package 4990 my @SettingList = $Kernel::OM->Get('Kernel::System::SysConfig::XML')->SettingListParse( 4991 XMLInput => ${$ContentRef}, 4992 XMLFilename => $ConfigXMLFile, 4993 ); 4994 4995 # get all the setting names from this file 4996 for my $Setting (@SettingList) { 4997 push @PackageSettings, $Setting->{XMLContentParsed}->{Name}; 4998 } 4999 } 5000 5001 # sort the settings 5002 @PackageSettings = sort @PackageSettings; 5003 5004 # run the migration of the effective values (only for the package settings) 5005 my $Success = $Kernel::OM->Get('Kernel::System::SysConfig::Migration')->MigrateConfigEffectiveValues( 5006 FileClass => 'Kernel::Config::Backups::ZZZAutoOTRS5', 5007 FilePath => $OTRS5ConfigFile, 5008 PackageSettings => \@PackageSettings, # only migrate the given package settings 5009 NoOutput => 1, # we do not want to print status output to the screen 5010 ); 5011 5012 # deploy only the package settings 5013 # (even if the migration of the effective values was not or only party successfull) 5014 $Success = $SysConfigObject->ConfigurationDeploy( 5015 Comments => $Param{Comments}, 5016 NoValidation => 1, 5017 UserID => 1, 5018 Force => 1, 5019 DirtySettings => \@PackageSettings, 5020 ); 5021 5022 # check error 5023 if ( !$Success ) { 5024 $Kernel::OM->Get('Kernel::System::Log')->Log( 5025 Priority => 'error', 5026 Message => "Could not deploy configuration!", 5027 ); 5028 return; 5029 } 5030 } 5031 5032 else { 5033 5034 my $Success = $SysConfigObject->ConfigurationDeploy( 5035 Comments => $Param{Comments}, 5036 NotDirty => 1, 5037 UserID => 1, 5038 Force => 1, 5039 ); 5040 if ( !$Success ) { 5041 $Kernel::OM->Get('Kernel::System::Log')->Log( 5042 Priority => 'error', 5043 Message => "Could not deploy configuration!", 5044 ); 5045 return; 5046 } 5047 } 5048 5049 return 1; 5050} 5051 5052=head2 _PackageInstallOrderListGet() 5053 5054Helper function for PackageInstallOrderListGet() to process the packages and its dependencies recursively. 5055 5056 my $Success = $PackageObject->_PackageInstallOrderListGet( 5057 Callers => { # packages in the recursive chain 5058 PackageA => 1, 5059 # ... 5060 }, 5061 InstalledVersions => { # list of installed packages and their versions 5062 PackageA => '1.0.1', 5063 # ... 5064 }, 5065 TargetPackages => { 5066 PackageA => '1.0.1', # list of packages to process 5067 # ... 5068 } 5069 InstallOrder => { # current install order 5070 PackageA => 2, 5071 PacakgeB => 1, 5072 # ... 5073 }, 5074 Failed => { # current failed packages or dependencies 5075 Cyclic => {}, 5076 NotFound => {}, 5077 WrongVersion => {}, 5078 DependencyFail => {}, 5079 }, 5080 OnlinePackageLookup => { 5081 PackageA => { 5082 Name => 'PackageA', 5083 Version => '1.0.1', 5084 PackageRequired => [ 5085 { 5086 Content => 'PackageB', 5087 Version => '1.0.2', 5088 }, 5089 # ... 5090 ], 5091 }, 5092 }, 5093 IsDependency => 1, # 1 or 0 5094 ); 5095 5096=cut 5097 5098sub _PackageInstallOrderListGet { 5099 my ( $Self, %Param ) = @_; 5100 5101 my $Success = 1; 5102 PACKAGENAME: 5103 for my $PackageName ( sort keys %{ $Param{TargetPackages} } ) { 5104 5105 next PACKAGENAME if $PackageName eq 'OTRSBusiness'; 5106 5107 # Prevent cyclic dependencies. 5108 if ( $Param{Callers}->{$PackageName} ) { 5109 $Param{Failed}->{Cyclic}->{$PackageName} = 1; 5110 $Success = 0; 5111 next PACKAGENAME; 5112 } 5113 5114 my $OnlinePackage = $Param{OnlinePackageLookup}->{$PackageName}; 5115 5116 # Check if the package can be obtained on-line. 5117 if ( !$OnlinePackage || !IsHashRefWithData($OnlinePackage) ) { 5118 $Param{Failed}->{NotFound}->{$PackageName} = 1; 5119 $Success = 0; 5120 next PACKAGENAME; 5121 } 5122 5123 # Check if the version of the on-line package is grater (or equal) to the required version, 5124 # in case of equal, reference still counts, but at update or install package must be 5125 # skipped. 5126 if ( $OnlinePackage->{Version} ne $Param{TargetPackages}->{$PackageName} ) { 5127 my $CheckOk = $Self->_CheckVersion( 5128 VersionNew => $OnlinePackage->{Version}, 5129 VersionInstalled => $Param{TargetPackages}->{$PackageName}, 5130 Type => 'Max', 5131 ); 5132 if ( !$CheckOk ) { 5133 $Param{Failed}->{WrongVersion}->{$PackageName} = 1; 5134 $Success = 0; 5135 next PACKAGENAME; 5136 } 5137 } 5138 5139 my %PackageDependencies = map { $_->{Content} => $_->{Version} } @{ $OnlinePackage->{PackageRequired} }; 5140 5141 # Update callers list locally to start recursion 5142 my %Callers = ( 5143 %{ $Param{Callers} }, 5144 $PackageName => 1, 5145 ); 5146 5147 # Start recursion with package dependencies. 5148 my $DependenciesSuccess = $Self->_PackageInstallOrderListGet( 5149 Callers => \%Callers, 5150 InstalledVersions => $Param{InstalledVersions}, 5151 TargetPackages => \%PackageDependencies, 5152 InstallOrder => $Param{InstallOrder}, 5153 OnlinePackageLookup => $Param{OnlinePackageLookup}, 5154 Failed => $Param{Failed}, 5155 IsDependency => 1, 5156 ); 5157 5158 if ( !$DependenciesSuccess ) { 5159 $Param{Failed}->{DependencyFail}->{$PackageName} = 1; 5160 $Success = 0; 5161 5162 # Do not process more dependencies. 5163 last PACKAGENAME if $Param{IsDependency}; 5164 5165 # Keep processing other initial packages. 5166 next PACKAGENAME; 5167 } 5168 5169 if ( $Param{InstallOrder}->{$PackageName} ) { 5170 5171 # Only increase the counter if is a dependency, if its a first level package then skip, 5172 # as it was already set from the dependencies of another package. 5173 if ( $Param{IsDependency} ) { 5174 $Param{InstallOrder}->{$PackageName}++; 5175 } 5176 5177 next PACKAGENAME; 5178 } 5179 5180 # If package wasn't set before it initial value must be 1, but in case the package is added 5181 # because its a dependency then it must be sum of all packages that requires it at the 5182 # moment + 1 e.g. 5183 # ITSMCore -> GeneralCatalog, Then GeneralCatalog needs to be 2 5184 # ITSMIncidenProblemManagement -> ITSMCore -> GeneralCatalog, Then GeneralCatalog needs to be 3 5185 my $InitialValue = $Param{IsDependency} ? scalar keys %Callers : 1; 5186 $Param{InstallOrder}->{$PackageName} = $InitialValue; 5187 } 5188 5189 return $Success; 5190} 5191 5192=head2 _PackageOnlineListGet() 5193 5194Helper function that gets the full list of available on-line packages. 5195 5196 my %OnlinePackages = $PackageObject->_PackageOnlineListGet(); 5197 5198Returns: 5199 5200 %OnlinePackages = ( 5201 PackageList => [ 5202 { 5203 Name => 'Test', 5204 Version => '6.0.20', 5205 File => 'Test-6.0.20.opm', 5206 ChangeLog => 'InitialRelease', 5207 Description => 'Test package.', 5208 Framework => [ 5209 { 5210 Content => '6.0.x', 5211 Minimum => '6.0.2', 5212 # ... , 5213 } 5214 ], 5215 License => 'GNU GENERAL PUBLIC LICENSE Version 3, November 2007', 5216 PackageRequired => [ 5217 { 5218 Content => 'TestRequitement', 5219 Version => '6.0.20', 5220 # ... , 5221 }, 5222 ], 5223 URL => 'http://otrs.org/', 5224 Vendor => 'OTRS AG', 5225 }, 5226 # ... 5227 ]; 5228 PackageLookup => { 5229 Test => { 5230 URL => 'http://otrs.org/', 5231 FromCloud => 1, # 1 or 0, 5232 Version => '6.0.20', 5233 File => 'Test-6.0.20.opm', 5234 }, 5235 # ... 5236 }, 5237 ); 5238 5239=cut 5240 5241sub _PackageOnlineListGet { 5242 5243 my ( $Self, %Param ) = @_; 5244 5245 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 5246 5247 my %RepositoryList = $Self->_ConfiguredRepositoryDefinitionGet(); 5248 5249 # Show cloud repositories if system is registered. 5250 my $RepositoryCloudList; 5251 my $RegistrationState = $Kernel::OM->Get('Kernel::System::SystemData')->SystemDataGet( 5252 Key => 'Registration::State', 5253 ) || ''; 5254 5255 if ( $RegistrationState eq 'registered' && !$Self->{CloudServicesDisabled} ) { 5256 $RepositoryCloudList = $Self->RepositoryCloudList( NoCache => 1 ); 5257 } 5258 5259 my %RepositoryListAll = ( %RepositoryList, %{ $RepositoryCloudList || {} } ); 5260 5261 my @PackageOnlineList; 5262 my %PackageSoruceLookup; 5263 5264 for my $URL ( sort keys %RepositoryListAll ) { 5265 5266 my $FromCloud = 0; 5267 if ( $RepositoryCloudList->{$URL} ) { 5268 $FromCloud = 1; 5269 5270 } 5271 5272 my @OnlineList = $Self->PackageOnlineList( 5273 URL => $URL, 5274 Lang => 'en', 5275 Cache => 1, 5276 FromCloud => $FromCloud, 5277 IncludeSameVersion => 1, 5278 ); 5279 5280 @PackageOnlineList = ( @PackageOnlineList, @OnlineList ); 5281 5282 for my $Package (@OnlineList) { 5283 $PackageSoruceLookup{ $Package->{Name} } = { 5284 URL => $URL, 5285 FromCloud => $FromCloud, 5286 Version => $Package->{Version}, 5287 File => $Package->{File}, 5288 }; 5289 } 5290 } 5291 5292 return ( 5293 PackageList => \@PackageOnlineList, 5294 PackageLookup => \%PackageSoruceLookup, 5295 ); 5296} 5297 5298=head2 _ConfiguredRepositoryDefinitionGet() 5299 5300Helper function that gets the full list of configured package repositories updated for the current 5301framework version. 5302 5303 my %RepositoryList = $PackageObject->_ConfiguredRepositoryDefinitionGet(); 5304 5305Returns: 5306 5307 %RepositoryList = ( 5308 'http://ftp.otrs.org/pub/otrs/packages' => 'OTRS Freebie Features', 5309 # ..., 5310 ); 5311 5312=cut 5313 5314sub _ConfiguredRepositoryDefinitionGet { 5315 my ( $Self, %Param ) = @_; 5316 5317 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 5318 5319 my %RepositoryList; 5320 if ( $ConfigObject->Get('Package::RepositoryList') ) { 5321 %RepositoryList = %{ $ConfigObject->Get('Package::RepositoryList') }; 5322 } 5323 if ( $ConfigObject->Get('Package::RepositoryRoot') ) { 5324 %RepositoryList = ( %RepositoryList, $Self->PackageOnlineRepositories() ); 5325 } 5326 5327 return () if !%RepositoryList; 5328 5329 # Make sure ITSM repository matches the current framework version. 5330 my @Matches = grep { $_ =~ m{http://ftp\.otrs\.org/pub/otrs/itsm/packages\d+/}msxi } sort keys %RepositoryList; 5331 5332 return %RepositoryList if !@Matches; 5333 5334 my @FrameworkVersionParts = split /\./, $Self->{ConfigObject}->Get('Version'); 5335 my $FrameworkVersion = $FrameworkVersionParts[0]; 5336 5337 my $CurrentITSMRepository = "http://ftp.otrs.org/pub/otrs/itsm/packages$FrameworkVersion/"; 5338 5339 # Delete all old ITSM repositories, but leave the current if exists 5340 for my $Repository (@Matches) { 5341 if ( $Repository ne $CurrentITSMRepository ) { 5342 delete $RepositoryList{$Repository}; 5343 } 5344 } 5345 5346 return %RepositoryList if exists $RepositoryList{$CurrentITSMRepository}; 5347 5348 # Make sure that current ITSM repository is in the list. 5349 $RepositoryList{$CurrentITSMRepository} = "OTRS::ITSM $FrameworkVersion Master"; 5350 5351 return %RepositoryList; 5352} 5353 5354=head2 _RepositoryCacheClear() 5355 5356Remove all caches related to the package repository. 5357 5358 my $Success = $PackageObject->_RepositoryCacheClear(); 5359 5360=cut 5361 5362sub _RepositoryCacheClear { 5363 my ( $Self, %Param ) = @_; 5364 5365 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 5366 5367 $CacheObject->CleanUp( 5368 Type => 'RepositoryList', 5369 ); 5370 $CacheObject->CleanUp( 5371 Type => 'RepositoryGet', 5372 ); 5373 5374 return 1; 5375} 5376 5377=head2 _ConfigurationFilesDeployCheck() 5378 5379check if package configuration files are deployed correctly. 5380 5381 my $Success = $PackageObject->_ConfigurationFilesDeployCheck( 5382 Name => 'Application A', 5383 Version => '1.0', 5384 ); 5385 5386=cut 5387 5388sub _ConfigurationFilesDeployCheck { 5389 my ( $Self, %Param ) = @_; 5390 5391 # check needed stuff 5392 for my $Needed (qw(Name Version)) { 5393 if ( !defined $Param{$Needed} ) { 5394 $Kernel::OM->Get('Kernel::System::Log')->Log( 5395 Priority => 'error', 5396 Message => "$Needed not defined!", 5397 ); 5398 return; 5399 } 5400 } 5401 5402 my $Package = $Self->RepositoryGet( %Param, Result => 'SCALAR' ); 5403 my %Structure = $Self->PackageParse( String => $Package ); 5404 5405 return 1 if !$Structure{Filelist}; 5406 return 1 if ref $Structure{Filelist} ne 'ARRAY'; 5407 5408 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 5409 5410 my $Success = 1; 5411 5412 FILE: 5413 for my $File ( @{ $Structure{Filelist} } ) { 5414 5415 my $Extension = substr $File->{Location}, -4, 4; 5416 5417 next FILE if lc $Extension ne '.xml'; 5418 5419 my $LocalFile = $Self->{Home} . '/' . $File->{Location}; 5420 5421 if ( !-e $LocalFile ) { 5422 $Success = 0; 5423 last FILE; 5424 } 5425 5426 my $Content = $MainObject->FileRead( 5427 Location => $Self->{Home} . '/' . $File->{Location}, 5428 Mode => 'binmode', 5429 ); 5430 5431 if ( !$Content ) { 5432 $Success = 0; 5433 last FILE; 5434 } 5435 5436 if ( ${$Content} ne $File->{Content} ) { 5437 $Success = 0; 5438 last FILE; 5439 } 5440 } 5441 5442 return $Success; 5443} 5444 5445sub DESTROY { 5446 my $Self = shift; 5447 5448 # execute all transaction events 5449 $Self->EventHandlerTransaction(); 5450 5451 return 1; 5452} 5453 54541; 5455 5456=end Internal: 5457 5458=head1 TERMS AND CONDITIONS 5459 5460This software is part of the OTRS project (L<https://otrs.org/>). 5461 5462This software comes with ABSOLUTELY NO WARRANTY. For details, see 5463the enclosed file COPYING for license information (GPL). If you 5464did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 5465 5466=cut 5467