<?php
/**
 * Copyright (с) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2025 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * https://www.cloudlinux.com/legal/
 */

namespace CloudLinux\Imunify\App\Bot;

/**
 * Atomic writer for wp-content/imunify-security/bot-settings.php —
 * the site-owner opt-out + preset file owned exclusively by the
 * Phase-1 dashboard widget.
 *
 * Paired with {@see OptOutFlag} on the read side. The agent never
 * touches this file; plugin_config.php is a separate channel for
 * admin-level state.
 *
 * Writes are write-temp-then-rename to avoid torn reads when a
 * concurrent request is mid-include. The directory is created with
 * mode 0750 and the file with 0440 to match the rest of the
 * wp-content/imunify-security/ payload convention.
 *
 * @since 4.0.0
 */
class BotSettingsWriter {

	const SETTINGS_DIR  = 'imunify-security';
	const SETTINGS_FILE = 'bot-settings.php';

	/**
	 * Absolute path to WordPress's wp-content directory.
	 *
	 * @var string
	 */
	private $wpContentDir;

	/**
	 * Construct a writer rooted at $wp_content_dir.
	 *
	 * @param string $wp_content_dir Absolute path to wp-content.
	 */
	public function __construct( $wp_content_dir ) {
		$this->wpContentDir = rtrim( (string) $wp_content_dir, '/' );
	}

	/**
	 * Persist the site-owner's enabled flag, optional preset, and stats flag.
	 *
	 * Because the whole file is rewritten on every call, callers must pass the
	 * complete desired state — a preset change must carry the current
	 * $stats_enabled value through, or it will be reset to the default.
	 *
	 * @param bool        $enabled       Whether bot protection is enabled on this site.
	 * @param string|null $preset        A Preset::* constant, or null to omit the
	 *                                   preset key (lets plugin_config.php remain
	 *                                   authoritative). Invalid non-null values are
	 *                                   coerced to Preset::BALANCED.
	 * @param bool        $stats_enabled Whether detailed bot-traffic stats capture is on.
	 * @return bool True iff the file exists at the target path after
	 *              the call.
	 */
	public function write( $enabled, $preset = null, $stats_enabled = true ) {
		if ( '' === $this->wpContentDir ) {
			return false;
		}

		$dir    = $this->wpContentDir . '/' . self::SETTINGS_DIR;
		$target = $dir . '/' . self::SETTINGS_FILE;

		if ( ! is_dir( $dir ) ) {
			// @phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			if ( ! @mkdir( $dir, 0750, true ) && ! is_dir( $dir ) ) {
				return false;
			}
		}

		$payload = array(
			'enabled'       => (bool) $enabled,
			'stats_enabled' => (bool) $stats_enabled,
		);
		if ( null !== $preset ) {
			$payload['preset'] = Preset::isValid( $preset ) ? $preset : Preset::BALANCED;
		}

		$body = "<?php\n"
			. "// Auto-generated by CloudLinux\\Imunify\\App\\Bot\\BotSettingsWriter.\n"
			. "// Site-owner opt-out + preset. Do not edit by hand.\n\n"
			. 'return ' . var_export( $payload, true ) . ";\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export

		return AtomicFileWriter::write( $target, $body, 0440 );
	}

	/**
	 * Absolute path the writer produces. Exposed so the widget layer
	 * can chown/chmod uniformly with sibling files.
	 *
	 * @return string
	 */
	public function targetPath() {
		return $this->wpContentDir . '/' . self::SETTINGS_DIR . '/' . self::SETTINGS_FILE;
	}
}
