#!/bin/bash
### Copyright 1999-2026. WebPros International GmbH. All rights reserved.
#

#
# Plesk script
#



#default values

product_default_conf()
{

PRODUCT_ROOT_D=/opt/psa
PRODUCT_RC_D=/etc/init.d
PRODUCT_ETC_D=/opt/psa/etc
PLESK_LIBEXEC_DIR=/usr/lib/plesk-9.0
HTTPD_VHOSTS_D=/var/www/vhosts
HTTPD_CONF_D=/etc/apache2
HTTPD_INCLUDE_D=/etc/apache2/conf-enabled
HTTPD_BIN=/usr/sbin/apache2
HTTPD_LOG_D=/var/log/apache2
HTTPD_SERVICE=apache2
QMAIL_ROOT_D=/var/qmail
PLESK_MAILNAMES_D=/var/qmail/mailnames
RBLSMTPD=/usr/sbin/rblsmtpd
NAMED_RUN_ROOT_D=/var/named/run-root
WEB_STAT=/usr/bin/webalizer
MYSQL_VAR_D=/var/lib/mysql
MYSQL_BIN_D=/usr/bin
MYSQL_SOCKET=/var/run/mysqld/mysqld.sock
PGSQL_DATA_D=/var/lib/postgresql/14/main
PGSQL_CONF_D=/etc/postgresql/14/main
PGSQL_BIN_D=/usr/lib/postgresql/14/bin
DUMP_D=/var/lib/psa/dumps
DUMP_TMP_D=/tmp
MAILMAN_ROOT_D=/usr/lib/mailman
MAILMAN_VAR_D=/var/lib/mailman
PYTHON_BIN=/usr/bin/python2
GPG_BIN=/usr/bin/gpg
TAR_BIN=/usr/lib/plesk-9.0/sw-tar
AWSTATS_ETC_D=/etc/awstats
AWSTATS_BIN_D=/usr/lib/cgi-bin
AWSTATS_TOOLS_D=/usr/share/awstats/tools
AWSTATS_DOC_D=/usr/share/awstats
OPENSSL_BIN=/usr/bin/openssl
LIB_SSL_PATH=/lib/libssl.so
LIB_CRYPTO_PATH=/lib/libcrypto.so
CLIENT_PHP_BIN=/opt/psa/bin/php-cli
SNI_SUPPORT=true
APS_DB_DRIVER_LIBRARY=/usr/lib/x86_64-linux-gnu/sw/libmysqlserver.so.2.0
SA_MAX_MAIL_SIZE=256000

}

# echo message to product log, also to console in debug mode
p_echo()
{
    if [ -n "$product_log" ] ; then
        echo "$@" >> "$product_log" 2>&1
    fi
    if [ -n "$PLESK_INSTALLER_DEBUG" -o -n "$PLESK_INSTALLER_VERBOSE" -o -z "$product_log" ] ; then
        echo "$@" >&2
    fi
}

# same as p_echo, but without new line
pnnl_echo()
{
	p_echo -n "$@"
}

int_err()
{
	report_problem "internal" "Internal error: $@"
	exit 1
}

p_see_product_log()
{
	log_is_in_dev "${product_log}" || printf " (see log file: ${product_log})" >&2
}

warn()
{
	local inten="$1"

	if [ -n "$PLESK_INSTALLER_DEBUG" -o -n "$PLESK_INSTALLER_VERBOSE" ]; then
		p_echo
		p_echo "WARNING!"
		pnnl_echo "Some problems are found during $inten"
		p_see_product_log
		p_echo
		p_echo "Continue..."
		p_echo
	fi

	report_problem "warning" "Warning: $inten"
}

detect_vz()
{
	[ -z "$PLESK_VZ_RESULT" ] || return $PLESK_VZ_RESULT

	PLESK_VZ_RESULT=1
	PLESK_VZ=0
	PLESK_VE_HW_NODE=0
	PLESK_VZ_TYPE=

	local issue_file="/etc/issue"
	local vzcheck_file="/proc/self/status"
	[ -f "$vzcheck_file" ] || return 1

	local env_id=`sed -ne 's|^envID\:[[:space:]]*\([[:digit:]]\+\)$|\1|p' "$vzcheck_file"`
	[ -n "$env_id" ] || return 1
	if [ "$env_id" = "0" ]; then
		# Either VZ/OpenVZ HW node or unjailed CloudLinux
		PLESK_VE_HW_NODE=1
		return 1
	fi

	if grep -q "CloudLinux" "$issue_file" >/dev/null 2>&1 ; then
		return 1
	fi

	if [ -f "/proc/vz/veredir" ]; then
		PLESK_VZ_TYPE="vz"
	elif [ -d "/proc/vz" ]; then
		PLESK_VZ_TYPE="openvz"
	fi

	PLESK_VZ=1
	PLESK_VZ_RESULT=0
	return 0
}

# detects lxc and docker containers
detect_lxc()
{
	[ -z "$PLESK_LXC_RESULT" ] || return $PLESK_LXC_RESULT
	PLESK_LXC_RESULT=1
	PLESK_LXC=0
	if  { [ -f /proc/1/cgroup ] && grep -q 'docker\|lxc' /proc/1/cgroup; } || \
		{ [ -f /proc/1/environ ] && cat /proc/1/environ | tr \\0 \\n | grep -q "container=lxc"; };
	then
		PLESK_LXC_RESULT=0
		PLESK_LXC=1
	fi
	return "$PLESK_LXC_RESULT"
}

problems_log_tail()
{
	[ -f "$product_problems_log" ] || return 0
	{
		tac "$product_problems_log" | awk '/^START/ { exit } { print }' | tac
	} 2>/dev/null
}

product_log_tail()
{
	[ -f "$product_log" ] || return 0
	{
		tac "$product_log" | awk '/^START/ { exit } { print }' | tac
	} 2>/dev/null
}

product_and_problems_log_tail()
{
	product_log_tail
	[ "$product_log" = "$product_problems_log" ] || problems_log_tail
}

log_is_in_dev()
{
	test "${1:0:5}" = "/dev/"
}
### Copyright 1999-2026. WebPros International GmbH. All rights reserved.

construct_report_template()
{
	local severity="${1:-error}"
	local summary="$2"

	local update_ticket="`get_update_ticket`"

	set_error_report_source
	set_error_report_component
	set_error_report_params
	set_error_report_environment

	true construct_report_code construct_report_debug construct_report_message

cat <<-EOL
<?xml version="1.0" encoding="UTF-8" ?>
<error>
  <source>$report_source</source>
  <severity>$severity</severity>
  <datetime>`date --iso-8601=seconds`</datetime>

  <component>$report_component</component>
  <summary><![CDATA[`echo "$summary" | sed -e 's/\]\]>/] ]>/g'`]]></summary>
  <message encoding="base64">`construct_report_message | base64`</message>

  <additional_info>
    <component_params encoding="base64">$report_params</component_params>
    <code encoding="base64">`construct_report_code | base64`</code>
    <debug encoding="base64">`construct_report_debug | base64`</debug>
    <environment encoding="base64">$report_environment</environment>
    <update_ticket>$update_ticket</update_ticket>
  </additional_info>
</error>
EOL
}

construct_report_code()
{
	local call_level=${1:-5}
	local func_level=$[call_level - 1]
	local lineno_func=${BASH_LINENO[ $func_level ]}
	local script_name=${BASH_SOURCE[ $[func_level + 1] ]}

	echo "# Call of ${FUNCNAME[$func_level]}() from ${FUNCNAME[$[func_level + 1]]}() at `readlink -m $script_name`:${BASH_LINENO[$func_level]}"
	head -n $[lineno_func + 4] "$script_name" 2>/dev/null | tail -n 8
}

construct_report_debug()
{
	local call_level=${1:-5}
	call_level=$[call_level-1]

	# Generate calls stack trace.
	for i in `seq $call_level ${#FUNCNAME[@]}`; do
		[ "${FUNCNAME[$i]}" != "main" ] || break

		local func_call="`sed -n -e "${BASH_LINENO[$i]}p" "${BASH_SOURCE[$[i+1]]}" 2>/dev/null |
			sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`"
		[ -n "$func_call" -a -z "${func_call##*${FUNCNAME[$i]}*}" ] || func_call="${FUNCNAME[$i]}"
		echo "#$[i - $call_level] `readlink -m ${BASH_SOURCE[$[i+1]]}`(${BASH_LINENO[$i]}): $func_call"
	done
}

construct_report_message()
{
	product_and_problems_log_tail

	echo ""
	if [ -n "$report_context" ]; then
		echo "Context: $report_context"
		echo ""
	fi
	if [ -n "$RP_LOADED_PATCHES" ]; then
		echo "Loaded runtime patches: $RP_LOADED_PATCHES"
		echo ""
	fi
}

# Construct report to send it to our errors tracker
construct_report()
{
	local severity="${1:-error}"
	local summary="$2"

	[ -n "$summary" ] || int_err "Unable to send error report. Some parameters are not defined."

	set_error_report_source
	get_product_versions

	construct_report_template "$severity" "$summary" \
		| $PRODUCT_ROOT_D/admin/bin/send-error-report --version "$product_this_version" $report_source >/dev/null 2>&1
}

# Use this function to report failed actions.
# Typical report should contain
# - reason or problem description (example: file copying failed)
# - how to resolve or investigate problem (example: check file permissions, free disk space)
# - how to re-run action (example: perform specific command, restart bootstrapper script, run installation again)
report_problem()
{
	local severity="${1:-error}"

	# Get first string of error as a summary of report
	shift

	local summary="$1"

	[ -n "$product_problems_log" ] || product_problems_log="/dev/stderr"

	p_echo
	if [ "0$problems_occured" -eq 0 ]; then
		echo "***** $process problem report *****" >> "$product_problems_log" 2>&1
	fi
	for problem_message in "$@"; do
		p_echo "$problem_message"
		if [ "$product_log" != "$product_problems_log" ]; then
			echo "$problem_message" >> "$product_problems_log" 2>&1
		fi
	done
	p_echo

	construct_report "$severity" "$summary"

	[ -n "$PLESK_INSTALLER_DEBUG" -o -n "$PLESK_INSTALLER_VERBOSE" ] || \
		product_log_tail

	problems_occured=1
}

set_error_report_source()
{
	[ -z "$1" ] || report_source="$1"
	[ -n "$report_source" ] || {
		if [ -n "$PACKAGE_ID" -o -n "$PACKAGE_ACTION" -o -n "$PACKAGE_NAME" -o -n "$PACKAGE_VERSION" ]; then
			report_source="install"
		else
			report_source="backend"
		fi
	}
}

set_error_report_component()
{
	local component="$1"

	if [ "$report_source" = "install" ]; then
		[ -n "$report_component" ] || report_component="$PACKAGE_ID"
		return 0
	fi

	[ -z "$component" ] || report_component="$1"
	[ -n "$report_component" ] || report_component="`basename $0`"
}

set_error_report_params()
{
	if [ "$report_source" = "install" ]; then
		[ -n "$report_params" ] || report_params="`echo "$PACKAGE_ACTION of $PACKAGE_NAME $PACKAGE_VERSION" | base64`"
		return 0
	fi

	[ -z "$*" ] || report_params="`echo "$*" | base64`"
	[ -n "$report_params" ] || report_params="`echo "$PLESK_SCRIPT_COMMAND_LINE" | base64`"
}

detect_virtualization()
{
	detect_vz
	detect_lxc
	local is_docker="`[ -f "/.dockerenv" ] && echo yes || :`"
	local systemd_detect_virt_ct="`/usr/bin/systemd-detect-virt -c 2>/dev/null | grep -v '^none$' || :`"
	local systemd_detect_virt_vm="`/usr/bin/systemd-detect-virt -v 2>/dev/null | grep -v '^none$' || :`"
	local virt_what="`/usr/sbin/virt-what 2>/dev/null | xargs || :`"

	if [ -n "$is_docker" ]; then
		echo "docker $virt_what"
	elif [ "$PLESK_VZ" = "1" ]; then
		echo "${PLESK_VZ_TYPE:-virtuozzo}"
	elif [ "$PLESK_LXC" = "1" ]; then
		echo "lxc $virt_what"
	elif [ -n "$systemd_detect_virt_ct" ]; then
		echo "$systemd_detect_virt_ct $systemd_detect_virt_vm"
	elif [ -n "$virt_what" ]; then
		echo "$virt_what"
	elif [ -n "$systemd_detect_virt_vm" ]; then
		echo "$systemd_detect_virt_vm"
	fi
}

default_error_report_environment()
{
	local virtualization="`detect_virtualization`"

	if [ -n "$virtualization" ]; then
		echo "virtualization: $virtualization"
	fi
}

set_error_report_environment()
{
	[ -z "$*" ] || report_environment="`echo "$*" | base64`"
	[ -n "$report_environment" ] || report_environment="`default_error_report_environment | base64`"
}

get_update_ticket()
{
	[ -r $PRODUCT_ROOT_D/var/update_ticket ] && cat $PRODUCT_ROOT_D/var/update_ticket | awk '{$1=$1};1'
}

get_product_versions()
{
	# Don't use global variables set elsewhere in this code. Use substitutions if needed.
	local prod_root_d="/opt/psa"

	product_name="psa"

	if [ -z "$product_this_version" ]; then
		# 1. Try to fetch version from file created by bootstrapper (should be 3-component).
		product_this_version="`cat "/var/lock/plesk-target-version" 2>/dev/null`"
		# 2. Fallback to $PRODUCT_ROOT_D/version (should be 3-component).
		if [ -z "$product_this_version" -a -r "$prod_root_d/version" ]; then
			product_this_version="`awk '{ print $1 }' "$prod_root_d/version"`"
		fi
		# 3. Fallback to hardcoded version (2-component). This may cause some other code to fail.
		if [ -z "$product_this_version" ]; then
			product_this_version="18.0"
			echo "Unable to determine \$product_this_version, will use less precise value '$product_this_version'" >&2
		fi
	fi

	product_version="$product_this_version"

	if [ -z "$product_prev_version" ]; then
		if [ -r "$prod_root_d/version.upg" ]; then
			product_prev_version=`awk '{ print $1 }' "$prod_root_d/version.upg"`
		elif [ -r "$prod_root_d/version" ]; then
			product_prev_version=`awk '{ print $1 }' "$prod_root_d/version"`
		else
			product_prev_version="$product_this_version"
		fi
	fi
}

### Copyright 1999-2026. WebPros International GmbH. All rights reserved.

err()
{
	echo "$*" >&2
}

php_module_extract_name()
{
	local config="$1"
	local state="${2:-}"  # "", enabled, disabled
	local short="$3"

	local prefix=""
	[ ! "$state" = "enabled" ] || prefix='^\s*'
	[ ! "$state" = "disabled" ] || prefix='^\s*;\s*'

	local module=$(perl -nle 'print $2 if /'"${prefix}"'(?:zend_|)extension\s*=\s*([\"'"\'"']?)(.*)\.so\1/' "$config")

	if [ -z "$module" -a -n "$short" ]; then
		perl -nle 'print $2 if /'"${prefix}"'(?:zend_|)extension\s*=\s*([\"'"\'"']?)(.*)\1/' "$config"
	else
		echo "$module"
	fi
}

php_module_is_enabled()
{
	local config="$1"
	local ext_s="$2"
	local short="$3"
	php_module_extract_name "$config" 'enabled' "${short}" | grep -q --ignore-case "^$ext_s\$"
}

php_module_is_disabled()
{
	local config="$1"
	local ext_s="$2"
	local short="$3"
	php_module_extract_name "$config" 'disabled' "${short}" | grep -q --ignore-case "^$ext_s\$"
}

php_module_enable()
{
	local config="$1"
	local ext_s="$2"
	local short="$3"

	local suffix=
	[ -n "$short" ] || suffix='\.so'

	perl -pi -e 's#^\s*;\s*((?:zend_|)extension\s*=\s*([\"'"\'"']?)'"${ext_s}"''"${suffix}"'\b\2)#\1#gi' "$config"
}

php_module_disable()
{
	local config="$1"
	local ext_s="$2"
	local short="$3"

	local suffix=
	[ -n "$short" ] || suffix='\.so'

	perl -pi -e 's#^\s*((?:zend_|)extension\s*=\s*([\"'"\'"']?)'"${ext_s}"''"${suffix}"'\b\2)#; \1#gi' "$config"
}

pecl_package_to_ext_name()
{
	# PECL package syntax: [channel/]name[-version|stability][.tar|tgz|xml]
	local package="$1"
	local file_name="${package##*/}"
	local name_version="${file_name%.gz}"
	name_version="${name_version%.[ttxz][agmi][rzlp]}"
	local ext_name="${name_version%%[-]*}"
	echo "$ext_name"
}

pecl_package_to_version_or_stability()
{
	local package="$1"
	local file_name="${package##*/}"
	local version_and_extension="${file_name#*-}"
	if [ "$version_and_extension" = "$file_name" ]; then
		version_and_extension=
	else
		version_and_extension="${version_and_extension%.gz}"
	fi
	local version="${version_and_extension%.[ttxz][agmi][rzlp]}"
	echo "$version"
}

is_ioncube_loader_package_name()
{
	[[ "$1" =~ ^ioncube_loader(_lin_[0-9]+\.[0-9]+)?(-[0-9.]+)?$ ]]
}

have()
{
	local needle="$1"
	[ -n "$needle" ] || {
		err "Error: not enough arguments for the 'have' function"
		return 1
	}

	shift
	[[ " $* " =~ " $needle " ]]
}

prioritize_configs_by_name()
{
	local extension_name="`basename "$1" .so`"
	extension_name="${extension_name,,}"
	[ -n "$extension_name" ] || {
		cat -
		return 0
	}

	local exact_matches=()
	local partial_matches=()
	local non_matches=()

	local path=
	while IFS= read -r path; do
		[ -n "$path" ] || continue
		local ext="`basename "$path" .ini`"
		case "${ext,,}" in
			"$extension_name") exact_matches+=("$path") ;;
			*"$extension_name"*) partial_matches+=("$path") ;;
			*) non_matches+=("$path") ;;
		esac
	done

	[ -z "${exact_matches[*]}"   ] || printf '%s\n' "${exact_matches[@]}"
	[ -z "${partial_matches[*]}" ] || printf '%s\n' "${partial_matches[@]}"
	[ -z "${non_matches[*]}"     ] || printf '%s\n' "${non_matches[@]}"
}

# vim:ft=sh:
#!/bin/bash
### Copyright 1999-2026. WebPros International GmbH. All rights reserved.
# vim:ft=sh:

option_error()
{
	err "$*"
	err ""
	usage
}

debug()
{
	if [ $_V -ge 1 ]; then
		echo "debug: $*" >&2
	fi
}

usage()
{
cat << EOT >&2
Usage: $prog [OPTIONS]
    -p,--php-cli                Appropriate PHP cli for operations. Mandatory.
    -c,--php-ini                php.ini config for operations. Mandatory.
    -l,--list                   Display extensions availiable for load
    -s,--list-all               Display all extensions (loadable and static)
    -a,--list-installable       Display installable extensions as JSON
    -e,--enable=ext             Enable specified extension. Can be specified several times.
    -d,--disable=ext            Disable specified extension. Can be specified several times.
    -i,--install=ext            Install extension. Can be specified several times.
    -u,--uninstall=ext          Uninstall extension. Can be specified several times.
    -v,--verbose                Be more verbose.
    -h,--help                   Display this message.

Notes on --install and --uninstall commands:
    * no checks whether the corresponding extensions are already installed and provided
      by system package manager are performed (i.e. running these commands for packaged
      extensions is destructive);
    * these commands accept PECL packages as arguments - this means you may specify
      versions, e.g. '--install redis-5.3.5', or states, e.g. '--install redis-beta',
      using '--install redis' also works and will use the latest stable version;
    * these commands mostly use PECL, except when operating on 'ioncube_loader' or
      'ioncube_loader_lin_<php_major>.<php_minor>' (preferable name), in which case
      the extension is downloaded directly from the vendor site;
    * '--install' expects that required system dependencies are already installed,
      this includes PHP development files, compiler, make, libraries' development files;
    * no version compatibility checks are performed except those that PECL does;
    * neither of the commands restarts or reloads any PHP-FPM or web server services.

Notes on --enable and --disable commands:
    * these commands accept extension names as arguments, but PECL packages are also
      accepted for uniformity with '--install' and '--uninstall' options (note that
      in general PECL package name may not match corresponding extension name).
EOT
exit 1
}

get_extension_type()
{
# Check got from ext/standard/dl.c. But may be we should attempt to load via dl or -d
# However, dl can be disabled, and failure of load in -d doesn't cause php to return 1

	local ext_file="$extension_dir/$1.so"
	if [ ! -e "$ext_file" ] ; then
		ext_file="$extension_dir/$(echo "$1" | tr '[:upper:]' '[:lower:]').so"
	fi


	if readelf -s --wide "$ext_file" | awk '{print $8}' 2>/dev/null | grep -q -E '^(_|)zend_extension_entry$'; then
		debug "$ext_file: zend_extension detected"
		echo "zend_extension"
		return
	elif readelf -s --wide "$ext_file" | awk '{print $8}' 2>/dev/null | grep -q -E '^(_|)get_module$'; then
		debug "$ext_file: common_extension detected"
		echo "extension"
		return
	else
		debug "$ext_file: not a php extension"
		echo ""
	fi
}

find_config()
{
	local ext="$1"
	local status="$2"
	for config_path in `get_configs | prioritize_configs_by_name "$ext"`; do
		php_module_extract_name "$config_path" "$status" "$shortname_allowed" |
			xargs -r -n1 basename |
			grep --ignore-case "^$ext\$" >/dev/null ||
			continue
		echo "$config_path"
		return
	done
}

enmod()
{
	local ext="$1"
	if [ -z "$ext" ]; then
		err "empty extension specified"
		return 1
	fi

	local config=$(find_config "$ext")
	local ext_s="$ext"
	local ext_type=$(get_extension_type "$ext")

	if [ -z "$config" ]; then
		err "cannot find config for extension '$ext'"
		return 1
	fi

	if [ "$ext_type" != "extension" -a "$ext_type" != "zend_extension" ]; then
		err "$ext: invalid extension format - not a PHP library"
		return 1
	fi

	if [ "$ext_type" = "zend_extension" ]; then
		ext_s="$extension_dir/${ext}"
	fi

	if php_module_is_enabled "$config" "$ext" "$shortname_allowed"; then
		debug "$ext (${ext}.so) already enabled in $config, do nothing"
		return 0
	fi

	if [ "$ext_type" = "zend_extension" ] && php_module_is_enabled "$config" "$ext_s" "$shortname_allowed"; then
		debug "$ext (${ext_s}.so) already enabled in $config, do nothing"
		return 0
	fi

	if php_module_is_disabled "$config" "$ext" "$shortname_allowed"; then
		debug "$ext (${ext}.so) disabled in $config, switch it on"
		php_module_enable "$config" "$ext" "$shortname_allowed"
		return 0
	fi

	if [ "$ext_type" = "zend_extension" ] && php_module_is_disabled "$config" "$ext_s" "$shortname_allowed"; then
		debug "$ext (${ext_s}.so) disabled in $config, switch it on"
		php_module_enable "$config" "$ext_s" "$shortname_allowed"
		return 0
	fi

	debug "$ext is not found in $config, add new line"
	echo "${ext_type}=${ext_s}.so" >> "$config"
	return 0
}

dismod()
{
	local ext="$1"
	if [ -z "$ext" ]; then
		err "empty extension specified"
		return 1
	fi

	if [ -z "`find_config "$ext"`" ]; then
		err "cannot find config for extension '$ext'"
		return 1
	fi

	# Disable the extension in all configuration files
	local config=
	while config="`find_config "$ext" "enabled"`" && [ -n "$config" ]; do
		local ext_s="$ext"
		local ext_type=$(get_extension_type "$ext")

		if [ "$ext_type" != "extension" -a "$ext_type" != "zend_extension" ]; then
			err "$ext: invalid extension format - not a PHP library"
			return 1
		fi

		if [ "$ext_type" = "zend_extension" ]; then
			ext_s="$extension_dir/${ext}"
		fi

		if php_module_is_disabled "$config" "$ext" "$shortname_allowed"; then
			debug "$ext ($ext) already disabled in $config, do nothing"
			continue
		fi

		if [ "$ext_type" = "zend_extension" ] && php_module_is_disabled "$config" "$ext_s" "$shortname_allowed"; then
			debug "$ext (${ext_s}.so) already disabled in $config, do nothing"
			continue
		fi

		if php_module_is_enabled "$config" "$ext" "$shortname_allowed"; then
			debug "$ext (${ext}.so) enabled in $config, switch it off"
			php_module_disable "$config" "$ext" "$shortname_allowed"
		fi

		if [ "$ext_type" = "zend_extension" ] && php_module_is_enabled "$config" "$ext_s" "$shortname_allowed"; then
			debug "$ext (${ext_s}.so) enabled in $config, switch it off"
			php_module_disable "$config" "$ext_s" "$shortname_allowed"
		fi
	done

	return 0
}

get_native_php_version()
{
	local php_bin="`readlink -e "$php_cli"`"
	if [[ "$php_bin" =~ ^/usr/bin/php.*$ ]] ; then
		echo "${php_bin:12}"
	fi
}

native_debian_php_extensions_op()
{
	local enmod_or_dismod_bin="$1"
	shift

	local extensions=("$@")
	for i in "${!extensions[@]}"; do
		# make sure ionCube Loader has the same name as the config under mods-availiable/
		! is_ioncube_loader_package_name "${extensions[$i]}" || extensions[$i]="ioncube-loader"
	done

	local php_version=`get_native_php_version`
	if [ -z "$php_version" ] ; then
		if [ -L "$php_cli" ] ; then
			err "Debian native PHP binary has unexpected link: '`readlink -e "$php_cli"`'. The endpoint should be at /usr/bin and named phpX.Y"
		else
			err "Unexpected path for debian native PHP binary: '$php_cli'. The binary should be at /usr/bin and named phpX.Y"
		fi
		return 1
	fi

	"$enmod_or_dismod_bin" "-v" "$php_version" "${extensions[@]}"
}

enable_extensions()
{

	[ -n "$1" ] || return 0

	if check_native_debian_php; then
		native_debian_php_extensions_op "$debian_mods_enmod_bin" "$@"
		return $?
	fi

	for i in "$@"; do
		if ! enmod $i; then
			err "Cannot enable extension '$i'"
			return 1
		fi
	done
}

disable_extensions()
{
	[ -n "$1" ] || return 0

	if check_native_debian_php; then
		native_debian_php_extensions_op "$debian_mods_dismod_bin" "$@"
		return $?
	fi

	for i in "$@"; do
		if ! dismod $i; then
			err "Cannot disable extension '$i'"
			return 1
		fi
	done
}

check_native_debian_php()
{

	if [ -n "${debian}" ]; then
		return $debian
	fi

	debian=1

	if [ ! -x "/usr/sbin/phpenmod" ]; then
		return "$debian"
	fi

	local php_version=`get_native_php_version`
	[ -n "$php_version" ] || return "$debian"

	debian=0
	debian_mods_availiable_dir="/etc/php/$php_version/mods-available"
	debian_mods_enmod_bin="/usr/sbin/phpenmod"
	debian_mods_dismod_bin="/usr/sbin/phpdismod"
	return "$debian"
}

get_configs()
{
	if ! check_native_debian_php; then
		_get_configs
		return
	fi

# On debian, we have separate configs for enabled and disabled extensions.
	if [ "$1" = "enabled" ]; then
		_get_configs
		return
	fi

	{
		_get_configs
		ls $debian_mods_availiable_dir/*.ini
	} | xargs -n1 readlink -f | sort -u
}

_get_configs()
{
	$php_cli -c $php_ini -r 'print(php_ini_scanned_files());' | tr ',' '\n' | sort -u | grep -v '^$'
	if [ -f "$php_ini" ] ; then
		echo "$php_ini"
	fi
}

get_all_extensions()
{
	"$php_cli" -c "$php_ini" -m |
		sed -ne '/^\[PHP Modules\]$/,/^\[/ p' |
		sed -e '/^\[/ d' -e '/^\s*$/ d' |
		tr '[A-Z]' '[a-z]' |
		sort -u |
		while read -r ext; do
			case "$ext" in
				"ioncube loader") ext="ioncube_loader" ;;
				"zend opcache") ext="opcache" ;;
			esac
			echo "$ext"
		done
}

list_extensions()
{
	local list_all="$1"
	declare -a got_extensions=()

	_list_extensions_by_status()
	{
		local status="$1"
		local mark="$2"
		for config_path in `get_configs "$status"`; do
			for line in `php_module_extract_name "$config_path" "$status" "$shortname_allowed"`; do
				[ -n "$line" ] || continue
				[ -f "$extension_dir/${line}.so" -o -f "${line}.so" ] || continue
				local ext="`basename "$line"`"
				! have "$ext" "${got_extensions[@]}" || continue
				got_extensions+=("$ext")
				if is_ioncube_loader_package_name "$ext" && [ "$ext" != "ioncube_loader" ]; then
					# ensure ioncube_loader is not listed as "static" even if its name differs
					got_extensions+=("ioncube_loader")
				fi
				echo "$ext $mark"
			done
		done
	}

	_list_extensions_static()
	{
		local mark="$1"
		for ext in `get_all_extensions`; do
			! have "$ext" "${got_extensions[@]}" || continue
			got_extensions+=("$ext")
			echo "$ext $mark"
		done
	}

	_list_extensions_by_status "enabled" "on"
	_list_extensions_by_status "" "off"
	[ -z "$list_all" ] ||
		_list_extensions_static "static"
}

list_installable_extensions()
{
	product_default_conf

	local ioncube_loader="`list_installable_ioncube_loader_as_python_dict`"
	local python_bin="$PRODUCT_ROOT_D/bin/py3-python"
	"$pecl_bin" list-all |
		"$python_bin" -c '
import sys, operator, json

lines = [""]
for line in sys.stdin:
	# Process line continuations for description
	if line[:1].isspace():
		lines[-1] += line.lstrip()
	else:
		lines.append(line)

fields = ["package", "latest", "local", "description"]
packages = ['"$ioncube_loader"']
slice_start = {}
slice_end = {}
cookie_cutter = {}

for line in lines:
	# Header includes only 3 first columns
	if not slice_start and line.split() == [field.upper() for field in fields[:3]]:
		starts = {item.lower(): line.index(item) for item in line.split()}
		slice_start = {sf: starts.get(sf) for sf in fields}
		slice_end = {sf: starts.get(ef) for sf, ef in zip(fields, fields[1:])}
		continue
	# Then determine the 4th column parameters based on the first row
	if not cookie_cutter and slice_start.get("local") and not slice_end.get("local"):
		last_columns = line[slice_start["local"]:]
		if last_columns[:1].isspace():
			desc_start = slice_start["local"] + (len(last_columns) - len(last_columns.lstrip()))
		else:
			desc_start = slice_start["local"] + (len(last_columns) - len(last_columns.split(maxsplit=1)[1]))
		slice_start["description"] = slice_end["local"] = desc_start
		slice_end["description"] = None
		cookie_cutter = {field: operator.itemgetter(slice(slice_start[field], slice_end[field])) for field in fields}
	# Parse each table row
	if cookie_cutter:
		pkg = {field: cutter(line).strip() for field, cutter in cookie_cutter.items()}
		pkg["package"] = pkg["package"].rsplit("/", maxsplit=1)[-1]
		pkg["latest"] = pkg["latest"] or pkg["local"]
		packages.append(pkg)

json.dump(packages, sys.stdout, indent=2)
		'
}

check_runtime_for_pecl()
{
	[ -z "${CHECK_RUNTIME_FOR_PECL_DONE:-}" ] || return 0

	debug "Check 'temp_dir' configured in $pecl_bin"

	local pecl_temp_dir="`"$pecl_bin" config-get temp_dir`"
	if [ -d "$pecl_temp_dir" ]; then
		# PECL will create directory if needed.
		# 'pecl config-set' doesn't work, but 'pear config-set' does.
		! findmnt -o OPTIONS -nkT "$pecl_temp_dir" | grep -qwF noexec ||
			echo "WARNING: PECL temp_dir '$pecl_temp_dir' is mounted with 'noexec'." \
				"Extension installation will most likely fail." \
				"Remount the FS with 'exec' option or configure a new directory" \
				"using '${pecl_bin/pecl/pear} config-set temp_dir ... system'."
	fi

	CHECK_RUNTIME_FOR_PECL_DONE="yes"
}

install_extension_pecl()
{
	local package="$1"
	local ext="`pecl_package_to_ext_name "$package"`"
	local libname="$ext.so"

	check_runtime_for_pecl

	debug "Execute: $pecl_bin install $package"
	"$pecl_bin" install "$package" </dev/null || return $?

	if [ ! -e "$extension_dir/$libname" ] ; then
		libname="$(echo "$libname" | tr '[:upper:]' '[:lower:]')"
	fi

	debug "Set up correct permissions on $extension_dir/$libname"
	[ ! -x "/usr/sbin/restorecon" ] || /usr/sbin/restorecon "$extension_dir/$libname"

	local extension_type="extension"
	case $("$pecl_bin" info "$package" | grep "Release Type") in
		*"Zend extension"*)
			extension_type="zend_extension"
			;;
	esac

	debug "Create: $config_dir/$ext.ini (in disabled state)"
	if check_native_debian_php; then
		cat > "$config_dir/$ext.ini" <<-EOT
		; Installed by Plesk via 'pecl'
		; priority=20
		$extension_type=$libname
		EOT
	else
		cat > "$config_dir/$ext.ini" <<-EOT
		; Installed by Plesk via 'pecl'
		; $extension_type=$libname
		EOT
	fi
}

uninstall_extension_pecl()
{
	local package="$1"
	local ext="`pecl_package_to_ext_name "$package"`"

	disable_extensions "$ext"

	debug "Execute: $pecl_bin uninstall $package"
	"$pecl_bin" uninstall "$package" </dev/null || return $?

	debug "Remove: $config_dir/$ext.ini (should already be disabled)"
	rm -f "$config_dir/$ext.ini"
}

list_installable_ioncube_loader_as_python_dict()
{
	local arch="`uname -m`"
	arch="${arch//_/-}"
	local php_major_minor="${phpversion%.*}"
	local ext_name="ioncube_loader_lin_${php_major_minor}"
	local ext_file="$extension_dir/$ext_name.so"
	local installed_version="`! test -f "$ext_file" ||
		strings "$ext_file" | grep '^version:' | cut -d' ' -f2`"

	echo "{
		'package': '$ext_name',
		'latest': 'latest',
		'local': '$installed_version',
		'description': 'ionCube PHP Loader',
	}"
}

install_extension_ioncube_loader()
{
	local package="$1"
	local ext="`pecl_package_to_ext_name "$package"`"
	local version="`pecl_package_to_version_or_stability "$package"`"
	local arch="`uname -m`"
	arch="${arch//_/-}"
	local url="https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_${arch}${version:+_$version}.tar.gz"
	local php_major_minor="${phpversion%.*}"
	local ext_name="ioncube_loader_lin_${php_major_minor}"

	is_ioncube_loader_package_name "$ext" || return 1

	debug "Download ionCube Loaders archive from $url"
	local archive=
	archive="`mktemp -t ioncube_loaders_${arch}${version:+_$version}.XXXXXXXX.tar.gz`" || {
		err "Failed to create local file to download ionCube Loaders to"
		return 1
	}
	curl -m 300 --retry 3 -fsSL "$url" -o "$archive" &&
		[[ "`file -b --mime-type "$archive"`" =~ ^application/(gzip|x-gzip)$ ]] ||
	{
		err "Failed to download ionCube Loaders archive (is the version correct?) from: $url"
		rm -f "$archive"
		return 1
	}

	debug "Extract ionCube Loader extension from the archive to $extension_dir/$ext_name.so"
	tar -tzf "$archive" --strip-components 1 -C "$extension_dir" "ioncube/$ext_name.so" >/dev/null || {
		err "Downloaded ionCube Loaders archive doesn't contain expected $ext_name.so file"
		rm -f "$archive"
		return 1
	}
	tar -xzf "$archive" --strip-components 1 -C "$extension_dir" "ioncube/$ext_name.so" &&
		[ -f "$extension_dir/$ext_name.so" ] ||
	{
		err "Failed to unpack ionCube loader file $ext_name.so"
		rm -f "$archive"
		return 1
	}

	# Clean up
	rm -f "$archive"

	debug "Set up correct permissions on $extension_dir/$ext_name.so"
	# We'll consider 'execstack -c "$extension_dir/$ext_name.so"' excessive nowadays
	chown root:root "$extension_dir/$ext_name.so"
	chmod 0644 "$extension_dir/$ext_name.so"
	[ ! -x "/usr/sbin/restorecon" ] || /usr/sbin/restorecon "$extension_dir/$ext_name.so"

	# The loader should be declared first in PHP configuration, hence the config name or priority
	if check_native_debian_php; then
		local config="$config_dir/ioncube-loader.ini"
		debug "Create: $config (uncommented, but not enabled)"
		cat > "$config" <<-EOT
		; Installed by Plesk (non-packaged file)
		; priority=00
		zend_extension=$ext_name.so
		EOT
	else
		local config="$config_dir/00-ioncube-loader.ini"
		debug "Create: $config (in disabled state)"
		cat > "$config" <<-EOT
		; Installed by Plesk (non-packaged file)
		; zend_extension=$ext_name.so
		EOT
	fi
}

uninstall_extension_ioncube_loader()
{
	local package="$1"
	local ext="`pecl_package_to_ext_name "$package"`"
	local php_major_minor="${phpversion%.*}"
	local ext_name="ioncube_loader_lin_${php_major_minor}"

	is_ioncube_loader_package_name "$ext" || return 1

	disable_extensions "$ext"

	debug "Remove: $extension_dir/$ext_name.so and $config_dir/*ioncube-loader.ini (should already be disabled)"
	if check_native_debian_php; then
		rm -f "$config_dir/ioncube-loader.ini"
	else
		rm -f "$config_dir/00-ioncube-loader.ini"
	fi
	rm -f "$extension_dir/$ext_name.so"
}

install_extensions()
{
	for ext in "$@"; do
		debug "Installing '$ext'"
		if is_ioncube_loader_package_name "$ext"; then
			install_extension_ioncube_loader "$ext" || return $?
		else
			install_extension_pecl "$ext" || return $?
		fi
	done
}

uninstall_extensions()
{
	for ext in "$@"; do
		debug "Uninstalling '$ext'"
		if is_ioncube_loader_package_name "$ext"; then
			uninstall_extension_ioncube_loader "$ext" || return $?
		else
			uninstall_extension_pecl "$ext" || return $?
		fi
	done
}

prog="`basename $0`"

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
umask 022

TEMP=`getopt -o vhlsap:c:e:d:i:u: \
	--long help,verbose,php-cli:,php-ini:,list,list-all,list-installable,enable:,disable:,install:,uninstall: \
	-n "$prog" -- "$@"`

if [ $? != 0 ]; then
	echo "Internal Error: getopt cannot parse this command line: $@" >&2
	exit 1
fi

php_cli=
php_ini=
declare -a ext_enable=()
declare -a ext_disable=()
declare -a ext_install=()
declare -a ext_uninstall=()
list_cmd=
list_all_cmd=
list_installable_cmd=

_V=0
eval set -- "$TEMP"
while true ; do
	case "$1" in
		-p|--php-cli) php_cli="$2"; shift 2;;
		-c|--php-ini) php_ini="$2"; shift 2;;
		-e|--enable) ext_enable+=("`pecl_package_to_ext_name "$2"`"); shift 2;;
		-d|--disable) ext_disable+=("`pecl_package_to_ext_name "$2"`"); shift 2;;
		-i|--install) ext_install+=("$2"); shift 2;;
		-u|--uninstall) ext_uninstall+=("$2"); shift 2;;
		-l|--list) list_cmd="1"; shift;;
		-s|--list-all) list_all_cmd="1"; shift;;
		-a|--list-installable) list_installable_cmd="1"; shift;;
		-v|--verbose) let _V++; shift;;
		-h|--help) usage ;;
		--) shift ; break ;;
		*) echo "Unknown option '$1'" ; exit 1 ;;
	esac
done

if [ -z "$php_cli" ]; then
	option_error "No php cli (--php-cli) specified"
fi

if [ ! -x "$php_cli" ]; then
	option_error "PHP cli ($php_cli) should be executable"
fi

pecl_bin="`dirname "$php_cli"`/pecl"
if [ ! -x "$pecl_bin" -a \( -n "${ext_install[*]}" -o -n "${ext_uninstall[*]}" \) ]; then
	option_error "PECL binary ($pecl_bin) should be executable"
fi

if [ -z "$php_ini" ]; then
	option_error "No php ini (--php-ini) specified"
fi

if [ ! -f "$php_ini" ]; then
	warn "PHP ini ($php_ini) doesn't exist, or not a file"
fi

extension_dir=$("$php_cli" -c "$php_ini" -r 'print(ini_get("extension_dir"));')
if [ -z "$extension_dir" -o ! -d "$extension_dir" ]; then
	option_error "Extension dir \"$extension_dir\" is empty or not a directory"
fi

config_dir="`"$php_cli" --ini -c "$php_ini" |
	sed -ne 's|^Scan for additional \.ini files in:\s*\(.*\)$|\1| p' | xargs`"
! check_native_debian_php || config_dir="$debian_mods_availiable_dir"
if [ -z "$config_dir" -o ! -d "$config_dir" ]; then
	option_error "PHP ini configuration directory \"$config_dir\" is empty or not a directory"
fi

if [ -z "$list_cmd" -a -z "$list_all_cmd" -a -z "$list_installable_cmd" -a \
		-z "${ext_enable[*]}" -a -z "${ext_disable[*]}" -a \
		-z "${ext_install[*]}" -a -z "${ext_uninstall[*]}" ]; then
	option_error "No action specified"
fi

if [ "`echo $list_cmd $list_all_cmd $list_installable_cmd | wc -w`" -gt 1 ]; then
	option_error "Cannot use multiple listing options (--list, --list-all, --list-installable) simultaneously"
fi

if [ -n "${ext_enable[*]}" -o -n "${ext_disable[*]}" -o \
		-n "${ext_install[*]}" -o -n "${ext_uninstall[*]}" ]; then
	[ -z "$list_cmd" ] ||
		option_error "Cannot use --list and --enable/--disable/--install/--uninstall simultaneously"
	[ -z "$list_all_cmd" ] ||
		option_error "Cannot use --list-all and --enable/--disable/--install/--uninstall simultaneously"
	[ -z "$list_installable_cmd" ] ||
		option_error "Cannot use --list-installable and --enable/--disable/--install/--uninstall simultaneously"
fi

phpversion=$("$php_cli" -r '
	if (defined("PHP_MAJOR_VERSION"))
		echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION;
	else
		echo phpversion();
')
shortname_allowed=""
case $phpversion in
	[45]*) shortname_allowed="" ;;
	7.0.*) shortname_allowed="" ;;
	7.1.*) shortname_allowed="" ;;
	*) shortname_allowed="1" ;;
esac

if [ -n "$list_cmd" ]; then
	list_extensions
	exit $?
elif [ -n "$list_all_cmd" ]; then
	list_extensions --all
	exit $?
elif [ -n "$list_installable_cmd" ]; then
	list_installable_extensions
	exit $?
fi

true &&
	install_extensions "${ext_install[@]}" &&
	uninstall_extensions "${ext_uninstall[@]}" &&
	enable_extensions "${ext_enable[@]}" &&
	disable_extensions "${ext_disable[@]}"
