package Cpanel::Easy::Apache;

# cpanel - Cpanel/Easy/Apache.pm                     Copyright 2017 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use base 'Cpanel::Easy';
use Cpanel::SafeDir   ();
use File::Spec        ();
use Cpanel::FileUtils ();
use Cpanel::Time      ();
use Cpanel::Path      ();

{

    sub new {
        my ( $class, $args_ref ) = @_;
        my $self = ref $class ? $class : bless {}, $class;

        # directories used in but not having their own arg
        mkdir '/var/cpanel/easy/';
        mkdir '/var/cpanel/easy/apache/';
        mkdir '/var/cpanel/easy/apache/profile/';
        mkdir '/var/cpanel/easy/apache/profile/custom/';
        mkdir '/usr/local/cpanel/logs/easy';
        mkdir '/usr/local/cpanel/logs/easy/apache/';

        $self->add_ea_ui_symlink();

        my $home =
            -d '/usr/home' ? '/usr/home'
          : -d '/home'     ? '/home'
          :                  '/var/cpanel';
        mkdir "$home/cpeasyapache/";
        chmod 0700, "$home/cpeasyapache/";    # make it so no user can do things here, process_tarballs_and_pkgs() emptied .../src so no symlink attacks can be done (immutable files will make it fail w/ error)

        $self->{'_'}{'cPanel::MemTest required MB'} = 90;    # adjust as needed

        # Substitution of the version happens during the build process
        $self->{'version'} = '3.36.11';
        if ( $self->{'version'} !~ /\d/ ) {
            $self->{'version'} = '3.5.1';
        }
        $self->{'pid_file'}             = $args_ref->{'pid_file'}             || '/var/run/easyapache.pid';
        $self->{'base_dir'}             = $args_ref->{'base_dir'}             || '/usr/local/apache';
        $self->{'time'}                 = time();
        $self->{'cfg_cache_dir'}        = $args_ref->{'cfg_cache_dir'}        || '/var/cpanel/easy/apache/cache';
        $self->{'log_dir'}              = $args_ref->{'log_file'}             || '/usr/local/cpanel/logs/easy/apache';
        $self->{'log_file'}             = $args_ref->{'log_file'}             || '/usr/local/cpanel/logs/easy/apache/build.' . $self->{'time'};
        $self->{'state_file'}           = $args_ref->{'state_file'}           || '/var/cpanel/easy/apache/state.yaml';
        $self->{'last_failed_file'}     = $args_ref->{'last_failed_file'}     || '/var/cpanel/easy/apache/last_failed.yaml';
        $self->{'prefs_file'}           = $args_ref->{'prefs_file'}           || '/var/cpanel/easy/apache/prefs.yaml';
        $self->{'report_touchfile'}     = $args_ref->{'report_touchfile'}     || '/var/cpanel/easy/apache/cp_report_repair';
        $self->{'path_md5_dir'}         = $args_ref->{'path_md5_dir'}         || '/var/cpanel/easy/apache/path_md5';
        $self->{'profile_main'}         = $args_ref->{'profile_main'}         || '/var/cpanel/easy/apache/profile/_main.yaml';
        $self->{'profile_last_success'} = $args_ref->{'profile_last_success'} || '/var/cpanel/easy/apache/profile/_last_success.yaml';
        $self->{'profile_default'}      = $args_ref->{'profile_default'}      || '/var/cpanel/easy/apache/profile/custom/cpanel_default.yaml';
        $self->{'profile_prev'}         = $args_ref->{'profile_prev'}         || '/var/cpanel/easy/apache/profile/_prev.yaml';
        $self->{'profile_custom_dir'}   = $args_ref->{'profile_custom_dir'}   || '/var/cpanel/easy/apache/profile/custom';
        $self->{'opt_mod_src_dir'}      = $args_ref->{'opt_mod_src_dir'}      || "$home/cpeasyapache/src";
        $self->{'opt_mod_patch_dir'}    = $args_ref->{'opt_mod_patch_dir'}    || sprintf( '%s/cppatch', $self->{'opt_mod_src_dir'} );
        $self->{'opt_mod_custom_dir'}   = $args_ref->{'opt_mod_custom_dir'}   || '/var/cpanel/easy/apache/custom_opt_mods';
        $self->{'rawenv_dir'}           = $args_ref->{'rawenv_dir'}           || '/var/cpanel/easy/apache/rawenv';
        $self->{'rawopts_dir'}          = $args_ref->{'rawopts_dir'}          || '/var/cpanel/easy/apache/rawopts';
        $self->{'runlog_dir'}           = $args_ref->{'runlog_dir'}           || '/var/cpanel/easy/apache/runlog';
        $self->{'runlog_file'}          = $args_ref->{'runlog_file'}          || '/var/cpanel/easy/apache/runlog/build.' . $self->{'time'};
        $self->{'variations'}                    = [];
        $self->{'state_file_needs_redone_file'}  = $args_ref->{'state_file_needs_redone_file'} || '/etc/easy.apache.state_file_needs_redone';
        $self->{'report_host'}                   = $args_ref->{'report_host'} || 'buildap.cpanel.net';
        $self->{'report_uri'}                    = $args_ref->{'report_uri'} || 'buildap-submit.pl';
        $self->{'cpaneleasy_build_running_file'} = $args_ref->{'cpaneleasy_build_running_file'} || '/usr/local/apache/AN_EASYAPACHE_BUILD_IS_CURRENTLY_RUNNING';

        # this tells us where to look in the object's path to generate the setup stash
        $self->{'state_config'} = {

            # Item.pm is the main item, Item/ contains any specific opt mods that 'recursive partial override'-ify any opt-mods in '.'
            # in UI they can only choose one
            'main_vers' => [qw(2_2 2_4)],
            'main_name' => 'Apache',        # Cpanel::Easy::
            'ignore'    => [],              # 'Item' = Ignore Item/ and Item.pm, Utils and UI are always treated like they are in this list
                                            # Cpanel::Easy::* name spaces to "include" in state hash as well
            'include'   => [
                qw(
                  Cpanel::Easy::PHP5
                  Cpanel::Easy::ModPerl
                  Cpanel::Easy::ModSec
                  Cpanel::Easy::ModMono
                  Cpanel::Easy::ModMono2
                  Cpanel::Easy::ModBandwidth
                  Cpanel::Easy::ModQos
                  Cpanel::Easy::ModJk5
                  Cpanel::Easy::Tomcat::7_0
                  Cpanel::Easy::PHPSuHosin
                  Cpanel::Easy::EAccelerator
                  Cpanel::Easy::Xcache
                  Cpanel::Easy::Zendopt
                  Cpanel::Easy::SourceGuardian
                  Cpanel::Easy::IonCubeLoader
                  Cpanel::Easy::IonCube5
                  Cpanel::Easy::ModRuid2
                  Cpanel::Easy::ModJk
                  )
            ],
        };

        $self->{'apache_ns_mods_in_basic'}   = [qw( Cpanel::Easy::Apache::PHPAsUser )];
        $self->{'apache_uri_version_lookup'} = {
            '2_2' => '2.2',
            '2_4' => '2.4',
        };
        $self->{'attach_files_to_report'} = [ $self->{'profile_main'}, $self->{'log_file'} ];

        $self->ensure_valid_obj_key( 'param_obj', 'param',  $args_ref, 'Cpanel::CPAN::Getopt::Param' );
        $self->ensure_valid_obj_key( 'ui_obj',    'isa_ui', $args_ref, 'Cpanel::Easy::Apache::UI::CLI' );

        # override Cpanel::Easy's versions:
        $self->{'default_prefs'} = {
            'verbose'                                           => 1,
            'notify_cpanel'                                     => 1,
            'do_not_revert_on_conf_failure'                     => 0,
            'do_not_revert_on_test_failure'                     => 0,
            'archive_backup'                                    => 0,
            'reset_httpd_config_to_default'                     => 0,
            'always_do_the_latest_phps'                         => 0,
            'prefer_profile_php_over_always_do_the_latest_phps' => 0,
            'always_do_the_oldest_phps'                         => 0,
            'prefer_profile_php_over_always_do_the_oldest_phps' => 0,
            'check_httpd_after_each_test'                       => 0,
            'move_broken_local_templates'                       => 0,
            'disable_cloudlinux_infrastructure'                 => 0,
        };

        $self->{'prefs_details_disp_order'} = [
            qw(
              reset_httpd_config_to_default
              always_do_the_latest_phps     prefer_profile_php_over_always_do_the_latest_phps
              always_do_the_oldest_phps     prefer_profile_php_over_always_do_the_oldest_phps
              archive_backup                notify_cpanel
              do_not_revert_on_conf_failure check_httpd_after_each_test do_not_revert_on_test_failure
              move_broken_local_templates   disable_cloudlinux_infrastructure
              )
        ];

        $self->{'prefs_details'} = {

            #            'verbose'       => {
            #                'name'    => 'Verbose',
            #                'reverse' => 0,
            #            },
            'reset_httpd_config_to_default' => {
                'name'    => q{Reset Apache Config to default - any customizations will be lost (this session only)},
                'reverse' => 0,
                'no_save' => 1,
            },
            'always_do_the_latest_phps' => {
                'name'    => 'Always do latest PHP (forces newest minor version of any major versions chosen) This takes precedence over "Always do oldest PHP".',
                'reverse' => 0,
            },
            'prefer_profile_php_over_always_do_the_latest_phps' => {
                'name'    => 'Only apply "Always do latest PHP" if PHP in profile is no longer available',
                'reverse' => 0,
            },
            'always_do_the_oldest_phps' => {
                'name'    => 'Always do oldest PHP (forces oldest minor version of any major versions chosen) This only applies if "Always do latest PHP" is not active.',
                'reverse' => 0,
            },
            'prefer_profile_php_over_always_do_the_oldest_phps' => {
                'name'    => 'Only apply "Always do oldest PHP" if PHP in profile is no longer available',
                'reverse' => 0,
            },
            'archive_backup' => {
                'name'    => q{Archive Backup (this session only)},
                'reverse' => 0,
                'no_save' => 1,
            },
            'notify_cpanel' => {
                'name'    => 'Report Errors to cPanel',
                'reverse' => 0,
            },
            'do_not_revert_on_conf_failure' => {
                'name'    => 'If httpd.conf generation fails or apache can not be restarted after conf generation, leave build in-place so I can manually resolve httpd.conf issues.',
                'reverse' => 0,
            },
            'do_not_revert_on_test_failure' => {
                'name'    => 'If tests fail that would normally revert the build, leave the build in-place so I can manually resolve any issues.',
                'reverse' => 0,
            },
            'check_httpd_after_each_test' => {
                'name'    => 'After each "post restart" test check that apache is still running',
                'reverse' => 0,
            },
            'move_broken_local_templates' => {
                'name'    => 'When there are problems with local templates and the default templates are tried, move the local one to a .broken extension.',
                'reverse' => 0,
            },
            'disable_cloudlinux_infrastructure' => {
                'name'    => 'Remove the Upgrade to CloudLinux banner from the EasyApache interface.',
                'reverse' => 0,
            },
        };

        # The CloudLinux ad (and associated preference) isn't meant to be showed on older cPanel versions,
        # or if you already have CloudLinux installed.
        if ( $self->has_cloudlinux_support() || $self->cmp_cpanel_tier( $self->get_cpanel_version(), '<', "11.40" ) ) {
            delete $self->{'prefs_details'}{'disable_cloudlinux_infrastructure'};
            delete $self->{'default_prefs'}{'disable_cloudlinux_infrastructure'};
            $self->{'prefs_details_disp_order'} = [ grep !/^disable_cloudlinux_infrastructure$/, @{ $self->{'prefs_details_disp_order'} } ];
        }

        # needs to happen before SUPER::new
        require Cpanel::Easy::Apache::Utils::OldProfile;
        Cpanel::Easy::Apache::Utils::OldProfile::run();

        $self->SUPER::new($args_ref);

        $self->{'_'}{'dont_panic_file'} = '/usr/local/apache/IF_YOU_ARE_LOOKING_FOR_MISSING_DIRECTORIES_READTHIS';

        $self->{'powered_by_line'}     = "Easy Apache v$self->{'version'}";
        $self->{'previous_httpd_vers'} = $self->_get_current_apache_version_key();

        $self->{'_'}{'wait_after_restart'} = 4;

        $self->repair_prefs();

        if ( defined $self->get_param('wait_after_restart') ) {
            my $int = int( $self->get_param('wait_after_restart') );
            $self->{'_'}{'wait_after_restart'} = $int if $int;
        }

        ##
        # Get CloudLinux purchase info from manage2 servers.  This is used by
        # the UI to encourage usage of a more secure system.  It should only be
        # displayed under the following circumstances:
        #  1. CloudLinux not installed
        #  2. On a device that supports CloudLinux (e.g. not OpenVZ)
        #  3. A UI will be displayed to user
        ##
        if ( !$self->has_cloudlinux_support() && $self->is_cloudlinux_supported() && !$self->get_param('build') ) {
            $self->{'purchase_cloudlinux_data'} = $self->get_purchase_cloudlinux_data();
        }

        push @{ $self->{'_'}{'post_httpd_restart_tests'} }, {
            'name'             => 'Basic HTTPD Process Check',
            'does_check_httpd' => 1,
            'command'          => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                # safaprestart
                ${$is_fatal_boolean_sr} = 1;

                # or kill 0, Cpanel::LoadFile::loadfile( $pid_file );
                $self->{'_'}{'post_httpd_restart_test_info'}{'pid'} = [ sort $easy->{'pid_obj'}->get_pidof('/usr/local/apache/bin/httpd') ]->[0];

                # since we didn't find a specific one check more loosely for only 'httpd'
                # if ( !$self->{'_'}{'post_httpd_restart_test_info'}{'pid'} ) {
                #     $self->{'_'}{'post_httpd_restart_test_info'}{'pid'} = [ sort $easy->{'pid_obj'}->get_pidof('httpd', 1) ]->[0];
                # }

                if ( !$self->{'_'}{'post_httpd_restart_test_info'}{'pid'} ) {
                    my $fail_retry = 10;
                    $easy->print_alert_color( 'yellow', q{No '[_1]' process found. Waiting '[_2]' seconds to try again.}, 'httpd', $fail_retry );
                    sleep $fail_retry;
                    $self->{'_'}{'post_httpd_restart_test_info'}{'pid'} = [ sort $easy->{'pid_obj'}->get_pidof('/usr/local/apache/bin/httpd') ]->[0];
                }

                if ( !$self->{'_'}{'post_httpd_restart_test_info'}{'pid'} ) {

                    # ok, so we have no pid, lets check to see if they've done some kernel madness that will give false positive
                    my $pid_max = `sysctl kernel.pid_max`;
                    if ( $pid_max =~ m/(\d+)/ ) {
                        my $max = $1;
                        if ( $max > 32768 ) {
                            $easy->print_alert_color( 'red', q{kernel.pid_max is higher than 32768, your system will likley behave abnormally until you reset it} );
                            sleep 7;
                            return ( 1, 'ok' );
                        }
                    }

                    $easy->print_alert_color( 'red', q{No '[_1]' process found. Please check that '[_2]' is running.}, 'httpd', 'Apache' );
                    $self->output_system_cmd( ['ps uxawwww | grep http'] );

                    @{ $self->{'_'}{'post_httpd_restart_tests'} } = ();    # remove rest of tests
                    return ( 0, 'Skipping rest of tests: [_1]', 'httpd not running' );
                }

                return ( 1, 'ok' );
            },
          },
          {
            'name'             => 'Pre-test ACL limits check',
            'does_check_httpd' => 1,
            'command'          => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                $easy->{'_'}{'acl_restricts_user_nobody'} = {} if ref $easy->{'_'}{'acl_restricts_user_nobody'} ne 'HASH';

                my $getfacl = Cpanel::SafeRun::findbin( 'getfacl', 'path' => [qw( /bin /sbin /usr/sbin /usr/local/sbin /usr/bin /usr/local/bin )] );
                $getfacl ||= 'getfacl';    # make sure we at least try for $PATH - case 4294

                for my $bin ('/usr/bin/perl') {
                    my $output = Cpanel::SafeRun::saferunallerrors( $getfacl, $bin );

                    $self->print_alert(q{Begin debug information'});

                    # $self->output_system_cmd( [$getfacl, $bin]);
                    my $string = $self->maketext( q{Output from '[_1]': [_2]}, "$getfacl $bin", "\n$output" );
                    $self->print_alert("\n$string");

                    $self->output_system_cmd( [ 'ls', '-l', $bin ] );

                    my $targ;
                    if ( -l $bin ) {    # lsattr fails for symlinks on ext4 filesystems
                        my $linkedto = readlink($bin);
                        if ( 0 == index $linkedto, '/' ) {
                            $targ = $linkedto;
                        }
                        else {
                            ( my $basepath = $bin ) =~ s{[^/]*/*$}{};
                            $targ = Cpanel::Path::relative2abspath( $linkedto, $basepath );
                        }
                    }
                    else {
                        $targ = $bin;
                    }

                    $self->output_system_cmd( [ 'lsattr', $targ ] );
                    $self->print_alert(q{End debug information'});
                    my @matches;
                    if (
                        (
                            @matches =
                            $output =~ m{
                        ^ # yes we want ^ not \A even though we are xms'ing'
                          (
                              (?: user | group )
                              [:]
                              nobody
                              [:]
                              (?: --- )  # yes we want (?: --- ) so its easy to add other permission sets: (?: --- | whatever )
                          )
                          \s*
                        $ # yes we want $ not \z even though we are xms'ing'

                    }xmsg
                        )
                        || ( @matches = $output =~ m{^(other::..-)\s*$}mg )
                      ) {
                        $easy->{'_'}{'acl_restricts_user_nobody'}{$bin} = {
                            'raw_getfacl_ooutput' => $output,
                            'regex_matches'       => \@matches,
                        };
                    }

                }

                return ( 1, 'ok' );
            },
          },
          {
            'name'    => 'Pre-test htaccess check',
            'command' => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                my $htaccess_found = 0;
                foreach my $directory_path (qw( /  /usr/ /usr/local/ /usr/local/cpanel/ /usr/local/cpanel/cgi-sys/ /usr/local/apache/ /usr/local/apache/htdocs/ )) {
                    if ( -e $directory_path . '.htaccess' ) {
                        $self->print_alert_color( 'red', 'Found .htaccess file at: [_1]', $directory_path . '.htaccess' );
                        $self->output_system_cmd( [ 'ls', '-l', $directory_path . '.htaccess' ] );
                        $htaccess_found = 1;
                    }
                }
                if ($htaccess_found) {
                    $self->print_alert_color(
                        'red',
                        "\n.htaccess files in any level of /usr/local/cpanel/cgi-sys or /usr/local/apache/htdocs paths may interfere with the normal functioning of Apache.  The remaining tests will be skipped.  Please verify Apache is functioning correctly and remove the .htaccess files if necessary.\n"
                    );
                    @{ $self->{'_'}{'post_httpd_restart_tests'} } = ();    # remove rest of tests
                }
                return ( 1, 'Ok' );
            },
          };

        push @{ $self->{'_'}{'post_httpd_restart_tests'} }, {
            'name'    => 'Basic HTTP Request for static content',
            'command' => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                if ( $self->file_md5_hex('/usr/local/cpanel/htdocs/index.html') ne $self->file_md5_hex('/usr/local/apache/htdocs/index.html') ) {
                    $self->print_alert(q{Custom htdocs detected. Skipping test: probably ok});
                    return ( 1, 'skip' );
                }

                my $content = $easy->get_localhost_uri('/index.html');

                if ( !$content ) {
                    return ( 0, q{There was no content, make sure apache is serving} );

                    # could not even get the rest through
                }
                elsif ( $content =~ m{(Great Success|/cgi-sys/defaultwebpage\.cgi)} ) {
                    return ( 1, 'ok' );
                }
                else {
                    return ( 0, q{content received but not the default cPanel page, probably ok} );
                }
            },
          },
          {
            'name'    => 'Basic SSL Request for static content',
            'command' => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                my ( $ip, $port ) = $easy->get_ip_and_port_of_ssl_vhost();
                if ( !$ip ) {
                    $self->print_alert(q{No SSL VirtualHost found skipping SSL test});
                    return ( 1, 'skip' );
                }

                my ( $content, $response, $headers_hr, $path ) = $easy->run_ssl_cgi_uri( $ip, $port, 0 );

                return ( 1, 'skip' ) if $response eq 'skip';

                if ( $content =~ m{Hello World \d+} ) {
                    return ( 1, 'ok' );
                }
                elsif ( $response =~ m{HTTP/1\.\d\s+(\d\d\d)}i ) {
                    my $code = $1;
                    return ( 1, 'ok' ) if $code > 199 && $code < 501;    # 2xx, 3xx, 4xx, 500
                }

                $response ||= '';
                return ( 0, q{SSL Request Response was '[_1]'}, $response );
            },
          },
          {
            'name'             => 'Multi SSL hits segfaults apache',
            'does_check_httpd' => 1,
            'command'          => sub {
                my ( $easy, $is_fatal_boolean_sr ) = @_;

                my ( $ip, $port ) = $easy->get_ip_and_port_of_ssl_vhost();
                if ( !$ip ) {
                    $self->print_alert(q{No SSL VirtualHost found skipping SSL test});
                    return ( 1, 'skip' );
                }

                my $number_of_tests = 10;
                my $path            = '';
                for my $n ( 1 .. $number_of_tests ) {
                    $self->print_to_log_and_screen( $self->maketext( q{Hit [_1] of [_2]}, $n, $number_of_tests ) );

                    my ( $content, $response, $headers_hr, $cgi_path ) = $easy->run_ssl_cgi_uri( $ip, $port, 1 );
                    $path = $cgi_path if !$path;
                    sleep 1;
                }

                unlink $path;    # $ip, $port, $keep - we kept it so now delete it

                if ( [ sort $self->{'pid_obj'}->get_pidof('/usr/local/apache/bin/httpd') ]->[0] ne $self->{'_'}{'post_httpd_restart_test_info'}{'pid'} ) {
                    $easy->print_alert_color( 'red', q{No '[_1]' process found. Please check that '[_2]' is running.}, 'httpd', 'Apache' );
                    @{ $self->{'_'}{'post_httpd_restart_tests'} } = ();    # remove rest of tests
                    return ( 0, 'Skipping rest of tests: [_1]', 'httpd not running' );
                }

                return ( 1, 'ok' );
            },
          };

        $self->add_cgi_test();

        if ( $self->get_param('simulate-failed-test-soft') ) {
            push @{ $self->{'_'}{'post_httpd_restart_tests'} }, {
                'name'    => 'Simulate failed test - soft',
                'command' => sub {
                    my ( $easy, $is_fatal_boolean_sr ) = @_;
                    return ( 0, q{Simulating test failure as per 'simulate-failed-test-soft' flag} );
                },
                'onlyonce' => 1,
            };
        }

        if ( $self->get_param('simulate-failed-test-hard') ) {
            push @{ $self->{'_'}{'post_httpd_restart_tests'} }, {
                'name'    => 'Simulate failed test - hard',
                'command' => sub {
                    my ( $easy, $is_fatal_boolean_sr ) = @_;
                    ${$is_fatal_boolean_sr} = 1;
                    return ( 0, q{Simulating test failure as per 'simulate-failed-test-hard' flag} );
                },
                'onlyonce' => 1,
            };
        }

        $self->add_command( 'post-confgen-pre-restart-commands', ['/usr/local/cpanel/scripts/initsslhttpd'] );
        $self->add_command( 'post-confgen-commands',             ['/usr/local/cpanel/scripts/initfpsuexec'] );
        $self->add_command( 'post-confgen-commands',             ['/usr/local/cpanel/scripts/initsslhttpd'] );    # yes both places, case 2661

        # Ensure that we adjust the permissions of mailman when the build is complete (Case 85553)
        if ( Cpanel::Version::Compare::compare( $Cpanel::Version::Tiny::VERSION_BUILD, '>=', '11.41' ) && -x '/usr/local/cpanel/scripts/fixmailman' ) {
            $self->add_command( 'post-confgen-commands', ['/usr/local/cpanel/scripts/fixmailman'] );
        }

        if ( -e '/usr/local/cpanel/scripts/update_apachectl' ) {
            $self->add_command( 'post-confgen-pre-restart-commands', ['/usr/local/cpanel/scripts/update_apachectl'] );
            $self->add_command( 'post-confgen-commands',             ['/usr/local/cpanel/scripts/update_apachectl'] );
        }

        my ( $httpd, $apachectl ) = qw[ /usr/sbin/httpd  /usr/local/apache/bin/apachectl ];

        unless ( -l $httpd && $apachectl eq readlink $httpd ) {
            $self->add_command( 'post-confgen-commands', [ 'rm', '-f', $httpd ] );
            $self->add_command( 'post-confgen-commands', [ 'ln', '-s', $apachectl, $httpd ] );
        }

        $self->add_command( 'post-confgen-commands', $self->cloudlinux_post_confgen_command() );

        if ( $self->get_param('restore-archive') ) {
            if ( $self->{'ui_obj'}->can('restore_backup') ) {
                $self->{'ui_obj'}->restore_backup($self);
            }
            else {
                $self->_header();
                $self->print_alert( q{Sorry, this UI does not currently support '[_1]'}, 'restore-archive' );
            }
            $self->_footer();
            exit;
        }

        if ( $self->get_param('version') ) {

            # more complicated? move to UI obj like restore-archive
            print "Content-type: text/plain\n\n" if ref $self->{'ui_obj'} eq 'Cpanel::Easy::Apache::UI::HTML';

            print "$self->{'powered_by_line'}\n\n";

            require Cpanel::SafeRun;
            my $cpanel_v = Cpanel::SafeRun::saferunallerrors(qw(/usr/local/cpanel/cpanel -V));
            print "[cpanel]\n$cpanel_v\n\n";

            my $apache_v = Cpanel::SafeRun::saferunallerrors(qw(/usr/local/apache/bin/httpd -v));
            print "[apache]\n$apache_v\n\n";

            my $uname_a = Cpanel::SafeRun::saferunallerrors(qw(uname -a));
            print "[server]\n$uname_a";

            exit;
        }

        if ( $self->get_param('show-apache-defaults') ) {

            # more complicated? move to UI obj like restore-archive
            print "Content-type: text/plain\n\n" if ref $self->{'ui_obj'} eq 'Cpanel::Easy::Apache::UI::HTML';

            print "$self->{'powered_by_line'}\n\n";
            print $self->get_apache_defaults_text();

            exit;
        }

        if ( $self->get_param('latest-versions') ) {

            # more complicated? move to UI obj like restore-archive
            print "Content-type: text/plain\n\n" if ref $self->{'ui_obj'} eq 'Cpanel::Easy::Apache::UI::HTML';

            print "$self->{'powered_by_line'}\n\n";

            print "[Apache]\n\t" . join( ', ', sort { $a gt $b } values %{ $self->_apache_versions() } ) . "\n\n";

            require Cpanel::Easy::PHP5;
            print "[PHP 5]\n\t" . join( ', ', map { my $v = "5.$_"; $v =~ s{\_}{.}g; $v } Cpanel::Easy::PHP5->versions() ) . "\n\n";

            exit;
        }

        $self->perl_profiler_reexec_check();

        $self->_get_conf_levels();

        my $expire_age = 2419200;

        # make seconds configurable = int( ??? );, if so:
        # $expire_age = int $expire_age; # make sure they don;t have bad stuff in config
        if ($expire_age) {
            if ( opendir my $log_dh, '/usr/local/cpanel/logs/easy/apache/' ) {
                my @logs = grep /^build\.\d+/, readdir($log_dh);
                close $log_dh;
                for my $log (@logs) {
                    my ( undef, $made_at ) = split /\./, $log;
                    $made_at =~ s{DEBUG.*}{};

                    if ( $made_at < ( time() - $expire_age ) ) {
                        my $file = '/usr/local/cpanel/logs/easy/apache/' . $log;
                        unlink $file;
                        if ( -e $file ) {
                            $self->print_alert( q{Could not remove '[_1]': [_2]}, $file, $! );
                        }
                    }
                }
            }
        }

        require Cpanel::Easy::Apache::Utils::PreConfCheck;
        Cpanel::Easy::Apache::Utils::PreConfCheck::run($self);

        if ( !$self->get_param('help') ) {
            $self->do_archive_backup() if !$self->_list_backup_archivedir();    # item 1, case 2329
        }

        if ( -d '/usr/local/apache/conf' && !-d '/usr/local/apache/conf_pre_ea3' ) {
            my @copy = ( '/usr/local/apache/conf', '/usr/local/apache/conf_pre_ea3' );
            Cpanel::FileUtils::safecopy(@copy)
              or $self->log_warn( q{'[_1]' did not return true}, 'safecopy(' . join( ',', @copy ) . ')' );
        }

        return $self;
    }

    sub add_ea_ui_symlink {
        my ($self)        = @_;
        my $ea_ui_path    = "/usr/local/cpanel/whostmgr/docroot/cgi/";
        my $ea_ui_symlink = $ea_ui_path . "easy_apache_ui";

        if ( !-l $ea_ui_symlink ) {

            # Rename with a time stamp
            rename $ea_ui_symlink, $ea_ui_symlink . "." . $self->get_timestamp();
        }

        # Create a symbolic link to easy_apache_ui
        symlink( "/var/cpanel/perl/easy/Cpanel/Easy/easy_apache_ui", "/usr/local/cpanel/whostmgr/docroot/cgi/easy_apache_ui" );
    }

    sub get_timestamp {
        my ( $sec, $min, $hr, $mday, $mon, $yr ) = ( localtime(time) )[ 0 .. 5 ];
        my $ts = sprintf( '%04d%02d%02d%02d%02d%02d', $yr + 1900, $mon + 1, $mday, $hr, $min, $sec );
        return $ts;
    }

    sub _apache_versions {
        my ($self) = @_;
        my %vs;

        for my $optmod ( @{ $self->{'state'}->{'order'} } ) {
            next unless $optmod =~ /^Cpanel::Easy::Apache::(\d+(?:_\d+)?)$/;
            my $v = $1;

            my $hr = $self->get_easyconfig_hr_from_ns_variations("Cpanel::Easy::Apache::$v");

            if ( $hr->{'version'} && $hr->{'version'} =~ m{(\d+[.]\d+[.]\d+)} ) {
                $vs{$v} = $1;
            }
            elsif ( $hr->{'src_cd2'} && $hr->{'src_cd2'} =~ m{(\d+[.]\d+[.]\d+)} ) {
                $vs{$v} = $1;
            }
        }

        return \%vs if %vs;
        return;
    }

    sub get_hook_apache_version {
        my ( $self, $profile_hr ) = @_;

        my $ns = 'Cpanel::Easy::Apache::' . $profile_hr->{'Apache'}{'version'};

        if ( $self->get_param('only') ) {
            return 0 if !$self->ns_is_or_belongs_to_list( $ns, $self->get_param('only') );
        }
        elsif ( $self->get_param('skip') ) {
            return 0 if $self->ns_is_or_belongs_to_list( $ns, $self->get_param('skip') );
        }

        return $self->{'apache_uri_version_lookup'}{ $profile_hr->{'Apache'}{'version'} };
    }

    sub get_hook_php_versions {
        my ( $self, $profile_hr ) = @_;

        my @phps = (0);
        my $version;

        my $ns = 'Cpanel::Easy::PHP5';

        if ( $self->get_param('only') ) {
            if ( !$self->ns_is_or_belongs_to_list( $ns, $self->get_param('only') ) ) {
                $version = 0;
            }
        }
        elsif ( $self->get_param('skip') ) {
            if ( $self->ns_is_or_belongs_to_list( $ns, $self->get_param('skip') ) ) {
                $version = 0;
            }
        }

        if ( !defined $version && $self->get_ns_value_from_profile( $ns, $profile_hr ) ) {
            for my $v ( $ns->versions() ) {
                if ( $self->get_ns_value_from_profile( $ns . '::' . $v, $profile_hr ) ) {
                    my $pretty_ver = "5.$v";
                    $pretty_ver =~ s{[_]}{.}g;
                    push @phps, $pretty_ver;
                    last;
                }
            }

            # if no specific one is set by this point then the checks done before this is called need fixed
        }
        else {
            push @phps, 0;
        }

        return join( ',', @phps );
    }

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

        return <<"END_TEXT";

  You have chosen to not restore the backup of the your previous apache build
in the event that a valid httpd.conf could not be generated or apache restarted.
This means that the new build is located at /usr/local/apache/ and you will need
to manually resolve any configuration issues so apache can run.

httpd.conf locations:
  before 'make install'      : $self->{'_'}{'httpd.conf-make_install_backup'}
  'make install' generated   : $self->{'_'}{'httpd.conf-make_install_created'}
  invalid template generated : $self->{'_'}{'httpd.conf-template_generated'}

Note: /usr/local/apache/conf/httpd.conf should be the same
      file as $self->{'_'}{'httpd.conf-template_generated'}
END_TEXT

    }

    sub check_apache_stopfile {
        my ($self) = @_;
        return if $self->get_param('makecpphp');    # stop files not applicable under makecpphp

        for my $stopfile (qw(/etc/apachedisable /etc/httpdisevil)) {
            if ( -e $stopfile ) {
                $self->_header();
                $self->print_alert( q{'[_1]' is disabled via '[_2]', please re-enable to continue.}, 'httpd', $stopfile );
                $self->_footer();
                exit;
            }
        }
    }

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

        if ( -e '/etc/httpddisable.wascreatedbyea3' ) {
            unlink '/etc/httpddisable.wascreatedbyea3'
              or $self->print_alert( q{Could not unlink '[_1]': [_2]}, '/etc/httpddisable.wascreatedbyea3', $! );
            if ( -e '/etc/httpddisable' ) {
                unlink '/etc/httpddisable'
                  or $self->print_alert( q{Could not unlink '[_1]': [_2]}, '/etc/httpddisable', $! );
            }
        }

        # todo chksrvd.conf value instead/along with ??
        $self->{'_'}{'checkservd_is_doing_httpd'} = -e '/etc/httpddisable' || -e '/etc/httpdisevil' ? 0 : 1;
        Cpanel::FileUtils::touchfile('/etc/httpddisable')
          or return ( 0, q{Could not touch '[_1]': [_2]}, '/etc/httpddisable', $! );
        if ( $self->{'_'}{'checkservd_is_doing_httpd'} ) {
            Cpanel::FileUtils::touchfile('/etc/httpddisable.wascreatedbyea3')
              or return ( 0, q{Could not touch '[_1]': [_2]}, '/etc/httpddisable.wascreatedbyea3', $! );
        }

        mkdir '/var/cpanel/mgmt_queue/' if !-d '/var/cpanel/mgmt_queue/';
        my $aprestart_q = '/var/cpanel/mgmt_queue/apache_update_no_restart';
        if ( -e $aprestart_q . '.wascreatedbyea3' ) {
            unlink $aprestart_q . '.wascreatedbyea3'
              or $self->print_alert( q{Could not unlink '[_1]': [_2]}, $aprestart_q . '.wascreatedbyea3', $! );
            if ( -e $aprestart_q ) {
                unlink $aprestart_q
                  or $self->print_alert( q{Could not unlink '[_1]': [_2]}, $aprestart_q, $! );
            }
        }

        $self->{'_'}{'i_turned_off_apache_update'} = -e $aprestart_q ? 0 : 1;

        Cpanel::FileUtils::touchfile($aprestart_q)
          or return ( 0, q{Could not add '[_1]' to management queue: [_2]}, 'apache_update_no_restart', $! );
        if ( $self->{'_'}{'i_turned_off_apache_update'} ) {
            Cpanel::FileUtils::touchfile( $aprestart_q . '.wascreatedbyea3' )
              or return ( 0, q{Could not touch '[_1]': [_2]}, $aprestart_q . '.wascreatedbyea3', $! );
        }

        if ( open my $dp_fh, '>', $self->{'_'}{'dont_panic_file'} ) {
            my $text = <<'END_DONT_PANIC';
While EasyApache is building Apache, some of the existing files have to be moved out of the way.

Anything moved or copied is in /usr/local/apache.backup for posterity. Manipulating those files in anyway can result in a broken Apache.

htdocs customizations will be preserved, see http://go.cpanel.net/easyapachecustom for details.

DSOs will be built after Apache for the new Apache.

END_DONT_PANIC
            print {$dp_fh} $text;
            close $dp_fh;
        }
        else {
            return ( 0, q{Could not open '[_1]' for writing: [_2]}, $self->{'_'}{'dont_panic_file'}, $! );
        }

        for my $relpath (qw(include bin lib libexec modules build htdocs)) {
            my $path = File::Spec->catdir( '/usr/local/apache', $relpath );

            next if !-e $path;

            if ( -l $path || -f $path ) {
                unlink $path || $self->display_path_diagnostic_info($path) || return ( 0, q{Could not remove '[_1]': [_2]}, $path, $! );
            }
            elsif ( -d $path ) {
                Cpanel::SafeDir::safermdir($path)
                  || $self->display_path_diagnostic_info($path)
                  || return ( 0, q{Could not remove '[_1]': [_2]}, $path, $! );
            }
            else {
                $self->print_alert( q{'[_1]' exists and is not a link, file, or directory}, $path );
            }
        }

        my $conf_file = $self->_get_main_httpd_conf();
        $self->{'_'}{'httpd.conf-make_install_backup'} = $conf_file . '.ea-orig';
        my $conf_backup = $self->{'_'}{'httpd.conf-make_install_backup'};

        if ( -e $conf_file ) {
            if ( -e $conf_backup ) {
                unlink $conf_backup || $self->display_path_diagnostic_info($conf_backup) || return ( 0, q{Could not remove '[_1]': [_2]}, $conf_backup, $! );
            }

            File::Copy::Recursive::fmove( $conf_file, $conf_backup )
              || $self->display_path_diagnostic_info( $conf_file, $conf_backup )
              || return ( 0, q{Could not move '[_1]' to [_2]: [_3]}, $conf_file, $conf_backup, $! );
            chmod 0600, $conf_backup;
        }

        my @cmd = qw( make install );
        my ( $mi_rc, @mi_text ) = $self->run_system_cmd_returnable( [@cmd] );
        chmod 0600, $conf_file              if ( -e $conf_file );
        chmod 0600, $conf_file . '.bak'     if ( -e $conf_file . '.bak' );
        chmod 0600, $conf_file . '.default' if ( -e $conf_file . '.default' );

        $self->{'cache'}{'hook_script_args'}[2] = $self->_get_httpd_version();    # update to full version number

        $self->{'_'}{'httpd.conf-make_install_created'} = $conf_file . '.ea-make-install';

        my $mk_ins_file = $self->{'_'}{'httpd.conf-make_install_created'};
        File::Copy::Recursive::fcopy( $conf_file, $mk_ins_file )
          or return ( 0, q{Could not copy '[_1]' to [_2]: [_3]}, $conf_file, $mk_ins_file, $! );
        chmod 0600, $mk_ins_file if ( -e $mk_ins_file );

        $self->{'new_httpd_vers'} = $self->_get_current_apache_version_key();

        # Restore previous config

        if ( $self->{'_'}{'prefs'}{'reset_httpd_config_to_default'} ) {
            $self->print_alert(q{Resetting httpd.conf to default});    # warn if they want to do this (IE pref is set) but the other conditions are not met ?
        }
        elsif ( $self->{'previous_httpd_vers'} && $self->{'previous_httpd_vers'} eq $self->{'new_httpd_vers'} && -e $conf_backup ) {
            unlink $conf_file;
            File::Copy::Recursive::fcopy( $conf_backup, $conf_file )
              or return ( 0, q{Could not copy '[_1]' to [_2]: [_3]}, $conf_backup, $conf_file, $! );
            chmod 0600, $conf_file;
            $self->{'trying_previous_httpd_conf'} = 1;
            $self->ensure_loadmodule_exists();
        }

        # move fresh htdocs to htdocs/ea3_apache_build_htdocs
        Cpanel::SafeDir::safemkdir('/usr/local/apache/htdocs/ea3_apache_build_htdocs')
          if !-d '/usr/local/apache/htdocs/ea3_apache_build_htdocs';
        if ( opendir my $ht_dh, '/usr/local/apache/htdocs/' ) {
            while ( my $file = readdir($ht_dh) ) {
                next if $file eq '.' || $file eq '..' || $file eq 'ea3_apache_build_htdocs';
                my @copy = ( "/usr/local/apache/htdocs/$file", "/usr/local/apache/htdocs/ea3_apache_build_htdocs/$file" );
                Cpanel::FileUtils::safemv(@copy)
                  or return ( 0, q{'[_1]' did not return true}, 'safecopy(' . join( ',', @copy ) . ')' );
            }
            closedir $ht_dh;
        }
        else {
            $self->print_alert( q{Could not readdir '[_1]': [_2]}, '/usr/local/apache/htdocs/', $! );
        }

        # restore their htdocs (sans ea3_apache_build_htdocs) from -d "$backupdir/htdocs/
        my $backupdir = $self->_get_backupdir();
        if ( -d "$backupdir/htdocs/" ) {
            if ( opendir my $htb_dh, "$backupdir/htdocs/" ) {
                while ( my $file = readdir($htb_dh) ) {
                    next if $file eq '.' || $file eq '..' || $file eq 'ea3_apache_build_htdocs';
                    my @copy = ( "$backupdir/htdocs/$file", "/usr/local/apache/htdocs/$file" );
                    Cpanel::FileUtils::safecopy(@copy)
                      or return ( 0, q{'[_1]' did not return true}, 'safecopy(' . join( ',', @copy ) . ')' );
                }
                closedir $htb_dh;
            }
            else {
                $self->print_alert( q{Could not readdir '[_1]': [_2]}, "$backupdir/htdocs/", $! );
            }
        }

        # create suspended.page if needed
        if ( !-d '/usr/local/apache/htdocs/suspended.page/' ) {
            my @copy = ( '/usr/local/cpanel/etc/suspended.page/', '/usr/local/apache/htdocs/' );
            Cpanel::FileUtils::safecopy(@copy)
              or return ( 0, q{'[_1]' did not return true}, 'safecopy(' . join( ',', @copy ) . ')' );
        }

        # create moving.page if needed
        if ( !-d '/usr/local/apache/htdocs/moving.page/' && -d '/usr/local/cpanel/etc/moving.page/' ) {
            my @copy = ( '/usr/local/cpanel/etc/moving.page/', '/usr/local/apache/htdocs/' );
            Cpanel::FileUtils::safecopy(@copy)
              or return ( 0, q{'[_1]' did not return true}, 'safecopy(' . join( ',', @copy ) . ')' );
        }

        # create htdocs/ files if needed
        for my $file (qw( index.html 400.shtml 401.shtml 403.shtml 404.shtml 500.shtml cp_errordocument.shtml )) {
            my $path = "/usr/local/cpanel/htdocs/$file";

            if ( !-e "/usr/local/apache/htdocs/$file" ) {
                if ( -e $path ) {
                    Cpanel::FileUtils::safecopy( $path, '/usr/local/apache/htdocs/' )
                      or $self->print_alert( q{'[_1]' did not return true}, "safecopy($file, /usr/local/apache/htdocs/)" );
                }
            }
        }

        if ($mi_rc) {
            if ( -x '/usr/local/cpanel/scripts/update_apache_directive' ) {
                my @stripped = $self->run_system_cmd_returnable( [ '/usr/local/cpanel/scripts/update_apache_directive', '--no-restart', 'LoadModule==#LoadModule', ] );
                return @stripped if !$stripped[0];
            }
        }

        $self->hook_script('/usr/local/cpanel/scripts/after_apache_make_install');

        return ( $mi_rc, @mi_text );
    }

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

        if ( -e $self->{'_'}{'dont_panic_file'} ) {
            unlink $self->{'_'}{'dont_panic_file'}
              or $self->print_alert( q{Could not unlink '[_1]': [_2]}, $self->{'_'}{'dont_panic_file'}, $! );
        }

        if ( $self->{'_'}{'checkservd_is_doing_httpd'} ) {
            for my $file (qw(/etc/httpddisable /etc/httpddisable.wascreatedbyea3)) {
                unlink $file
                  or $self->print_alert( q{Could not unlink '[_1]': [_2]}, $file, $! );
            }
        }

        if ( $self->{'_'}{'i_turned_off_apache_update'} ) {
            for my $file (qw(/var/cpanel/mgmt_queue/apache_update_no_restart /var/cpanel/mgmt_queue/apache_update_no_restart.wascreatedbyea3)) {
                next if !-e $file;
                unlink $file
                  or $self->print_alert( q{Could not remove '[_1]' from management queue: [_2]}, 'apache_update_no_restart', $! );
            }
        }
    }

    sub install_profile {
        my $self = shift;
        my $force_working_profile;

        my $profile = ref $_[0] ne 'HASH' ? $self->determine_profile( $_[0] ) : '';
        $self->{'working_profile'} = $self->deserialize($profile) if $profile;
        my $v = $self->{'working_profile'}{'Apache'}{'version'};
        $self->{'_'}{'apache_src_path'} = $self->{'opt_mod_src_dir'} . '/' . $self->{'state'}{'optmods'}{'Apache'}{'_main'}{$v}{'src_cd2'};

        my ( $rc, @text ) = $self->pre_build_config_setup();
        if ( !$rc ) {
            $self->print_alert(@text);
            return $rc;
        }

        if ( $self->{'_'}{'prefs'}{'always_do_the_latest_phps'} ) {
            my $ns = "Cpanel::Easy::PHP5";
            eval qq{ require $ns; };
            my @vers        = $ns->versions();
            my $find_latest = 1;

            if ( $self->{'_'}{'prefs'}{'prefer_profile_php_over_always_do_the_latest_phps'} ) {
                for my $v (@vers) {
                    $find_latest = 0 if $self->get_ns_value_from_profile( $ns . '::' . $v, $self->{'working_profile'} );
                }
            }

            if ($find_latest) {
                $self->set_param( "php5", $ns->latest_version() );
                for my $v (@vers) {
                    $force_working_profile->{ $ns . '::' . $v } = $v eq $ns->latest_version() ? 1 : 0;
                }
            }
        }
        elsif ( $self->{'_'}{'prefs'}{'always_do_the_oldest_phps'} ) {
            my $ns = "Cpanel::Easy::PHP5";
            eval qq{ require $ns; };
            my @vers        = reverse $ns->versions();
            my $find_oldest = 1;

            if ( $self->{'_'}{'prefs'}{'prefer_profile_php_over_always_do_the_oldest_phps'} ) {
                for my $v (@vers) {
                    $find_oldest = 0 if $self->get_ns_value_from_profile( $ns . '::' . $v, $self->{'working_profile'} );
                }
            }

            if ($find_oldest) {
                $self->set_param( "php5", $ns->oldest_version() );
                for my $v (@vers) {
                    $force_working_profile->{ $ns . '::' . $v } = $v eq $ns->oldest_version() ? 1 : 0;
                }
            }
        }

        my $mpm = $self->get_configured_mpm();
        if ( $mpm ne 'prefork' ) {
            $self->print_alert_color( 'red', q{You have selected the '[_1]' MPM. If you have any problems building or running anything after this build you'll have to rebuild without a specific MPM chosen.}, $mpm );
        }

        $self->SUPER::install_profile( $_[0], $force_working_profile );
    }

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

        my %lev = ( 0 => 1 );    # make sure at least default is done...

        for my $lv ( split( /,/, $self->{'_'}{'prefs'}{'conf_levels'} ) ) {
            $lev{ int $lv }++;
        }

        $self->{'_'}{'prefs'}{'conf_levels'} = join( ',', sort { $b <=> $a } keys %lev );
        return split( /,/, $self->{'_'}{'prefs'}{'conf_levels'} );
    }

#### apache cpanelsync related stuff ##
    sub _cpanelsync {
        my ($self) = @_;

        return 1 if $self->get_param('skip-cpanelsync');

        my $changes = 0;

        my ( $rc, $exit ) = $self->run_system_cmd( [ '/usr/local/cpanel/scripts/cpanelsync', $self->{'_'}{'cpsources'}{'HTTPUPDATE'}, "$self->{'httpupdate_uri'}/Cpanel", $self->{'opt_mod_dir'} . '/Cpanel/', ] );

        # if files are added, updated or deleted:
        if ( $self->_cpanelsync_changed_files( $self->{'last_run_system_cmd'} ) ) {
            $changes++;
        }

        my $path = $self->{'profile_custom_dir'};
        $path =~ s{/var/cpanel/}{};

        $self->run_system_cmd( [ '/usr/local/cpanel/scripts/cpanelsync', $self->{'_'}{'cpsources'}{'HTTPUPDATE'}, "$self->{'httpupdate_uri'}/profiles/$path", $self->{'profile_custom_dir'} . '/', ] );

        # if files are added, updated or deleted:
        if ( $self->_cpanelsync_changed_files( $self->{'last_run_system_cmd'} ) ) {
            $changes++;
        }

        # if files are added, updated or deleted:
        if ($changes) {
            if ( !Cpanel::FileUtils::touchfile( $self->{'state_file_needs_redone_file'} ) ) {
                $self->log_warn( [ q{touchfile: '[_1]' failed: [_2]}, $self->{'state_file_needs_redone_file'}, $! ] );
                return;
            }

            no warnings 'redefine';
            require Cpanel::CPAN::Module::Refresh;
            my $refresh = Cpanel::CPAN::Module::Refresh->new();
            for my $freshify ( keys %INC ) {
                next if $freshify !~ m{^Cpanel/Easy/};
                $refresh->refresh_module($freshify);
            }
        }

        return 1;
    }

    sub _cpanelsync_changed_files {
        my ( $self, $output ) = @_;

        return 1 if $output =~ m{Removing\s+file};
        my @fetches = $output =~ m{Fetching\s+(\S+)}g;

        for my $url (@fetches) {
            return 1 if $url !~ m{\.cpanelsync(\.\w+)?$};
        }

        return;
    }

#### apache backup related stuff ##

    sub _get_buildok_archivedir {
        my ($self) = @_;
        return $self->{'base_dir'} . '.ea3-buildok-confgen-failed';
    }

    sub archive_ok_build {
        my ($self) = @_;
        $self->print_alert_color( 'blue', q{Archiving successful build so it can be restored via --restore-archive once the httpd.conf are resolved} );

        my $okbuild_dir = $self->_get_buildok_archivedir();
        if ( -d $okbuild_dir ) {
            Cpanel::SafeDir::safermdir($okbuild_dir);    # quietly or loud ?
        }
        mkdir $okbuild_dir;                              # quietly or loud ?

        if ( opendir my $base_dh, $self->{'base_dir'} ) {

            # backup $self->{'base_dir'} minus $self->_skip_file_for_backup($file); to $self->_get_buildok_archivedir()
            my @to_backup = grep { !$self->_skip_file_for_backup($_) } readdir($base_dh);
            close $base_dh;
            for my $item (@to_backup) {
                my $path = File::Spec->catdir( $self->{'base_dir'}, $item );
                Cpanel::FileUtils::safecopy( $path, $okbuild_dir )
                  or $self->print_alert( q{'[_1]' did not return true}, qq{safecopy($path, $okbuild_dir)} );
            }

            # now do httpd.conf
            mkdir $okbuild_dir . '/conf/';    # quietly or loud ?

            my $httpdconf = $self->{'base_dir'} . '/conf/httpd.conf';    # default to this if broken file does not exist...
            if ( -e $self->{'_'}{'httpd.conf-template_generated'} ) {
                $httpdconf = $self->{'_'}{'httpd.conf-template_generated'};
            }
            else {

                # ... and tell them that happened
                $self->print_alert( q{'[_1]' does not exist, using '[_2]' instead}, $self->{'_'}{'httpd.conf-template_generated'}, $httpdconf );
            }

            Cpanel::FileUtils::safecopy( $httpdconf, $okbuild_dir . '/conf/httpd.conf' )
              or $self->print_alert( q{'[_1]' did not return true}, qq{safecopy($httpdconf, $okbuild_dir/conf/httpd.conf)} );
        }
        else {
            $self->print_alert( q{Could not readdir '[_1]': [_2]}, $self->{'base_dir'}, $! );
        }

        $self->print_alert_color( 'blue', q{Done Archiving successful build} );
    }

    sub _create_backup {
        my ( $self, $no_archive ) = @_;

        # void context, die if can't do it

        if ( $self->get_param('only') ) {
            if ( !grep /^(Apache|Cpanel::Easy::Apache)$/, $self->get_param('only') ) {
                $self->print_alert('Skipping backup, --only does not include Apache');
                return;
            }
        }
        elsif ( $self->get_param('skip') ) {
            if ( grep /^(Apache|Cpanel::Easy::Apache)$/, $self->get_param('skip') ) {
                $self->print_alert('Skipping backup, --skip includes Apache');
                return;
            }
        }

        if ( $self->_no_apache() ) {
            $self->print_alert('Skipping backup, no working apache found');
            return;
        }

        $self->_do_bu_file_move( { 'copy' => 1 } );

        $self->do_archive_backup() if !$no_archive && $self->get_param('archive_backup');    # item 2a, case 2329

        $self->print_alert('Backup of working apache complete.');
    }

    sub _do_bu_file_move {
        my ( $self, $opts_hr ) = @_;

        my ( $act_name, $act_cr ) =
          $opts_hr->{'copy'}
          ? ( 'rcopy', \&File::Copy::Recursive::rcopy )
          : ( 'rmove', \&File::Copy::Recursive::rmove );

        my $backupdir = $self->_get_backupdir();
        $self->print_alert("Creating backup of working apache in '$backupdir'.");

        if ( -d $backupdir ) {

            # wipe out contents *except* non_apache/
            if ( opendir my $bu_dh, $backupdir ) {
                my @remove;
                while ( my $item = readdir($bu_dh) ) {

                    next if $item eq 'non_apache' || $item =~ m{^[.]+$};
                    push @remove, $item;
                }
                closedir $bu_dh;

                for my $goner (@remove) {
                    my $full_path = "$backupdir/$goner";
                    if ( -d $full_path && !-l $full_path ) {
                        Cpanel::SafeDir::safermdir($full_path);    # quietly or loud ?
                    }
                    else {
                        unlink $full_path;                         # quietly or loud ?
                    }
                }
            }
            else {
                $self->print_alert( q{Could not readdir '[_1]': [_2]}, $backupdir, $! );
            }
        }
        else {
            mkdir $backupdir;
        }

        if ( opendir my $base_dh, $self->{'base_dir'} ) {
            my @files = readdir $base_dh;
            close $base_dh;
            foreach my $file (@files) {
                next if $self->_skip_file_for_backup($file);

                my $copy_me = File::Spec->catdir( $self->{'base_dir'}, $file );
                my $copy_to = File::Spec->catdir( $backupdir,          $file );

                my $_act_name = $act_name;
                my $_act_cr   = $act_cr;
                if ( -l $copy_me ) {
                    my $target = readlink($copy_me);
                    $self->print_alert_color( 'blue', q{Symlink encountered ([_1]), preserving target ([_2])...}, $copy_me, $target );
                    ( $_act_name, $_act_cr ) =
                      $act_name eq 'rcopy'
                      ? ( 'fcopy', \&File::Copy::Recursive::fcopy )
                      : ( 'fmove', \&File::Copy::Recursive::fmove );

                    # next if -l $copy_me; # etc/ -> conf/ = bad; symlinks not good to move and copy around
                }

                $_act_cr->( $copy_me, $copy_to, )
                  or $self->print_to_log_and_screen( $self->maketext( qq{Could not $_act_name() '[_1]': [_2]}, $copy_me, $!, ) );
            }

            $self->{'_'}{'backup_created'} = time();
        }
        else {
            $self->print_to_log_and_screen( $self->maketext( q{Could not opendir() '[_1]': [_2]}, $self->{'base_dir'}, $!, ) );
            $self->log_die( [ q{Could not opendir() '[_1]': [_2]}, $self->{'base_dir'}, $! ] );
        }
    }

    sub _restore_backup_if_needed {
        my ( $self, $do_regardless, $alternate_path ) = @_;

        if ( $self->{'_'}{'restore_backup'} ) {
            if ( -e $self->{'_'}{'httpd.conf-make_install_backup'} ) {
                my $httpdconf = $self->_get_main_httpd_conf();
                unlink $httpdconf if -e $httpdconf;
                File::Copy::Recursive::fmove( $self->{'_'}{'httpd.conf-make_install_backup'}, $httpdconf )
                  or return ( 0, q{Could not move '[_1]' to [_2]: [_3]}, $self->{'_'}{'httpd.conf-make_install_backup'}, $httpdconf, $! );
                chmod 0600, $httpdconf;
            }
        }

        if ( !$do_regardless ) {
            return 1 if !$self->{'_'}{'restore_backup'};    # should be set to true whenever we determine its needed

            # nothing to restore || no need to
            if ( !$self->{'_'}{'backup_created'} ) {
                $self->print_alert('No original working apache backup to restore');
                return 1;
            }

        }

        $self->archive_ok_build() if $self->{'_'}{'build_ok_but_confgen_failed'};

        $self->restore_any_non_apache_file_backups();       # files added via $self->add_non_apache_abspath_to_backup

        my $modlib = $self->{'working_profile'}{'Apache'}{'version'} eq '1' ? '/usr/local/apache/libexec' : '/usr/local/apache/modules';
        $self->_ensure_path_is_gone($modlib)
          or $self->print_alert( q{Could not remove '[_1]' before backup restore}, $modlib );

        if ( defined $alternate_path && $alternate_path ) {
            if ( !-d $alternate_path ) {
                $self->print_alert( q{Directory '[_1]' does not exist}, $alternate_path );
            }
        }

        my $backupdir = $alternate_path || $self->_get_backupdir();

        if ( -d $backupdir ) {

            $alternate_path
              ? $self->print_alert_color( 'red', q{Restoring '[_1]'}, $alternate_path )
              : $self->print_alert_color( 'red', 'Restoring original working apache' );

            if ( opendir my $base_dh, $backupdir ) {
                my @files = readdir $base_dh;
                close $base_dh;

                foreach my $file (@files) {
                    next if $self->_skip_file_for_backup($file);    # how'd they get in there anyway?

                    $self->_restore_to_basedir( $file, $backupdir )
                      or $self->print_to_log_and_screen(
                        $self->maketext(
                            q{Could not restore '[_1]' to '[_2]', that will need done manually!},
                            File::Spec->catdir( $backupdir,          $file ),
                            File::Spec->catdir( $self->{'base_dir'}, $file ),
                        )
                      );
                }

                $self->run_one_cmd( ['/usr/local/cpanel/scripts/initsslhttpd'] );    # case 4286
                $self->restart_httpd();

                $self->_rebuild_globalcache();
                return 1;
            }
            else {
                $self->print_to_log_and_screen( $self->maketext( q{Could not opendir() '[_1]': [_2]}, $self->{'base_dir'}, $!, ) );
            }
        }
        else {
            if ( !$self->_httpd_is_running() ) {
                $self->restart_httpd();
            }
            $self->_rebuild_globalcache();
            return 1;
        }

        if ( !$self->_httpd_is_running() ) {
            $self->restart_httpd();
        }
        $self->_rebuild_globalcache();
        return;
    }

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

        if ( -e $self->_get_main_httpd_conf() && -x $self->_get_main_httpd_bin() ) {
            return;
        }

        return 1;    # logic below not used in favor of logic above as per case 1216

        return 1 if !-x $self->_get_main_httpd_bin();

        return if $self->_httpd_is_running;

        $self->restart_httpd();

        # ? $self->_post_restart_httpd_tests();
        return if $self->_httpd_is_running;

        return 1;
    }

    sub _restore_to_basedir {
        my ( $self, $file, $backupdir ) = @_;

        my $restore = File::Spec->catdir( $backupdir,          $file );
        my $clean   = File::Spec->catdir( $self->{'base_dir'}, $file );
        my $isgone  = $self->_ensure_path_is_gone($clean);

        # at this point, it is in backup and not in base_dir if $rc
        if ($isgone) {
            if ( File::Copy::Recursive::rcopy( $restore, $clean ) ) {
                return 1;
            }
        }

        return;
    }

    sub _skip_file_for_backup {
        my ( $self, $file ) = @_;
        return 1 if $file =~ m{^\.+$} || $file =~ m{^non_apache$} || $file =~ m{^conf$} || $file =~ m{logs} || $file =~ m{^conf_pre_ea3$};
        return 1 if -p $file;    # Any other specials to avoid ? -S -b -c -t
        return;
    }

    sub _get_backupdir {
        my ($self) = @_;
        return $self->{'base_dir'} . '.backup';
    }

    sub _get_backup_archivedir {
        my ($self) = @_;
        return $self->{'base_dir'} . '.backup_archive';
    }

    sub _list_backup_archivedir {
        my ($self) = @_;
        my $dir = $self->_get_backup_archivedir();
        Cpanel::SafeDir::safemkdir($dir) if !-d $dir;
        if ( opendir my $arch_dh, $dir ) {
            my @files = grep { -d "$dir/$_" } grep !m{ \A [.]+ \z }xms, readdir($arch_dh);
            closedir $arch_dh;
            return @files;
        }
        else {
            $self->print_alert( q{Could not readdir '[_1]': [_2]}, $dir, $! );
        }
        return;
    }

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

        my $bu = $self->_get_backupdir();
        if ( !-d $bu ) {
            $self->_do_bu_file_move( { 'copy' => 1 } );
        }

        my $ar = $self->_get_backup_archivedir();
        $ar .= '/' . Cpanel::Time::time2dnstime() . ".$self->{'time'}";

        Cpanel::FileUtils::safecopy( $bu, $ar )
          or return ( 0, q{'[_1]' did not return true}, qq{safecopy($bu, $ar)} );
        return ( 1, 'ok' );
    }

    sub _ensure_path_is_gone {
        my ( $self, $path ) = @_;

        return 1 if !-e $path;

        if ( -d $path && !-l $path ) {
            my $rc = Cpanel::SafeDir::safermdir($path);

            if ( !$rc ) {
                $self->log_warn( [ q{Could not safermdir() '[_1]': [_2]}, $path, $!, ] );
                return;
            }
        }
        else {
            my $rc = unlink($path);
            if ( !$rc ) {
                $self->log_warn( [ q{Could not unlink() '[_1]': [_2]}, $path, $! ] );
                return;
            }
        }

        return 1;
    }

    sub add_non_apache_abspath_to_backup {
        my ( $self, $file ) = @_;
        $file = Cpanel::FileUtils::cleanpath($file);

        return if $file !~ m{ \A [/] }xms;    # not abspath
        return if !-e $file;

        if ( !exists $self->{'_'}{'add_non_apache_abspath_to_backup'} ) {
            $self->{'_'}{'add_non_apache_abspath_to_backup'} = 0;
        }

        my $backupdir = $self->_get_backupdir();

        if ( -d "$backupdir/non_apache/" ) {
            if ( !$self->{'_'}{'add_non_apache_abspath_to_backup'} ) {
                Cpanel::SafeDir::safermdir("$backupdir/non_apache/");
            }
        }

        Cpanel::SafeDir::safemkdir("$backupdir/non_apache/") if !-d "$backupdir/non_apache/";

        my $long_name = join( '___', split( /\//, $file ) );

        if ( -e "$backupdir/non_apache/$long_name" ) {
            unlink "$backupdir/non_apache/$long_name";
        }

        Cpanel::FileUtils::safecopy( $file, "$backupdir/non_apache/$long_name" );

        $self->{'_'}{'add_non_apache_abspath_to_backup'}++;

        return 1;
    }

    sub restore_any_non_apache_file_backups {
        my ($self) = @_;
        my $backupdir = $self->_get_backupdir();

        return 1 if !-d "$backupdir/non_apache/";

        if ( opendir my $bu_dh, "$backupdir/non_apache/" ) {
            my @restore_us = grep !/^[.]+$/, readdir($bu_dh);
            closedir $bu_dh;
            for my $file (@restore_us) {
                my $path = '/' . join( '/', split( /[_]{3}/, $file ) );
                Cpanel::FileUtils::safecopy( "$backupdir/non_apache/$file", $path );
                if ( -e $path . '.cache' ) {
                    unlink $path . '.cache';
                }
            }
        }
        else {
            $self->print_alert( q{Could not readdir '[_1]': [_2]}, "$backupdir/non_apache/", $! );
        }

        return 1;
    }

    sub display_path_diagnostic_info {
        my $self = shift;
        local $!;
        foreach my $path (@_) {
            $self->print_alert( q{Performing diagnostic checks on '[_1]'}, $path );
            $self->print_alert( q{ls -alR [_1]},                           $path );
            $self->run_system_cmd( [ 'ls', '-alR', $path ] );
            $self->print_alert( q{lsattr -R [_1]}, $path );
            $self->run_system_cmd( [ 'lsattr', '-R', $path ] );
        }
        return 0;
    }

    sub get_apache_defaults_text {
        my ($self) = @_;
        return <<'END_AP_DEF';
Using a cPanel profile (with no changes in the UI or manually) the following modules are built for Apache by default:

[2.2]
  Compiled in (IE /usr/local/apache/bin/httpd -l)
        core.c
        mod_authn_file.c
        mod_authn_default.c
        mod_authz_host.c
        mod_authz_groupfile.c
        mod_authz_user.c
        mod_authz_default.c
        mod_auth_basic.c
        mod_include.c
        mod_filter.c
        mod_log_config.c
        mod_logio.c
        mod_env.c
        mod_expires.c
        mod_headers.c
        mod_setenvif.c
        mod_proxy.c
        mod_proxy_connect.c
        mod_proxy_ftp.c
        mod_proxy_http.c
        mod_proxy_ajp.c
        mod_proxy_balancer.c
        mod_ssl.c
        prefork.c
        http_core.c
        mod_mime.c
        mod_status.c
        mod_autoindex.c
        mod_asis.c
        mod_suexec.c
        mod_cgi.c
        mod_negotiation.c
        mod_dir.c
        mod_actions.c
        mod_userdir.c
        mod_alias.c
        mod_rewrite.c
        mod_so.c

  DSO (Eg: .so files in /usr/local/apache/modules/)
        mod_auth_passthrough.so
        mod_bwlimited.so
        mod_disable_suexec.so ( no LoadModule by default )

END_AP_DEF

    }
}

1;
