<?php

declare(strict_types=1);

namespace SecurWP\Modules\Firewall\ExtendedProtection;

use SecurWP\Includes\DataDirectoryProtection;

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Extended WAF Protection Manager
 *
 * Handles installation, removal, and status reporting for the
 * auto_prepend_file-based WAF bootstrap layer.
 *
 */
class Manager {

    /**
     * Name of the data directory (relative to WP_CONTENT_DIR).
     */
    private const DATA_DIR_NAME = 'securwp-data';

    /**
     * Filename of the deployed bootstrap script.
     */
    private const BOOTSTRAP_FILE = 'bootstrap.php';

    /**
     * WAF config filename.
     */
    private const CONFIG_FILE = 'waf-config.php';
    private const VERIFY_NONCE_FILE = 'waf-verify-nonces.php';

    /**
     * Stored plugin version for the deployed bootstrap copy.
     */
    private const BOOTSTRAP_VERSION_OPTION = 'securwp_waf_bootstrap_version';

    /**
     * Marker strings used in .htaccess.
     */
    private const HTACCESS_BEGIN = '# BEGIN SecurWP WAF';
    private const HTACCESS_END   = '# END SecurWP WAF';
    private const USER_INI_BEGIN = '; BEGIN SecurWP WAF';
    private const USER_INI_END   = '; END SecurWP WAF';

    /**
     * Read a server value safely for runtime environment detection.
     */
    private static function server_value( string $key ): string {
        $value = filter_input( INPUT_SERVER, $key, FILTER_UNSAFE_RAW );
        return is_string( $value ) ? wp_unslash( $value ) : '';
    }

    // ---------------------------------------------------------------
    // Public API
    // ---------------------------------------------------------------

    /**
     * Detect the web server type for WAF bootstrap purposes.
     *
     * Delegates base detection to the central ServerConfigWriter (which
     * handles admin overrides, constant overrides, LiteSpeed SAPI, and
     * correct detection order). Then refines the result into the more
     * granular types needed for auto_prepend_file configuration:
     *
     *   apache  -> apache_mod (mod_php) or apache_cgi (CGI/FastCGI)
     *   nginx   -> nginx_fpm
     *   unknown -> iis check, then unknown
     *
     * @return string One of: apache_mod, apache_cgi, litespeed, nginx_fpm, iis, unknown.
     */
    public static function detect_server(): string {
        $base = \SecurWP\Includes\ServerConfigWriter::detect_server();

        switch ( $base ) {
            case 'apache':
                // mod_php can use php_value in .htaccess; CGI/FastCGI must use .user.ini.
                return function_exists( 'apache_get_modules' ) ? 'apache_mod' : 'apache_cgi';

            case 'litespeed':
                return 'litespeed';

            case 'nginx':
                return 'nginx_fpm';

            case 'unknown':
            default:
                // ServerConfigWriter does not detect IIS (no rules to generate),
                // but the WAF Manager needs it for manual config instructions.
                $software = self::server_value( 'SERVER_SOFTWARE' );
                if ( stripos( $software, 'microsoft-iis' ) !== false ) {
                    return 'iis';
                }
                return 'unknown';
        }
    }

    /**
     * Install the extended WAF protection.
     *
     * 1. Creates the data directory.
     * 2. Copies Bootstrap.php into the data directory.
     * 3. Writes the initial WAF config via ConfigWriter.
     * 4. Configures auto_prepend_file via .htaccess or .user.ini.
     *
     * @return array{success: bool, method?: string, error?: string}
     */
    public static function install(): array {
        $data_dir       = self::get_data_dir();
        $bootstrap_src  = __DIR__ . '/Bootstrap.php';
        $bootstrap_dest = $data_dir . '/' . self::BOOTSTRAP_FILE;

        // 1. Create data directory.
        if ( ! is_dir( $data_dir ) ) {
            if ( ! wp_mkdir_p( $data_dir ) ) {
                return [
                    'success' => false,
                    'error'   => 'Could not create data directory: ' . $data_dir,
                ];
            }
        }

        // Protect the data directory from direct web access.
        self::protect_data_dir( $data_dir );

        // 2. Copy Bootstrap.php to the data directory.
        if ( ! is_file( $bootstrap_src ) ) {
            return [
                'success' => false,
                'error'   => 'Bootstrap source file not found: ' . $bootstrap_src,
            ];
        }

        if ( ! self::copy_bootstrap_file( $bootstrap_src, $bootstrap_dest ) ) {
            return [
                'success' => false,
                'error'   => 'Could not copy bootstrap file to: ' . $bootstrap_dest,
            ];
        }

        self::mark_bootstrap_version();

        // 3. Write initial config.
        if ( ! ConfigWriter::write() ) {
            // Non-fatal -- the WAF will simply pass all traffic until a config exists.
        }

        // 4. Configure the web server to use auto_prepend_file.
        $server = self::detect_server();

        switch ( $server ) {
            case 'apache_mod':
            case 'litespeed':
                $result = self::install_htaccess( $bootstrap_dest );
                if ( $result !== true ) {
                    return [
                        'success' => false,
                        'error'   => $result,
                    ];
                }
                return [ 'success' => true, 'method' => 'htaccess' ];

            case 'apache_cgi':
            case 'nginx_fpm':
            case 'unknown':
                // CGI/FastCGI-based servers use .user.ini in the document root.
                $result = self::install_user_ini( $bootstrap_dest );
                if ( $result !== true ) {
                    return [
                        'success' => false,
                        'error'   => $result,
                    ];
                }
                return [ 'success' => true, 'method' => 'user_ini' ];

            case 'iis':
                return [
                    'success' => false,
                    'error'   => 'IIS requires manual configuration. Please add auto_prepend_file = "' . $bootstrap_dest . '" to your php.ini or web.config.',
                ];

            default:
                return [
                    'success' => false,
                    'error'   => 'Unsupported server type: ' . $server,
                ];
        }
    }

    /**
     * Uninstall the extended WAF protection.
     *
     * Removes all configuration artifacts:
     *  - .htaccess SecurWP WAF section
     *  - .user.ini auto_prepend_file directive
     *  - Deployed bootstrap file
     *  - WAF config file
     *
     * @return array{success: bool, error?: string}
     */
    public static function uninstall(): array {
        $errors = [];

        // 1. Remove from .htaccess.
        $htaccess = self::get_htaccess_path();
        if ( is_file( $htaccess ) && wp_is_writable( $htaccess ) ) {
            $result = self::remove_htaccess_section( $htaccess );
            if ( $result !== true ) {
                $errors[] = $result;
            }
        }

        // 2. Remove from .user.ini.
        $user_ini = self::get_user_ini_path();
        if ( is_file( $user_ini ) && wp_is_writable( $user_ini ) ) {
            $result = self::remove_user_ini_directive( $user_ini );
            if ( $result !== true ) {
                $errors[] = $result;
            }
        }

        // 3. Delete config so bootstrap becomes a no-op immediately.
        // Keep bootstrap.php on disk because PHP-FPM caches .user.ini
        // for up to user_ini.cache_ttl seconds (default 300). Deleting
        // bootstrap.php while the cached directive still points to it
        // would cause fatal errors on every request.
        $config = self::get_config_path();
        if ( is_file( $config ) ) {
            wp_delete_file( $config );
        }

        $verify_nonce_file = self::get_data_dir() . '/' . self::VERIFY_NONCE_FILE;
        if ( is_file( $verify_nonce_file ) ) {
            wp_delete_file( $verify_nonce_file );
        }

        delete_option( self::BOOTSTRAP_VERSION_OPTION );
        delete_option( 'securwp_waf_policy_signature' );

        if ( ! empty( $errors ) ) {
            return [
                'success' => false,
                'error'   => implode( '; ', $errors ),
            ];
        }

        return [ 'success' => true ];
    }

    /**
     * Refresh the deployed bootstrap when the plugin source changes.
     */
    public static function ensure_bootstrap_current(): bool {
        $bootstrap_path = self::get_bootstrap_path();
        if ( ! is_file( $bootstrap_path ) ) {
            return false;
        }

        $bootstrap_src = __DIR__ . '/Bootstrap.php';
        if ( ! is_file( $bootstrap_src ) ) {
            return false;
        }

        $current_version = defined( 'SECURWP_VERSION' ) ? (string) SECURWP_VERSION : '';
        $stored_version  = (string) get_option( self::BOOTSTRAP_VERSION_OPTION, '' );

        if ( $current_version !== '' && $stored_version === $current_version ) {
            return true;
        }

        $source_hash = self::file_hash( $bootstrap_src );
        $target_hash = self::file_hash( $bootstrap_path );

        if ( $source_hash !== '' && $source_hash === $target_hash ) {
            self::mark_bootstrap_version();
            return true;
        }

        if ( ! self::copy_bootstrap_file( $bootstrap_src, $bootstrap_path ) ) {
            return false;
        }

        self::mark_bootstrap_version();
        return true;
    }

    /**
     * Get the current status of the extended WAF protection.
     *
     * @return array{
     *   active: bool,
     *   runtime_active: bool,
     *   installed_on_disk: bool,
     *   pending_activation: bool,
     *   pending_message: string|null,
     *   server_type: string,
     *   bootstrap_exists: bool,
     *   config_exists: bool,
     *   prepend_path: string,
     *   stale_runtime_prepend: bool,
     *   conflict_detected: bool
     * }
     */
    public static function get_status(): array {
        $bootstrap_path = self::get_bootstrap_path();
        $config_path    = self::get_config_path();
        $prepend_value  = (string) ini_get( 'auto_prepend_file' );

        $bootstrap_exists          = is_file( $bootstrap_path );
        $config_exists             = is_file( $config_path );
        $user_ini_directive_exists = self::user_ini_directive_exists_on_disk();
        $htaccess_directive_exists = self::htaccess_directive_exists_on_disk();
        $directive_exists_on_disk  = ( $user_ini_directive_exists || $htaccess_directive_exists );
        $foreign_user_ini_path     = self::foreign_user_ini_prepend_path_on_disk();
        $foreign_htaccess_path     = self::foreign_htaccess_prepend_path_on_disk();
        $foreign_prepend_path      = $foreign_user_ini_path !== '' ? $foreign_user_ini_path : $foreign_htaccess_path;
        $foreign_directive_on_disk = ( $foreign_prepend_path !== '' );
        $installed_on_disk         = ( $bootstrap_exists && $directive_exists_on_disk );
        $runtime_matches_bootstrap = ( $prepend_value !== '' && realpath( $prepend_value ) === realpath( $bootstrap_path ) );
        $securwp_waiting_for_runtime = ( $directive_exists_on_disk && ! $foreign_directive_on_disk && ! $runtime_matches_bootstrap );
        $stale_runtime_prepend     = ( $prepend_value !== '' && ! $runtime_matches_bootstrap && ! $directive_exists_on_disk && ! $foreign_directive_on_disk );
        $conflict_detected         = $foreign_directive_on_disk || ( $prepend_value !== '' && ! $runtime_matches_bootstrap && ! $stale_runtime_prepend && ! $securwp_waiting_for_runtime );

        // PHP-FPM caches .user.ini for user_ini.cache_ttl seconds (default 300).
        // After uninstall, ini_get() may still return the old value. Cross-check
        // the actual .user.ini/.htaccess file to detect stale cache.
        if ( $runtime_matches_bootstrap ) {
            $runtime_matches_bootstrap = $directive_exists_on_disk;
        }

        $pending_activation = (
            ! $runtime_matches_bootstrap
            && ! $conflict_detected
            && $bootstrap_exists
            && ( $user_ini_directive_exists || $securwp_waiting_for_runtime )
        );
        $pending_message = $pending_activation ? self::build_pending_activation_message() : null;

        return [
            'active'            => $runtime_matches_bootstrap,
            'runtime_active'    => $runtime_matches_bootstrap,
            'installed_on_disk' => $installed_on_disk,
            'pending_activation' => $pending_activation,
            'pending_message'   => $pending_message,
            'server_type'       => self::detect_server(),
            'bootstrap_exists'  => $bootstrap_exists,
            'config_exists'     => $config_exists,
            // Show the configured path when SecurWP has installed the hook on disk.
            'prepend_path'      => ( $conflict_detected || $stale_runtime_prepend )
                ? ( $prepend_value !== '' ? $prepend_value : $foreign_prepend_path )
                : (
                    $installed_on_disk
                        ? ( $runtime_matches_bootstrap && $prepend_value !== '' ? $prepend_value : $bootstrap_path )
                        : ''
                ),
            'stale_runtime_prepend' => $stale_runtime_prepend,
            'conflict_detected' => $conflict_detected,
        ];
    }

    /**
     * Check whether SecurWP's auto_prepend_file directive exists in .user.ini.
     */
    private static function user_ini_directive_exists_on_disk(): bool {
        return self::file_contains_bootstrap_directive( self::get_user_ini_path() );
    }

    /**
     * Check whether SecurWP's auto_prepend_file directive exists in .htaccess.
     */
    private static function htaccess_directive_exists_on_disk(): bool {
        return self::file_contains_bootstrap_directive( self::get_htaccess_path() );
    }

    /**
     * Check whether a config file contains SecurWP's bootstrap path.
     *
     * @param string $path Absolute path to the config file.
     */
    private static function file_contains_bootstrap_directive( string $path ): bool {
        $bootstrap_path = self::get_bootstrap_path();
        if ( ! is_file( $path ) || ! is_readable( $path ) ) {
            return false;
        }

        $contents = @file_get_contents( $path );
        if ( $contents === false ) {
            return false;
        }

        return ( strpos( $contents, 'auto_prepend_file' ) !== false && strpos( $contents, $bootstrap_path ) !== false );
    }

    /**
     * Return a non-SecurWP auto_prepend_file value from .user.ini, if present.
     */
    private static function foreign_user_ini_prepend_path_on_disk(): string {
        $path = self::get_user_ini_path();
        if ( ! is_file( $path ) || ! is_readable( $path ) ) {
            return '';
        }

        $contents = @file_get_contents( $path );
        if ( $contents === false ) {
            return '';
        }

        $cleaned = self::strip_user_ini_directive( $contents );
        return self::extract_user_ini_prepend_path( $cleaned );
    }

    /**
     * Return a non-SecurWP prepend directive path from .htaccess, if present.
     */
    private static function foreign_htaccess_prepend_path_on_disk(): string {
        $path = self::get_htaccess_path();
        if ( ! is_file( $path ) || ! is_readable( $path ) ) {
            return '';
        }

        $contents = @file_get_contents( $path );
        if ( $contents === false ) {
            return '';
        }

        $cleaned = self::strip_htaccess_section( $contents );
        return self::extract_htaccess_prepend_path( $cleaned );
    }

    /**
     * Build the status message shown while PHP-FPM is still applying .user.ini.
     */
    private static function build_pending_activation_message(): string {
        $ttl = self::get_user_ini_cache_ttl();
        if ( $ttl > 0 ) {
            return sprintf(
                'SecurWP has updated .user.ini, but PHP-FPM may cache auto_prepend_file changes for up to %d seconds before the server-level WAF becomes active.',
                $ttl
            );
        }

        return 'SecurWP has updated .user.ini, but the current PHP-FPM worker has not applied the new auto_prepend_file value yet. Reload the page in a few seconds to confirm activation.';
    }

    /**
     * Get the configured .user.ini cache TTL for the current PHP runtime.
     */
    private static function get_user_ini_cache_ttl(): int {
        $ttl = ini_get( 'user_ini.cache_ttl' );
        if ( $ttl === false ) {
            return 0;
        }

        return max( 0, (int) $ttl );
    }

    /**
     * Quick check whether the extended WAF is installed and pointing to our bootstrap.
     *
     * @return bool
     */
    public static function is_installed(): bool {
        $bootstrap_path = self::get_bootstrap_path();

        if ( ! is_file( $bootstrap_path ) ) {
            return false;
        }

        $prepend_value = (string) ini_get( 'auto_prepend_file' );
        if ( $prepend_value === '' ) {
            return false;
        }

        return realpath( $prepend_value ) === realpath( $bootstrap_path );
    }

    // ---------------------------------------------------------------
    // Path helpers
    // ---------------------------------------------------------------

    /**
     * @return string Absolute path to the data directory.
     */
    public static function get_data_dir(): string {
        return WP_CONTENT_DIR . '/' . self::DATA_DIR_NAME;
    }

    /**
     * @return string Absolute path to the deployed bootstrap file.
     */
    public static function get_bootstrap_path(): string {
        return self::get_data_dir() . '/' . self::BOOTSTRAP_FILE;
    }

    /**
     * @return string Absolute path to the WAF config file.
     */
    public static function get_config_path(): string {
        return self::get_data_dir() . '/' . self::CONFIG_FILE;
    }

    /**
     * @return string Absolute path to the .htaccess file.
     */
    private static function get_htaccess_path(): string {
        return ABSPATH . '.htaccess';
    }

    /**
     * @return string Absolute path to the .user.ini file.
     */
    private static function get_user_ini_path(): string {
        return ABSPATH . '.user.ini';
    }

    /**
     * Copy the bootstrap atomically to avoid partial reads by PHP-FPM.
     */
    private static function copy_bootstrap_file( string $source, string $destination ): bool {
        $directory = dirname( $destination );
        if ( ! is_dir( $directory ) && ! wp_mkdir_p( $directory ) ) {
            return false;
        }

        $filesystem = self::get_filesystem();
        if ( ! $filesystem ) {
            return false;
        }

        $contents = $filesystem->get_contents( $source );
        if ( ! is_string( $contents ) ) {
            return false;
        }

        $temp = $destination . '.tmp.' . wp_generate_password( 8, false, false );

        if ( ! $filesystem->put_contents( $temp, $contents, defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644 ) ) {
            return false;
        }

        if ( ! $filesystem->move( $temp, $destination, true ) ) {
            wp_delete_file( $temp );
            return false;
        }

        return true;
    }

    /**
     * Return the initialized WordPress filesystem adapter.
     *
     * @return \WP_Filesystem_Base|null
     */
    private static function get_filesystem(): ?\WP_Filesystem_Base {
        global $wp_filesystem;

        if ( $wp_filesystem instanceof \WP_Filesystem_Base ) {
            return $wp_filesystem;
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        WP_Filesystem();

        return $wp_filesystem instanceof \WP_Filesystem_Base ? $wp_filesystem : null;
    }

    private static function file_hash( string $path ): string {
        $hash = @sha1_file( $path );
        return is_string( $hash ) ? $hash : '';
    }

    private static function mark_bootstrap_version(): void {
        if ( defined( 'SECURWP_VERSION' ) ) {
            update_option( self::BOOTSTRAP_VERSION_OPTION, (string) SECURWP_VERSION, false );
        }
    }

    // ---------------------------------------------------------------
    // .htaccess management
    // ---------------------------------------------------------------

    /**
     * Add the auto_prepend_file directive to .htaccess between SecurWP markers.
     *
     * @param string $bootstrap_path Absolute path to the deployed bootstrap file.
     * @return true|string True on success, error message on failure.
     */
    private static function install_htaccess( string $bootstrap_path ) {
        $htaccess = self::get_htaccess_path();
        $contents = '';

        if ( is_file( $htaccess ) ) {
            $contents = @file_get_contents( $htaccess );
            if ( $contents === false ) {
                return 'Could not read .htaccess file.';
            }

            // Remove any existing SecurWP WAF section first.
            $contents = self::strip_htaccess_section( $contents );
        }

        $directive  = self::HTACCESS_BEGIN . "\n";
        $directive .= 'php_value auto_prepend_file "' . $bootstrap_path . '"' . "\n";
        $directive .= self::HTACCESS_END . "\n";

        // Prepend our section so it loads before other directives.
        $new_contents = $directive . "\n" . ltrim( $contents );

        if ( @file_put_contents( $htaccess, $new_contents, LOCK_EX ) === false ) {
            return 'Could not write to .htaccess file.';
        }

        return true;
    }

    /**
     * Remove the SecurWP WAF section from .htaccess contents.
     *
     * @param string $contents Current .htaccess content.
     * @return string Cleaned content.
     */
    private static function strip_htaccess_section( string $contents ): string {
        $pattern = self::managed_block_pattern( self::HTACCESS_BEGIN, self::HTACCESS_END );
        return preg_replace( $pattern, '', $contents ) ?? $contents;
    }

    /**
     * Remove the SecurWP WAF section from the .htaccess file on disk.
     *
     * @param string $htaccess Absolute path to .htaccess.
     * @return true|string True on success, error message on failure.
     */
    private static function remove_htaccess_section( string $htaccess ) {
        $contents = @file_get_contents( $htaccess );
        if ( $contents === false ) {
            return 'Could not read .htaccess file.';
        }

        $cleaned = self::strip_htaccess_section( $contents );

        // Only write back if something actually changed.
        if ( $cleaned !== $contents ) {
            // Remove leftover empty lines at the beginning.
            $cleaned = ltrim( $cleaned, "\n\r" );
            if ( @file_put_contents( $htaccess, $cleaned, LOCK_EX ) === false ) {
                return 'Could not write to .htaccess file.';
            }
        }

        return true;
    }

    // ---------------------------------------------------------------
    // .user.ini management
    // ---------------------------------------------------------------

    /**
     * Write the auto_prepend_file directive into .user.ini.
     *
     * @param string $bootstrap_path Absolute path to the deployed bootstrap file.
     * @return true|string True on success, error message on failure.
     */
    private static function install_user_ini( string $bootstrap_path ) {
        $user_ini = self::get_user_ini_path();
        $contents  = '';

        if ( is_file( $user_ini ) ) {
            $contents = @file_get_contents( $user_ini );
            if ( $contents === false ) {
                return 'Could not read .user.ini file.';
            }

            // Remove only SecurWP-managed directives.
            $contents = self::strip_user_ini_directive( $contents );
        }

        if ( self::has_conflicting_user_ini_prepend( $contents ) ) {
            return 'Existing auto_prepend_file detected in .user.ini. Remove it manually before enabling SecurWP extended protection.';
        }

        $directive  = self::USER_INI_BEGIN . "\n";
        $directive .= 'auto_prepend_file = "' . $bootstrap_path . '"' . "\n";
        $directive .= self::USER_INI_END . "\n";

        $new_contents = $directive . "\n" . ltrim( $contents );

        if ( @file_put_contents( $user_ini, $new_contents, LOCK_EX ) === false ) {
            return 'Could not write to .user.ini file.';
        }

        return true;
    }

    /**
     * Remove auto_prepend_file lines from .user.ini content.
     *
     * @param string $contents Current .user.ini content.
     * @return string Cleaned content.
     */
    private static function strip_user_ini_directive( string $contents ): string {
        $pattern = self::managed_block_pattern( self::USER_INI_BEGIN, self::USER_INI_END );
        return preg_replace( $pattern, '', $contents ) ?? $contents;
    }

    /**
     * Build a regex that matches a SecurWP-managed config block.
     */
    private static function managed_block_pattern( string $begin, string $end ): string {
        $begin_pattern = preg_quote( $begin, '/' );
        $end_pattern   = preg_quote( $end, '/' );

        return '/' . $begin_pattern . '.*?' . $end_pattern . '\s*/s';
    }

    /**
     * Remove the auto_prepend_file directive from the .user.ini file on disk.
     *
     * @param string $user_ini Absolute path to .user.ini.
     * @return true|string True on success, error message on failure.
     */
    private static function remove_user_ini_directive( string $user_ini ) {
        $contents = @file_get_contents( $user_ini );
        if ( $contents === false ) {
            return 'Could not read .user.ini file.';
        }

        $cleaned = self::strip_user_ini_directive( $contents );

        if ( $cleaned !== $contents ) {
            $cleaned = ltrim( $cleaned, "\n\r" );
            if ( @file_put_contents( $user_ini, $cleaned, LOCK_EX ) === false ) {
                return 'Could not write to .user.ini file.';
            }
        }

        return true;
    }

    /**
     * Detect non-SecurWP auto_prepend_file directives in .user.ini.
     *
     * @param string $contents
     * @return bool
     */
    private static function has_conflicting_user_ini_prepend( string $contents ): bool {
        return preg_match( '/^\s*auto_prepend_file\s*=.+$/mi', $contents ) === 1;
    }

    /**
     * Extract an auto_prepend_file value from .user.ini content.
     */
    private static function extract_user_ini_prepend_path( string $contents ): string {
        if ( preg_match( '/^\s*auto_prepend_file\s*=\s*"?([^"\r\n;]+)"?\s*$/mi', $contents, $matches ) !== 1 ) {
            return '';
        }

        return trim( (string) $matches[1] );
    }

    /**
     * Extract an auto_prepend_file value from .htaccess content.
     */
    private static function extract_htaccess_prepend_path( string $contents ): string {
        if ( preg_match( '/^\s*php_value\s+auto_prepend_file\s+"?([^"\r\n]+)"?\s*$/mi', $contents, $matches ) !== 1 ) {
            return '';
        }

        return trim( (string) $matches[1] );
    }

    // ---------------------------------------------------------------
    // Data directory protection
    // ---------------------------------------------------------------

    /**
     * Write protective files (index.php and .htaccess) into the data directory.
     *
     * @param string $data_dir Absolute path to the data directory.
     */
    private static function protect_data_dir( string $data_dir ): void {
        DataDirectoryProtection::ensure_local_protection( $data_dir );
    }
}
