#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - scripts/migrate_legacy_wordpress_to_modern_wordpress
#                                                  Copyright 2018 cPanel, L.L.C.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

package scripts::migrate_legacy_wordpress_to_modern_wordpress;

use strict;
use warnings;

use Cpanel::Logger::Quiet               ();
use Cpanel::Output::Formatted::Terminal ();
use Cpanel::SafeDir::MK                 ();
use Cpanel::WordPress::Deprecation      ();
use Getopt::Long                        ();
use Term::ReadKey                       ();

use Cpanel::Imports;

our $LOGGER_DIR = '/var/cpanel/logs/migrate_legacy_wordpress_to_modern_wordpress';

exit run( \@ARGV ) unless caller;

sub run {
    my ($args) = @_;

    my $opts = {
        users     => [],
        instances => [],
        dryrun    => 0,
        color     => 1,
        all       => 0,
        verbose   => 0,
        help      => 0,
    };

    my $arg_parse_error;

    {
        local $SIG{__WARN__} = sub {
            $arg_parse_error = shift;
        };

        Getopt::Long::GetOptionsFromArray(
            $args,
            'dryrun!'     => \$opts->{dryrun},
            'color!'      => \$opts->{color},
            'user=s@'     => \$opts->{users},
            'all'         => \$opts->{all},
            'instance=s@' => \$opts->{instances},
            'verbose'     => \$opts->{verbose},
            'help'        => \$opts->{help},
        );
    }

    if ( !-d $LOGGER_DIR ) {
        Cpanel::SafeDir::MK::safemkdir( $LOGGER_DIR, 0700 );
    }

    my ( $term_width, $term_height ) = Term::ReadKey::GetTerminalSize();
    my $log_path = $LOGGER_DIR . "/run." . time() . ".log";
    my $CONF     = {
        dryrun     => $opts->{dryrun},
        verbose    => $opts->{verbose},
        color      => $opts->{color},
        formatter  => ( $opts->{color} ? Cpanel::Output::Formatted::Terminal->new() : undef ),
        logger     => Cpanel::Logger::Quiet->new( { alternate_logfile => $log_path } ),
        term_width => $term_width,
    };

    if ($arg_parse_error) {
        _print_error( "$arg_parse_error", $CONF );
        print STDERR _usage_text();
        return 1;
    }

    if ( $opts->{help} ) {
        print _usage_text();
        return 0;
    }

    my @instances = $opts->{instances} && ref $opts->{instances} eq 'ARRAY' ? @{ $opts->{instances} } : ();
    my @users     = $opts->{users}     && ref $opts->{users} eq 'ARRAY'     ? @{ $opts->{users} }     : ();
    my $all = $opts->{all} ? 1 : 0;
    $CONF->{all} = $all;

    if ( @instances && @users != 1 ) {
        _print_error( locale()->maketext("You must provide a [asis,--user] to use [asis,--instance]."),                      $CONF ) if !@users;
        _print_error( locale()->maketext("You can only provide [asis,--instance] if you also only pass one [asis,--user]."), $CONF ) if @users > 1;
        print STDERR _usage_text();
        return 1;
    }

    if ( $all && @users > 1 ) {
        _print_error( locale()->maketext("You can select [asis,--all] or [asis,--user], but not both."), $CONF );
        print STDERR _usage_text();
        return 1;
    }

    if ( $> != 0 ) {

        # not root, regular users
        _print_error( locale()->maketext("You must run this script as the root user."), $CONF );
        print STDERR _usage_text();
        return 2;
    }

    my $migrator = Cpanel::WordPress::Deprecation->new($CONF);

    my $exit_status = 0;
    my $notify      = sub {
        my ( $type, $DATA, $CONF ) = @_;
        my $message = format_message( $DATA, $CONF );
        if ( $type eq 'error' ) {
            _print_error( $message, $CONF );
            $exit_status = 1;
        }
        elsif ( $type eq 'warn' ) {
            _print_warn( $message, $CONF );
        }
        elsif ( $type eq 'success' ) {
            _print_success( $message, $CONF );
        }
        elsif ( $type eq 'info' ) {
            _print_info( $message, $CONF );
        }
        else {
            die "Unrecognized notification type.";    # DEVELOPER ONLY MESSAGE
        }
        return;
    };

    if ($all) {
        _print_header( locale()->maketext('Converting all users …'), $CONF );
        $migrator->convert_legacy_to_modern_for_all_users($notify);
        _print_footer( $log_path, $CONF );
    }
    elsif ( @users && !@instances ) {
        _print_header( locale()->maketext('Converting selected users …'), $CONF );
        $migrator->convert_legacy_to_modern_for_users( \@users, $notify );
        _print_footer( $log_path, $CONF );
    }
    elsif ( @users == 1 && @instances > 0 ) {
        _print_header( locale()->maketext( 'Converting selected instances for the user ‘[_1]’ …', $users[0] ), $CONF );
        $migrator->convert_legacy_to_modern_for_instances( $users[0], \@instances, $notify );
        _print_footer( $log_path, $CONF );
    }
    else {
        _print_error( locale()->maketext("You must specify [asis,--all], a single [asis,--user] or multiple [asis,--user], or a single [asis,--user] and one or more [asis,--instance]."), $CONF );
        print STDERR _usage_text();
        return 2;
    }
    return $exit_status;
}

sub _print_header {
    my ( $message, $CONF ) = @_;
    if ( $CONF->{verbose} ) {
        _print_seperator($CONF);
        _print_bold( $message, $CONF );
        _print_seperator($CONF);
        _print_lf( 1, $CONF );
    }
    return;
}

sub _print_footer {
    my ( $log_path, $CONF ) = @_;
    _print_lf( 1, $CONF );
    _print_info( locale()->maketext( "You can view the complete migration log at the following location: [_1]", $log_path ) );
    return;
}

sub _print_error {
    my ( $message, $CONF ) = @_;
    my $formatted;
    if ( $CONF->{formatter} ) {
        $formatted = $CONF->{formatter}->_format_text( 'red', locale()->maketext( 'Error: [_1]', $message ) );
    }
    else {
        $formatted = locale()->maketext( 'Error: [_1]', $message );
    }
    _print_message($formatted);
    _log_message( locale()->maketext( 'Error: [_1]', $message ), $CONF );
    return;
}

sub _print_warn {
    my ( $message, $CONF ) = @_;
    my $formatted;
    if ( $CONF->{formatter} ) {
        $formatted = $CONF->{formatter}->_format_text( 'orange', locale()->maketext( 'Warning: [_1]', $message ) );
    }
    else {
        $formatted = locale()->maketext( 'Warning: [_1]', $message );
    }
    _print_message($formatted);
    _log_message( locale()->maketext( 'Warning: [_1]', $message ), $CONF );
    return;
}

sub _print_success {
    my ( $message, $CONF ) = @_;
    my $formatted;
    if ( $CONF->{formatter} ) {
        $formatted = $CONF->{formatter}->_format_text( 'green', locale()->maketext( 'Success: [_1]', $message ) );
    }
    else {
        $formatted = locale()->maketext( 'Success: [_1]', $message );
    }
    _print_message($formatted);
    _log_message( locale()->maketext( 'Success: [_1]', $message ), $CONF );
    return;
}

sub _print_info {
    my ( $message, $CONF ) = @_;
    _print_message($message);
    _log_message( $message, $CONF );
    return;
}

sub _print_lf {
    my ( $count, $CONF ) = @_;
    my $out = "\n" x $count;
    _print_info( $out, $CONF );
    _log_message( $out, $CONF );
    return;
}

sub _print_seperator {
    my $CONF      = shift;
    my $width     = ( $CONF->{term_width} || 80 ) / 2;
    my $seperator = ( "-" x $width ) . "\n";
    _print_info($seperator);
    _log_message( $seperator, $CONF );
    return;
}

sub _print_bold {
    my ( $message, $CONF ) = @_;
    my $formatted;
    if ( $CONF->{formatter} ) {
        $formatted = $CONF->{formatter}->_format_text( 'bold', $message );
    }
    else {
        $formatted = $message;
    }
    _print_message($formatted);
    _log_message( $message, $CONF );
    return;
}

sub _print_message {
    my ( $message, $CONF ) = @_;
    return if !$message;
    $message =~ s/^\s+|\s+$//g;
    return if !$message;

    print $message . "\n";
    return;
}

sub _log_message {
    my ( $message, $CONF ) = @_;
    return if !$message;
    $message =~ s/^\s+|\s+$//g;
    return if !$message;

    $CONF->{logger}->info($message) if $CONF->{logger};
    return;
}

sub format_message {
    my ( $DATA, $CONF ) = @_;

    if ( exists $DATA->{error} ) {

        # can() checks for string, stderr, and stdout are effectively checking whether this is a Cpanel::UntrustedException.
        # It most likely will be because that's what gets re-thrown by Cpanel::ForkSync when the child throws something.

        my $error =
            $DATA->{error}->can('to_locale_string_no_id') ? $DATA->{error}->to_locale_string_no_id()
          : $DATA->{error}->can('string')                 ? $DATA->{error}->string()
          :                                                 $DATA->{error};

        # In some cases (e.g., internal PHP errors), the most meaningful message is whatever was printed to stdout (not even stderr).
        if ( ref $DATA->{error} eq 'Cpanel::UntrustedException' && $DATA->{error}->class eq 'Cpanel::Exception::ProcessFailed::Error' ) {
            $error .= "\n" . $DATA->{error}->get('stdout') if $DATA->{error}->get('stdout');
            $error .= "\n" . $DATA->{error}->get('stderr') if $DATA->{error}->get('stderr');
        }

        if ( exists $DATA->{conf_file} ) {
            return locale()->maketext( 'The conversion of the ‘[_1]’ site for the user ‘[_2]’ failed with the following error: [_3]', $DATA->{conf_file}, $DATA->{user}, $error );
        }
        else {
            return locale()->maketext( 'The conversion failed for the user ‘[_1]’ with the error: [_2]', $DATA->{user}, $error );
        }
    }
    elsif ( exists $DATA->{conf_file} ) {
        return locale()->maketext( 'Starting conversion of the ‘[_1]’ site for the user ‘[_2]’ …', $DATA->{conf_file}, $DATA->{user} ) if $DATA->{start} && $CONF->{verbose};
        return locale()->maketext( 'The system completed the conversion of the ‘[_1]’ site for the user ‘[_2]’.', $DATA->{conf_file}, $DATA->{user} ) if $DATA->{done};
    }
    elsif ( exists $DATA->{user} ) {
        if ( $DATA->{start} && ( $DATA->{count} > 0 || $DATA->{count} == -1 ) ) {
            return "\n" . locale()->maketext( 'Starting conversion for the user ‘[_1]’ …', $DATA->{user} );
        }
        if ( $DATA->{done} ) {
            if ( $DATA->{not_processed} ) {
                return "";
            }
            elsif ( $DATA->{failures} && $DATA->{success} ) {
                return locale()->maketext( 'The conversion did not complete for the user ‘[_1]’. The system successfully converted [_2] sites and failed to convert [_3] sites. Review the errors or warnings above.', $DATA->{user}, $DATA->{success}, $DATA->{failures} );
            }
            elsif ( $DATA->{failures} ) {
                return locale()->maketext( 'The conversion failed for the user ‘[_1]’. Review the errors above.', $DATA->{user} );
            }
            elsif ( $DATA->{success} ) {
                return locale()->maketext( 'The conversion succeeded for the user ‘[_1]’.', $DATA->{user} );
            }
            elsif ( $CONF->{verbose} ) {
                return locale()->maketext( 'The user ‘[_1]’ does not have any legacy [asis,WordPress] sites.', $DATA->{user} );
            }
        }
    }
    else {
        return locale()->maketext('Starting conversions …') if $DATA->{start} && $CONF->{verbose};
        return locale()->maketext('Done.') if $DATA->{done};
    }
    return "";
}

sub _usage_text {
    return <<EOU;
usage: $0 {OPTIONS} {--user <name> ...} {--instance <path> ...}

Migrates sites from the legacy WordPress addon previously distributed by cPanel to the newer RPM based distribution of
WordPress.

  --all      - optional, if provided will attempt to convert all legacy wordpress instances to
                         modern wordpress instances.
  --user     - optional, one or more users to convert. Use multiple --user to request conversion for
                         additional users.
  --instance - optional, one or more instances. If provided, you must pass a single user argument as
                         well. Use multiple --instance arguments to request conversion for additional
                         sites from the same user.

OPTIONS:

  --no-color - optional, if provided will force output to be non-colorized. Defaults to colorized if not passed.
  --dryrun   - optional, if provided only reports information about current installed legacy instances.
  --verbose  - optional, add more information to the output.
  --help     - outputs the help document

Examples:

To convert all legacy WordPress sites on the server:

  $0 --all

To convert all the legacy WordPress sites for a single user frank:

  $0 --user frank

To convert all the legacy WordPress sites for a few users frank, tommy, and jenny:

  $0 --user frank --user tommy --user jenny

To convert a specific legacy WordPress site for a user frank:

  $0 --user frank --instance /home/frank/.cpaddons/cPanel::Blogs::WordPress.1.yaml

EOU
}

1;

