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::Console::Command::Admin::Package::List;
10
11use strict;
12use utf8;
13use warnings;
14
15use parent qw(Kernel::System::Console::BaseCommand);
16
17our @ObjectDependencies = (
18    'Kernel::Config',
19    'Kernel::System::Cache',
20    'Kernel::System::Main',
21    'Kernel::System::Package',
22);
23
24sub Configure {
25    my ( $Self, %Param ) = @_;
26
27    $Self->Description('List all installed OTRS packages.');
28
29    $Self->AddOption(
30        Name        => 'package-name',
31        Description => '(Part of) package name to filter for. Omit to show all installed packages.',
32        Required    => 0,
33        HasValue    => 1,
34        ValueRegex  => qr/.*/,
35    );
36
37    $Self->AddOption(
38        Name        => 'show-deployment-info',
39        Description => 'Show package and files status (package deployment info).',
40        Required    => 0,
41        HasValue    => 0,
42    );
43
44    $Self->AddOption(
45        Name        => 'show-verification-info',
46        Description => 'Show package OTRS Verify™ status.',
47        Required    => 0,
48        HasValue    => 0,
49    );
50
51    $Self->AddOption(
52        Name        => 'delete-verification-cache',
53        Description => 'Delete OTRS Verify™ cache, so verification info is fetch again from OTRS group servers.',
54        Required    => 0,
55        HasValue    => 0,
56    );
57
58    return;
59}
60
61sub PreRun {
62    my ( $Self, %Param ) = @_;
63
64    my $ShowVerificationInfoOption    = $Self->GetOption('show-verification-info');
65    my $DeleteVerificationCacheOption = $Self->GetOption('delete-verification-cache');
66
67    if ( $DeleteVerificationCacheOption && !$ShowVerificationInfoOption ) {
68        die "--delete-verification-cache requires --show-verification-info";
69    }
70
71    return;
72}
73
74sub Run {
75    my ( $Self, %Param ) = @_;
76
77    $Self->Print("<yellow>Listing all installed packages...</yellow>\n");
78
79    my @Packages = $Kernel::OM->Get('Kernel::System::Package')->RepositoryList();
80
81    if ( !@Packages ) {
82        $Self->Print("<green>There are no packages installed.</green>\n");
83        return $Self->ExitCodeOk();
84    }
85
86    my $PackageNameOption             = $Self->GetOption('package-name');
87    my $ShowDeploymentInfoOption      = $Self->GetOption('show-deployment-info');
88    my $ShowVerificationInfoOption    = $Self->GetOption('show-verification-info');
89    my $DeleteVerificationCacheOption = $Self->GetOption('delete-verification-cache');
90
91    my $CloudServicesDisabled = $Kernel::OM->Get('Kernel::Config')->Get('CloudServices::Disabled') || 0;
92
93    # Do not show verification status is cloud services are disabled.
94    if ( $CloudServicesDisabled && $ShowVerificationInfoOption ) {
95        $ShowVerificationInfoOption = 0;
96        $Self->Print("<red>Cloud Services are disabled OTRS Verify information can not be retrieved</red>\n");
97    }
98
99    # Get package object
100    my $PackageObject = $Kernel::OM->Get('Kernel::System::Package');
101
102    my %VerificationInfo;
103
104    PACKAGE:
105    for my $Package (@Packages) {
106
107        # Just show if PackageIsVisible flag is enabled.
108        if (
109            defined $Package->{PackageIsVisible}
110            && !$Package->{PackageIsVisible}->{Content}
111            )
112        {
113            next PACKAGE;
114        }
115
116        if ( defined $PackageNameOption && length $PackageNameOption ) {
117            my $PackageString = $Package->{Name}->{Content} . '-' . $Package->{Version}->{Content};
118            next PACKAGE if $PackageString !~ m{$PackageNameOption}i;
119        }
120
121        my %Data = $Self->_PackageMetadataGet(
122            Tag       => $Package->{Description},
123            StripHTML => 0,
124        );
125        $Self->Print("+----------------------------------------------------------------------------+\n");
126        $Self->Print("| <yellow>Name:</yellow>        $Package->{Name}->{Content}\n");
127        $Self->Print("| <yellow>Version:</yellow>     $Package->{Version}->{Content}\n");
128        $Self->Print("| <yellow>Vendor:</yellow>      $Package->{Vendor}->{Content}\n");
129        $Self->Print("| <yellow>URL:</yellow>         $Package->{URL}->{Content}\n");
130        $Self->Print("| <yellow>License:</yellow>     $Package->{License}->{Content}\n");
131        $Self->Print("| <yellow>Description:</yellow> $Data{Description}\n");
132
133        if ($ShowDeploymentInfoOption) {
134            my $PackageDeploymentOK = $PackageObject->DeployCheck(
135                Name    => $Package->{Name}->{Content},
136                Version => $Package->{Version}->{Content},
137                Log     => 0,
138            );
139
140            my %PackageDeploymentInfo = $PackageObject->DeployCheckInfo();
141            if ( defined $PackageDeploymentInfo{File} && %{ $PackageDeploymentInfo{File} } ) {
142                $Self->Print(
143                    '| <red>Deployment:</red>  ' . ( $PackageDeploymentOK ? 'OK' : 'Not OK' ) . "\n"
144                );
145                for my $File ( sort keys %{ $PackageDeploymentInfo{File} } ) {
146                    my $FileMessage = $PackageDeploymentInfo{File}->{$File};
147                    $Self->Print("| <red>File Status:</red> $File => $FileMessage\n");
148                }
149            }
150            else {
151                $Self->Print(
152                    '| <yellow>Pck. Status:</yellow> ' . ( $PackageDeploymentOK ? 'OK' : 'Not OK' ) . "\n"
153                );
154            }
155        }
156
157        if ($ShowVerificationInfoOption) {
158
159            if ( !%VerificationInfo ) {
160
161                # Clear the package verification cache to get fresh results.
162                if ($DeleteVerificationCacheOption) {
163                    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
164                        Type => 'PackageVerification',
165                    );
166                }
167
168                # Get verification info for all packages (this will create the cache again).
169                %VerificationInfo = $PackageObject->PackageVerifyAll();
170            }
171
172            if (
173                !defined $VerificationInfo{ $Package->{Name}->{Content} }
174                || $VerificationInfo{ $Package->{Name}->{Content} } ne 'verified'
175                )
176            {
177                $Self->Print("| <red>OTRS Verify:</red> Not Verified\n");
178            }
179            else {
180                $Self->Print("| <yellow>OTRS Verify:</yellow> Verified\n");
181            }
182        }
183    }
184    $Self->Print("+----------------------------------------------------------------------------+\n");
185
186    $Self->Print("<green>Done.</green>\n");
187    return $Self->ExitCodeOk();
188}
189
190# =item _PackageMetadataGet()
191#
192# locates information in tags that are language specific.
193# First, 'en' is looked for, if that is not present, the first found language will be used.
194#
195#     my %Data = $CommandObject->_PackageMetadataGet(
196#         Tag       => $Package->{Description},
197#         StripHTML => 1,         # optional, perform HTML->ASCII conversion (default 1)
198#     );
199#
200#     my %Data = $Self->_PackageMetadataGet(
201#         Tag => $Structure{IntroInstallPost},
202#         AttributeFilterKey   => 'Type',
203#         AttributeFilterValue =>  'pre',
204#     );
205#
206# Returns the content and the title of the tag in a hash:
207#
208#     my %Result = (
209#         Description => '...',   # tag content
210#         Title       => '...',   # tag title
211#     );
212#
213# =cut
214
215sub _PackageMetadataGet {
216    my ( $Self, %Param ) = @_;
217
218    return if !ref $Param{Tag};
219
220    my $AttributeFilterKey   = $Param{AttributeFilterKey};
221    my $AttributeFilterValue = $Param{AttributeFilterValue};
222
223    my $Title       = '';
224    my $Description = '';
225
226    TAG:
227    for my $Tag ( @{ $Param{Tag} } ) {
228        if ($AttributeFilterKey) {
229            if ( lc $Tag->{$AttributeFilterKey} ne lc $AttributeFilterValue ) {
230                next TAG;
231            }
232        }
233        if ( !$Description && $Tag->{Lang} eq 'en' ) {
234            $Description = $Tag->{Content} || '';
235            $Title       = $Tag->{Title}   || '';
236        }
237    }
238    if ( !$Description ) {
239        TAG:
240        for my $Tag ( @{ $Param{Tag} } ) {
241            if ($AttributeFilterKey) {
242                if ( lc $Tag->{$AttributeFilterKey} ne lc $AttributeFilterValue ) {
243                    next TAG;
244                }
245            }
246            if ( !$Description ) {
247                $Description = $Tag->{Content} || '';
248                $Title       = $Tag->{Title}   || '';
249            }
250        }
251    }
252
253    if ( !defined $Param{StripHTML} || $Param{StripHTML} ) {
254        $Title       =~ s/(.{4,78})(?:\s|\z)/| $1\n/gm;
255        $Description =~ s/^\s*//mg;
256        $Description =~ s/\n/ /gs;
257        $Description =~ s/\r/ /gs;
258        $Description =~ s/\<style.+?\>.*\<\/style\>//gsi;
259        $Description =~ s/\<br(\/|)\>/\n/gsi;
260        $Description =~ s/\<(hr|hr.+?)\>/\n\n/gsi;
261        $Description =~ s/\<(\/|)(pre|pre.+?|p|p.+?|table|table.+?|code|code.+?)\>/\n\n/gsi;
262        $Description =~ s/\<(tr|tr.+?|th|th.+?)\>/\n\n/gsi;
263        $Description =~ s/\.+?<\/(td|td.+?)\>/ /gsi;
264        $Description =~ s/\<.+?\>//gs;
265        $Description =~ s/  / /mg;
266        $Description =~ s/&amp;/&/g;
267        $Description =~ s/&lt;/</g;
268        $Description =~ s/&gt;/>/g;
269        $Description =~ s/&quot;/"/g;
270        $Description =~ s/&nbsp;/ /g;
271        $Description =~ s/^\s*\n\s*\n/\n/mg;
272        $Description =~ s/(.{4,78})(?:\s|\z)/| $1\n/gm;
273    }
274    return (
275        Description => $Description,
276        Title       => $Title,
277    );
278}
279
280sub _PackageContentGet {
281    my ( $Self, %Param ) = @_;
282
283    my $FileString;
284
285    if ( -e $Param{Location} ) {
286        my $ContentRef = $Kernel::OM->Get('Kernel::System::Main')->FileRead(
287            Location => $Param{Location},
288            Mode     => 'utf8',             # optional - binmode|utf8
289            Result   => 'SCALAR',           # optional - SCALAR|ARRAY
290        );
291        if ($ContentRef) {
292            $FileString = ${$ContentRef};
293        }
294        else {
295            $Self->PrintError("Can't open: $Param{Location}: $!");
296            return;
297        }
298    }
299    elsif ( $Param{Location} =~ /^(online|.*):(.+?)$/ ) {
300        my $URL         = $1;
301        my $PackageName = $2;
302        if ( $URL eq 'online' ) {
303            my %List = %{ $Kernel::OM->Get('Kernel::Config')->Get('Package::RepositoryList') };
304            %List = (
305                %List,
306                $Kernel::OM->Get('Kernel::System::Package')->PackageOnlineRepositories()
307            );
308            for ( sort keys %List ) {
309                if ( $List{$_} =~ /^\[-Master-\]/ ) {
310                    $URL = $_;
311                }
312            }
313        }
314        if ( $PackageName !~ /^.+?.opm$/ ) {
315            my @Packages = $Kernel::OM->Get('Kernel::System::Package')->PackageOnlineList(
316                URL  => $URL,
317                Lang => $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage'),
318            );
319            PACKAGE:
320            for my $Package (@Packages) {
321                if ( $Package->{Name} eq $PackageName ) {
322                    $PackageName = $Package->{File};
323                    last PACKAGE;
324                }
325            }
326        }
327        $FileString = $Kernel::OM->Get('Kernel::System::Package')->PackageOnlineGet(
328            Source => $URL,
329            File   => $PackageName,
330        );
331        if ( !$FileString ) {
332            $Self->PrintError("No such file '$Param{Location}' in $URL!");
333            return;
334        }
335    }
336    else {
337        if ( $Param{Location} =~ /^(.*)\-(\d{1,4}\.\d{1,4}\.\d{1,4})$/ ) {
338            $FileString = $Kernel::OM->Get('Kernel::System::Package')->RepositoryGet(
339                Name    => $1,
340                Version => $2,
341            );
342        }
343        else {
344            PACKAGE:
345            for my $Package ( $Kernel::OM->Get('Kernel::System::Package')->RepositoryList() ) {
346                if ( $Param{Location} eq $Package->{Name}->{Content} ) {
347                    $FileString = $Kernel::OM->Get('Kernel::System::Package')->RepositoryGet(
348                        Name    => $Package->{Name}->{Content},
349                        Version => $Package->{Version}->{Content},
350                    );
351                    last PACKAGE;
352                }
353            }
354        }
355        if ( !$FileString ) {
356            $Self->PrintError("No such file '$Param{Location}' or invalid 'package-version'!");
357            return;
358        }
359    }
360
361    return $FileString;
362}
363
3641;
365