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/&/&amp;/g;
4358    $Text =~ s/</&lt;/g;
4359    $Text =~ s/>/&gt;/g;
4360    $Text =~ s/"/&quot;/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