!C99Shell v. 2.5 [PHP 8 Update] [24.05.2025]!

Software: Apache. PHP/8.1.30 

uname -a: Linux server1.tuhinhossain.com 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC
2025 x86_64
 

uid=1002(picotech) gid=1003(picotech) groups=1003(picotech),0(root)  

Safe-mode: OFF (not secure)

/usr/share/webmin/virtual-server/   drwxrwxr-x
Free 28.64 GB of 117.98 GB (24.28%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Self remove    Logout    


Viewing file:     php-lib.pl (82.78 KB)      -rwxrwxr-x
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# Functions for PHP configuration

# get_domain_php_mode(&domain)
# Returns 'mod_php' if PHP is run via Apache's mod_php, 'cgi' if run via
# a CGI script, 'fcgid' if run via fastCGI. This is detected by looking for the
# Action lines in httpd.conf.
sub get_domain_php_mode
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_get_web_php_mode", $d);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
&require_apache();
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'},
                           $d->{'web_port'});
if ($virt) {
    # First check for FPM socket, using a single ProxyPassMatch
    local @ppm = &apache::find_directive("ProxyPassMatch", $vconf);
    foreach my $ppm (@ppm) {
        if ($ppm =~ /unix:([^\|]+)/ ||
            $ppm =~ /fcgi:\/\/(localhost|127\.0\.0\.1):\d+/) {
            return 'fpm';
            }
        }

    # Also check for FPM socket in a FilesMatch block
    foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) {
        next if ($f->{'words'}->[0] ne '\.php$');
        foreach my $h (&apache::find_directive("SetHandler", $f->{'members'})) {
            if ($h =~ /proxy:fcgi:\/\/(localhost|127\.0\.0\.1)/ ||
                $h =~ /proxy:unix:/) {
                return 'fpm';
                }
            }
        }

    # Look for an action, possibly in a directory, that runs the FCGI
    # wrapper for PHP scripts
    local @actions = &apache::find_directive("Action", $vconf);
    local $pdir = &public_html_dir($d);
    local ($dir) = grep { $_->{'words'}->[0] eq $pdir ||
                  $_->{'words'}->[0] eq $pdir."/" ||
                  &path_glob_match($_->{'words'}->[0], $pdir) }
            &apache::find_directive_struct("Directory", $vconf);
    if ($dir) {
        push(@actions, &apache::find_directive("Action",
                               $dir->{'members'}));
        foreach my $f (&apache::find_directive("FCGIWrapper",
                            $dir->{'members'})) {
            if ($f =~ /^\Q$d->{'home'}\E\/fcgi-bin\/php\S+\.fcgi/) {
                return 'fcgid';
                }
            }
        }

    # Look for an action that runs PHP via the CGI wrapper
    foreach my $a (@actions) {
        if ($a =~ /^application\/x-httpd-php[0-9\.]+\s+\/cgi-bin\/php\S+\.cgi/) {
            return 'cgi';
            }
        }

    # Look for a mapping from PHP scripts to plain text for 'none' mode
    if ($dir) {
        local @types = &apache::find_directive(
                "AddType", $dir->{'members'});
        foreach my $t (@types) {
            if ($t =~ /text\/plain\s+\.php/) {
                return 'none';
                }
            }
        }
    }

# Fall back to mod_php, if enabled
if (&get_apache_mod_php_version()) {
    return 'mod_php';
    }
return 'none';
}

# save_domain_php_mode(&domain, mode, [port], [new-domain])
# Changes the method a virtual web server uses to run PHP. Returns undef on
# success or an error message on failure.
sub save_domain_php_mode
{
local ($d, $mode, $port, $newdom) = @_;
local $p = &domain_has_website($d);
$p || return "Virtual server does not have a website";
local $tmpl = &get_template($d->{'template'});
local $oldmode = &get_domain_php_mode($d);
local @vers = sort { &compare_version_numbers($a->[0], $b->[0]) }
           &list_available_php_versions($d, $mode);

# Work out the default PHP version for FPM
if ($mode eq "fpm") {
    local @fpms = grep { !$_->{'err'} } &list_php_fpm_configs();
    @fpms || return "No FPM versions found!";
    my $curr = &get_php_fpm_config($d->{'php_fpm_version'});
    if (!$curr) {
        # Current version isn't actually valid! Fall back to default
        delete($d->{'php_fpm_version'});
        }
    if (!$d->{'php_fpm_version'}) {
        # Work out the default FPM version from the template
        if (@vers) {
            # Use version from template, or the max version
            my $fpm;
            if ($tmpl->{'web_phpver'}) {
                ($fpm) = grep { $_->[0] eq
                    $tmpl->{'web_phpver'} } @vers;
                }
            $fpm ||= $vers[$#vers];
            $d->{'php_fpm_version'} = $fpm->[0];
            }
        else {
            # This should never happen?
            my $defconf = $tmpl->{'web_phpver'} ?
                &get_php_fpm_config($tmpl->{'web_phpver'}) : undef;
            $defconf ||= $fpms[0];
            $d->{'php_fpm_version'} = $defconf->{'shortversion'};
            }
        }
    }

if ($mode =~ /mod_php|none/ && $oldmode !~ /mod_php|none/) {
    # Save the PHP version for later recovery
    local $oldver = &get_domain_php_version($d, $oldmode);
    $d->{'last_php_version'} = $oldver;
    }

my $oldplog;
if ($mode ne $oldmode) {
    # Save the PHP error log path
    $oldplog = &get_domain_php_error_log($d);
    }

# Work out source php.ini files
local (%srcini, %subs_ini);
$mode eq "none" || @vers || return "No PHP versions found for mode $mode";
foreach my $ver (@vers) {
    $subs_ini{$ver->[0]} = 0;
    local $srcini = $tmpl->{'web_php_ini_'.$ver->[0]};
    if (!$srcini || $srcini eq "none" || !-r $srcini) {
        $srcini = &get_global_php_ini($ver->[0], $mode);
        }
    else {
        $subs_ini{$ver->[0]} = 1;
        }
    $srcini{$ver->[0]} = $srcini;
    }
local @srcinis = &unique(values %srcini);

# Copy php.ini file into etc directory, for later per-site modification
local $etc = "$d->{'home'}/etc";
if (!-d $etc) {
    &make_dir_as_domain_user($d, $etc, 0755);
    }
foreach my $ver (@vers) {
    # Create separate .ini file for each PHP version, if missing
    local $subs_ini = $subs_ini{$ver->[0]};
    local $srcini = $srcini{$ver->[0]};
    local $inidir = "$etc/php$ver->[0]";
    if ($srcini && !-r "$inidir/php.ini") {
        # Copy file, set permissions, fix session.save_path, and
        # clear out extension_dir (because it can differ between
        # PHP versions)
        if (!-d $inidir) {
            &make_dir_as_domain_user($d, $inidir, 0755);
            }
        if (-r "$etc/php.ini" && !-l "$etc/php.ini") {
            # We are converting from the old style of a single
            # php.ini file to the new multi-version one .. just
            # copy the existing file for all versions, which is
            # assumed to be working
            &copy_source_dest_as_domain_user(
                $d, "$etc/php.ini", "$inidir/php.ini");
            }
        elsif ($subs_ini) {
            # Perform substitions on config file
            local $inidata = &read_file_contents($srcini);
            $inidata || return "Failed to read $srcini, ".
                       "or file is empty";
            $inidata = &substitute_virtualmin_template($inidata,$d);
            &open_tempfile_as_domain_user(
                $d, INIDATA, ">$inidir/php.ini");
            &print_tempfile(INIDATA, $inidata);
            &close_tempfile_as_domain_user($d, INIDATA);
            }
        else {
            # Just copy verbatim
            local ($ok, $err) = &copy_source_dest_as_domain_user(
                $d, $srcini, "$inidir/php.ini");
            $ok || return "Failed to copy $srcini to ".
                      "$inidir/php.ini : $err";
            }

        # Clear any caching on file
        &unflush_file_lines("$inidir/php.ini");
        undef($phpini::get_config_cache{"$inidir/php.ini"});

        local ($uid, $gid) = (0, 0);
        if (!$tmpl->{'web_php_noedit'}) {
            ($uid, $gid) = ($d->{'uid'}, $d->{'ugid'});
            }
        if (&foreign_check("phpini") && -r "$inidir/php.ini") {
            # Fix up session save path, extension_dir and
            # gc_probability / gc_divisor
            &foreign_require("phpini");
            local $pconf = &phpini::get_config("$inidir/php.ini");
            local $tmp = &create_server_tmp($d);
            &phpini::save_directive($pconf, "session.save_path",
                        $tmp);
            &phpini::save_directive($pconf, "upload_tmp_dir", $tmp);
            if (scalar(@srcinis) == 1 && scalar(@vers) > 1) {
                # Only if the same source is used for multiple
                # PHP versions.
                &phpini::save_directive($pconf, "extension_dir",
                            undef);
                }

            # On some systems, these are not set and so sessions are
            # never cleaned up.
            local $prob = &phpini::find_value(
                "session.gc_probability", $pconf);
            local $div = &phpini::find_value(
                "session.gc_divisor", $pconf);
            &phpini::save_directive($pconf,
                "session.gc_probability", 1) if (!$prob);
            &phpini::save_directive($pconf,
                "session.gc_divisor", 100) if (!$div);

            # Set timezone to match system
            local $tz;
            if (&foreign_check("time")) {
                &foreign_require("time");
                if (&time::has_timezone()) {
                    $tz = &time::get_current_timezone();
                    }
                }
            if ($tz) {
                &phpini::save_directive($pconf,
                    "date.timezone", $tz);
                }

            &flush_file_lines("$inidir/php.ini");
            }
        &set_ownership_permissions($uid, $gid, 0755, "$inidir/php.ini");
        }
    }

# Call plugin-specific function to perform webserver setup
if ($p ne 'web') {
    my $err = &plugin_call($p, "feature_save_web_php_mode",
                   $d, $mode, $port, $newdom);
    $d->{'php_mode'} = $mode if (!$err);
    return $err;
    }
&require_apache();

# Create wrapper scripts
if ($mode ne "mod_php" && $mode ne "fpm" && $mode ne "none") {
    &create_php_wrappers($d, $mode);
    }

# Setup PHP-FPM pool
if ($mode eq "fpm") {
    &create_php_fpm_pool($d);
    }
else {
    &delete_php_fpm_pool($d);
    }

# Add the appropriate directives to the Apache config
local $conf = &apache::get_config();
local @ports = ( $d->{'web_port'},
         $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
@ports = ( $port ) if ($port);    # Overridden to just do SSL or non-SSL
local $fdest = "$d->{'home'}/fcgi-bin";
local $pfound = 0;
foreach my $p (@ports) {
    local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p);
    next if (!$vconf);
    $pfound++;

    # Find <directory> sections containing PHP directives.
    # If none exist, add them in either the directory for
    # public_html, or the <virtualhost> if it already has them
    local @phpconfs;
    local @dirstrs = &apache::find_directive_struct("Directory",
                            $vconf);
    foreach my $dirstr (@dirstrs) {
        local @wrappers = &apache::find_directive("FCGIWrapper",
                    $dirstr->{'members'});
        local @actions =
            grep { $_ =~ /^application\/x-httpd-php/ }
            &apache::find_directive("Action",
                        $dirstr->{'members'});
        if (@wrappers || @actions) {
            push(@phpconfs, $dirstr);
            }
        }
    if (!@phpconfs) {
        # No directory has them yet. Add to the <virtualhost> if it
        # already directives for cgi, the <directory> otherwise.
        # Unless we are using fcgid, in which case it must always be
        # added to the directory.
        local @pactions =
            grep { $_ =~ /^application\/x-httpd-php\d+/ }
            &apache::find_directive("Action", $vconf);
        local $pdir = &public_html_dir($d);
        local ($dirstr) = grep { $_->{'words'}->[0] eq $pdir ||
                     $_->{'words'}->[0] eq $pdir."/" }
            &apache::find_directive_struct("Directory", $vconf);
        if ($mode eq "fcgid") {
            $dirstr || return "No &lt;Directory&gt; section ".
                      "found for mod_fcgid directives";
            push(@phpconfs, $dirstr);
            }
        elsif ($dirstr && !@pactions) {
            push(@phpconfs, $dirstr);
            }
        else {
            push(@phpconfs, $virt);
            }
        }

    # Work out which PHP version each directory uses currently
    local %pdirs;
    if (!$newdom) {
        %pdirs = map { $_->{'dir'}, $_->{'version'} }
                 &list_domain_php_directories($d);
        }

    # Update all of the directories
    local @avail = map { $_->[0] }
               &list_available_php_versions($d, $mode);
    local %allvers = map { $_, 1 } @all_possible_php_versions;
    foreach my $phpstr (@phpconfs) {
        # Remove all Action and AddType directives for suexec PHP
        local $phpconf = $phpstr->{'members'};
        local @actions = &apache::find_directive("Action", $phpconf);
        @actions = grep { !/^application\/x-httpd-php\d+/ }
                @actions;
        local @types = &apache::find_directive("AddType", $phpconf);
        @types = grep { !/^application\/x-httpd-php\d+/ &&
                !/\.php[0-9\.]*$/ } @types;

        # Remove all AddHandler and FCGIWrapper directives for fcgid
        local @handlers = &apache::find_directive("AddHandler",
                              $phpconf);
        @handlers = grep { !(/^fcgid-script\s+\.php(.*)$/ &&
                     ($1 eq '' || $allvers{$1})) } @handlers;
        local @wrappers = &apache::find_directive("FCGIWrapper",
                              $phpconf);
        @wrappers = grep {
            !(/^\Q$fdest\E\/php[0-9\.]+\.fcgi\s+\.php(.*)$/ &&
                ($1 eq '' || $allvers{$1})) } @wrappers;

        # Add needed Apache directives. Don't add the AddHandler,
        # Alias and Directory if already there.
        local $ver = $pdirs{$phpstr->{'words'}->[0]} ||
                 $tmpl->{'web_phpver'} ||
                 $avail[$#avail];
        $ver = $avail[$#avail] if (&indexof($ver, @avail) < 0);
        if ($mode eq "cgi") {
            foreach my $v (@avail) {
                push(@actions, "application/x-httpd-php$v ".
                           "/cgi-bin/php$v.cgi");
                }
            }
        elsif ($mode eq "fcgid") {
            push(@handlers, "fcgid-script .php");
            foreach my $v (@avail) {
                push(@handlers, "fcgid-script .php$v");
                }
            push(@wrappers, "$fdest/php$ver.fcgi .php");
            foreach my $v (@avail) {
                push(@wrappers, "$fdest/php$v.fcgi .php$v");
                }
            }
        elsif ($mode eq "none") {
            foreach my $v (@avail) {
                push(@types, "text/plain .php$v");
                }
            push(@types, "text/plain .php");
            }
        if ($mode eq "cgi" || $mode eq "mod_php") {
            foreach my $v (@avail) {
                push(@types,"application/x-httpd-php$v .php$v");
                }
            }
        if ($mode eq "cgi") {
            push(@types, "application/x-httpd-php$ver .php");
            }
        elsif ($mode eq "mod_php" || $mode eq "fcgid") {
            push(@types, "application/x-httpd-php .php");
            }
        @types = &unique(@types);
        &apache::save_directive("Action", \@actions, $phpconf, $conf);
        &apache::save_directive("AddType", \@types, $phpconf, $conf);
        &apache::save_directive("AddHandler", \@handlers,
                    $phpconf, $conf);
        &apache::save_directive("FCGIWrapper", \@wrappers,
                    $phpconf, $conf);

        # For fcgid mode, the directory needs to have Options ExecCGI
        local ($opts) = &apache::find_directive("Options", $phpconf);
        if ($opts && $mode eq "fcgid" && $opts !~ /ExecCGI/) {
            $opts .= " +ExecCGI";
            &apache::save_directive("Options", [ $opts ],
                        $phpconf, $conf);
            }
        }

    # For FPM mode, we need a proxy directive at the top level
    local $fsock = &get_php_fpm_socket_file($d, 1);
    local $fport = $d->{'php_fpm_port'};
    local $fmode = $fport ? 'port' :
               -r $fsock ? 'socket' :
               $tmpl->{'php_sock'} ? 'socket' : 'port';
    local @ppm = &apache::find_directive("ProxyPassMatch", $vconf);
    local @oldppm = grep { /unix:([^\|]+)/ || /fcgi:\/\/(localhost|127\.0\.0\.1):\d+/ } @ppm;
    if ($fsock) {
        @ppm = grep { !/unix:([^\|]+)/ } @ppm;
        }
    if ($fport) {
        @ppm = grep { !/fcgi:\/\/(localhost|127\.0\.0\.1):\d+/ } @ppm;
        }
    local $files;
    foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) {
        $files = $f if ($f->{'words'}->[0] eq '\.php$');
        }
    if ($mode eq "fpm" && ($apache::httpd_modules{'core'} < 2.4 || @oldppm)) {
        # Use a proxy directive for older Apache or if this is what's
        # already in use
        local $phd = $phpconfs[0]->{'words'}->[0];
        if ($fmode eq 'socket') {
            # Use socket file
            push(@ppm, "^/(.*\.php(/.*)?)\$ unix:${fsock}|fcgi://127.0.0.1${phd}/\$1");
            }
        else {
            # Allocate and use a port number
            $fport = &get_php_fpm_socket_port($d);
            push(@ppm, "^/(.*\.php(/.*)?)\$ fcgi://127.0.0.1:${fport}${phd}/\$1");
            }
        }
    elsif ($mode eq "fpm" && $apache::httpd_modules{'core'} >= 2.4) {
        # Can use a FilesMatch block with SetHandler inside instead
        my $wanth;
        if ($fmode eq 'socket') {
            # Use a socket file
            $wanth = 'proxy:unix:'.$fsock."|fcgi://127.0.0.1";
            }
        else {
            # Allocate, save and use a TCP port
            my $fport = &get_php_fpm_socket_port($d);
            $wanth = 'proxy:fcgi://127.0.0.1:'.$fport;
            }
        if (!$files) {
            # Add a new FilesMatch block with the socket
            $files = { 'name' => 'FilesMatch',
                       'type' => 1,
                       'value' => '\.php$',
                       'words' => ['\.php$'],
                       'members' => [
                         { 'name' => 'SetHandler',
                           'value' => $wanth,
                         },
                       ],
                     };
            &apache::save_directive_struct(
                undef, $files, $vconf, $conf);
            }
        else {
            # Add the SetHandler directive to the FilesMatch block
            &apache::save_directive("SetHandler", [$wanth],
                        $files->{'members'}, $conf);
            }
        }
    else {
        # For non-FPM mode, remove the whole files block,
        # and forget about any ports or sockets
        if ($files) {
            &apache::save_directive_struct(
                $files, undef, $vconf, $conf);
            }
        @ppm = grep { !/unix:|fcgi:/ } @ppm;
        delete($d->{'php_fpm_port'});
        }
    &apache::save_directive("ProxyPassMatch", \@ppm, $vconf, $conf);

    # For non-mod_php mode, we need a RemoveHandler .php directive at
    # the <virtualhost> level to supress mod_php which may still be active
    local @remove = &apache::find_directive("RemoveHandler", $vconf);
    @remove = grep { !(/^\.php(.*)$/ && ($1 eq '' || $allvers{$1})) }
               @remove;
    if ($mode ne "mod_php") {
        push(@remove, ".php");
        foreach my $v (@avail) {
            push(@remove, ".php$v");
            }
        }
    @remove = &unique(@remove);
    &apache::save_directive("RemoveHandler", \@remove, $vconf, $conf);

    # For non-mod_php mode, use php_admin_value to turn off mod_php in
    # case it gets enabled in a .htaccess file
    if (&get_apache_mod_php_version()) {
        local @admin = &apache::find_directive("php_admin_value",
                               $vconf);
        @admin = grep { !/^engine\s+/ } @admin;
        if ($mode ne "mod_php") {
            push(@admin, "engine Off");
            }
        &apache::save_directive("php_admin_value", \@admin,
                    $vconf, $conf);
        }

    # For fcgid mode, set IPCCommTimeout to either the configured value
    # or the PHP max execution time + 1, so that scripts run via fastCGI
    # aren't disconnected
    if ($mode eq "fcgid") {
        local $maxex;
        if ($config{'fcgid_max'} eq "*") {
            # Don't set
            $maxex = undef;
            }
        elsif ($config{'fcgid_max'} eq "") {
            # From PHP config
            local $inifile = &get_domain_php_ini($d, $ver);
            if (-r $inifile) {
                &foreign_require("phpini");
                local $iniconf = &phpini::get_config($inifile);
                $maxex = &phpini::find_value(
                    "max_execution_time", $iniconf);
                }
            }
        else {
            # Fixed number
            $maxex = int($config{'fcgid_max'})-1;
            }
        if (defined($maxex)) {
            &set_fcgid_max_execution_time($d, $maxex, $mode, $p);
            }
        }
    else {
        # For other modes, don't set
        &apache::save_directive("IPCCommTimeout", [ ],
                    $vconf, $conf);
        }

    # For fcgid mode, set max request size to 1GB, which is the default
    # in older versions of mod_fcgid but is smaller in versions 2.3.6 and
    # later.
    local $setmax;
    if ($mode eq "fcgid") {
        if ($gconfig{'os_type'} eq 'debian-linux' &&
                    $gconfig{'os_version'} >= 6) {
            # Debian 6 and Ubuntu 10 definately use mod_fcgid 2.3.6+
            $setmax = 1;
            }
        elsif ($gconfig{'os_type'} eq 'redhat-linux' &&
                       $gconfig{'os_version'} >= 14 &&
               &foreign_check("software")) {
            # CentOS 6 and Fedora 14+ may have it..
            &foreign_require("software");
            local @pinfo = &software::package_info("mod_fcgid");
            if (&compare_versions($pinfo[4], "2.3.6") >= 0) {
                $setmax = 1;
                }
            }
        }
    &apache::save_directive("FcgidMaxRequestLen",
                $setmax ? [ 1024*1024*1024 ] : [ ],
                $vconf, $conf);

    &flush_file_lines();
    }

# Update PHP mode cache
$d->{'php_mode'} = $mode;

local @vlist = map { $_->[0] } &list_available_php_versions($d);
if ($mode !~ /mod_php|none/ && $oldmode =~ /mod_php|none/ &&
    $d->{'last_php_version'} &&
    &indexof($d->{'last_php_version'}, @vlist) >= 0) {
    # Restore PHP version from before mod_php or none modes
    my $err = &save_domain_php_directory($d, &public_html_dir($d),
                   $d->{'last_php_version'}, 1);
    return $err if ($err);
    }

if (defined($oldplog)) {
    # Restore the old PHP error log
    &save_domain_php_error_log($d, $oldplog);
    }

# Link ~/etc/php.ini to the per-version ini file
&create_php_ini_link($d, $mode);
&create_php_bin_links($d, $mode);

&register_post_action(\&restart_apache);
$pfound || return "Apache virtual host was not found";

return undef;
}

# set_fcgid_max_execution_time(&domain, value, [mode], [port])
# Set the IPCCommTimeout directive to follow the given PHP max execution time
sub set_fcgid_max_execution_time
{
local ($d, $max, $mode, $port) = @_;
$mode ||= &get_domain_php_mode($d);
return 0 if ($mode ne "fcgid");
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_set_fcgid_max_execution_time",
                $d, $max, $mode, $port);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
local @ports = ( $d->{'web_port'},
         $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
@ports = ( $port ) if ($port);    # Overridden to just do SSL or non-SSL
local $conf = &apache::get_config();
local $pfound = 0;
local $changed = 0;
foreach my $p (@ports) {
        local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p);
        next if (!$vconf);
    $pfound++;
    local @newdir = &apache::find_directive("FcgidIOTimeout", $vconf);
    local $dirname = @newdir ? "FcgidIOTimeout" : "IPCCommTimeout";
    local $oldvalue = &apache::find_directive($dirname, $vconf);
    local $want = $max ? $max + 1 : $max_php_fcgid_timeout;
    if ($oldvalue ne $want) {
        &apache::save_directive($dirname, [ $want ],
                    $vconf, $conf);
        &flush_file_lines($virt->{'file'});
        $changed++;
        }
    }
$pfound || &error("Apache virtual host was not found");
if ($changed) {
    &register_post_action(\&restart_apache);
    }
}

# get_fcgid_max_execution_time(&domain)
# Returns the current max FCGId execution time, or undef for unlimited
sub get_fcgid_max_execution_time
{
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_get_fcgid_max_execution_time", $d);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
local $v = &apache::find_directive("IPCCommTimeout", $vconf);
$v ||= &apache::find_directive("FcgidIOTimeout", $vconf);
return $v == 9999 ? undef : $v ? $v-1 : 40;
}

# set_php_max_execution_time(&domain, max)
# Updates the max execution time in all php.ini files, and possibly in the FPM
# config file
sub set_php_max_execution_time
{
local ($d, $max) = @_;
&foreign_require("phpini");
foreach my $ini (&list_domain_php_inis($d)) {
    local $f = $ini->[1];
    local $conf = &phpini::get_config($f);
    &phpini::save_directive($conf, "max_execution_time", $max);
    &flush_file_lines($f);
    }
my $mode = &get_domain_php_mode($d);
if ($mode eq "fpm") {
    &save_php_fpm_ini_value($d, "max_execution_time",
                $max == 0 ? undef : $max);
    }
}

# get_php_max_execution_time(&domain)
# Returns the max execution time from a php.ini file
sub get_php_max_execution_time
{
local ($d, $max) = @_;
&foreign_require("phpini");
foreach my $ini (&list_domain_php_inis($d)) {
    local $f = $ini->[1];
    local $conf = &phpini::get_config($f);
    local $max = &phpini::find_value("max_execution_time", $conf);
    return $max if ($max ne '');
    }
return undef;
}

# create_php_wrappers(&domain, phpmode)
# Creates all phpN.cgi wrappers for some domain
sub create_php_wrappers
{
local ($d, $mode) = @_;
local $dest = $mode eq "fcgid" ? "$d->{'home'}/fcgi-bin" : &cgi_bin_dir($_[0]);
local $tmpl = &get_template($d->{'template'});

if (!-d $dest) {
    # Need to create fcgi-bin
    &make_dir_as_domain_user($d, $dest, 0755);
    }

local $suffix = $mode eq "fcgid" ? "fcgi" : "cgi";
local $dirvar = $mode eq "fcgid" ? "PWD" : "DOCUMENT_ROOT";

# Make wrappers mutable
&set_php_wrappers_writable($d, 1);

# For each version of PHP, create a wrapper
local $pub = &public_html_dir($d);
local $children = &get_domain_php_children($d);
foreach my $v (&list_available_php_versions($d, $mode)) {
    next if (!$v->[1]);    # No executable available?!
    &open_tempfile_as_domain_user($d, PHP, ">$dest/php$v->[0].$suffix");
    local $t = "php".$v->[0].$suffix;
    if ($tmpl->{$t} && $tmpl->{$t} ne 'none') {
        # Use custom script from template
        local $s = &substitute_domain_template($tmpl->{$t}, $d);
        $s =~ s/\t/\n/g;
        $s .= "\n" if ($s !~ /\n$/);
        &print_tempfile(PHP, $s);
        }
    else {
        # Automatically generate
        local $shell = -r "/bin/bash" ? "/bin/bash" : "/bin/sh";
        local $common = "#!$shell\n".
                "PHPRC=\$$dirvar/../etc/php$v->[0]\n".
                "export PHPRC\n".
                "umask 022\n";
        if ($mode eq "fcgid") {
            local $defchildren = $tmpl->{'web_phpchildren'};
            $defchildren = undef if ($defchildren eq "none");
            if ($defchildren) {
                $common .= "PHP_FCGI_CHILDREN=$defchildren\n";
                $common .= "export PHP_FCGI_CHILDREN\n";
                }
            $common .= "PHP_FCGI_MAX_REQUESTS=99999\n";
            $common .= "export PHP_FCGI_MAX_REQUESTS\n";
            }
        elsif ($mode eq "cgi") {
            $common .= "if [ \"\$REDIRECT_URL\" != \"\" ]; then\n";
            $common .= "  SCRIPT_NAME=\$REDIRECT_URL\n";
            $common .= "  export SCRIPT_NAME\n";
            $common .= "fi\n";
            }
        &print_tempfile(PHP, $common);
        if ($v->[1] =~ /-cgi$/) {
            # php-cgi requires the SCRIPT_FILENAME variable
            &print_tempfile(PHP,
                    "SCRIPT_FILENAME=\$PATH_TRANSLATED\n");
            &print_tempfile(PHP,
                    "export SCRIPT_FILENAME\n");
            }
        &print_tempfile(PHP, "exec $v->[1]\n");
        }
    &close_tempfile_as_domain_user($d, PHP);
    &set_permissions_as_domain_user($d, 0755, "$dest/php$v->[0].$suffix");

    # Put back the old number of child processes
    if ($children >= 0) {
        &save_domain_php_children($d, $children, 1);
        }

    # Also copy the .fcgi wrapper to public_html, which is needed due to
    # broken-ness on some Debian versions!
    if ($mode eq "fcgid" && $gconfig{'os_type'} eq 'debian-linux' &&
            $gconfig{'os_version'} < 5) {
        &copy_source_dest_as_domain_user(
            $d, "$dest/php$v->[0].$suffix",
            "$pub/php$v->[0].$suffix");
        &set_permissions_as_domain_user(
            $d, 0755, "$pub/php$v->[0].$suffix");
        }
    }

# Re-apply resource limits
if (defined(&supports_resource_limits) && &supports_resource_limits()) {
    local $pd = $d->{'parent'} ? &get_domain($d->{'parent'}) : $d;
    &set_php_wrapper_ulimits($d, &get_domain_resource_limits($pd));
    }

# Make wrappers immutable, to prevent deletion by users (which can crash Apache)
&set_php_wrappers_writable($d, 0);
}

# set_php_wrappers_writable(&domain, flag, [subdomains-too])
# If possible, make PHP wrapper scripts mutable or immutable
sub set_php_wrappers_writable
{
local ($d, $writable, $subs) = @_;
if (&has_command("chattr")) {
    foreach my $dir ("$d->{'home'}/fcgi-bin", &cgi_bin_dir($d)) {
        foreach my $f (glob("$dir/php?.*cgi")) {
            my @st = stat($f);
            if (-r $f && !-l $f && $st[4] == $d->{'uid'}) {
                &system_logged("chattr ".
                   ($writable ? "-i" : "+i")." ".quotemeta($f).
                   " >/dev/null 2>&1");
                }
            }
        }
    if ($subs) {
        # Also do sub-domains, as their CGI directories are under
        # parent's domain.
        foreach my $sd (&get_domain_by("subdom", $d->{'id'})) {
            &set_php_wrappers_writable($sd, $writable);
            }
        }
    }
}

# set_php_wrapper_ulimits(&domain, &resource-limits)
# Add, update or remove ulimit lines to set RAM and process restrictions
sub set_php_wrapper_ulimits
{
local ($d, $rv) = @_;
foreach my $dir ("$d->{'home'}/fcgi-bin", &cgi_bin_dir($d)) {
    foreach my $f (glob("$dir/php?.*cgi")) {
        local $lref = &read_file_lines_as_domain_user($d, $f);
        foreach my $u ([ 'v', int($rv->{'mem'}/1024) ],
                   [ 'u', $rv->{'procs'} ],
                   [ 't', $rv->{'time'}*60 ]) {
            if ($u->[0] eq 't' &&
                $dir eq "$d->{'home'}/fcgi-bin") {
                # CPU time limit makes no sense for fcgi, as it
                # breaks the long-running php-cgi processes
                next;
                }

            # Find current line
            local $lnum;
            for(my $i=0; $i<@$lref; $i++) {
                if ($lref->[$i] =~ /^ulimit\s+\-(\S)\s+(\d+)/ &&
                    $1 eq $u->[0]) {
                    $lnum = $i;
                    last;
                    }
                }
            if ($lnum && $u->[1]) {
                # Set value
                $lref->[$lnum] = "ulimit -$u->[0] $u->[1]";
                }
            elsif ($lnum && !$u->[1]) {
                # Remove limit
                splice(@$lref, $lnum, 1);
                }
            elsif (!$lnum && $u->[1]) {
                # Add at top of file
                splice(@$lref, 1, 0, "ulimit -$u->[0] $u->[1]");
                }
            }
        # If using process limits, we can't exec PHP as there will
        # be no chance for the limit to be applied :(
        local $ll = scalar(@$lref) - 1;
        if ($lref->[$ll] =~ /php/) {
            if ($rv->{'procs'} && $lref->[$ll] =~ /^exec\s+(.*)/) {
                # Remove exec
                $lref->[$ll] = $1;
                }
            elsif (!$rv->{'procs'} && $lref->[$ll] !~ /^exec\s+/) {
                # Add exec
                $lref->[$ll] = "exec ".$lref->[$ll];
                }
            }
        &flush_file_lines_as_domain_user($d, $f);
        }
    }
}

# set_php_fpm_ulimits(&domain, &resource-limits)
# Update the FPM config with resource limits
sub set_php_fpm_ulimits
{
my ($d, $res) = @_;
my $conf = &get_php_fpm_config($d);
return 0 if (!$conf);
if ($res->{'procs'}) {
    my $php_max_children =
        $res->{'procs'} > $max_php_fcgid_children ? 
            $max_php_fcgid_children : $res->{'procs'};
    &save_php_fpm_config_value($d, "pm.max_children", $php_max_children);
    &save_php_fpm_config_value($d, "pm.start_servers", &get_php_start_servers($php_max_children));
    &save_php_fpm_config_value($d, "pm.max_spare_servers", &get_php_max_spare_servers($php_max_children));
    }
else {
    my $tmpl = &get_template($d->{'template'});
    my $php_max_children = $tmpl->{'web_phpchildren'};
    $php_max_children = get_php_max_childred_allowed()
        if ($php_max_children eq "none" || !$php_max_children);
    &save_php_fpm_config_value($d, "pm.max_children", $php_max_children);
    &save_php_fpm_config_value($d, "pm.start_servers", &get_php_start_servers($php_max_children));
    &save_php_fpm_config_value($d, "pm.max_spare_servers", &get_php_max_spare_servers($php_max_children));
    }
&register_post_action(\&restart_php_fpm_server, $conf);
}

# supported_php_modes([&domain])
# Returns a list of PHP execution modes possible for a domain
sub supported_php_modes
{
my ($d) = @_;
my $p = &domain_has_website($d);
if ($p ne 'web') {
    return &plugin_call($p, "feature_web_supported_php_modes", $d);
    }
my @rv = ( "none" );
foreach my $ver (&list_available_php_versions()) {
    push(@rv, @{$ver->[2]});
    }
return &unique(@rv);
}

# php_mode_numbers_map()
# Returns a map from mode names (like 'cgi') to template numbers
sub php_mode_numbers_map
{
return { 'mod_php' => 0,
     'cgi' => 1,
     'fcgid' => 2,
     'fpm' => 3,
     'none' => 4, };
}

# list_available_php_versions([&domain], [forcemode])
# Returns a list of PHP versions and their executables installed on the system,
# for use by a domain. If forcemode is not set, the mode is taken from the
# domain.
sub list_available_php_versions
{
local ($d, $mode) = @_;
if ($d) {
    $mode ||= &get_domain_php_mode($d);
    }
return () if ($mode eq "none");
&require_apache();

# In FPM mode, only the versions for which packages are installed can be used
my @rv;
if ($mode eq "fpm" || !$mode) {
    foreach my $conf (grep { !$_->{'err'} } &list_php_fpm_configs()) {
        my $ver = $conf->{'shortversion'};
        my $cmd = $conf->{'cmd'} || &php_command_for_version($ver, 0);
        if (!$cmd && $ver =~ /^5\./) {
            # Try just PHP version 5
            $ver = 5;
            $cmd = &php_command_for_version($ver, 0);
            }
        $cmd ||= &has_command("php");
        if ($cmd) {
            push(@rv, [ $ver, $cmd, ["fpm"] ]);
            }
        }
    }

# Add the mod_php version if installed
if ($mode eq "mod_php" || !$mode) {
    my $v = &get_apache_mod_php_version();
    if ($v) {
        my $cmd = &has_command("php$v") ||
              &has_command("php");
        push(@rv, [ $v, $cmd, ["mod_php"] ]);
        }
    }

# For CGI and fCGId modes, check which PHP commands exist
foreach my $v (@all_possible_php_versions) {
    my $phpn = &php_command_for_version($v, 1);
    $vercmds{$v} = $phpn if ($phpn);
    }

# Add extra configured PHP commands, and determine their versions
foreach my $path (split(/\t+/, $config{'php_paths'})) {
    next if (!-x $path);
    &clean_environment();
    local $out = &backquote_command("$path -v 2>&1 </dev/null");
    &reset_environment();
    if ($out =~ /PHP\s+(\d+.\d+)/ && !$vercmds{$1}) {
        $vercmds{$1} = $path;
        }
    }

local $php = &has_command("php-cgi");
if ($php && scalar(keys %vercmds) != scalar(@all_possible_php_versions)) {
    # What version is the php command? If it is a version we don't have
    # a command for yet, use it.
    if (!$php_command_version_cache) {
        &clean_environment();
        local $out = &backquote_command("$php -v 2>&1 </dev/null");
        &reset_environment();
        if ($out =~ /PHP\s+(\d+\.\d+)/) {
            my $v = $1;
            $v = int($v) if (int($v) <= 5);
            $php_command_version_cache = $v;
            }
        }
    if ($php_command_version_cache) {
        $vercmds{$php_command_version_cache} ||= $php;
        }
    }

# Add versions found to the final list
foreach my $v (sort { $a <=> $b } (keys %vercmds)) {
    my ($already) = grep { $_->[0] eq $v } @rv;
    if ($already) {
        $already->[2] = [ &unique(@{$already->[2]}, "fcgid", "cgi") ];
        }
    else {
        push(@rv, [ $v, $vercmds{$v}, ["fcgid", "cgi"] ]);
        }
    }

# If a mode was given, filter down to it
if ($mode) {
    @rv = grep { &indexof($mode, @{$_->[2]}) >= 0 } @rv;
    }

@rv = sort { $a->[0] <=> $b->[0] } @rv;
return @rv;
}

# php_command_for_version(ver, [cgi-mode])
# Given a version like 5.4 or 5, returns the full path to the PHP executable
sub php_command_for_version
{
my ($v, $cgimode) = @_;
$cgimode ||= 0;
if (!$php_command_for_version_cache{$v,$cgimode}) {
    my @opts;
    if ($gconfig{'os_type'} eq 'solaris') {
        # On Solaris with CSW packages, php-cgi is in a directory named
        # after the PHP version
        push(@opts, "/opt/csw/php$v/bin/php-cgi");
        }
    push(@opts, "php$v-cgi", "php-cgi$v", "php$v");
    $v =~ s/^(\d+\.\d+)\.\d+$/$1/;
    my $nodotv = $v;
    $nodotv =~ s/\.//;
    if ($nodotv ne $v) {
        # For a version like 5.4, check for binaries like php54 and
        # /opt/rh/php54/root/usr/bin/php
        push(@opts, "php$nodotv-cgi",
                "php-cgi$nodotv",
                "/opt/rh/php$nodotv/root/usr/bin/php-cgi",
                "/opt/rh/rh-php$nodotv/root/usr/bin/php-cgi",
                "/opt/atomic/atomic-php$nodotv/root/usr/bin/php-cgi",
                "/opt/atomic/atomic-php$nodotv/root/usr/bin/php",
                "/opt/rh/php$nodotv/bin/php-cgi",
                "/opt/remi/php$nodotv/root/usr/bin/php-cgi",
                "php$nodotv",
                "/opt/rh/php$nodotv/root/usr/bin/php",
                "/opt/rh/rh-php$nodotv/root/usr/bin/php",
                "/opt/rh/php$nodotv/bin/php",
                glob("/opt/phpfarm/inst/bin/php-cgi-$v.*"));
        }
    if ($cgimode == 1) {
        # Only include -cgi commands
        @opts = grep { /-cgi/ } @opts;
        }
    elsif ($cgimode == 2) {
        # Skip -cgi commands
        @opts = grep { !/-cgi/ } @opts;
        }
    my $phpn;
    foreach my $o (@opts) {
        $phpn = &has_command($o);
        last if ($phpn);
        }
    $php_command_for_version_cache{$v,$cgimode} = $phpn;
    }
return $php_command_for_version_cache{$v,$cgimode};
}

# get_php_version(number|command, [&domain])
# Given a PHP based version like 4 or 5, or a path to PHP, return the real
# version number, like 5.2.7
sub get_php_version
{
local ($cmd, $d) = @_;
if (exists($get_php_version_cache{$cmd})) {
    return $get_php_version_cache{$cmd};
    }
if ($cmd !~ /^\//) {
    # A number was given .. find the matching command
    my $shortcmd = $cmd;
    $shortcmd =~ s/^(\d+\.\d+)\..*/$1/;  # Reduce version to 5.x
    local ($phpn) = grep { $_->[0] == $cmd ||
                   $_->[0] == $shortcmd }
                 &list_available_php_versions($d);
    if (!$phpn && $cmd =~ /^5\./) {
        # Also try just version '5'
        ($phpn) = grep { $_->[0] == 5 }
                   &list_available_php_versions($d);
        }
    if (!$phpn && $cmd == 5) {
        # If the system ONLY has PHP 7, consider it compatible with
        # PHP major version 5
        ($phpn) = grep { $_->[0] >= $cmd }
                             &list_available_php_versions($d);
        }
    if (!$phpn) {
        $get_php_version_cache{$cmd} = undef;
        return undef;
        }
    $cmd = $phpn->[1] || &has_command("php$cmd") || &has_command("php");
    }
&clean_environment();
local $out = &backquote_command("$cmd -v 2>&1 </dev/null");
&reset_environment();
if ($out =~ /PHP\s+([0-9\.]+)/) {
    $get_php_version_cache{$cmd} = $1;
    return $1;
    }
$get_php_version_cache{$cmd} = undef;
return undef;
}

# can_domain_php_directories(&domain, [mode])
# Returns 1 if the PHP version can be set on specific directories
sub can_domain_php_directories
{
my ($d, $mode) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web' &&
    &plugin_defined($p, "feature_can_domain_php_directories")) {
    return &plugin_call($p, "feature_can_domain_php_directories",
                $d, $mode);
    }
$mode ||= &get_domain_php_mode($d);
return $mode eq "fpm" ? 0 : 1;
}

# list_domain_php_directories(&domain)
# Returns a list of directories for which different versions of PHP have
# been configured.
sub list_domain_php_directories
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_list_web_php_directories", $d);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
&require_apache();
local $conf = &apache::get_config();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
return ( ) if (!$virt);
local $mode = &get_domain_php_mode($d);
if ($mode eq "mod_php") {
    # All are run as version from Apache module
    local @avail = &list_available_php_versions($d, $mode);
    if (@avail) {
        return ( { 'dir' => &public_html_dir($d),
               'version' => $avail[0]->[0],
               'mode' => $mode } );
        }
    else {
        return ( );
        }
    }
elsif ($mode eq "fpm") {
    # Version is stored in the domain's config
    return ( { 'dir' => &public_html_dir($d),
           'version' => $d->{'php_fpm_version'},
           'mode' => $mode } );
    }
elsif ($mode eq "none") {
    # No PHP, so no directories
    return ( );
    }

# Find directories with either FCGIWrapper or AddType directives, and check
# which version they specify for .php files
local @dirs = &apache::find_directive_struct("Directory", $vconf);
local @rv;
foreach my $dir (@dirs) {
    local $n = $mode eq "cgi" ? "AddType" :
           $mode eq "fcgid" ? "FCGIWrapper" : undef;
    foreach my $v (&apache::find_directive($n, $dir->{'members'})) {
        local $w = &apache::wsplit($v);
        if (&indexof(".php", @$w) > 0) {
            # This is for .php files .. look at the php version
            if ($w->[0] =~ /php([0-9\.]+)\.(cgi|fcgi)/ ||
                $w->[0] =~ /x-httpd-php([0-9\.]+)/) {
                # Add version and dir to list
                push(@rv, { 'dir' => $dir->{'words'}->[0],
                        'version' => $1,
                        'mode' => $mode });
                last;
                }
            }
        }
    }
return @rv;
}

# save_domain_php_directory(&domain, dir, phpversion, [skip-ini-copy])
# Sets up a directory to run PHP scripts with a specific version of PHP.
# Should only be called on domains in cgi or fcgid mode! Returns undef on
# success, or an error message on failure (ie. because the virtualhost couldn't
# be found, or the PHP mode was wrong)
sub save_domain_php_directory
{
local ($d, $dir, $ver, $noini) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_save_web_php_directory",
                $d, $dir, $ver);
    }
elsif (!$p) {
    return "Virtual server does not have a website!";
    }
&require_apache();
local $mode = &get_domain_php_mode($d);
return "PHP versions cannot be set in mod_php mode" if ($mode eq "mod_php");
local $oldlog = &get_domain_php_error_log($d);

if ($mode eq "fpm") {
    # Remove the old version pool and create a new one if needed.
    # Since it will be on the same port, no Apache changes are needed.
    my $phd = &public_html_dir($d);
    $dir eq $phd || return "FPM version can only be changed for the top-level directory";
    if ($ver ne $d->{'php_fpm_version'}) {
        my $oldlisten = &get_php_fpm_config_value($d, "listen");
        my $confs = &list_php_fpm_config_values($d);
        my @phpvs = &copyable_fpm_configs($confs);
        &delete_php_fpm_pool($d);
        $d->{'php_fpm_version'} = $ver;
        &save_domain($d);
        &create_php_fpm_pool($d, $oldlisten);
        foreach my $pv (@phpvs) {
            &save_php_fpm_config_value($d, $pv->[0], $pv->[1]);
            }
        }
    }
else {
    # Config needs to be updated for each Apache virtualhost
    local $any = 0;
    local @ports = ( $d->{'web_port'},
             $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
    local %allvers = map { $_, 1 } @all_possible_php_versions;
    foreach my $p (@ports) {
        local $conf = &apache::get_config();
        local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $p);
        next if (!$virt);

        # Check for an existing <Directory> block
        local @dirs = &apache::find_directive_struct("Directory", $vconf);
        local ($dirstr) = grep { $_->{'words'}->[0] eq $dir } @dirs;
        if ($dirstr) {
            # Update the AddType or FCGIWrapper directives, so that
            # .php scripts use the specified version, and all other
            # .phpN use version N.
            if ($mode eq "cgi") {
                local @types = &apache::find_directive(
                    "AddType", $dirstr->{'members'});
                @types = grep { $_ !~ /^application\/x-httpd-php[57]/ }
                          @types;
                foreach my $v (&list_available_php_versions($d)) {
                    push(@types, "application/x-httpd-php$v->[0] ".
                             ".php$v->[0]");
                    }
                push(@types, "application/x-httpd-php$ver .php");
                &apache::save_directive("AddType", \@types,
                            $dirstr->{'members'}, $conf);
                &flush_file_lines($dirstr->{'file'});
                }
            elsif ($mode eq "fcgid") {
                local $dest = "$d->{'home'}/fcgi-bin";
                local @wrappers = &apache::find_directive(
                    "FCGIWrapper", $dirstr->{'members'});
                @wrappers = grep {
                    !(/^\Q$dest\E\/php\S+\.fcgi\s+\.php(\S*)$/ &&
                     ($1 eq '' || $allvers{$1})) } @wrappers;
                foreach my $v (&list_available_php_versions($d)) {
                    push(@wrappers,
                         "$dest/php$v->[0].fcgi .php$v->[0]");
                    }
                push(@wrappers, "$dest/php$ver.fcgi .php");
                @wrappers = &unique(@wrappers);
                &apache::save_directive("FCGIWrapper", \@wrappers,
                            $dirstr->{'members'}, $conf);
                &flush_file_lines($dirstr->{'file'});
                }
            }
        else {
            # Add the directory
            local @phplines;
            if ($mode eq "cgi") {
                # Directives for plain CGI
                foreach my $v (&list_available_php_versions($d)) {
                    push(@phplines,
                         "Action application/x-httpd-php$v->[0] ".
                         "/cgi-bin/php$v->[0].cgi");
                    push(@phplines,
                         "AddType application/x-httpd-php$v->[0] ".
                         ".php$v->[0]");
                    }
                push(@phplines,
                     "AddType application/x-httpd-php$ver .php");
                }
            elsif ($mode eq "fcgid") {
                # Directives for fcgid
                local $dest = "$d->{'home'}/fcgi-bin";
                push(@phplines, "AddHandler fcgid-script .php");
                push(@phplines, "FCGIWrapper $dest/php$ver.fcgi .php");
                foreach my $v (&list_available_php_versions($d)) {
                    push(@phplines,
                         "AddHandler fcgid-script .php$v->[0]");
                    push(@phplines,
                         "FCGIWrapper $dest/php$v->[0].fcgi ".
                         ".php$v->[0]");
                    }
                }
            my $olist = $apache::httpd_modules{'core'} >= 2.2 ?
                    " ".&get_allowed_options_list() : "";
            my $granteddir = "Require all granted";
            if ($apache::httpd_modules{'core'} < 2.4) {
                $granteddir = "Allow from all";
                }
            local @lines = (
                "    <Directory $dir>",
                "        Options +IncludesNOEXEC +SymLinksifOwnerMatch +ExecCGI",
                "        $granteddir",
                "        AllowOverride All".$olist,
                (map { "        ".$_ } @phplines),
                "    </Directory>"
                );
            local $lref = &read_file_lines($virt->{'file'});
            splice(@$lref, $virt->{'eline'}, 0, @lines);
            &flush_file_lines($virt->{'file'});
            undef(@apache::get_config_cache);
            }
        $any++;
        }
    return "No Apache virtualhosts found" if (!$any);
    }

# Make sure we have all the wrapper scripts
&create_php_wrappers($d, $mode);

# Re-create php.ini link
&create_php_ini_link($d, $mode);
&create_php_bin_links($d, $mode);

# Copy in php.ini file for version if missing
if (!$noini) {
    my @inifiles = &find_domain_php_ini_files($d);
    my ($iniver) = grep { $_->[0] eq $ver } @inifiles;
    if (!$iniver) {
        &save_domain_php_mode($d, $mode);
        }
    }

# Re-save PHP version
&save_domain_php_error_log($d, $oldlog);

&register_post_action(\&restart_apache);
return undef;
}

# delete_domain_php_directory(&domain, dir)
# Delete the <Directory> section for a custom PHP version in some directory
sub delete_domain_php_directory
{
local ($d, $dir) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_delete_web_php_directory", $d, $dir);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
&require_apache();
local $conf = &apache::get_config();
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
return 0 if (!$virt);
local $mode = &get_domain_php_mode($d);

local @dirs = &apache::find_directive_struct("Directory", $vconf);
local ($dirstr) = grep { $_->{'words'}->[0] eq $dir } @dirs;
if ($dirstr) {
    local $lref = &read_file_lines($dirstr->{'file'});
    splice(@$lref, $dirstr->{'line'},
           $dirstr->{'eline'}-$dirstr->{'line'}+1);
    &flush_file_lines($dirstr->{'file'});
    undef(@apache::get_config_cache);

    &register_post_action(\&restart_apache);
    return 1;
    }
return 0;
}

# list_domain_php_inis(&domain, [force-mode])
# Returns a list of php.ini files used by a domain, and their PHP versions and
# commands.
sub list_domain_php_inis
{
local ($d, $mode) = @_;
local @inis;
foreach my $v (&list_available_php_versions($d, $mode)) {
    local $ifile = "$d->{'home'}/etc/php$v->[0]/php.ini";
    if (-r $ifile) {
        push(@inis, [ $v->[0], $ifile, $v->[1] ]);
        }
    }
if (!@inis) {
    local $ifile = "$d->{'home'}/etc/php.ini";
    if (-r $ifile) {
        push(@inis, [ undef, $ifile, undef ]);
        }
    }
return @inis;
}

# find_domain_php_ini_files(&domain)
# Returns the same information as list_domain_php_inis, but looks at files under
# the home directory only
sub find_domain_php_ini_files
{
local ($d) = @_;
local @inis;
foreach my $f (glob("$d->{'home'}/etc/php*/php.ini")) {
    if ($f =~ /php([0-9\.]+)\/php.ini$/) {
        push(@inis, [ $1, $f ]);
        }
    }
return @inis;
}

# get_domain_php_ini(&domain, php-version, [dir-only])
# Returns the php.ini file path for this domain and a PHP version
sub get_domain_php_ini
{
local ($d, $phpver, $dir) = @_;
local @inis = &list_domain_php_inis($d);
local ($ini) = grep { $_->[0] == $phpver } @inis;
if (!$ini) {
    ($ini) = grep { !$_->[0]} @inis;
    }
if (!$ini && -r "$d->{'home'}/etc/php.ini") {
    # For domains with no matching version file
    $ini = [ undef, "$d->{'home'}/etc/php.ini" ];
    }
if (!$ini) {
    return undef;
    }
else {
    $ini->[1] =~ s/\/php.ini$//i if ($dir);
    return $ini->[1];
    }
}

# get_global_php_ini(phpver, mode)
# Returns the full path to the global PHP config file
sub get_global_php_ini
{
local ($ver, $mode) = @_;
local $nodotv = $ver;
$nodotv =~ s/\.//g;
local $shortv = $ver;
$shortv =~ s/^(\d+\.\d+)\..*$/$1/g;
foreach my $i ("/opt/rh/php$nodotv/root/etc/php.ini",
           "/opt/rh/php$nodotv/lib/php.ini",
           "/etc/opt/rh/rh-php$nodotv/php.ini",
           "/opt/remi/php$nodotv/root/etc/php.ini",
           "/etc/opt/remi/php$nodotv/php.ini",
           "/opt/atomic/atomic-php$nodotv/root/etc/php.ini",
           "/etc/php.ini",
           $mode eq "mod_php" ? ("/etc/php$ver/apache/php.ini",
                     "/etc/php$ver/apache2/php.ini",
                     "/etc/php$nodotv/apache/php.ini",
                                     "/etc/php$nodotv/apache2/php.ini",
                     "/etc/php$shortv/apache/php.ini",
                                     "/etc/php$shortv/apache2/php.ini",
                    )
                  : ("/etc/php$ver/cgi/php.ini",
                     "/etc/php$nodotv/cgi/php.ini",
                     "/etc/php$shortv/cgi/php.ini",
                     "/etc/php/$ver/cgi/php.ini",
                     "/etc/php/$nodotv/cgi/php.ini",
                     "/etc/php/$shortv/cgi/php.ini",
                    ),
           "/opt/csw/php$ver/lib/php.ini",
           "/usr/local/lib/php.ini",
           "/usr/local/etc/php.ini",
           "/usr/local/etc/php.ini-production") {
    return $i if (-r $i);
    }
return undef;
}

# get_php_mysql_socket(&domain)
# Returns the PHP mysql socket path to use for some domain, from the
# global config file. Returns 'none' if not possible, or an empty string
# if not set.
sub get_php_mysql_socket
{
local ($d) = @_;
return 'none' if (!&foreign_check("phpini"));
local $mode = &get_domain_php_mode($d);
local @vers = &list_available_php_versions($d, $mode);
return 'none' if (!@vers);
local $tmpl = &get_template($d->{'template'});
local $inifile = $tmpl->{'web_php_ini_'.$vers[0]->[0]};
if (!$inifile || $inifile eq "none" || !-r $inifile) {
    $inifile = &get_global_php_ini($vers[0]->[0], $mode);
    }
&foreign_require("phpini");
local $gconf = &phpini::get_config($inifile);
local $sock = &phpini::find_value("mysql.default_socket", $gconf);
return $sock;
}

# get_domain_php_fpm_mode(&domain)
# Get the PHP-FPM process manager mode for some domain
sub get_domain_php_fpm_mode
{
my ($d) = @_;
my $pm = &get_php_fpm_config_value($d, "pm");
return $pm || 'dynamic';
}

# get_domain_php_children(&domain)
# For a domain using fcgi to run PHP, returns the number of child processes.
# Returns 0 if not set, -1 if the file doesn't even exist, -2 if not supported
sub get_domain_php_children
{
my ($d) = @_;
my $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_get_web_php_children", $d);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
my $mode = &get_domain_php_mode($d);
if ($mode eq "fcgid") {
    # Set in wrapper script 
    my ($ver) = &list_available_php_versions($d, "fcgid");
    return -2 if (!$ver);
    my $childs = 0;
    &open_readfile_as_domain_user($d, WRAPPER,
        "$d->{'home'}/fcgi-bin/php$ver->[0].fcgi") || return -1;
    while(<WRAPPER>) {
        if (/^PHP_FCGI_CHILDREN\s*=\s*(\d+)/) {
            $childs = $1;
            }
        }
    &close_readfile_as_domain_user($d, WRAPPER);
    return $childs;
    }
elsif ($mode eq "fpm") {
    # Set in pool config file
    my $conf = &get_php_fpm_config($d);
    return -1 if (!$conf);
    my $file = $conf->{'dir'}."/".$d->{'id'}.".conf";
    my $lref = &read_file_lines($file, 1);
    my $childs = 0;
    foreach my $l (@$lref) {
        if ($l =~ /pm\.max_children\s*=\s*(\d+)/) {
            $childs = $1;
            }
        }
    &unflush_file_lines($file);
    return $childs == get_php_max_childred_allowed() ? 0 : $childs;
    }
else {
    return -2;
    }
}

# save_domain_php_fpm_mode(&domain, mode)
# Save the PHP-FPM process manager mode for some domain
sub save_domain_php_fpm_mode
{
my ($d, $modetype) = @_;
&save_php_fpm_config_value($d, 'pm', $modetype);
}

# save_domain_php_children(&domain, children, [no-writable])
# Update all of a domain's PHP wrapper scripts with the new number of children
sub save_domain_php_children
{
local ($d, $children, $nowritable) = @_;
local $p = &domain_has_website($d);
if ($p && $p ne 'web') {
    return &plugin_call($p, "feature_save_web_php_children", $d,
                $children, $nowritable);
    }
elsif (!$p) {
    return "Virtual server does not have a website";
    }
my $mode = &get_domain_php_mode($d);
if ($mode eq "fcgid") {
    # Update in FCGI wrapper scripts
    local $count = 0;
    &set_php_wrappers_writable($d, 1) if (!$nowritable);
    foreach my $ver (&list_available_php_versions($d, "fcgi")) {
        local $wrapper = "$d->{'home'}/fcgi-bin/php$ver->[0].fcgi";
        next if (!-r $wrapper);

        # Find the current line
        local $lref = &read_file_lines_as_domain_user($d, $wrapper);
        local $idx;
        for(my $i=0; $i<@$lref; $i++) {
            if ($lref->[$i] =~ /PHP_FCGI_CHILDREN\s*=\s*\d+/) {
                $idx = $i;
                }
            }

        # Update, remove or add
        if ($children && defined($idx)) {
            $lref->[$idx] = "PHP_FCGI_CHILDREN=$children";
            }
        elsif (!$children && defined($idx)) {
            splice(@$lref, $idx, 1);
            }
        elsif ($children && !defined($idx)) {
            # Add before export line
            local $found = 0;
            for(my $e=0; $e<@$lref; $e++) {
                if ($lref->[$e] =~ /^export\s+PHP_FCGI_CHILDREN/) {
                    splice(@$lref, $e, 0,
                           "PHP_FCGI_CHILDREN=$children");
                    $found++;
                    last;
                    }
                }
            if (!$found) {
                # Add both lines at top
                splice(@$lref, 1, 0,
                       "PHP_FCGI_CHILDREN=$children",
                       "export PHP_FCGI_CHILDREN");
                }
            }
        &flush_file_lines_as_domain_user($d, $wrapper);
        }
    &set_php_wrappers_writable($d, 0) if (!$nowritable);
    &register_post_action(\&restart_apache);
    return 1;
    }
elsif ($mode eq "fpm") {
    # Update in FPM pool file
    my $conf = &get_php_fpm_config($d);
    return 0 if (!$conf);
    my $file = $conf->{'dir'}."/".$d->{'id'}.".conf";
    return 0 if (!-r $file);
    &lock_file($file);
    my $lref = &read_file_lines($file);
    $children = get_php_max_childred_allowed() if ($children == 0);    # Recommended default
    foreach my $l (@$lref) {
        if ($l =~ /pm\.max_children\s*=\s*(\d+)/) {
            $l = "pm.max_children = $children";
            }
        if ($l =~ /pm\.start_servers\s*=\s*(\d+)/) {
            $l = "pm.start_servers = " . get_php_start_servers($children) . "";
            }
        if ($l =~ /pm\.max_spare_servers\s*=\s*(\d+)/) {
            $l = "pm.max_spare_servers = " . get_php_max_spare_servers($children) . "";
            }
        }
    &flush_file_lines($file);
    &unlock_file($file);
    &register_post_action(\&restart_php_fpm_server, $conf);
    return 1;
    }
else {
    return 0;
    }
}

# check_php_configuration(&domain, php-version, php-command)
# Returns an error message if the domain's PHP config is invalid
sub check_php_configuration
{
local ($d, $ver, $cmd) = @_;
$cmd ||= &has_command("php".$ver) || &has_command("php");
local $mode = &get_domain_php_mode($d);
if ($mode eq "mod_php") {
    local $gini = &get_global_php_ini($ver, $mode);
    if ($gini) {
        $gini =~ s/\/php.ini$//;
        $ENV{'PHPRC'} = $gini;
        }
    }
else {
    $ENV{'PHPRC'} = &get_domain_php_ini($d, $ver, 1);
    }
&clean_environment();
local $out = &backquote_command("$cmd -d error_log= -m 2>&1");
local @errs;
foreach my $l (split(/\r?\n/, $out)) {
    $l = &html_tags_to_text($l);
    if ($l =~ /(PHP\s+)?Fatal\s+error:\s*(.*)/) {
        my $msg = $2;
        $msg =~ s/\s+in\s+\S+\s+on\s+line\s+\d+//;
        push(@errs, $msg);
        }
    }
&reset_environment();
delete($ENV{'PHPRC'});
return join(", ", @errs);
}

# list_php_modules(&domain, php-version, php-command)
# Returns a list of PHP modules available for some domain. Uses caching.
sub list_php_modules
{
local ($d, $ver, $cmd) = @_;
local $mode = &get_domain_php_mode($d);
if (!defined($main::php_modules{$ver,$d->{'id'}})) {
    $cmd ||= &has_command("php".$ver) || &has_command("php");
    $main::php_modules{$ver} = [ ];
    if ($mode eq "mod_php" || $mode eq "fpm") {
        # Use global PHP config, since with mod_php we can't do
        # per-domain configurations
        local $gini = &get_global_php_ini($ver, $mode);
        if ($gini) {
            $gini =~ s/\/php.ini$//;
            $ENV{'PHPRC'} = $gini;
            }
        }
    elsif ($d) {
        # Use domain's php.ini
        $ENV{'PHPRC'} = &get_domain_php_ini($d, $ver, 1);
        }
    &clean_environment();
    local $_;
    &open_execute_command(PHP, "$cmd -m", 1);
    while(<PHP>) {
        s/\r|\n//g;
        if (/^\S+$/ && !/\[/) {
            push(@{$main::php_modules{$ver,$d->{'id'}}}, $_);
            }
        }
    close(PHP);
    &reset_environment();
    delete($ENV{'PHPRC'});
    }
return @{$main::php_modules{$ver,$d->{'id'}}};
}

# fix_php_ini_files(&domain, &fixes)
# Updates values in all php.ini files in a domain. The fixes parameter is
# a list of array refs, containing old values, new value and regexp flag.
# If the old value is undef, anything matches. May print stuff. Returns the
# number of changes made.
sub fix_php_ini_files
{
local ($d, $fixes) = @_;
local ($mode, $rv);
if (defined(&get_domain_php_mode) &&
    ($mode = &get_domain_php_mode($d)) &&
    $mode ne "mod_php" &&
    $mode ne "fpm" &&
    &foreign_check("phpini")) {
    &foreign_require("phpini");
    &$first_print($text{'save_apache10'});
    foreach my $i (&list_domain_php_inis($d)) {
        &unflush_file_lines($i->[1]);    # In case cached
        undef($phpini::get_config_cache{$i->[1]});
        local $pconf = &phpini::get_config($i->[1]);
        foreach my $f (@$fixes) {
            local $ov = &phpini::find_value($f->[0], $pconf);
            local $nv = $ov;
            if (!defined($f->[1])) {
                # Always change
                $nv = $f->[2];
                }
            elsif ($f->[3] && $ov =~ /\Q$f->[1]\E/) {
                # Regexp change
                $nv =~ s/\Q$f->[1]\E/$f->[2]/g;
                }
            elsif (!$f->[3] && $ov eq $f->[1]) {
                # Exact match change
                $nv = $f->[2];
                }
            if ($nv ne $ov) {
                # Update in file
                &phpini::save_directive($pconf, $f->[0], $nv);
                &flush_file_lines($i->[1]);
                $rv++;
                }
            }
        }
    &$second_print($text{'setup_done'});
    }
return $rv;
}

# fix_php_fpm_pool_file(&domain, &fixes)
# Updates values in PHP FPM config file
sub fix_php_fpm_pool_file
{
my ($d, $fixes) = @_;
my ($mode, $rv, $conf, $file);
if (defined(&get_domain_php_mode) &&
    ($mode = &get_domain_php_mode($d)) &&
    $mode eq "fpm" &&
    &foreign_check("phpini")) {
    &foreign_require("phpini");
    $conf = &get_php_fpm_config($d);
    if ($conf) {
        $file = $conf->{'dir'}."/".$d->{'id'}.".conf";
        }
    if (-r $file) {
        &$first_print($text{'save_apache12'});
        &unflush_file_lines($file);    # In case cached
        undef($phpini::get_config_cache{$file});
        my $fpmconf = &phpini::get_config($file);
        foreach my $f (@{$fixes}) {
            my $ov = &phpini::find_value($f->[0], $fpmconf);
            my $nv = $ov;
            if (!defined($f->[1])) {
                # Always change
                $nv = $f->[2];
                }
            elsif ($f->[3] && $ov =~ /\Q$f->[1]\E/) {
                # Regexp change
                $nv =~ s/\Q$f->[1]\E/$f->[2]/;
                }
            elsif (!$f->[3] && $ov eq $f->[1]) {
                # Exact match change
                $nv = $f->[2];
                }
            if ($nv ne $ov) {
                # Update in file
                &phpini::save_directive($fpmconf, $f->[0], $nv);
                &flush_file_lines($file);
                $rv++;
                }
            }
        if ($rv) {
            &$second_print($text{'setup_done'});
            }
        else {
            &$second_print($text{'save_apache12none'});
            }
        }
    }
return $rv;
}

# fix_php_extension_dir(&domain)
# If the extension_dir in a domain's php.ini file is invalid, try to fix it
sub fix_php_extension_dir
{
local ($d) = @_;
return if (!&foreign_check("phpini"));
&foreign_require("phpini");
foreach my $i (&list_domain_php_inis($d)) {
    local $pconf = &phpini::get_config($i->[1]);
    local $ed = &phpini::find_value("extension_dir", $pconf);
    if ($ed && !-d $ed) {
        # Doesn't exist .. maybe can fix
        my $newed = $ed;
        if ($newed =~ /\/lib\//) {
            $newed =~ s/\/lib\//\/lib64\//;
            }
        elsif ($newed =~ /\/lib64\//) {
            $newed =~ s/\/lib64\//\/lib\//;
            }
        if (!-d $newed) {
            # Couldn't find it, give up and clear
            $newed = undef;
            }
        &phpini::save_directive($pconf, "extension_dir", $newed);
        }
    }
}

# get_domain_php_version(&domain, [php-mode])
# Get the PHP version used by the domain by default (for public_html)
sub get_domain_php_version
{
local ($d, $mode) = @_;
$mode ||= &get_domain_php_mode($d);
if ($mode ne "mod_php") {
    local @dirs = &list_domain_php_directories($d);
    local $phd = &public_html_dir($d);
        local ($hdir) = grep { $_->{'dir'} eq $phd } @dirs;
        $hdir ||= $dirs[0];
    return $hdir ? $hdir->{'version'} : undef;
    }
return undef;
}

# create_php_ini_link(&domain, [php-mode])
# Create a link from etc/php.ini to the PHP version used by the domain's
# public_html directory
sub create_php_ini_link
{
local ($d, $mode) = @_;
local $ver = &get_domain_php_version($d, $mode);
if ($ver) {
    local $etc = "$d->{'home'}/etc";
    &unlink_file_as_domain_user($d, "$etc/php.ini");
    &symlink_file_as_domain_user($d, "php".$ver."/php.ini", "$etc/php.ini");
    }
}

# create_php_bin_links(&domain, [php-mode])
# Create a link from ~/bin/php to the PHP CLI for the primary version
sub create_php_bin_links
{
my ($d, $mode) = @_;
return if ($mode eq "none");
my $bindir = $d->{'home'}.'/bin';
&unlink_file_as_domain_user($d, $bindir.'/php');
my ($ver, $cli);
if (($ver = &get_domain_php_version($d, $mode)) &&
    ($cli = &php_command_for_version($ver, 2))) {
    &make_dir_as_domain_user($d, $bindir, 0755);
    &symlink_file_as_domain_user($d, $cli, $bindir.'/php');
    }
}

# get_php_fpm_config([version|&domain])
# Returns the first valid FPM config for a domain
sub get_php_fpm_config
{
my ($ver) = @_;
if (ref($ver)) {
    $ver = $ver->{'php_fpm_version'};
    }
my @confs = grep { !$_->{'err'} } &list_php_fpm_configs();
if ($ver) {
    @confs = grep { $_->{'version'} eq $ver ||
            $_->{'shortversion'} eq $ver } @confs;
    }
return @confs ? $confs[0] : undef;
}

# get_php_fpm_config_file(&domain)
# Returns the FPM pool config file for a domain
sub get_php_fpm_config_file
{
my ($d) = @_;
my $conf = &get_php_fpm_config($d);
return undef if (!$conf);
return $conf->{'dir'}."/".$d->{'id'}.".conf";
}

# list_php_fpm_configs()
# Returns hash refs with details of the system's php-fpm configurations. Assumes
# use of standard packages.
sub list_php_fpm_configs
{
if ($php_fpm_config_cache) {
    return @$php_fpm_config_cache;
    }

# What version packages are installed?
return ( ) if (!&foreign_installed("software"));
&foreign_require("software");
return ( ) if (!defined(&software::package_info));
my @rv;
my %donever;

# Get all interesting packages
my @pkgnames = ("php-fpm",
        (map { "php${_}-fpm" } @all_possible_short_php_versions),
        (map { my $v = $_; $v =~ s/\.//g;
             ("php${v}-php-fpm", "php${v}-fpm", "php${v}w-fpm",
               "rh-php${v}-php-fpm", "php${_}-fpm",
               "php${v}u-fpm") } @all_possible_php_versions));
if (&get_webmin_version() >= 1.985 ||
    $software::config{'package_system'} eq 'debian') {
    &software::list_packages(@pkgnames);
    }
else {
    &software::list_packages();
    }
my %pkgs;
for(my $i=0; $software::packages{$i,'name'}; $i++) {
    $pkgs{$software::packages{$i,'name'}} = [
        $software::packages{$i,'name'},
        $software::packages{$i,'class'},
        $software::packages{$i,'desc'},
        $software::packages{$i,'arch'},
        $software::packages{$i,'version'} ];
    }

foreach my $pname (@pkgnames) {
    my $pinfo = $pkgs{$pname};
    next if (!$pinfo);

    # The php-fpm package on Ubuntu is just a meta-package
    if ($pname eq "php-fpm" && $pinfo->[3] eq "all" &&
        $gconfig{'os_type'} eq 'debian-linux') {
        next;
        }

    # Normalize the version
    my $rv = { 'package' => $pname };
    $rv->{'version'} = $pinfo->[4];
    $rv->{'version'} =~ s/\-.*$//;
    $rv->{'version'} =~ s/\+.*$//;
    $rv->{'version'} =~ s/^\d+://;
    next if ($donever{$rv->{'version'}}++);
    $rv->{'shortversion'} = $rv->{'version'};
    $rv->{'shortversion'} =~ s/^(\d+\.\d+)\..*/$1/;  # Reduce version to 5.x
    if (($pname eq "php-fpm" || $pname eq "php5-fpm") &&
        $rv->{'shortversion'} =~ /^5/) {
        # For historic reasons, we just use the version number '5' for
        # the first PHP 5.x version on the system.
        $rv->{'shortversion'} = 5;
        }
    $rv->{'pkgversion'} = $rv->{'shortversion'};
    $rv->{'pkgversion'} =~ s/\.//g;
    push(@rv, $rv);

    # Config directory for per-domain pool files
    my @verdirs;
    DIR: foreach my $cdir ("/etc/php-fpm.d",
                   "/etc/php*/fpm/pool.d",
                   "/etc/php*/fpm/php-fpm.d",
                   "/etc/php/*/fpm/pool.d",
                   "/etc/opt/remi/php*/php-fpm.d",
                   "/etc/opt/rh/rh-php*/php-fpm.d",
                   "/usr/local/etc/php-fpm.d") {
        foreach my $realdir (glob($cdir)) {
            if ($realdir && -d $realdir) {
                my @files = glob("$realdir/*");
                if (@files) {
                    push(@verdirs, $realdir);
                    }
                }
            }
        }
    if (!@verdirs) {
        $rv->{'err'} = $text{'php_fpmnodir'};
        next;
        }
    my ($bestdir) = grep { /(php|\/)\Q$rv->{'version'}\E\// ||
                   /(php|\/)\Q$rv->{'pkgversion'}\E\// ||
                   /(php|\/)\Q$rv->{'shortversion'}\E\// } @verdirs;
    $bestdir ||= $verdirs[0];
    $rv->{'dir'} = $bestdir;

    # Init script for this version
    &foreign_require("init");
    my $shortver = $rv->{'version'};
    $shortver =~ s/^(\d+\.\d+)\..*/$1/g;
    my $nodot = $shortver;
    $nodot =~ s/\.//g;
    foreach my $init ("php${shortver}-fpm",
              "php-fpm${shortver}",
              "rh-php${nodot}-php-fpm",
              "php${nodot}-php-fpm") {
        my $st = &init::action_status($init);
        if ($st) {
            $rv->{'init'} = $init;
            $rv->{'enabled'} = $st > 1;
            last;
            }
        }
    if (!$rv->{'init'}) {
        # Init script for any version as a fallback
        my @nodot = map { my $u = $_; $u =~ s/\.//g; $u }
                @all_possible_php_versions;
        foreach my $init ("php-fpm",
                  (map { "php${_}-fpm" }
                       @all_possible_short_php_versions),
                  (map { "php${_}-fpm" }
                       @all_possible_php_versions),
                  (map { "rh-php${_}-php-fpm" } @nodot),
                  (map { "php${_}-php-fpm" } @nodot)) {
            my $st = &init::action_status($init);
            if ($st) {
                $rv->{'init'} = $init;
                $rv->{'enabled'} = $st > 1;
                last;
                }
            }
        }
    if (!$rv->{'init'}) {
        $rv->{'err'} = $text{'php_fpmnoinit2'};
        next;
        }

    # Apache modules
    if ($config{'web'}) {
        &require_apache();
        if (!$apache::httpd_modules{'mod_proxy'}) {
            $rv->{'err'} = &text('php_fpmnomod', 'mod_proxy');
            }
        }
    }

# If FPM was setup without a package or init script, also allow it
if ($config{'php_fpm_cmd'} && -x $config{'php_fpm_cmd'} &&
    $config{'php_fpm_pool'} && -d $config{'php_fpm_pool'}) {
    my $rv = { 'cmd' => $config{'php_fpm_cmd'},
           'fromconfig' => 1,
           'init' => $config{'php_fpm_init'},
           'dir' => $config{'php_fpm_pool'},
         };
    $rv->{'version'} = &get_php_version($rv->{'cmd'});
    $rv->{'shortversion'} = $rv->{'version'};
    $rv->{'shortversion'} =~ s/^(\d+\.\d+)\..*/$1/;  # Reduce version to 5.x
    if ($rv->{'init'}) {
        my $st = &init::action_status($rv->{'init'});
        if ($st == 0) {
            $rv->{'err'} = &text('php_fpmnoinit3', $rv->{'init'});
            }
        elsif ($st == 2) {
            $rv->{'enabled'} = 1;
            }
        }
    push(@rv, $rv);
    }

$php_fpm_config_cache = \@rv;
return @rv;
}

# get_php_fpm_socket_file(&domain, [dont-make-dir])
# Returns the path to the default per-domain PHP-FPM socket file. Creates the
# directory if needed.
sub get_php_fpm_socket_file
{
my ($d, $nomkdir) = @_;
# Universal FPM socket dir
my $base = "/var/php-fpm";
# Accommodate old styles in read only mode
my $basefile = $base."/".$d->{'id'}.".sock";
return $basefile if (-r $basefile);
# Distro dependent FPM socket dirs (if exists)
my $rhelbase = "/run/php-fpm";
my $debbase = "/run/php";
if ($config{'fpm_socket_dir'}) {
    $base = $config{'fpm_socket_dir'};
    }
elsif (-d $rhelbase && $gconfig{'os_type'} eq 'redhat-linux') {
    $base = $rhelbase;
    }
elsif (-d $debbase && $gconfig{'os_type'} eq 'debian-linux') {
    $base = $debbase;
    }
if (!-d $base && !$nomkdir) {
    &make_dir($base, 0755);
    }
return $base."/".$d->{'id'}.".sock";
}

# get_php_fpm_socket_port(&domain)
# Selects a dynamic port number for a per-domain PHP FPM socket
sub get_php_fpm_socket_port
{
my ($d) = @_;
if ($d->{'php_fpm_port'}) {
    # Already chosen
    return $d->{'php_fpm_port'};
    }
my %used = map { $_->{'php_fpm_port'}, $_ }
           grep { $_->{'php_fpm_port'} } &list_domains();
my $base = $config{'php_fpm_port'} || 8000;
my $rv = &allocate_free_tcp_port(\%used, $base);
$rv || &error("Failed to allocate FPM port starting at $base");
$d->{'php_fpm_port'} = $rv;
return $rv;
}

# get_domain_php_fpm_port(&domain)
# Returns a status code (0=error, 1=port, 2=file) and the actual TCP port or
# socket file used for FPM
sub get_domain_php_fpm_port
{
my ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne 'web') {
    if (!&plugin_defined($p, "feature_get_domain_php_fpm_port")) {
        return (-1, "Not supported by plugin $p");
        }
    return &plugin_call($p, "feature_get_domain_php_fpm_port", $d);
    }

# Find the Apache virtualhost
&require_apache();
my ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'},
                        $d->{'web_port'});
return (0, "No Apache virtualhost found") if (!$virt);

# What port is Apache on?
my $webport;
foreach my $p (&apache::find_directive("ProxyPassMatch", $vconf)) {
    if ($p =~ /fcgi:\/\/localhost:(\d+)/ ||
        $p =~ /fcgi:\/\/127\.0\.0\.1:(\d+)/ ||
        $p =~ /unix:([^\|]+)/) {
        $webport = $1;
        }
    }
foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) {
    next if ($f->{'words'}->[0] ne '\.php$');
    foreach my $h (&apache::find_directive("SetHandler",
                           $f->{'members'})) {
        if ($h =~ /proxy:fcgi:\/\/localhost:(\d+)/ ||
            $h =~ /proxy:fcgi:\/\/127\.0\.0\.1:(\d+)/ ||
            $h =~ /proxy:unix:([^\|]+)/) {
            my $webport2 = $1;
            if ($webport && $webport != $webport2) {
                return (0, "Port $webport in ProxyPassMatch ".
                       "is different from port $webport2 ".
                       "in FilesMatch");
                }
            $webport ||= $webport2;
            }
        }
    }
return (0, "No FPM SetHandler or ProxyPassMatch directive found")
    if (!$webport);

# Which port is the FPM server actually using?
my $fpmport;
my $listen = &get_php_fpm_config_value($d, "listen");
if ($listen =~ /^\S+:(\d+)$/ ||
    $listen =~ /^(\d+)$/ ||
    $listen =~ /^(\/\S+)$/) {
    $fpmport = $1;
    }
return (0, "No listen directive found in FPM config") if (!$fpmport);

if ($fpmport ne $webport) {
    return (0, "Apache config port $webport does not ".
           "match FPM config $fpmport");
    }
return ($fpmport =~ /^\d+$/ ? 1 : 2, $fpmport);
}

# save_domain_php_fpm_port(&domain, port|socket)
# Update the TCP port or socket used for FPM for a domain. Returns undef on
# success or an error message on failure.
sub save_domain_php_fpm_port
{
my ($d, $socket) = @_;
my $p = &domain_has_website($d);
if ($p ne 'web') {
    if (!&plugin_defined($p, "feature_save_domain_php_fpm_port")) {
        return "Not supported by plugin $p";
        }
    return &plugin_call($p, "feature_save_domain_php_fpm_port", $d,$socket);
    }

# First update the Apache config
&require_apache();
&obtain_lock_web($d);
my @ports = ( $d->{'web_port'},
          $d->{'ssl'} ? ( $d->{'web_sslport'} ) : ( ) );
my $found = 0;
foreach my $p (@ports) {
    my ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'}, $p);
    next if (!$virt);
    foreach my $f (&apache::find_directive_struct("FilesMatch", $vconf)) {
        next if ($f->{'words'}->[0] ne '\.php$');
        my @sh = &apache::find_directive("SetHandler",
                                                 $f->{'members'});
        for(my $i=0; $i<@sh; $i++) {
            if ($sh[$i] =~ /proxy:fcgi:\/\/localhost:(\d+)/ ||
                $sh[$i] =~ /proxy:fcgi:\/\/127\.0\.0\.1:(\d+)/ ||
                $sh[$i] =~ /proxy:unix:([^\|]+)/) {
                # Found the directive to update
                if ($socket =~ /^\d+$/) {
                    $sh[$i] = "proxy:fcgi://127.0.0.1:".$socket;
                    }
                else {
                    $sh[$i] = "proxy:unix:".$socket.
                          "|fcgi://127.0.0.1";
                    }
                $found++;
                }
            }
        &apache::save_directive(
            "SetHandler", \@sh, $f->{'members'}, $conf);
        &flush_file_lines($virt->{'file'});
        }
    }
&release_lock_web($d);
$found || return "No Apache VirtualHost containing an FPM SetHandler found";

# Second update the FPM server port
my $conf = &get_php_fpm_config($d);
&save_php_fpm_config_value($d, "listen", $socket);
if ($socket =~ /^\//) {
    # Also set correct owner for the file if switching to socket mode
    &save_php_fpm_config_value($d, "listen.owner", $d->{'user'});
    &save_php_fpm_config_value($d, "listen.group", $d->{'ugroup'});
    }
&register_post_action(\&restart_php_fpm_server, $conf);
&register_post_action(\&restart_apache);

return undef;
}

# check_php_fpm_port_clash(&domain)
# Checks if any other FPM pool is using the same port or socket,
# and if so returns it and the port number or socket file name
sub check_php_fpm_port_clash
{
my ($d) = @_;
my (undef, $port) = &get_domain_php_fpm_port($d);
my $dconf = &get_php_fpm_config($d);
my @fpms = &list_php_fpm_configs();
foreach my $conf (@fpms) {
    my @pools = &list_php_fpm_pools($conf);
    foreach my $p (@pools) {
        next if ($p eq $d->{'id'} &&
             $conf->{'version'} eq $dconf->{'version'});
        my $t = get_php_fpm_pool_config_value($conf, $p, "listen");
        next if (!$t);
        $t =~ s/^\S+:(\d+)$/$1/g;    # Remove listen:
        if ($t eq $port) {
            my $otherid = $port =~ /\/(\d+)\.sock/ ? $1 : undef;
            return ($p, $conf, $port, $otherid);
            }
        }
    }
return ();
}

# list_php_fpm_pools(&conf)
# Returns a list of all pool IDs for some FPM config
sub list_php_fpm_pools
{
my ($conf) = @_;
my @rv;
opendir(DIR, $conf->{'dir'});
foreach my $f (readdir(DIR)) {
    if ($f =~ /^(\S+)\.conf$/) {
        push(@rv, $1);
        }
    }
closedir(DIR);
return @rv;
}

# create_php_fpm_pool(&domain, [force-listen])
# Create a per-domain pool config file
sub create_php_fpm_pool
{
my ($d, $forcelisten) = @_;
my $tmpl = &get_template($d->{'template'});
my $conf = &get_php_fpm_config($d);
return $text{'php_fpmeconfig'} if (!$conf);
my $file = $conf->{'dir'}."/".$d->{'id'}.".conf";
my $oldlisten = $forcelisten || &get_php_fpm_config_value($d, "listen");
my $mode;
if ($oldlisten) {
    $mode = $oldlisten =~ /^\// ? 1 : 0;
    }
else {
    $mode = $tmpl->{'php_sock'};
    }
my $port;
if ($mode) {
    $port = $oldlisten || &get_php_fpm_socket_file($d);
    }
else {
    $port = &get_php_fpm_socket_port($d);
    }
$port = "127.0.0.1:".$port if ($port =~ /^\d+$/);
&lock_file($file);
if (-r $file) {
    # Fix up existing one, in case user or group changed
    &save_php_fpm_config_value($d, "user", $d->{'user'});
    &save_php_fpm_config_value($d, "group", $d->{'ugroup'});
    &save_php_fpm_config_value($d, "listen", $port);
    if (&get_php_fpm_config_value($d, "listen.owner")) {
        &save_php_fpm_config_value($d, "listen.owner", $d->{'user'});
        &save_php_fpm_config_value($d, "listen.group", $d->{'ugroup'});
        }
    }
else {
    # Create a new file
    my $tmpl = &get_template($d->{'template'});
    my $defchildren = $tmpl->{'web_phpchildren'};
    if ($defchildren eq "none" || !$defchildren) {
        $defchildren = get_php_max_childred_allowed();
        }
    my $defmaxspare = get_php_max_spare_servers($defchildren);
    my $defstartservers = get_php_start_servers($defchildren);
    local $tmp = &create_server_tmp($d);
    my $lref = &read_file_lines($file);
    @$lref = ( "[$d->{'id'}]",
           "user = ".$d->{'user'},
           "group = ".$d->{'ugroup'},
           "listen.owner = ".$d->{'user'},
           "listen.group = ".$d->{'ugroup'},
           "listen.mode = 0660",
           "listen = ".$port,
           "pm = dynamic", 
           "pm.max_children = $defchildren",
           "pm.start_servers = $defstartservers",
           "pm.min_spare_servers = 1",
           "pm.max_spare_servers = $defmaxspare",
              "php_value[upload_tmp_dir] = $tmp",
           "php_value[session.save_path] = $tmp" );
    &flush_file_lines($file);

    # Add / override custom options (with substitution)
    if ($tmpl->{'php_fpm'} ne 'none') {
        foreach my $l (split(/\t+/,
            &substitute_domain_template($tmpl->{'php_fpm'}, $d))) {
            next if ($l !~ /^\s*(\S+)\s*=\s*(.*)/);
            my ($n, $v) = ($1, $2);
            &save_php_fpm_config_value($d, $n, $v);
            }
        }
    }
my $parent = $d->{'parent'} ? &get_domain_by($d->{'parent'}) : $d;
if ($parent->{'jail'}) {
    my $dir = &get_domain_jailkit($parent);
    &save_php_fpm_config_value($d, "chroot", $dir);
    }
&unlock_file($file);
if ($port =~ /^\// && !-e $port) {
    # Pre-create the socket file
    open(TOUCH, ">$port");
    close(TOUCH);
    &set_ownership_permissions($d->{'user'}, $d->{'ugroup'}, 0660, $port);
    }
&register_post_action(\&restart_php_fpm_server, $conf);
return undef;
}

# delete_php_fpm_pool(&domain)
# Remove the per-domain pool configuration file
sub delete_php_fpm_pool
{
my ($d) = @_;
my @fpms = &list_php_fpm_configs();
my $found = 0;
foreach my $conf (@fpms) {
    my @pools = &list_php_fpm_pools($conf);
    foreach my $p (@pools) {
        if ($p eq $d->{'id'}) {
            my $file = $conf->{'dir'}."/".$d->{'id'}.".conf";
            if (-r $file) {
                &unlink_logged($file);
                &unflush_file_lines($file);
                my $sock = &get_php_fpm_socket_file($d, 1);
                &unlink_logged($sock) if (-r $sock);
                &register_post_action(
                    \&restart_php_fpm_server, $conf);
                }
            }
        }
    }
}

# restart_php_fpm_server([&config])
# Post-action script to restart the server
sub restart_php_fpm_server
{
my ($conf) = @_;
$conf ||= &get_php_fpm_config();
&$first_print(&text('php_fpmrestart', $conf->{'shortversion'}));
if ($conf->{'init'}) {
    &foreign_require("init");
    my ($ok, $err) = &init::restart_action($conf->{'init'});
    if ($ok) {
        &$second_print($text{'setup_done'});
        return 1;
        }
    else {
        &$second_print(&text('php_fpmeinit', $err));
        return 0;
        }
    }
else {
    &$second_print($text{'php_fpmnoinit'});
    return 0;
    }
}

# get_php_fpm_config_value(&domain, name)
# Returns the value of a config setting from the domain's pool file
sub get_php_fpm_config_value
{
my ($d, $name) = @_;
my $conf = &get_php_fpm_config($d);
return undef if (!$conf);
return &get_php_fpm_pool_config_value($conf, $d->{'id'}, $name);
}

# list_php_fpm_config_values(&domain)
# Returns an array ref of name/value pairs from the FPM config for a domain
sub list_php_fpm_config_values
{
my ($d) = @_;
my $conf = &get_php_fpm_config($d);
return undef if (!$conf);
return &list_php_fpm_pool_config_values($conf, $d->{'id'});
}

# get_php_fpm_pool_config_value(&conf, domain-id, name)
# Returns the value of a config setting from any pool file
sub get_php_fpm_pool_config_value
{
my ($conf, $id, $name) = @_;
my $file = $conf->{'dir'}."/".$id.".conf";
my $lref = &read_file_lines($file, 1);
foreach my $l (@$lref) {
    if ($l =~ /^\s*(\S+)\s*=\s*(.*)/ && $1 eq $name) {
        return $2;
        }
    }
return undef;
}

# list_php_fpm_pool_config_values(&conf, domain-id)
# Returns an array ref of name/value pairs from the FPM config
sub list_php_fpm_pool_config_values
{
my ($conf, $id) = @_;
my $file = $conf->{'dir'}."/".$id.".conf";
return &list_php_fpm_file_config_values($file);
}

# list_php_fpm_file_config_values(file)
# Returns an array ref of name/value pairs from an FPM pool file
sub list_php_fpm_file_config_values
{
my ($file) = @_;
my $lref = &read_file_lines($file, 1);
my @rv;
foreach my $l (@$lref) {
    if ($l =~ /^\s*(\S+)\s*=\s*(.*)/) {
        push(@rv, [ $1, $2 ]);
        }
    }
return \@rv;
}

# get_php_fpm_ini_value(&domain, name)
# Returns the value of a PHP ini setting from the domain's pool file
sub get_php_fpm_ini_value
{
my ($d, $name) = @_;
my $k = "php_value";
my $rv = &get_php_fpm_config_value($d, "php_value[${name}]");
if (!defined($rv)) {
    my $k = "php_admin_value";
    $rv = &get_php_fpm_config_value($d, "php_admin_value[${name}]");
    if (!defined($rv)) {
        $k = undef;
        }
    }
return wantarray ? ($rv, $k) : $rv;
}

# list_php_fpm_ini_values(&domain)
# Returns an array ref of name/value/admin triplets for FPM PHP ini settings
sub list_php_fpm_ini_values
{
my ($d) = @_;
my $all = &list_php_fpm_config_values($d);
return undef if (!$all);
my @rv;
foreach my $c (@$all) {
    if ($c->[0] =~ /^(php_value|php_admin_value)\[(\S+)\]$/) {
        push(@rv, [ $2, $c->[1], $1 eq "php_admin_value" ? 1 : 0 ]);
        }
    }
return \@rv;
}

# save_php_fpm_config_value(&domain, name, value)
# Adds, updates or deletes an config setting in the domain's pool file
sub save_php_fpm_config_value
{
my ($d, $name, $value) = @_;
my $conf = &get_php_fpm_config($d);
return 0 if (!$conf);
return &save_php_fpm_pool_config_value($conf, $d->{'id'}, $name, $value);
}

# save_php_fpm_pool_config_value(&conf, id, name, value)
# Adds, updates or deletes an config setting in a pool file
sub save_php_fpm_pool_config_value
{
my ($conf, $id, $name, $value) = @_;
my $file = $conf->{'dir'}."/".$id.".conf";
return if (!-r $file);
&lock_file($file);
my $lref = &read_file_lines($file);
my $found = -1;
my $lnum = 0;
foreach my $l (@$lref) {
    if ($l =~ /^\s*(\S+)\s*=\s*(.*)/ && $1 eq $name) {
        $found = $lnum;
        last;
        }
    $lnum++;
    }
if ($found >= 0 && defined($value)) {
    # Update existing line
    $lref->[$found] = "$name = $value";
    }
elsif ($found >=0 && !defined($value)) {
    # Remove existing line
    splice(@$lref, $found, 1);
    }
elsif ($found < 0 && defined($value)) {
    # Need to add new line
    push(@$lref, "$name = $value");
    }
&flush_file_lines($file);
&unlock_file($file);
&register_post_action(\&restart_php_fpm_server, $conf);
return 1;
}

# save_php_fpm_ini_value(&domain, name, value, [admin?])
# Adds, updates or deletes an ini setting in the domain's pool file
sub save_php_fpm_ini_value
{
my ($d, $name, $value, $admin) = @_;
my (undef, $k) = &get_php_fpm_ini_value($d, $name);
$k ||= ($admin ? "php_admin_value" : "php_value");
return &save_php_fpm_config_value($d, $k."[".$name."]", $value);
}

# increase_fpm_port(string)
# Increase the number in a port string
sub increase_fpm_port
{
my ($t) = @_;
if ($t =~ /^(\d+)$/) {
    return $t + 1;
    }
elsif ($t =~ /^(.*):(\d+)$/) {
    return $1.":".($2 + 1);
    }
return undef;
}

# get_apache_mod_php_version()
# If Apache has mod_phpX installed, return the version number
sub get_apache_mod_php_version
{
return $apache_mod_php_version_cache if ($apache_mod_php_version_cache);
&require_apache();
my $major = $apache::httpd_modules{'mod_php5'} ? 5 :
            $apache::httpd_modules{'mod_php7'} ? "7.0" : undef;
return undef if (!$major);
foreach my $php ("php$major", "php") {
    next if (!&has_command($php));
    &clean_environment();
    my $out = &backquote_command("$php -v 2>&1 </dev/null");
    &reset_environment();
    if ($out =~ /PHP\s+(\d+\.\d+)/) {
        $major = $1;
        last;
        }
    }
$apache_mod_php_version_cache = $major;
return $major;
}

# cleanup_php_sessions(&domain, dry-run)
# Remove old PHP session files for some domain
sub cleanup_php_sessions
{
my ($d, $dryrun) = @_;

# Find the session files dir from php config
my $etc = "$d->{'home'}/etc";
&foreign_require("phpini");
my $pconf = &phpini::get_config("$etc/php.ini");
my $tmp = &phpini::find_value("session.save_path", $pconf);
$tmp ||= $d->{'home'}."/tmp";

# Look for session files that are too old
my $days = $config{'php_session_age'} || 7;
my $cutoff = time() - $days * 24 * 60 * 60;
my @rv;
opendir(DIR, $tmp) || return ();
foreach my $f (readdir(DIR)) {
    next if ($f !~ /^sess_/);
    my @st = stat($tmp."/".$f);
    next if (!@st);
    if ($st[9] < $cutoff) {
        push(@rv, $tmp."/".$f);
        }
    }
closedir(DIR);

# Delete any found
if (!$dryrun) {
    &unlink_file_as_domain_user($d, @rv);
    }
return @rv;
}

# get_php_max_childred_allowed()
# Get PHP-FPM recommended allowed number for sub-processes,
# which is calculated as total available RAM devided by
# 64 MiB (aprox. cunsumed by each PHP-FPM process) and 
# devided by 4 (as we assume that a maximum recommended 
# default which PHP can use is 20% of all available RAM
# on the system). However, manually it will be possible
# to rase the number to use all available RAM as defined
# using `$max_php_fcgid_children` variable. Also on systems
# with a lot of RAM limit maximum recommended to sensible 16.
sub get_php_max_childred_allowed
{
return $main::get_real_memory_size_cache
    if (defined($main::get_real_memory_size_cache));
my $max;
my $mem = &get_real_memory_size();
if ($mem) {
    my $sysram_mb = $mem / 1024 / 1024;
    $max = int(($sysram_mb / 64) / 5);
    if ($max > 16) {
        $max = 16;
        }
    }
else {
     $max = $max_php_fcgid_children;
    }

# Low memory systems should not
# return values lower than 1
$max ||= 1;
$main::get_real_memory_size_cache = $max;
return int($max);
}

sub get_php_max_spare_servers
{
my ($defchildren) = @_;
my $defmaxspare = $defchildren <= 1 ? $defchildren :
        $defchildren >= 4 ? int($defchildren / 2) : 2;
return int($defmaxspare);
}

sub get_php_start_servers
{
my ($defchildren) = @_;
my $min_spare_servers = 1;
my $max_spare_servers = get_php_max_spare_servers($defchildren);
my $start_servers = $min_spare_servers + ($max_spare_servers - $min_spare_servers) / 4;
return int($start_servers) || 1;
}

# get_domain_php_error_log(&domain)
# Returns the PHP error log for a domain, from it's php.ini file. Return a path
# if set, an empty string if unset, or undef if it's not possible to set.
sub get_domain_php_error_log
{
my ($d) = @_;
my $mode = &get_domain_php_mode($d);
my $phplog;
if ($mode eq "fpm") {
    $phplog = &get_php_fpm_ini_value($d, "error_log") || "";
    }
elsif ($mode ne "none" && $mode ne "mod_php") {
    $phplog = "";
    &foreign_require("phpini");
    foreach my $i (&list_domain_php_inis($d)) {
        my $pconf = &phpini::get_config($i->[1]);
        $phplog = &phpini::find_value("error_log", $pconf);
        last if ($phplog);
        }
    }
else {
    return undef;
    }
if ($phplog && $phplog !~ /^\//) {
    $phplog = $d->{'home'}.'/'.$phplog;
    }
return $phplog;
}

# save_domain_php_error_log(&domain, [logfile])
# Set or remove the PHP error log file for a domain in its php.ini file
sub save_domain_php_error_log
{
my ($d, $phplog) = @_;
$phplog = undef if (!$phplog);
my $oldd = { %$d };
my $mode = &get_domain_php_mode($d);
my $p = &domain_has_website($d);
if ($mode eq "fpm") {
    &save_php_fpm_ini_value($d, "error_log", $phplog, 0);
    my $loge = &get_php_fpm_ini_value($d, "log_errors");
    if (lc($loge) ne "on") {
        &save_php_fpm_ini_value($d, "log_errors", "On", 0);
        }
    }
elsif ($mode ne "none" && $mode ne "mod_php") {
    &foreign_require("phpini");
    foreach my $i (&list_domain_php_inis($d)) {
        &lock_file($i->[1]);
        my $pconf = &phpini::get_config($i->[1]);
        &phpini::save_directive($pconf, "error_log", $phplog);
        my $loge = &phpini::find_value("log_errors", $pconf);
        if (lc($loge) ne "on") {
            &phpini::save_directive($pconf, "log_errors", "On");
            }
        &flush_file_lines($i->[1], undef, 1);
        &unlock_file($i->[1]);
        }
    if ($mode eq 'fcgid') {
        if ($p eq "web") {
            &register_post_action(\&restart_apache);
            }
        elsif ($p) {
            &plugin_call($p, "feature_restart_web_php", $d);
            }
        }
    }
else {
    return "PHP error log cannot be set in $mode mode";
    }
$d->{'php_error_log'} = $phplog || "";
if ($phplog && !-r $phplog) {
    # Make sure the log file exists
    &open_tempfile_as_domain_user($d, PHPLOG, ">$phplog", 1, 1);
    &close_tempfile_as_domain_user($d, PHPLOG);
    }
&push_all_print();
&set_all_null_print();
&modify_logrotate($d, $oldd);
&pop_all_print();
return undef;
}

# can_php_error_log(&domain|mode)
# Returns 1 if the PHP error log can be set for a domain or PHP mode
sub can_php_error_log
{
my ($mode) = @_;
$mode = &get_domain_php_mode($mode) if (ref($mode));
return $mode ne "none" && $mode ne "mod_php";
}

# get_default_php_error_log(&domain)
# Returns the default PHP log path for a domain, based on the template
sub get_default_php_error_log
{
my ($d) = @_;
my $tmpl = &get_template($d->{'template'});
my $path = $tmpl->{'php_log_path'} || "logs/php_log";
$path = &substitute_domain_template($path, $d);
$path = $d->{'home'}.'/'.$path if ($path !~ /^\//);
return $path;
}

# copyable_fpm_configs(&conf)
# Returns only FPM configs that should be copied when changing version,
# cloning or restoring a backup
sub copyable_fpm_configs
{
my ($confs) = @_;
my @rv;
foreach my $pv (@$confs) {
    push(@rv, $pv) if ($pv->[0] =~ /^(php_value|php_admin_value|env)\[/ ||
                   $pv->[0] =~ /^pm\./);
    }
return @rv;
}

1;


:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ ok ]

:: Make Dir ::
 
[ ok ]
:: Make File ::
 
[ ok ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0161 ]--