#!/usr/local/bin/perl

=head1 modify-php-ini.pl

Changes PHP variables for some or all domains.

This command can be used to change the value of a PHP configuration variable
(set in the php.ini file, FPM .conf file and Apache configuration) for one or
many virtual servers at once. The servers to update can be selected with the
C<--domain> or C<--user> flags, or you can choose to modify them all with the
C<--all-domains> option.

If your system supports multiple PHP versions, you can limit the changes
to the config for a specific version with the C<--php-version> flag followed
by a number, like 7.4 or 8.2.

The variables to change are set with the C<--ini-name> flag, which can be
given multiple times to change more than one variable. The new values are
set either with the C<--ini-value> flag (followed by a number or string),
or the C<--no-ini-value> flag to completely remove a setting.

When updating PHP-FPM configuration, the C<--fpm-admin-value> flag can be used
to force a setting to be written as an admin-level directive (C<php_admin_value>),
and the C<--fpm-value> flag can be used to force a regular user-level directive
(C<php_value>). If neither flag is given, the existing directive type is
preserved where possible.

=cut

package virtual_server;
if (!$module_name) {
	$main::no_acl_check++;
	$ENV{'WEBMIN_CONFIG'} ||= "/etc/webmin";
	$ENV{'WEBMIN_VAR'} ||= "/var/webmin";
	if ($0 =~ /^(.*)\/[^\/]+$/) {
		chdir($pwd = $1);
		}
	else {
		chop($pwd = `pwd`);
		}
	$0 = "$pwd/modify-php-ini.pl";
	require './virtual-server-lib.pl';
	$< == 0 || die "modify-php-ini.pl must be run as root";
	}
&licence_status();
&foreign_require("phpini");
@OLDARGV = @ARGV;
&set_all_text_print();

# Parse command-line args
while(@ARGV > 0) {
	my $a = shift(@ARGV);
	if ($a eq "--domain") {
		push(@domains, shift(@ARGV));
		}
	elsif ($a eq "--user") {
		push(@users, shift(@ARGV));
		}
	elsif ($a eq "--all-domains") {
		$all_doms = 1;
		}
	elsif ($a eq "--ini-name") {
		push(@ini_names, shift(@ARGV));
		}
	elsif ($a eq "--ini-value") {
		push(@ini_values, shift(@ARGV));
		}
	elsif ($a eq "--no-ini-value") {
		push(@ini_values, undef);
		}
	elsif ($a eq "--fpm-admin-value") {
		$type = 1;
		}
	elsif ($a eq "--fpm-value") {
		$type = 0;
		}
	elsif ($a eq "--php-version") {
		$php_ver = shift(@ARGV);
		}
	elsif ($a eq "--multiline") {
		$multiline = 1;
		}
	elsif ($a eq "--help") {
		&usage();
		}
	else {
		&usage("Unknown parameter $a");
		}
	}

# Validate parameters
@domains || @users || $all_doms || usage("No domains to modify specified");
@ini_names || &usage("The --ini-name parameter must be given at least once");
@ini_names == @ini_values ||
	&usage("The number of names and values must be the same");

# Get the domains
if (@domains || @users) {
	@doms = &get_domains_by_names_users(\@domains, \@users, \&usage);
	}
else {
	@doms = &list_domains();
	}

# Do each domain
foreach my $d (@doms) {
	# Check if we can manage this domain
	&$first_print("Updating $d->{'dom'} ..");
	if (!&domain_has_website($d) || $d->{'alias'}) {
		&$second_print(".. no website enabled");
		next;
		}

	# Update php.ini files
	$mode = &get_domain_php_mode($d);
	if ($mode ne "mod_php") {
		# Get the ini files
		@inis = &list_domain_php_inis($d);
		if ($php_ver) {
			@inis = grep { $_->[0] == $php_ver } @inis;
			if (!@inis) {
				&$second_print(".. no PHP configuration for ".
					       "version $php_ver found");
				next;
				}
			}

		# Update settings in each one
		my ($inifile, $ininame, $inivalue);
		eval {
			local $main::error_must_die = 1;
			foreach $ini (@inis) {
				$inifile = $ini->[1];
				&lock_file($ini->[1]);
				$conf = &phpini::get_config($ini->[1]);
				for(my $i=0; $i<@ini_names; $i++) {
					$ininame = $ini_names[$i];
					$inivalue = $ini_values[$i];
					&phpini::save_directive(
						$conf, $ini_names[$i],
						$ini_values[$i]);
					}
				&flush_file_lines($ini->[1], undef, 1);
				&unlock_file($ini->[1]);
				}
			};
		if ($@) {
			&$second_print(".. failed to update \"$inifile\" with ".
				"setting \"$ininame\" to \"$inivalue\": $@");
			next;
			}
		}

	# Update Apache PHP directives
	$mod_php = 0;
	if ($d->{'web'} && &get_apache_mod_php_version()) {
		&obtain_lock_web($d);
		my @ports;
		push(@ports, $d->{'web_port'}) if ($d->{'web'});
		push(@ports, $d->{'web_sslport'}) if ($d->{'ssl'});
		foreach my $port (@ports) {
			my ($virt, $vconf, $conf) = &get_apache_virtual(
							$d->{'dom'}, $port);
			next if (!$virt);

			# Find currently set PHP variables
			my @phpv = &apache::find_directive(
					"php_value", $vconf);
			for(my $i=0; $i<@ini_names; $i++) {
				my $found = 0;
				for(my $j=0; $j<@phpv; $j++) {
					if ($phpv[$j] =~ /^(\S+)\s/ &&
					    $1 eq $ini_names[$i]) {
						if (defined($ini_values[$i])) {
							$phpv[$j] = $ini_names[$i]." ".$ini_values[$i];
							}
						else {
							splice(@phpv, $j, 1);
							}
						$found = 1;
						last;
						}
					}
				if (!$found) {
					push(@phpv, $ini_names[$i]." ".
						    $ini_values[$i]);
					}
				}
			&apache::save_directive("php_value",
						\@phpv, $vconf, $conf);
			&flush_file_lines($virt->{'file'});
			&release_lock_web($d);
			&register_post_action(\&restart_apache);
			$mod_php = 1;
			}
		}

	# Update PHP-FPM pool file settings
	if ($mode eq "fpm") {
		for(my $i=0; $i<@ini_names; $i++) {
			&save_php_fpm_ini_value($d, $ini_names[$i],
						$ini_values[$i], undef, $type);
			}
		}

	&$second_print(".. updated ",scalar(@ini_names)," variables in ",
		       scalar(@inis)," files",
		       ($mod_php ? " and Apache configuration" : "").
		       ($mode eq "fpm" ? " and FPM configuration" : ""));
	}

&run_post_actions();
&virtualmin_api_log(\@OLDARGV);

sub usage
{
print "$_[0]\n\n" if ($_[0]);
print "Changes PHP variables for some or all domains.\n";
print "\n";
print "virtualmin modify-php-ini --domain name | --user name | --all-domains\n";
print "                         [--php-version number]\n";
print "                         <--ini-name name>+ <--ini-value value>+\n";
exit(1);
}
