package Cpanel::Easy::Utils::cPVers;

# cpanel - Cpanel/Easy/Utils/cPVers.pm            Copyright(c) 2014 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cpanel license. Unauthorized copying is prohibited

use strict;
use warnings;
no warnings qw(redefine);

use Cpanel::HttpRequest      ();
use Cpanel::Version          ();
use Cpanel::Version::Compare ();
use Cpanel::SafeRun          ();

=pod

=head1 Description

Utilities that handle cPanel & WHM version comparison.

=head1 API

The following sections contain a description of each API in this module.

=cut

# cpanel version cache
our $CPANEL_VERSION;

=pod

=head2 Cpanel::Easy::Utils::cPVers::get_cpanel_version()

Retrieves the cPanel & WHM version number of the system.

  Input:
    N/A

  Output:
    cPanel & WHM version number (typically quad-dot notation)

  Notes:
    This routine caches the version number to memory to reduce
    the function call count.

=cut

sub get_cpanel_version {
    my ($self) = @_;
    return $CPANEL_VERSION if defined $CPANEL_VERSION;
    my $version = Cpanel::Version->can('get_version_full') ? Cpanel::Version::get_version_full() : Cpanel::Version::getversion();
    $CPANEL_VERSION = $version;
    return $version;
}

sub cpanel_version_check {
    my ($self) = @_;

    # This subroutine behaves and returns values as though named cpanel_is_up_to_date().

    # Do not perform the update if this is a development sandbox
    return 1 if -e '/var/cpanel/dev_sandbox';

    my $tree;
    my $vers = $self->get_cpanel_version();
    my $latest_vers;

    if ( $vers =~ m{ ^ \d+ ([.]\d+)+ $ }x && !__compare_versions( $vers, '<', '11.30.0.33' ) ) {
        my @cmd = qw[ /usr/local/cpanel/scripts/updatenow --checkremoteversion ];
        Cpanel::SafeRun::saferunallerrors(@cmd);
        my $ret = $? >> 8;
        return 42 != $ret;    # 42 means update is available
    }

    if ( $Cpanel::Version::VERSION ge '4.0' ) {
        require Cpanel::Update;
        require Cpanel::Config::Sources;
        $tree = Cpanel::Update::Config::get_tier();

        my $CPSRC        = Cpanel::Config::Sources::loadcpsources();
        my $rUPCONF      = Cpanel::Update::Config::load();
        my $remote_tiers = eval { Cpanel::Update::get_remote_tiers_info($CPSRC) };

        # Skip if the tier appears to be invalid.
        return 1 if ( !$remote_tiers or $@ );

        $latest_vers = Cpanel::Update::get_remote_version_from_tier( $rUPCONF, $remote_tiers );
    }
    else {
        $self->print_alert(q{Cannot determine which version of cPanel you have installed.});
        return;
    }

    if ( $tree && exists $self->{'minimum_version'}{$tree} ) {
        my $ver = $vers;
        $ver =~ s{[-]$tree.*}{};

        if ( exists $self->{'minimum_version'}{$tree}{'version'} && __compare_versions( $self->{'minimum_version'}{$tree}{'version'}, '>', $ver ) ) {
            $self->_header();
            $self->print_alert( q{Your cPanel is too old, '[_1]' must be at least version '[_2]'}, $tree, $self->{'minimum_version'}{$tree}{'version'} );
            $self->_footer();
            exit;
        }
    }

    if ( $latest_vers && $latest_vers ne '1' && $latest_vers ne $vers ) {
        $self->{'_'}{'Cpanel::Easy::Utils::cPVers out of date'}++;
        $self->_header();
        $self->{'_'}{'cpanel_version_message'} = qq{
An updated version of '$tree' is available. Your current version is '$vers'.
Version '$latest_vers' is now available. In order to ensure a trouble free build,
cPanel recommends updating to the latest build of cPanel available for your requested version.
};

        $self->print_alert_color( 'red', $self->{'_'}{'cpanel_version_message'} );
        return;    # not really used but its there if we want it
    }
    return 1;      # not really used but its there if we want it
}

# quick fix for case 6346, Cpanel::Version::compare()
sub __compare_versions {
    my ( $check, $mode, $against ) = @_;

    if ( ref $check eq 'HASH' ) {
        $mode    = $check->{'comparison'};
        $against = $check->{'version_b'};
        $check   = $check->{'version_a'};
    }
    elsif ( @_ == 2 ) {
        $against = $mode;
        $mode    = '>=';
    }

    ## fix it for them ?
    # chomp($check);
    # chomp($against);
    ## remove beginning or ending dots ?
    # $check   =~ s{(?:^[.]|[.]$)}{}g;
    # $against =~ s{(?:^[.]|[.]$)}{}g;

    my %modes = (
        '>' => sub {
            return if $_[0] eq $_[1];    # no need to continue if they are the same

            my ( $maj, $min, $rev, $sup ) = split /\./, $_[0];
            my ( $mj,  $mn,  $rv,  $sp )  = split /\./, $_[1];

            if ( "$maj.$min" > "$mj.$mn" ) {
                return 1;
            }
            elsif ( "$maj.$min" < "$mj.$mn" ) {
                return;
            }
            elsif ( "$rev.$sup" > "$rv.$sp" ) {
                return 1;
            }

            return;
        },
        '<' => sub {
            return if $_[0] eq $_[1];    # no need to continue if they are the same

            my ( $maj, $min, $rev, $sup ) = split /\./, $_[0];
            my ( $mj,  $mn,  $rv,  $sp )  = split /\./, $_[1];

            if ( "$maj.$min" < "$mj.$mn" ) {
                return 1;
            }
            elsif ( "$maj.$min" > "$mj.$mn" ) {
                return;
            }
            elsif ( "$rev.$sup" < "$rv.$sp" ) {
                return 1;
            }

            return;
        },
        '==' => sub { $_[0] eq $_[1] },
        '!=' => sub { $_[0] ne $_[1] },
    );

    for my $sym (qw(> <)) {
        $modes{ $sym . '=' } = sub {
            return 1 if $modes{'=='}->(@_);
            return 1 if $modes{$sym}->(@_);
            return;
        };
    }

    if ( !exists $modes{$mode} ) {

        # warn/log - invalid mode
        return;
    }

    for my $ver ( $check, $against ) {
        if ( $ver !~ m{ \A \d+(?:\.\d+)* \z }xms ) {

            # warn/log - invalid version
            return;
        }
    }

    # make sure we have 2: 3 decimal version numbers
    my @check_len   = split( /\./, $check );
    my @against_len = split( /\./, $against );

    if ( @check_len > 4 ) {

        # warn/log - invalid version
        return;
    }
    elsif ( @check_len < 4 ) {
        for ( 1 .. 4 - @check_len ) {
            $check .= '.0';
        }
    }

    if ( @against_len > 4 ) {

        # warn/log - invalid version
        return;
    }
    elsif ( @against_len < 4 ) {
        for ( 1 .. 4 - @against_len ) {
            $against .= '.0';
        }
    }

    for my $ver ( $check, $against ) {
        if ( $ver !~ m { \A \d+\.\d+\.\d+\.\d+ \z }xms ) {

            # warn/log - invalid version
            return;
        }
    }

    return $modes{$mode}->( $check, $against );
}

=pod

=head2 Cpanel::Easy::Utils::cPVers::cmp_cpanel_tier()

Compares two cPanel versions by only using the major and minor numbers.

  Input:
    Scalar -- cPanel version number
    Scalar -- boolean operator
    Scalar -- cPanel version number

  Output:
    Boolean value -- 1 if TRUE, 0 if FALSE

=cut

sub cmp_cpanel_tier {
    my $self  = shift;
    my $left  = shift;
    my $bool  = shift;
    my $right = shift;

    $left =~ /^(\d+\.\d+)/;
    $left = $1;
    $right =~ /^(\d+\.\d+)/;
    $right = $1;

    # use boolean cmp in return to provide consistent return value
    # in inconsistent compare() function
    return ( Cpanel::Version::Compare::compare( $left, $bool, $right ) ? 1 : 0 );
}

1;
