<?php

/**
 * Forked from Simple Custom CSS and JS v3.44 by SilkyPress.com
 * @link https://wordpress.org/plugins/custom-css-js/
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

// MobileDetect v4.8.x
use Detection\Exception\MobileDetectException;
use Detection\MobileDetectStandalone;

if ( version_compare( PHP_VERSION, '8.0.0', '>=' ) ) {
	if ( ! class_exists( 'MobileDetect' ) ) {
		// MobileDetect v4.8.x
		require_once ASENHA_PATH . 'vendor/serbanghita/mobile-detect/4.8.x/standalone/autoloader.php';
		require_once ASENHA_PATH . 'vendor/serbanghita/mobile-detect/4.8.x/src/MobileDetectStandalone.php';
	}
} else {
	// MobileDetect v3.74.x
	require_once ASENHA_PATH . 'vendor/serbanghita/mobile-detect/3.74.x/MobileDetect.php';
}

if ( ! class_exists( 'Code_Snippets_Manager' ) ) :
	/**
	 * Main Code_Snippets_Manager Class
	 *
	 * @class Code_Snippets_Manager
	 */
	final class Code_Snippets_Manager {

		/**
		 * Option key under admin_site_enhancements_extra for unattributed / supplemental CSM errors.
		 *
		 * @var string
		 */
		public const CSM_ERROR_LOG_OPTION_KEY = 'code_snippets_manager_error_log';

		/**
		 * Max entries to keep in the CSM error log (FIFO trim on append).
		 *
		 * @var int
		 */
		public const CSM_ERROR_LOG_MAX_ENTRIES = 50;

		/**
		 * Post meta: md5 hash of on-disk PHP snippet file when last passed PHP_Validator (skip runtime validation when unchanged).
		 *
		 * @var string
		 */
		public const CSM_PHP_FILE_HASH_META_KEY = '_csm_php_validated_file_hash';

		/**
		 * Transient name for deferred admin-only attribution recovery after unattributed fatal + error log.
		 *
		 * @var string
		 */
		public const CSM_PENDING_RECOVERY_TRANSIENT = 'csm_pending_attrib_recovery_v1';

		/**
		 * Key in admin_site_enhancements_extra: one-time disk migration for PHP snippet files missing leading <?php (ASE Pro 8.6.1).
		 *
		 * @var string
		 */
		public const CSM_PHP_OPEN_TAG_DISK_MIGRATION_EXTRA_KEY = 'csm_php_open_tag_disk_migration_done';

		// public $search_tree         = false; // Legacy - used up to ASE Pro v7.6.3. No longer referenced anywhere on this file.
		public $snippets_tree       = false; // Current - since ASE Pro v7.6.4
		protected static $_instance = null;
		private $settings           = array();

		/**
		 * Stack of snippet post IDs currently executing (snippet include or wrapped shortcode). Top = active attribution target.
		 *
		 * @var int[]
		 */
		private static $attribution_stack = array();

		/**
		 * Whether the unified shutdown handler was registered for this request.
		 *
		 * @var bool
		 */
		private static $csm_shutdown_registered = false;

		/**
		 * Snippet post IDs whose PHP successfully completed execution this request (array keys = IDs, deduplicated).
		 *
		 * @var array<int, true>
		 */
		private static $snippets_evaluated_this_request = array();

		/**
		 * When set, shutdown handler prunes the CSM error log row with this recovery_token after writing meta.
		 *
		 * @var string
		 */
		private static $recovery_log_prune_token = '';

		/**
		 * PHP snippet post ID saved on this request (Publish/Update), used as a strict same-request fallback.
		 *
		 * @var int
		 */
		private static $saved_php_snippet_post_id_this_request = 0;

		/**
		 * Main Code_Snippets_Manager Instance
		 *
		 * Ensures only one instance of Code_Snippets_Manager is loaded or can be loaded
		 *
		 * @static
		 * @return Code_Snippets_Manager - Main instance
		 */
		public static function instance() {
			if ( is_null( self::$_instance ) ) {
				self::$_instance = new self();
			}
			return self::$_instance;
		}

		/**
		 * Push snippet post ID onto the attribution stack (eval or wrapped shortcode).
		 *
		 * @param int $post_id Snippet post ID.
		 * @return void
		 */
		public static function push_attribution_stack( $post_id ) {
			$post_id = absint( $post_id );
			if ( $post_id > 0 ) {
				self::$attribution_stack[] = $post_id;
			}
		}

		/**
		 * Pop the last attribution ID after successful snippet execution or shortcode callback.
		 *
		 * @return void
		 */
		public static function pop_attribution_stack() {
			array_pop( self::$attribution_stack );
		}

		/**
		 * Record that a snippet's PHP completed without a caught Throwable this request.
		 *
		 * @param int $post_id Snippet post ID.
		 * @return void
		 */
		public static function record_snippet_evaluated_this_request( $post_id ) {
			$post_id = absint( $post_id );
			if ( $post_id > 0 ) {
				self::$snippets_evaluated_this_request[ $post_id ] = true;
			}
		}

		/**
		 * Post IDs recorded via record_snippet_evaluated_this_request() this request.
		 *
		 * @return int[]
		 */
		public static function get_snippets_evaluated_this_request_ids() {
			return array_map( 'absint', array_keys( self::$snippets_evaluated_this_request ) );
		}

		/**
		 * Save canonical PHP snippet post ID for this request only (captured during snippet Publish/Update flow).
		 *
		 * @param int $post_id Snippet post ID.
		 * @return void
		 */
		public static function set_saved_php_snippet_post_id_this_request( $post_id ) {
			self::$saved_php_snippet_post_id_this_request = absint( $post_id );
		}

		/**
		 * Canonical PHP snippet post ID saved this request, or 0 when unavailable.
		 *
		 * @return int
		 */
		public static function get_saved_php_snippet_post_id_this_request() {
			return absint( self::$saved_php_snippet_post_id_this_request );
		}

		/**
		 * Show one-time notice after successful attribution recovery (manage_options only).
		 *
		 * @return void
		 */
		public function maybe_show_csm_recovery_admin_notice() {
			if ( ! current_user_can( 'manage_options' ) ) {
				return;
			}
			$key = 'asenha_csm_recovery_notice_' . get_current_user_id();
			$msg = get_transient( $key );
			if ( ! is_string( $msg ) || '' === $msg ) {
				return;
			}
			delete_transient( $key );
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
		}

		/**
		 * One-time: prepend <?php to PHP snippet files on disk when missing (ASE Pro 8.6.1).
		 * Runs silently on plugins_loaded priority 1 so files are fixed before snippet hooks at default priority.
		 *
		 * @return void
		 */
		public function maybe_run_csm_php_open_tag_disk_migration_once() {
			if ( self::is_csm_php_open_tag_disk_migration_done() ) {
				return;
			}
			self::run_csm_php_open_tag_disk_migration();
			self::set_csm_php_open_tag_disk_migration_done();
		}

		/**
		 * Privileged admin-only follow-up: turn off safe mode and include the candidate snippet file to attribute errors by path.
		 *
		 * @return void
		 */
		public function maybe_run_pending_attribution_recovery() {
			if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
				return;
			}
			$payload = get_transient( self::CSM_PENDING_RECOVERY_TRANSIENT );
			if ( ! is_array( $payload ) || empty( $payload['recovery_token'] ) || empty( $payload['candidate_ids'] ) ) {
				return;
			}
			delete_transient( self::CSM_PENDING_RECOVERY_TRANSIENT );

			$token         = (string) $payload['recovery_token'];
			$candidate_ids = array_map( 'absint', (array) $payload['candidate_ids'] );
			$candidate_ids = array_values( array_filter( $candidate_ids ) );
			if ( 1 !== count( $candidate_ids ) ) {
				return;
			}
			$post_id = (int) $candidate_ids[0];

			$upload_dir = wp_upload_dir();
			$file_path  = $upload_dir['basedir'] . '/code-snippets/' . $post_id . '.php';
			if ( ! file_exists( $file_path ) ) {
				return;
			}

			$wp_config_options = array(
				'add'       => true,
				'raw'       => true,
				'normalize' => false,
			);
			$wp_config = new ASENHA\Classes\WP_Config_Transformer();
			$wp_config->update( 'constant', 'CSM_SAFE_MODE', 'false', $wp_config_options );

			self::ensure_csm_shutdown_registered();
			self::$recovery_log_prune_token = $token;
			self::push_attribution_stack( $post_id );
			try {
				// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- intentional probe of canonical snippet file.
				include $file_path;
				self::remove_csm_error_log_entry_by_token( $token );
				self::$recovery_log_prune_token = '';
				set_transient(
					'asenha_csm_recovery_notice_' . get_current_user_id(),
					__( 'Code Snippets Manager finished attribution recovery: the error log entry was removed. If the snippet did not reproduce a fatal error, verify it manually.', 'admin-site-enhancements' ),
					120
				);
			} catch ( Throwable $recovery_throwable ) {
				$this->apply_recovery_caught_throwable_meta( $post_id, $recovery_throwable );
				self::remove_csm_error_log_entry_by_token( $token );
				self::$recovery_log_prune_token = '';
			} finally {
				self::pop_attribution_stack();
			}
		}

		/**
		 * Store non-fatal error meta when recovery probe catches a throwable.
		 *
		 * @param int       $post_id Snippet post ID.
		 * @param Throwable $t       Caught error.
		 * @return void
		 */
		private function apply_recovery_caught_throwable_meta( $post_id, $t ) {
			$post_id = absint( $post_id );
			if ( $post_id < 1 || ! is_object( $t ) ) {
				return;
			}
			$message       = $t->getMessage();
			$line          = $t->getLine();
			$error_message = $message . ' on line ' . $line;
			update_post_meta( $post_id, 'php_snippet_has_error', true );
			update_post_meta( $post_id, 'php_snippet_error_type', 'non-fatal' );
			update_post_meta( $post_id, 'php_snippet_error_code', 'unknown' );
			update_post_meta( $post_id, 'php_snippet_error_message', '<span class="error-message">' . esc_html( $error_message ) . '</span>' );
			update_post_meta( $post_id, 'php_snippet_error_via', 'attribution_recovery' );
			update_post_meta( $post_id, 'safe_mode_activation_via', '' );
		}

		/**
		 * Resolve snippet ID for shutdown / wp_die attribution:
		 * stack top, else sole evaluated snippet, else same-request saved PHP snippet ID, else 0.
		 *
		 * @return int
		 */
		public static function resolve_shutdown_error_attribution_snippet_post_id() {
			$top = self::get_active_attribution_snippet_post_id();
			if ( $top > 0 ) {
				return $top;
			}
			$ids = self::get_snippets_evaluated_this_request_ids();
			if ( 1 === count( $ids ) ) {
				return (int) $ids[0];
			}
			$saved_snippet_id = self::get_saved_php_snippet_post_id_this_request();
			if ( $saved_snippet_id > 0 ) {
				return $saved_snippet_id;
			}
			return 0;
		}

		/**
		 * Active snippet post ID for error attribution (top of stack), or 0.
		 *
		 * @return int
		 */
		public static function get_active_attribution_snippet_post_id() {
			if ( empty( self::$attribution_stack ) ) {
				return 0;
			}
			$top = end( self::$attribution_stack );
			return absint( $top );
		}

		/**
		 * Whether shutdown error_get_last() file points to CSM snippet code (manager file, eval legacy path, or canonical uploads path).
		 *
		 * @param string $file Error file from error_get_last().
		 * @return bool
		 */
		public static function is_shutdown_error_from_csm_snippet( $file ) {
			if ( ! is_string( $file ) || '' === $file ) {
				return false;
			}
			if ( self::snippet_post_id_from_canonical_snippet_file_path( $file ) > 0 ) {
				return true;
			}
			if ( false !== strpos( $file, '/premium/code-snippets-manager/' ) ) {
				return true;
			}
			if ( false !== strpos( $file, 'code-snippets-manager.php' ) ) {
				return true;
			}
			// PHP reports eval'd snippet body as "eval()'d code"; only treat as CSM when attribution stack is non-empty.
			if ( false !== stripos( $file, "eval()'d code" ) && self::get_active_attribution_snippet_post_id() > 0 ) {
				return true;
			}
			return false;
		}

		/**
		 * Resolve snippet post ID from canonical uploads path .../code-snippets/{id}.php (include/runtime errors).
		 *
		 * @param string $file Absolute file path from PHP error.
		 * @return int Post ID or 0.
		 */
		public static function snippet_post_id_from_canonical_snippet_file_path( $file ) {
			if ( ! is_string( $file ) || '' === $file ) {
				return 0;
			}
			$upload_dir = wp_upload_dir();
			if ( ! empty( $upload_dir['error'] ) ) {
				return 0;
			}
			$dir       = wp_normalize_path( $upload_dir['basedir'] . '/code-snippets' );
			$file_norm = wp_normalize_path( $file );
			if ( 0 !== strpos( $file_norm, $dir ) ) {
				return 0;
			}
			$base = basename( $file_norm );
			if ( ! preg_match( '/^(\d+)\.php$/', $base, $m ) ) {
				return 0;
			}
			return absint( $m[1] );
		}

		/**
		 * Heuristic: fatal in WordPress hook runner likely from a deferred snippet-registered callback (safety net when path gate misses).
		 *
		 * @param array $error_raw error_get_last() shape: type, file, message, line.
		 * @return bool
		 */
		public static function is_shutdown_error_likely_deferred_snippet_hook_fatal( $error_raw ) {
			if ( ! is_array( $error_raw ) ) {
				return false;
			}
			$code = isset( $error_raw['type'] ) ? intval( $error_raw['type'] ) : 0;
			$fatal_error_codes = array( 1, 16, 256 );
			if ( ! in_array( $code, $fatal_error_codes, true ) ) {
				return false;
			}
			if ( empty( self::$snippets_evaluated_this_request ) ) {
				return false;
			}
			$file = isset( $error_raw['file'] ) ? $error_raw['file'] : '';
			if ( ! is_string( $file ) || '' === $file ) {
				return false;
			}
			$wp_includes = wp_normalize_path( ABSPATH . 'wp-includes/' );
			$file_norm   = wp_normalize_path( $file );
			if ( 0 !== strpos( $file_norm, $wp_includes ) ) {
				return false;
			}
			$basename = strtolower( basename( $file_norm ) );
			$allowed  = array( 'class-wp-hook.php', 'plugin.php' );
			if ( ! in_array( $basename, $allowed, true ) ) {
				return false;
			}
			$message = isset( $error_raw['message'] ) ? $error_raw['message'] : '';
			if ( ! is_string( $message ) || '' === $message ) {
				return false;
			}
			$msg_lower = strtolower( $message );
			$evaluated_count = count( self::$snippets_evaluated_this_request );

			$tier_a = ( false !== strpos( $msg_lower, 'call_user_func' ) )
				|| ( false !== strpos( $msg_lower, 'valid callback' ) )
				|| ( false !== strpos( $msg_lower, 'undefined function' ) )
				|| ( false !== strpos( $msg_lower, 'undefined method' ) )
				|| ( false !== strpos( $msg_lower, 'call to undefined function' ) );

			$tier_b = false;
			if ( 1 === $evaluated_count ) {
				$tier_b = ( false !== strpos( $msg_lower, 'allowed memory size' ) )
					|| ( false !== strpos( $msg_lower, 'maximum execution time' ) )
					|| ( false !== strpos( $msg_lower, 'uncaught typeerror' ) )
					|| ( false !== strpos( $msg_lower, 'uncaught error' ) );
			}

			return $tier_a || $tier_b;
		}

		/**
		 * Format stack trace text as HTML for the CSM error log (per-line divs with separators).
		 *
		 * @param string $stack_trace_raw Raw stack trace substring (after "Stack trace:").
		 * @return string HTML with escaped lines, or empty string.
		 */
		public static function format_csm_error_log_stack_trace_html( $stack_trace_raw ) {
			if ( ! is_string( $stack_trace_raw ) || '' === trim( $stack_trace_raw ) ) {
				return '';
			}
			$normalized = str_replace( ABSPATH, '/', $stack_trace_raw );
			$lines      = preg_split( '/\R/u', $normalized );
			if ( ! is_array( $lines ) ) {
				return '';
			}
			$parts = array();
			foreach ( $lines as $line ) {
				$line = trim( $line );
				if ( '' === $line ) {
					continue;
				}
				$parts[] = '<div class="csm-error-log-trace-line">' . esc_html( $line ) . '</div>';
			}
			if ( empty( $parts ) ) {
				return '';
			}
			return '<div class="csm-error-log-trace-lines">' . implode( '', $parts ) . '</div>';
		}

		/**
		 * Append one entry to the CSM error log (admin_site_enhancements_extra).
		 *
		 * @param array $entry Log row (merged with defaults).
		 * @return void
		 */
		public static function append_csm_error_log_entry( array $entry ) {
			$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
			if ( ! is_array( $options_extra ) ) {
				$options_extra = array();
			}
			$log = isset( $options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] ) ? $options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] : array();
			if ( ! is_array( $log ) ) {
				$log = array();
			}
			if ( ! isset( $entry['logged_at_utc'] ) ) {
				$entry['logged_at_utc'] = gmdate( 'c' );
			}
			$log[] = $entry;
			if ( count( $log ) > self::CSM_ERROR_LOG_MAX_ENTRIES ) {
				$log = array_slice( $log, -self::CSM_ERROR_LOG_MAX_ENTRIES );
			}
			$options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] = $log;
			update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );

			if ( isset( $entry['recovery_token'] ) && is_string( $entry['recovery_token'] ) && '' !== $entry['recovery_token'] && isset( $entry['severity'] ) && 'fatal' === $entry['severity'] ) {
				self::maybe_arm_pending_attribution_recovery( $entry['recovery_token'] );
			}
		}

		/**
		 * Remove the error log row whose recovery_token matches.
		 *
		 * @param string $token Recovery token.
		 * @return void
		 */
		public static function remove_csm_error_log_entry_by_token( $token ) {
			if ( ! is_string( $token ) || '' === $token ) {
				return;
			}
			$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
			if ( ! is_array( $options_extra ) ) {
				$options_extra = array();
			}
			$log = isset( $options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] ) ? $options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] : array();
			if ( ! is_array( $log ) ) {
				return;
			}
			$new = array();
			foreach ( $log as $row ) {
				if ( ! is_array( $row ) ) {
					continue;
				}
				if ( isset( $row['recovery_token'] ) && (string) $row['recovery_token'] === $token ) {
					continue;
				}
				$new[] = $row;
			}
			if ( count( $new ) === count( $log ) ) {
				return;
			}
			$options_extra[ self::CSM_ERROR_LOG_OPTION_KEY ] = $new;
			update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
		}

		/**
		 * Arm pending admin-only recovery when exactly one candidate snippet can be probed.
		 *
		 * @param string $recovery_token Token stored in the log row.
		 * @return void
		 */
		public static function maybe_arm_pending_attribution_recovery( $recovery_token ) {
			if ( ! is_string( $recovery_token ) || '' === $recovery_token ) {
				return;
			}
			$ids   = self::get_snippets_evaluated_this_request_ids();
			$saved = self::get_saved_php_snippet_post_id_this_request();
			$candidates = array();
			if ( 1 === count( $ids ) ) {
				$candidates = array( (int) $ids[0] );
			} elseif ( empty( $ids ) && $saved > 0 ) {
				$candidates = array( (int) $saved );
			}
			if ( 1 !== count( $candidates ) ) {
				return;
			}
			set_transient(
				self::CSM_PENDING_RECOVERY_TRANSIENT,
				array(
					'recovery_token' => $recovery_token,
					'candidate_ids'  => $candidates,
				),
				WEEK_IN_SECONDS
			);
		}

		/**
		 * After writing the PHP snippet file, validate and store md5 so runtime can skip PHP_Validator when unchanged.
		 *
		 * @param int    $post_id       Snippet post ID.
		 * @param string $absolute_path Absolute path to {id}.php.
		 * @return void
		 */
		public static function update_php_snippet_validated_hash_after_file_write( $post_id, $absolute_path ) {
			$post_id = absint( $post_id );
			if ( $post_id < 1 || ! is_string( $absolute_path ) || ! file_exists( $absolute_path ) ) {
				if ( $post_id > 0 ) {
					delete_post_meta( $post_id, self::CSM_PHP_FILE_HASH_META_KEY );
				}
				return;
			}
			$instance = self::instance();
			$php_code = $instance->get_php_code( $absolute_path );
			$validator = new ASENHA\Classes\PHP_Validator( $php_code );
			$vr        = $validator->validate();
			if ( false === $vr ) {
				update_post_meta( $post_id, self::CSM_PHP_FILE_HASH_META_KEY, md5_file( $absolute_path ) );
			} else {
				delete_post_meta( $post_id, self::CSM_PHP_FILE_HASH_META_KEY );
			}
		}

		/**
		 * Whether the one-time PHP open-tag disk migration has completed.
		 *
		 * @return bool
		 */
		public static function is_csm_php_open_tag_disk_migration_done() {
			$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
			if ( ! is_array( $options_extra ) ) {
				return false;
			}
			return ! empty( $options_extra[ self::CSM_PHP_OPEN_TAG_DISK_MIGRATION_EXTRA_KEY ] );
		}

		/**
		 * Mark the one-time PHP open-tag disk migration as done.
		 *
		 * @return void
		 */
		public static function set_csm_php_open_tag_disk_migration_done() {
			$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
			if ( ! is_array( $options_extra ) ) {
				$options_extra = array();
			}
			$options_extra[ self::CSM_PHP_OPEN_TAG_DISK_MIGRATION_EXTRA_KEY ] = true;
			update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
		}

		/**
		 * Ensure PHP snippet post content starts with <?php (same rules as process_php_snippet in admin-screens.php).
		 *
		 * @param string $post_content Raw snippet body.
		 * @return string
		 */
		public static function normalize_php_snippet_post_content_for_disk( $post_content ) {
			if ( ! is_string( $post_content ) ) {
				return '';
			}
			$post_content = preg_replace( '/^\s*\n/', '', $post_content );
			$first_five    = substr( $post_content, 0, 5 );
			if ( '<?php' !== $first_five ) {
				$post_content = '<?php' . PHP_EOL . $post_content;
			}
			return $post_content;
		}

		/**
		 * Prepend <?php to canonical {id}.php files in uploads/code-snippets when missing (8.6.1 migration).
		 *
		 * @return int Number of files updated.
		 */
		public static function run_csm_php_open_tag_disk_migration() {
			$upload_dir = wp_upload_dir();
			if ( ! empty( $upload_dir['error'] ) ) {
				return 0;
			}
			$dir = trailingslashit( $upload_dir['basedir'] ) . 'code-snippets';
			if ( ! is_dir( $dir ) || ! is_readable( $dir ) ) {
				return 0;
			}
			$files = glob( $dir . '/*.php' );
			if ( ! is_array( $files ) ) {
				return 0;
			}
			$updated = 0;
			foreach ( $files as $file_path ) {
				$base = basename( $file_path );
				if ( ! preg_match( '/^(\d+)\.php$/', $base, $m ) ) {
					continue;
				}
				$post_id = absint( $m[1] );
				$raw     = file_get_contents( $file_path );
				if ( false === $raw ) {
					continue;
				}
				$without_bom = preg_replace( '/^\xEF\xBB\xBF/', '', $raw );
				$trim_check  = ltrim( $without_bom, " \t\n\r\0\x0B" );
				if ( strncmp( $trim_check, '<?php', 5 ) === 0 ) {
					continue;
				}
				$new_content = '<?php' . PHP_EOL . $without_bom;
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- uploads dir; matches CSM snippet save.
				if ( false !== @file_put_contents( $file_path, $new_content ) ) {
					++$updated;
					self::update_php_snippet_validated_hash_after_file_write( $post_id, $file_path );
				}
			}
			return $updated;
		}

		/**
		 * Register the unified shutdown handler once per request.
		 *
		 * @return void
		 */
		private static function ensure_csm_shutdown_registered() {
			if ( self::$csm_shutdown_registered ) {
				return;
			}
			register_shutdown_function( array( __CLASS__, 'csm_shutdown_handler_static' ) );
			self::$csm_shutdown_registered = true;
		}

		/**
		 * Static entry point for register_shutdown_function.
		 *
		 * @return void
		 */
		public static function csm_shutdown_handler_static() {
			if ( is_null( self::$_instance ) ) {
				return;
			}
			self::instance()->csm_shutdown_handler_inner();
		}

		/**
		 * Cloning is forbidden.
		 */
		public function __clone() {
			_doing_it_wrong( __FUNCTION__, __( 'An error has occurred. Please reload the page and try again.' ), '1.0' );
		}

		/**
		 * Unserializing instances of this class is forbidden.
		 */
		public function __wakeup() {
			_doing_it_wrong( __FUNCTION__, __( 'An error has occurred. Please reload the page and try again.' ), '1.0' );
		}

		/**
		 * Code_Snippets_Manager Constructor
		 *
		 * @access public
		 */
		public function __construct() {

			$options = get_option( ASENHA_SLUG_U, array() );
			$extra_options = get_option( ASENHA_SLUG_U . '_extra', array() );

			include_once 'includes/admin-install.php';
			// register_activation_hook( __FILE__, array( 'Code_Snippets_Manager_Install', 'install' ) );

			// Check if role should be disabled
			$disable_custom_role = isset( $options['code_snippets_disable_custom_role'] ) ? $options['code_snippets_disable_custom_role'] : false;

			if ( $disable_custom_role ) {
				// Remove role if it exists
				add_action( 'admin_init', array( $this, 'maybe_remove_custom_role' ), PHP_INT_MAX );
			} else {
				// Create role (default behavior)
				add_action( 'admin_init', array( 'Code_Snippets_Manager_Install', 'create_roles' ), PHP_INT_MAX );
			}

			add_action( 'init', array( 'Code_Snippets_Manager_Install', 'register_post_type' ), 1 );
			add_action( 'init', array( 'Code_Snippets_Manager_Install', 'register_category' ), 1 );
			add_action( 'init', array( $this, 'register_php_snippet_shortcode' ) );
			add_action( 'wp_ajax_execute_php_snippet_on_demand', array( $this, 'execute_php_snippet_on_demand' ) );

			// Make sure we only flush rewrite rules once as it's an expensive operation
			$flush_rewrite_rules_needed = $options['code_snippets_manager_flush_rewrite_rules_needed'];

			if ( $flush_rewrite_rules_needed ) {
				flush_rewrite_rules();
				$options['code_snippets_manager_flush_rewrite_rules_needed'] = false;
				update_option( ASENHA_SLUG_U, $options, true );
			}

			$this->set_constants();

			add_action( 'plugins_loaded', array( $this, 'maybe_run_csm_php_open_tag_disk_migration_once' ), 1 );

			if ( is_admin() ) {
				include_once 'includes/admin-screens.php';
				include_once 'includes/admin-config.php';
				add_action( 'admin_init', array( $this, 'maybe_run_pending_attribution_recovery' ), 1 );
				add_action( 'admin_notices', array( $this, 'maybe_show_csm_recovery_admin_notice' ) );
			}
			
			// Upgrade to or use new snippets data store introduced in ASE Pro v7.6.4
			if ( ! isset( $extra_options['code_snippets'] ) ) {
				$this->upgrade_snippets_data_store( $extra_options );
			} else {
				$this->snippets_tree = $extra_options['code_snippets'];
			}

			$this->settings = isset( $extra_options['code_snippets_manager_settings'] ) ? $extra_options['code_snippets_manager_settings'] : array();

			if ( ! isset( $this->settings['remove_comments'] ) ) {
				$this->settings['remove_comments'] = false;
			}

			if ( ! $this->snippets_tree || count( $this->snippets_tree ) == 0 ) {
				return false;
			}

			if ( is_null( self::$_instance ) ) {

				// Execute CSS, JS, HTML snippets
				$this->print_code_actions();
				
				// Execute PHP snippets.
				$php_snippets = ( isset( $this->snippets_tree['php'] ) ) ? $this->snippets_tree['php'] : array();

				if ( ! empty( $php_snippets ) ) {
					foreach ( $this->snippets_tree['php'] as $snippet_id => $snippet_info ) {
						$execution_method = isset( $snippet_info['execution_method'] ) ? $snippet_info['execution_method'] : 'on_page_load';
						$execution_location_type = isset( $snippet_info['execution_location_type'] ) ? $snippet_info['execution_location_type'] : 'hook';
						$execution_location = isset( $snippet_info['execution_location'] ) ? $snippet_info['execution_location'] : 'plugins_loaded';
						$execution_location_details = isset( $snippet_info['execution_location_details'] ) ? $snippet_info['execution_location_details'] : 'everywhere';
						
						$should_execute = false;
						
						switch ( $execution_method ) {
							case 'on_page_load';
								switch ( $execution_location_type ) {
									case 'hook';
										switch ( $execution_location_details ) {
											case 'everywhere';
												if ( is_admin() ) {
													$should_execute = true;
												} else {
													$should_execute = true;
												}
												break;
											
											case 'admin';
												if ( is_admin() ) {
													$should_execute = true;
												} else {
													$should_execute = false;
												}
												break;
											
											case 'frontend';
												if ( is_admin() ) {
													$should_execute = false;
												} else {
													$should_execute = true;
												}
												break;
										}
										break;
										
									case 'shortcode';
										// Do nothing here, as the PHP snippet will be executed via the [php_snippet] shortcode on the page it is placed on
										break;
								}							
								break;
								
							case 'on_demand';
								// Do nothing here, as the PHP snippet will be executed on demand
								break;

							case 'via_secure_url';
								$secure_token_in_url = isset( $_GET['codex_token'] ) ? sanitize_text_field( $_GET['codex_token'] ) : '';
								if ( $secure_token_in_url === $snippet_info['secure_url_token'] ) {
									$should_execute = true;
								}
								break;
						}
						
						if ( $should_execute ) {
							// Use a closure here so we can pass an argument. Ref: https://stackoverflow.com/a/34168439
							add_action( $execution_location, function() use ( $snippet_info ) { $this->execute_php_snippet( $snippet_info ); } );
						}
					}					
				}

				if ( isset ( $this->snippets_tree['jquery'] ) && true === $this->snippets_tree['jquery'] ) {
					add_action( 'wp_enqueue_scripts', 'Code_Snippets_Manager::wp_enqueue_scripts' );
				}

			}
			
		}

	    /**
	     * Register the shortcode to execute PHP snippet
	     * 
	     * @since 7.6.5
	     */
	    public function register_php_snippet_shortcode() {
	        add_shortcode( 'php_snippet', array( $this, 'php_snippet_shortcode_handler' ) );
	    }
	    
	    /**
	     * Execute the PHP snippet
	     * 
	     * @since 7.6.6
	     */
	    public function php_snippet_shortcode_handler( $atts ) {
	        $atts = shortcode_atts( array(
	            'id'    => '',
	        ), $atts );
	        
			$post_id = absint( $atts['id'] );

			$filename = $post_id . '.php';
			$upload_dir = wp_upload_dir();
			$file_path = $upload_dir['basedir'] . '/code-snippets/' . $filename;

			// Check if safe mode is enabled
			$is_safe_mode_is_enabled = $this->is_safe_mode_enabled();

			// Check if snippet is active
			$is_snippet_active = $this->is_snippet_active( $post_id );
	        
			// Safe mode is not enabled
			if ( ! $is_safe_mode_is_enabled ) {
				// PHP snippet is active
				if ( $is_snippet_active ) {
					$snippet_info = $this->snippets_tree['php'][$post_id];
					
					return $this->execute_php_snippet( $snippet_info );
				}
			}

	    }

		/**
		 * Remove the code_snippets_editor role if it exists.
		 *
		 * @since 7.6.9
		 * @return void
		 */
		public function maybe_remove_custom_role() {
			if ( get_role( 'code_snippets_editor' ) ) {
				remove_role( 'code_snippets_editor' );
			}
		}

		/**
		 * Activate/deactivate a code
		 *
		 * @return void
		 */
		public function execute_php_snippet_on_demand() {
			if ( ! isset( $_GET['snippet_id'] ) ) {
				die();
			}

			$snippet_id = absint( $_GET['snippet_id'] );
			$response = 'error';
			
			if ( check_admin_referer( 'csm-execute-php-snippet-' . $snippet_id ) ) {

				if ( 'asenha_code_snippet' === get_post_type( $snippet_id ) ) {

					// Check if safe mode is enabled
					$is_safe_mode_is_enabled = $this->is_safe_mode_enabled();

					// Check if snippet is active
					$is_snippet_active = $this->is_snippet_active( $snippet_id );
			        
					// Safe mode is not enabled
					if ( ! $is_safe_mode_is_enabled ) {
						// PHP snippet is active
						if ( $is_snippet_active ) {
							$snippet_info = $this->snippets_tree['php'][$snippet_id];
							
							$this->execute_php_snippet( $snippet_info );
						}
					}

					$response = 'success';
				}
			}
			echo $response;

			die();
		}
				
		/**
		 * Upgrade snippets data store to new option node at 
		 * from admin_site_enhancements_extra['code_snippets_manager_tree'] in version up to v7.6.3
		 * to admin_site_enhancements_extra['code_snippets'] in v7.6.4 onward
		 * 
		 * @since 7.6.4
		 */
		public function upgrade_snippets_data_store( $extra_options ) {
			$legacy_code_snippets = isset( $extra_options['code_snippets_manager_tree'] ) ? $extra_options['code_snippets_manager_tree'] : array();
			$current_code_snippets = array();
			
			foreach ( $legacy_code_snippets as $snippet_args => $snippet_filenames ) {

				// CSS snippet
				if ( false !== strpos( $snippet_args, '-css-' ) ) {
					$snippet_args_parts = explode( '-', $snippet_args );
					$load_type = $snippet_args_parts[3]; // external | internal
					$position_on_page = $snippet_args_parts[2]; // header | footer
					$location = $snippet_args_parts[0]; // array of frontend &| admin &| login
					$priority = 10;
					$execution_location = '';

					foreach ( $snippet_filenames as $snippet_filename ) {
						$snippet_filename_parts = explode( '.', $snippet_filename );

						$snippet_id = $snippet_filename_parts[0]; // equals to post ID, e.g. 6123
						$snippet_title = get_the_title( $snippet_id );
						
						$current_code_snippets['css'][$snippet_id]['id'] = $snippet_id; // e.g. 6123
						$current_code_snippets['css'][$snippet_id]['filename'] = $snippet_filename; // e.g. 6123.css or 6123.css?v=1234
						$current_code_snippets['css'][$snippet_id]['title'] = $snippet_title; // e.g. Custom colors for wp-admin
						$current_code_snippets['css'][$snippet_id]['load_type'] = $load_type; // external (as a file) | internal (inline)
						$current_code_snippets['css'][$snippet_id]['position_on_page'] = $position_on_page; // header | footer
						$current_code_snippets['css'][$snippet_id]['location'][] = $location; // array of frontend &| admin &| login
						// $current_code_snippets['css'][$snippet_id]['location_specifics'] = $location_specifics;
						$current_code_snippets['css'][$snippet_id]['execution_location'] = $execution_location; // between 1 to 9999
						$current_code_snippets['css'][$snippet_id]['priority'] = $priority; // between 1 to 9999
						// $current_code_snippets['css'][$snippet_id]['device'] = $device; // all | desktop | mobile
						// $current_code_snippets['css'][$snippet_id]['is_scheduled'] = $is_scheduled; // boolean true | false
						// $current_code_snippets['css'][$snippet_id]['schedule_start'] = $schedule_start;
						// $current_code_snippets['css'][$snippet_id]['schedule_end'] = $schedule_end;
					}
				}

				// JS snippet
				if ( false !== strpos( $snippet_args, '-js-' ) ) {
					$snippet_args_parts = explode( '-', $snippet_args );
					$load_type = $snippet_args_parts[3]; // external | internal
					$position_on_page = $snippet_args_parts[2]; // header | footer
					$location = $snippet_args_parts[0]; // array of frontend &| admin &| login
					$priority = 10;
					$execution_location = '';

					foreach ( $snippet_filenames as $snippet_filename ) {
						$snippet_filename_parts = explode( '.', $snippet_filename );
						$snippet_id = $snippet_filename_parts[0]; // equals to post ID, e.g. 6123
						$snippet_title = get_the_title( $snippet_id );

						$current_code_snippets['js'][$snippet_id]['id'] = $snippet_id; // e.g. 6123
						$current_code_snippets['js'][$snippet_id]['filename'] = $snippet_filename; // e.g. 6123.js
						$current_code_snippets['js'][$snippet_id]['title'] = $snippet_title; // e.g. Add custom element in admin bar
						$current_code_snippets['js'][$snippet_id]['load_type'] = $load_type; // external (as a file) | internal (inline)
						$current_code_snippets['js'][$snippet_id]['position_on_page'] = $position_on_page; // header | footer
						$current_code_snippets['js'][$snippet_id]['location'][] = $location; // array of frontend &| admin &| login
						// $current_code_snippets['js'][$snippet_id]['location_specifics'] = $location_specifics;
						$current_code_snippets['js'][$snippet_id]['execution_location'] = $execution_location; // between 1 to 9999
						$current_code_snippets['js'][$snippet_id]['priority'] = $priority; // between 1 to 9999
						// $current_code_snippets['js'][$snippet_id]['device'] = $device; // all | desktop | mobile
						// $current_code_snippets['js'][$snippet_id]['is_scheduled'] = $is_scheduled; // boolean true | false
						// $current_code_snippets['js'][$snippet_id]['schedule_start'] = $schedule_start;
						// $current_code_snippets['js'][$snippet_id]['schedule_end'] = $schedule_end;
					}
				}

				// HTML snippet
				if ( false !== strpos( $snippet_args, '-html-' ) ) {
					$snippet_args_parts = explode( '-', $snippet_args );
					$position_on_page = $snippet_args_parts[2]; // header | body_open | footer
					$location = $snippet_args_parts[0]; // array of frontend &| admin
					$priority = 10;
					$execution_location = '';

					foreach ( $snippet_filenames as $snippet_filename ) {
						$snippet_filename_parts = explode( '.', $snippet_filename );
						$snippet_id = $snippet_filename_parts[0]; // equals to post ID, e.g. 6123
						$snippet_title = get_the_title( $snippet_id );

						$current_code_snippets['html'][$snippet_id]['id'] = $snippet_id; // e.g. 6123
						$current_code_snippets['html'][$snippet_id]['filename'] = $snippet_filename; // e.g. 6123.html
						$current_code_snippets['html'][$snippet_id]['title'] = $snippet_title; // e.g. Add custom element in admin bar
						// load_type is 'both' by default for HTMl snippet
						$current_code_snippets['html'][$snippet_id]['position_on_page'] = $position_on_page; // header | body_open | footer
						$current_code_snippets['html'][$snippet_id]['location'][] = $location; // array of frontend &| admin
						// $current_code_snippets['html'][$snippet_id]['location_specifics'] = $location_specifics;
						$current_code_snippets['html'][$snippet_id]['execution_location'] = $execution_location; // between 1 to 9999
						$current_code_snippets['html'][$snippet_id]['priority'] = $priority; // between 1 to 9999
						// $current_code_snippets['html'][$snippet_id]['device'] = $device; // all | desktop | mobile
						// $current_code_snippets['html'][$snippet_id]['is_scheduled'] = $is_scheduled; // boolean true | false
						// $current_code_snippets['html'][$snippet_id]['schedule_start'] = $schedule_start;
						// $current_code_snippets['html'][$snippet_id]['schedule_end'] = $schedule_end;
					}
				}
				
				// PHP snippet
				if ( false !== strpos( $snippet_args, '-php-' ) ) {
					$location_type = 'global';
					$location = 'sitewide';
					$execution_location = 'plugins_loaded';
					$priority = 10;
					
					foreach ( $snippet_filenames as $snippet_filename ) {
						$snippet_filename_parts = explode( '.', $snippet_filename );
						$snippet_id = $snippet_filename_parts[0]; // equals to post ID, e.g. 6123
						$snippet_title = get_the_title( $snippet_id );

						$current_code_snippets['php'][$snippet_id]['id'] = $snippet_id; // e.g. 6123
						$current_code_snippets['php'][$snippet_id]['filename'] = $snippet_filename; // e.g. 6123.php
						$current_code_snippets['php'][$snippet_id]['title'] = $snippet_title; // e.g. Add custom element in admin bar
						$current_code_snippets['php'][$snippet_id]['location_type'] = $location_type; // global | page-specific
						$current_code_snippets['php'][$snippet_id]['location'][] = $location; // sitewide | admin | frontend
						// $current_code_snippets['php'][$snippet_id]['location_specifics'] = $location_specifics;
						// $current_code_snippets['php'][$snippet_id]['execution_method'] = $execution_method; // on_page_load | on_demand
						$current_code_snippets['php'][$snippet_id]['execution_location'] = $execution_location; // plugins_loaded | after_setup_theme | etc
						$current_code_snippets['php'][$snippet_id]['priority'] = $priority; // between 1 to 9999
						// $current_code_snippets['php'][$snippet_id]['device'] = $device; // all | desktop | mobile
						// $current_code_snippets['php'][$snippet_id]['is_scheduled'] = $is_scheduled; // boolean true | false
						// $current_code_snippets['php'][$snippet_id]['schedule_start'] = $schedule_start;
						// $current_code_snippets['php'][$snippet_id]['schedule_end'] = $schedule_end;
					}
				}

				// CSS snippet
				if ( false !== strpos( $snippet_args, 'jquery' ) ) {
					$use_jquery = $snippet_filenames; // true or false;
					$current_code_snippets['jquery'] = $use_jquery;
				}
				
			}
			$extra_options['code_snippets'] = $current_code_snippets;
			update_option( ASENHA_SLUG_U . '_extra', $extra_options );
			
			sleep( 2 );

			unset( $extra_options['code_snippets_manager_tree'] );
			update_option( ASENHA_SLUG_U . '_extra', $extra_options );

			$this->snippets_tree = $current_code_snippets;			
		}

		/**
		 * Add the appropriate wp actions
		 */
		public function print_code_actions() {

			// Current - since ASE Pro 7.6.4
			foreach ( $this->snippets_tree as $snippet_type => $snippets ) {
				if ( in_array( $snippet_type, array( 'css', 'js', 'html' ) ) ) {
					foreach ( $snippets as $snippet ) {

						foreach( $snippet['location'] as $location ) {
							$action = '';
							
							switch ( $location ) {
								case 'frontend';
									$action = 'wp_';
									break;

								case 'admin';
									$action = 'admin_';
									break;

								case 'login';
									$action = 'login_';
									break;
							}

							switch ( $snippet['position_on_page'] ) {
								case 'header';
									$action .= 'head';
									break;

								case 'body_open';
									$action .= 'body_open';
									break;

								case 'footer';
									$action .= 'footer';
									break;
							}
							
							$priority = ( 'wp_footer' === $action ) ? 40 : 10;
							
							add_action( $action, array( $this, 'print_' . $snippet_type . '_' . $snippet['id'] ), $priority );

							// Allow loading "frontend CSS" snippets inside the block editor edit screen (post.php / post-new.php)
							// when the "Type of page" conditional is set to "Block editor".
							// Uses enqueue_block_assets which properly injects CSS into the editor canvas (iframe).
							if ( 'frontend' === $location && 'css' === $snippet_type && $this->snippet_targets_block_editor( $snippet ) ) {
								add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_editor_css_' . $snippet['id'] ), 10 );
							}
						}

					}
				}
			}
				
		}

		/**
		 * Print the custom code.
		 */
		public function __call( $function, $args ) {

			// Handle block editor CSS injection via enqueue_block_assets hook.
			if ( strpos( $function, 'enqueue_block_editor_css_' ) === 0 ) {
				$snippet_id = str_replace( 'enqueue_block_editor_css_', '', $function );
				$this->enqueue_block_editor_css( $snippet_id );
				return;
			}

			if ( strpos( $function, 'print_' ) === false ) {
				return false;
			}

			$function = str_replace( 'print_', '', $function );
				
			// Current - since ASE Pro 7.6.4
			$function_parts = explode( '_', $function );
			$snippet_type = $function_parts[0]; // css | js | html | php
			$snippet_id = $function_parts[1]; // e.g. 6123
			
			$snippets_tree = $this->snippets_tree;

			if ( ! isset( $snippets_tree[$snippet_type][$snippet_id] ) ) {
				return false;
			}

			$snippet_options = $snippets_tree[$snippet_type][$snippet_id];
			// if ( '9750' == $snippet_id ) {
			// 	vi( $snippet_options );				
			// }

			// Get current URL
			
			// Calling Common_Methods here may trigger fatal error in certain PHP snippets when loaded in the frontend
			// $common_methods = new ASENHA\Classes\Common_Methods;
			// $current_url = $common_methods->get_current_url(); // e.g. https://www.site.com/some-page

			// Let's use the raw method here directly
			$url = ( is_ssl() ? 'https://' : 'http://' ) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
			$url_parts = explode( '?', $url, 2 ); // limit to max of 2 elements with last element containing the rest of the string
			if ( isset( $url_parts[0] ) ) {
				$output = trim( $url_parts[0], '/' );
			}

			$current_url = $output ? urldecode( $output ) : '/';
			
			// Check for frontend conditionals.
			// - Always enforce on frontend requests (non-admin, non-login).
			// - Also enforce on wp-admin when the snippet targets the block editor (so it won't load on other admin screens).
			$should_check_conditionals = false;

			if ( ! is_admin() && false === strpos( $current_url, 'wp-login.php' ) ) {
				$should_check_conditionals = true;
			} elseif ( is_admin() && $this->snippet_targets_block_editor( $snippet_options ) ) {
				$should_check_conditionals = true;
			}

			if ( $should_check_conditionals && ! $this->is_conditionals_fulfilled( $snippet_id, $snippet_type, $snippet_options ) ) {
				return false; // Do not proceed / do not load CSS/JS/HTML snippet when conditionals are not met
			}
			
			$where = isset( $snippets_tree[$snippet_type][$snippet_id]['load_type'] ) ? $snippets_tree[$snippet_type][$snippet_id]['load_type'] : 'internal'; // external | internal
			$type = $snippet_type; // css | js | html
			$tag   = array( 'css' => 'style', 'js' => 'script' );

			$type_attr = ( 'js' === $type && ! current_theme_supports( 'html5', 'script' ) ) ? ' type="text/javascript"' : '';
			$type_attr = ( 'css' === $type && ! current_theme_supports( 'html5', 'style' ) ) ? ' type="text/css"' : $type_attr;

			$upload_url = str_replace( array( 'https://', 'http://' ), '//', CSM_UPLOAD_URL ) . '/';

			if ( 'internal' === $where ) {

				$before = $this->settings['remove_comments'] ? '' : '<!-- start Code Snippets Manager -->' . PHP_EOL;
				$after  = $this->settings['remove_comments'] ? '' : '<!-- end Code Snippets Manager -->' . PHP_EOL;

				if ( 'css' === $type || 'js' === $type ) {
					$before .= '<' . $tag[ $type ] . ' ' . $type_attr . '>' . PHP_EOL;
					$after   = '</' . $tag[ $type ] . '>' . PHP_EOL . $after;
				}

			}

			$_filename = $snippets_tree[$snippet_type][$snippet_id]['filename'];

			if ( 'internal' ===  $where && ( strstr( $_filename, 'css' ) || strstr( $_filename, 'js' ) ) ) {
				if ( $this->settings['remove_comments'] || empty( $type_attr ) ) {
					$code_snippet = @file_get_contents( CSM_UPLOAD_DIR . '/' . $_filename );
					if ( $this->settings['remove_comments'] ) {
							$code_snippet = str_replace( array( 
									'<!-- start Code Snippets Manager -->' . PHP_EOL, 
									'<!-- end Code Snippets Manager -->' . PHP_EOL 
							), '', $code_snippet );
					}
					if ( empty( $type_attr ) ) {
						$code_snippet = str_replace( array( ' type="text/javascript"', ' type="text/css"' ), '', $code_snippet );
					}
					echo $code_snippet;
				} else {
					echo @file_get_contents( CSM_UPLOAD_DIR . '/' . $_filename );
				}
			}

			if ( 'internal' === $where && ! strstr( $_filename, 'css' ) && ! strstr( $_filename, 'js' ) ) {
				$post = get_post( $_filename );
				echo $before . $post->post_content . $after;
			}

			if ( 'external' === $where && 'js' === $type ) {
				echo PHP_EOL . "<script{$type_attr} src='{$upload_url}{$_filename}'></script>" . PHP_EOL;
			}

			if ( 'external' === $where && 'css' === $type ) {
				$shortfilename = preg_replace( '@\.css\?v=.*$@', '', $_filename );
				echo PHP_EOL . "<link rel='stylesheet' id='{$shortfilename}-css' href='{$upload_url}{$_filename}'{$type_attr} media='all' />" . PHP_EOL;
			}

			if ( 'html' === $type ) {
				$post_id = str_replace( '.html', '', $_filename );
				$post      = get_post( $post_id );
				echo $post->post_content . PHP_EOL;
			}
				
		}
		
		/**
		 * Frontend conditionals check
		 * 
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L672
		 * @since 7.6.7

		 * @return boolean
		 */
		public function is_conditionals_fulfilled( $snippet_id, $snippet_type, $snippet_options ) {
			// if ( '9750' == $snippet_id ) {
			// 	vi( $snippet_options );
			// }
			$result = true; // By default, return true, i.e. conditionals is fulfilled
			$conditionals = isset( $snippet_options['conditionals'] ) ? $snippet_options['conditionals'] : array();
			// if ( '9750' == $snippet_id ) {
			// 	vi( $conditionals );
			// }

			// Check if we have conditionals to work with
			if ( ! ( empty( $conditionals ) || isset( $conditionals[0] ) && empty( $conditionals[0] ) ) ) {
				foreach ( $conditionals[0] as $filter ) {
					// if ( '9750' == $snippet_id ) {
					// 	vi( $filter );
					// }

					// $filter here has 'conditions' and 'type' (showif | hideif)
					// There is only one $filter to iterate through

					$filter_conditions = $this->get_item_property( $filter, 'conditions' );
					// if ( '9750' == $snippet_id ) {
					// 	vi( $filter_conditions );
					// }

					if ( empty( $filter_conditions ) ) {
						continue; // skip this iteration, continue to the next one
					}
					
					// Set initial state for AND conditionals
					$and_conditions = null;
					
					// Let's go through AND scopes, i.e. groups of OR conditionals
					foreach ( $filter_conditions as $and_scope ) {
						$and_scope_conditionals = $this->get_item_property( $and_scope, 'conditions' );
						// if ( '9750' == $snippet_id ) {
						// 	vi( $and_scope_conditionals );
						// }

						if ( empty( $and_scope_conditionals ) ) {
							continue; // skip this iteration, continue to the next one								
						}

						// Set initial state for OR conditionals within this iteraation of the AND scopes/groups
						$or_conditions = null;

						// Let's go through OR conditionals
						foreach( $and_scope_conditionals as $or_conditional ) {
							$method_name = str_replace( '-', '_', $this->get_item_property( $or_conditional, 'param' ) ); // e.g. location-post-type becomse location_post_type
							$operator    = $this->get_item_property( $or_conditional, 'operator' );
							$value       = $this->get_item_property( $or_conditional, 'value' );
							
							// Get the result of the OR condition
							if ( is_null( $or_conditions ) ) {
								$or_conditions = $this->call_method( $method_name, $operator, $value );
							} else {
								$or_conditions = $or_conditions || $this->call_method( $method_name, $operator, $value ); // Returns true if either $or_conditions or $this->call_method is true. Returns false if both are false.
							}
						}

						// Get the current result of AND condition
						if ( is_null( $and_conditions ) ) {
							$and_conditions = $or_conditions; // First $and_scope, so $and_conditions is still null. Set value as $or_conditions
						} else {
							$and_conditions = $and_conditions && $or_conditions; // Returns true only if both $and_conditions and $or_conditions are true. Otherwise, will return false.
						}
						
						$and_conditions = is_null( $and_conditions ) ? $or_conditions : $and_conditions && $or_conditions;  // For the later option, returns true only if both $and_conditions and $or_conditions are true. Otherwise, will return false.
					}
					// if ( '9750' == $snippet_id ) {
					// 	vi( $and_conditions, '', 'for page' );
					// 	vi( $or_conditions, '', 'for page'  );
					// }

					// Get the final result of the conditionals.
					$result = ( 'showif' == $this->get_item_property( $filter, 'type' ) ) ? $and_conditions : ! $and_conditions;	
				}
			}
						
			return $result;
		}

		/**
		 * Get property of an item
		 * 
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L655
		 * @since 7.6.7
		 */
		public function get_item_property( $item, $property ) {
			if ( is_object( $item ) ) {
				return $item->$property ?? null; // return null if there is no such property
			} elseif ( isset( $item[$property] ) ) {
				return $item[$property];
			}
		}

		/**
		* Call specified method
		*
		* @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L724
		* @since 7.6.7

		* @param $method_name
		* @param $operator
		* @param $value
		*
		* @return bool
		*/
		private function call_method( $method_name, $operator, $value ) {
			if ( method_exists( $this, $method_name ) ) {
				return $this->$method_name( $operator, $value );
			} else {
				return false;
			}
		}

		/**
		 * Check whether the snippet is configured to load inside the block editor edit screen.
		 *
		 * This is based on the saved conditionals, specifically:
		 * Location -> Type of page -> Block editor (value: is_block_editor).
		 *
		 * @since 7.9.99
		 *
		 * @param array $snippet_options Snippet options array.
		 * @return bool
		 */
		private function snippet_targets_block_editor( $snippet_options ) {
			$conditionals = isset( $snippet_options['conditionals'] ) ? $snippet_options['conditionals'] : array();

			if ( empty( $conditionals ) ) {
				return false;
			}

			return $this->conditionals_contain_param_value( $conditionals, 'location-page-type', 'is_block_editor' );
		}

		/**
		 * Enqueue CSS for block editor via wp_add_inline_style().
		 *
		 * Called from enqueue_block_assets hook for CSS snippets targeting the block editor.
		 * This properly injects CSS into the editor canvas (iframe) where admin_head cannot reach.
		 *
		 * @since 7.9.99
		 *
		 * @param int $snippet_id The snippet post ID.
		 * @return void
		 */
		private function enqueue_block_editor_css( $snippet_id ) {
			$snippet_id = absint( $snippet_id );
			$snippets_tree = $this->snippets_tree;

			if ( ! isset( $snippets_tree['css'][ $snippet_id ] ) ) {
				return;
			}

			$snippet_options = $snippets_tree['css'][ $snippet_id ];

			// Check conditionals (location_page_type, location_post_type, etc. are block-editor-aware).
			if ( ! $this->is_conditionals_fulfilled( $snippet_id, 'css', $snippet_options ) ) {
				return;
			}

			$filename = isset( $snippet_options['filename'] ) ? (string) $snippet_options['filename'] : '';
			if ( '' === $filename ) {
				return;
			}

			// Strip cachebuster query string from filename (e.g. ".css?v=1234").
			$filename = preg_replace( '@\?v=.*$@', '', $filename );
			$filename = ltrim( $filename, '/' );

			$file_path = trailingslashit( CSM_UPLOAD_DIR ) . $filename;
			if ( ! file_exists( $file_path ) ) {
				return;
			}

			$css = @file_get_contents( $file_path );
			if ( ! is_string( $css ) || '' === $css ) {
				return;
			}

			// Strip <style> wrapper if present (inline snippets include it).
			$css = $this->strip_style_tag_wrapper( $css );

			if ( '' === $css ) {
				return;
			}

			// Inject into wp-block-library which is always present in block editor.
			wp_add_inline_style( 'wp-block-library', $css );
		}

		/**
		 * Strip a wrapping <style> tag from CSS, if present.
		 *
		 * @since 7.9.99
		 *
		 * @param string $css Raw CSS (may contain <style> wrapper).
		 * @return string
		 */
		private function strip_style_tag_wrapper( $css ) {
			$css = trim( $css );

			// Remove optional <style ...> wrapper used by inline snippets.
			$css = preg_replace( '@^\s*<style\b[^>]*>\s*@i', '', $css );
			$css = preg_replace( '@\s*</style>\s*$@i', '', $css );

			return trim( $css );
		}

		/**
		 * Recursively scan the saved conditionals structure for a param/value match.
		 *
		 * The structure is a JSON-decoded tree of arrays/stdClass objects (filters/scopes/conditions).
		 *
		 * @since 7.9.99
		 *
		 * @param mixed  $data   Conditionals data (array/object/scalar).
		 * @param string $param  Conditional param name (e.g. location-page-type).
		 * @param string $needle Conditional value to match (e.g. is_block_editor).
		 * @return bool
		 */
		private function conditionals_contain_param_value( $data, $param, $needle ) {
			if ( is_object( $data ) ) {
				if ( isset( $data->param ) && $param === $data->param ) {
					return $this->conditional_value_contains( $data->value ?? null, $needle );
				}

				foreach ( get_object_vars( $data ) as $value ) {
					if ( $this->conditionals_contain_param_value( $value, $param, $needle ) ) {
						return true;
					}
				}
			} elseif ( is_array( $data ) ) {
				foreach ( $data as $value ) {
					if ( $this->conditionals_contain_param_value( $value, $param, $needle ) ) {
						return true;
					}
				}
			}

			return false;
		}

		/**
		 * Determine if a conditional value contains a specific selection.
		 *
		 * @since 7.9.99
		 *
		 * @param mixed  $value  Conditional value (string/array).
		 * @param string $needle Value to match.
		 * @return bool
		 */
		private function conditional_value_contains( $value, $needle ) {
			if ( is_array( $value ) ) {
				return in_array( $needle, $value, true );
			}

			return (string) $needle === (string) $value;
		}

		/**
		 * Check whether current admin screen is the block editor post edit screen.
		 *
		 * Covers `post.php` and `post-new.php` when Gutenberg/block editor is active.
		 *
		 * @since 7.9.99
		 *
		 * @return bool
		 */
		private function is_block_editor_edit_screen() {
			if ( ! is_admin() ) {
				return false;
			}

			if ( ! function_exists( 'get_current_screen' ) ) {
				return false;
			}

			$screen = get_current_screen();
			if ( ! $screen ) {
				return false;
			}

			// Post editor screens are `base=post` (post.php / post-new.php).
			if ( 'post' !== $screen->base ) {
				return false;
			}

			if ( method_exists( $screen, 'is_block_editor' ) ) {
				return (bool) $screen->is_block_editor();
			}

			return false;
		}

		/**
		 * Get the post ID being edited in the block editor, when available.
		 *
		 * @since 7.9.99
		 *
		 * @return int Post ID or 0 for new posts / unknown.
		 */
		private function get_block_editor_post_id() {
			if ( ! $this->is_block_editor_edit_screen() ) {
				return 0;
			}

			return isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
		}

		/**
		 * Get the post type being edited in the block editor.
		 *
		 * Works for both `post.php` (existing post) and `post-new.php` (new post).
		 *
		 * @since 7.9.99
		 *
		 * @return string Post type slug, or empty string on failure.
		 */
		private function get_block_editor_post_type() {
			if ( ! $this->is_block_editor_edit_screen() ) {
				return '';
			}

			$post_id = $this->get_block_editor_post_id();
			if ( $post_id ) {
				$post_type = get_post_type( $post_id );
				if ( is_string( $post_type ) && '' !== $post_type ) {
					return $post_type;
				}
			}

			if ( function_exists( 'get_current_screen' ) ) {
				$screen = get_current_screen();
				if ( $screen && ! empty( $screen->post_type ) ) {
					return (string) $screen->post_type;
				}
			}

			if ( isset( $_GET['post_type'] ) ) {
				return sanitize_key( $_GET['post_type'] );
			}

			return '';
		}

		/**
		 * A some selected page
		 *
		 * @since 7.6.7
		 * @param $operator
		 * @param $value
		 *
		 * @return boolean
		 */
		private function location_page_type( $operator, $conditional_page_type ) {
			if ( $this->is_block_editor_edit_screen() ) {
				$actual_page_type = 'is_block_editor';
			} elseif ( is_front_page() ) {
				$actual_page_type = 'is_front_page';
			} elseif ( is_home() ) {
				$actual_page_type = 'is_home';
			} elseif ( is_singular() ) {
				$actual_page_type = 'is_singular';
			} elseif ( is_author() ) {
				$actual_page_type = 'is_author';
			} elseif ( is_date() ) {
				$actual_page_type = 'is_date';
			} elseif ( is_archive() ) {
				$actual_page_type = 'is_archive';
			} elseif ( is_search() ) {
				$actual_page_type = 'is_search';
			} elseif ( is_404() ) {
				$actual_page_type = 'is_404';
			} else {
				$actual_page_type = '';
			}

			return $this->check_by_operator( $operator, $actual_page_type, $conditional_page_type );
		}

		/**
		 * A post type of the current page viewed by the user
		 *
		 * @since 7.6.7
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L1139
		 * 
		 * @param $operator
		 * @param $conditional_post_type
		 *
		 * @return boolean
		 */
		private function location_post_type( $operator, $conditional_post_type ) {
			if ( $this->is_block_editor_edit_screen() ) {
				$actual_post_type = $this->get_block_editor_post_type();
				if ( '' !== $actual_post_type ) {
					return $this->check_by_operator( $operator, $conditional_post_type, $actual_post_type );
				}

				return false;
			}

			if ( is_singular() ) {
				$actual_post_type = get_post_type();
				return $this->check_by_operator( $operator, $conditional_post_type, $actual_post_type );
			}

			return false;
		}
		
		/**
		 * A single page/post/CPT viewed by the user
		 */
		private function location_single_post( $operator, $conditional_single_posts ) {			
			$conditional_single_post_ids = array();

			if ( ! empty( $conditional_single_posts ) ) {
				foreach ( $conditional_single_posts as $post ) {
					$post_parts = explode( '__', $post ); // e.g. 5920__movie__Mission Impossible
					$conditional_single_post_ids[] = $post_parts[0];
				}
			}

			if ( $this->is_block_editor_edit_screen() ) {
				$post_id = $this->get_block_editor_post_id();
				if ( $post_id ) {
					return $this->check_by_operator( $operator, $conditional_single_post_ids, $post_id );
				}

				return false;
			}

			if ( is_singular() ) {
				return $this->check_by_operator( $operator, $conditional_single_post_ids, get_the_ID() );
			}

			return false;
		}

		/**
		 * A taxonomy archive page viewed by the user
		 *
		 * @since 7.6.7
		 * @link https://plugins.trac.wordpress.org/browser/insert-headers-and-footers/tags/2.2.5/includes/conditional-logic/class-wpcode-conditional-page.php#L299
		 * 
		 * @param $operator
		 * @param $conditional_taxonomy_type
		 *
		 * @return boolean
		 */
		private function location_taxonomy_type( $operator, $conditional_taxonomy_type ) {
			global $wp_query;

			if ( is_null( $wp_query ) ) {
				return '';
			}

			$queried_object = get_queried_object();

			$actual_taxonomy_type = isset( $queried_object->taxonomy ) ? $queried_object->taxonomy : ''; // the taxonomy slug

			return $this->check_by_operator( $operator, $conditional_taxonomy_type, $actual_taxonomy_type );
		}

		/**
		 * A taxonomy archive page for a term, viewed by the user
		 *
		 * @since 7.6.7
		 * @link https://plugins.trac.wordpress.org/browser/insert-headers-and-footers/tags/2.2.5/includes/conditional-logic/class-wpcode-conditional-page.php#L314
		 * 
		 * @param $operator
		 * @param $conditional_taxonomy_term
		 *
		 * @return boolean
		 */
		private function location_taxonomy_term( $operator, $conditional_taxonomy_term ) {
			if ( is_string( $conditional_taxonomy_term ) ) {
				$conditional_taxonomy_term_parts = explode( '__', $conditional_taxonomy_term );
				$conditional_taxonomy_term_id = intval( $conditional_taxonomy_term_parts[0] ); // an integer
				$conditional_taxonomy_term_slug = $conditional_taxonomy_term_parts[1];				
			}
			
			if ( is_array( $conditional_taxonomy_term ) ) {
				if ( count( $conditional_taxonomy_term ) > 1 ) {
					// Two or more terms were selected
					$term_ids = array();
					foreach( $conditional_taxonomy_term as $term ) {
						$term_parts = explode( '__', $term );
						$term_ids[] = intval( $term_parts[0] );
					}					
					$conditional_taxonomy_term_id = $term_ids; // an array of integers
				} else {
					// Only one term were selected
					$term_parts = explode( '__', $conditional_taxonomy_term[0] );
					$conditional_taxonomy_term_id = intval( $term_parts[0] ); // an integer
				}
			}
			// vi( $conditional_taxonomy_term_id );
			
			global $wp_query;

			if ( is_null( $wp_query ) ) {
				return array();
			}
			
			$actual_taxonomy_term_ids = '';

			if ( is_tax() || is_category() || is_tag() ) {
				$queried_object = get_queried_object();

				$actual_taxonomy_term_ids = isset( $queried_object->term_id ) ? $queried_object->term_id : '';
			}

			if ( $this->is_block_editor_edit_screen() ) {
				$post_id = $this->get_block_editor_post_id();

				$actual_taxonomy_term_ids = get_terms(
					array(
						'object_ids' => $post_id,
						'fields'     => 'ids',
					)
				);
			}

			if ( is_singular() ) {
				$actual_taxonomy_term_ids = get_terms(
					array(
						'object_ids' => array( get_the_ID() ),
						'fields'     => 'ids',
					)
				);
			}
			// vi( $actual_taxonomy_term_ids );

			return $this->check_by_operator( $operator, $conditional_taxonomy_term_id, $actual_taxonomy_term_ids );
		}

		/**
		 * A URL of the current page viewed by the user
		 *
		 * @since 7.6.7
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L1111
		 * 
		 * @param $operator
		 * @param $conditional_url
		 *
		 * @return boolean
		 */
		private function location_url( $operator, $conditional_url ) {
			$common_methods = new ASENHA\Classes\Common_Methods;
			$current_url = $common_methods->get_current_url(); // e.g. https://www.site.com/some-page

			return $current_url ? $this->check_by_operator( $operator, trim( $current_url, '/' ), trim( trim( $conditional_url ), '/' ) ) : false;
		}
		
		/**
		 * User login status
		 * 
		 * @since 7.6.7
		 * 
		 * @return bool
		 */
		public function user_login_status( $operator, $conditional_login_status ) {
			switch ( $conditional_login_status ) {
				case 'yes';
					$conditional_login_status = true;
					break;
					
				case 'no';
					$conditional_login_status = false;
					break;
			}
			
			return $this->check_by_operator( $operator, $conditional_login_status, is_user_logged_in() );
		}

		/**
		 * A role of the user who is viewing your website. The role "guest" is applied for unregistered users.
		 *
		 * @since 7.6.7
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L869
		 *
		 * @param string $operator
		 * @param string $conditional_user_role
		 *
		 * @return boolean
		 */
		private function user_role( $operator, $conditional_user_role ) {
			if ( ! is_user_logged_in() ) {
				return $this->check_by_operator( $operator, $conditional_user_role, 'guest' );
			} else {
				$current_user = wp_get_current_user();
				if ( ! ( $current_user instanceof WP_User ) ) {
					return false;
				}
				$current_user_roles = array_values( $current_user->roles );

				return $this->check_by_operator( $operator, $conditional_user_role, $current_user_roles );
			}
		}

		/**
		 * Device type check
		 *
		 * @since 7.8.10
		 * @param $operator
		 * @param $value
		 *
		 * @return boolean
		 */
		private function location_device_type( $operator, $conditional_device_type ) {
			if ( version_compare( PHP_VERSION, '8.0.0', '>=' ) ) {
				// MobileDetect v4.8.x
				$detection = new MobileDetectStandalone();
			} else {
				// MobileDetect v3.74.x
				$detection = new \Detection\AsenhaMobileDetect;
			}

			// $detection->setUserAgent('iPad'); // For testing
			// $detection->setUserAgent('iPhone'); // For testing
			// $user_agent = $detection->getUserAgent(); // For testing
			
			$actual_device_type = 'unknown';
			$is_desktop = false;
			$is_tablet = false;
			$is_mobile = false;
			

			if ( version_compare( PHP_VERSION, '8.0.0', '>=' ) ) {
				// MobileDetect v4.8.x
				try {
					$is_tablet = $detection->isTablet();
					$is_mobile = $detection->isMobile();
				} catch ( \Detection\Exception\MobileDetectException $e ) {
					// Handle errors here...
				}
			} else {
				// MobileDetect v3.74.x
				$is_tablet = $detection->isTablet();
				$is_mobile = $detection->isMobile();
			}
			
			if ( ! $is_tablet && ! $is_mobile ) {
				$is_desktop = true;
			}
						
			if ( $is_desktop ) {
				$actual_device_type = 'desktop';
			}
			
			if ( ! $is_desktop && $is_tablet && $is_mobile ) {
				$actual_device_type = 'tablet';				
			}

			if ( ! $is_desktop && ! $is_tablet && $is_mobile ) {
				$actual_device_type = 'mobile';				
			}

			return $this->check_by_operator( $operator, $actual_device_type, $conditional_device_type );
		}

		/**
		 * Check whether a conditional is fulfilled by supplying the operator and values
		 *
		 * @since 7.6.6
		 * @link https://plugins.trac.wordpress.org/browser/insert-php/tags/2.4.10/includes/class.execute.snippet.php#L829
		 * 
		 * @param $operation
		 * @param $first
		 * @param $second
		 * @param $third
		 *
		 * @return bool
		 */
		public function check_by_operator( $operation, $first, $second, $third = false ) {
			switch ( $operation ) {
				case 'equals':
				case 'in':
					if ( ! is_array( $first ) && is_array( $second ) ) {
						return in_array( $first, $second );
					} elseif ( is_array( $first ) && ! is_array( $second ) ) {
						return in_array( $second, $first );						
					} elseif ( is_array( $first ) && is_array( $second ) ) {
						$intersect = array_intersect( $first, $second );
						if ( count( $intersect ) > 0 ) {
							return true;
						} else {
							return false;
						}
					} else {
						return $first === $second;
					}
				case 'notequal':
				case 'notin':
					if ( is_array( $second ) ) {
						return ! in_array( $first, $second );
					} elseif ( is_array( $first ) ) {
						return ! in_array( $second, $first );						
					} else {
						return $first !== $second;
					}
				case 'less':
				case 'older':
					return $first > $second;
				case 'greater':
				case 'younger':
					return $first < $second;
				case 'contains':
					return strpos( $first, $second ) !== false;
				case 'notcontain':
					return strpos( $first, $second ) === false;
				case 'between':
					return $first < $second && $second < $third;

				default:
					return $first === $second;
			}
		}

		/**
		 * Enqueue the jQuery library, if necessary
		 */
		public static function wp_enqueue_scripts() {
			wp_enqueue_script( 'jquery' );
		}

		
		/**
		 * Execute PHP code
		 */
		public function execute_php_snippet( $snippet_info ) {
			$post_id = absint( $snippet_info['id'] );

			$upload_dir = wp_upload_dir();
			$file_path = $upload_dir['basedir'] . '/code-snippets/' . $snippet_info['filename'];
			
			$execution_method = ( isset( $snippet_info['execution_method'] ) ) ? $snippet_info['execution_method'] : 'on_page_load';
			$execution_location_type = ( isset( $snippet_info['execution_location_type'] ) ) ? $snippet_info['execution_location_type'] : 'hook';

			// Check if safe mode is enabled
			$is_safe_mode_is_enabled = $this->is_safe_mode_enabled();

			// Check if snippet is active
			$is_snippet_active = $this->is_snippet_active( $post_id );
			
			$validation_result = 'No validation has been performed yet';
			$execution_result = 'No execution has been performed yet';

			// Safe mode is not enabled
			if ( ! $is_safe_mode_is_enabled ) {
				// PHP snippet is active
				if ( $is_snippet_active ) {

					if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
						$validation_result = array(
							'message' => __( 'Snippet file is missing or not readable.', 'admin-site-enhancements' ),
							'line'    => 1,
						);
					} else {
						$stored_hash   = get_post_meta( $post_id, self::CSM_PHP_FILE_HASH_META_KEY, true );
						$current_hash  = md5_file( $file_path );
						$skip_validate = ( is_string( $stored_hash ) && '' !== $stored_hash && $stored_hash === $current_hash );

						// One shutdown handler per request attributes errors via the attribution stack (include + wrapped shortcodes).
						self::ensure_csm_shutdown_registered();

						if ( $skip_validate ) {
							$validation_result = false;
						} else {
							$php_code = $this->get_php_code( $file_path );
							$validator = new ASENHA\Classes\PHP_Validator( $php_code );
							$validation_result = $validator->validate();
							if ( false === $validation_result && is_string( $current_hash ) && '' !== $current_hash ) {
								update_post_meta( $post_id, self::CSM_PHP_FILE_HASH_META_KEY, $current_hash );
							}
						}

						if ( false === $validation_result ) {
							$wp_filter_before = $this->snapshot_wp_filter_callbacks_for_attribution_diff();

							global $shortcode_tags;
							$shortcode_tags_before = is_array( $shortcode_tags ) ? $shortcode_tags : array();

							self::push_attribution_stack( $post_id );
							try {
								if ( $this->php_snippet_file_requires_eval_fallback( $file_path ) ) {
									// On-disk file missing <?php: include() would echo raw source (ASE Pro 8.6.1). Match pre-8.6.1 eval of stripped body.
									$php_code_for_eval = $this->get_php_code( $file_path );
									// phpcs:ignore Squiz.PHP.Eval.Discouraged -- intentional fallback when canonical file lacks opening tag.
									$execution_result = eval( $php_code_for_eval );
								} else {
									// Execute canonical on-disk file (OPcache-friendly; errors report real file path).
									// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- intentional Code Snippets Manager runtime.
									$execution_result = include $file_path;
								}
							} catch ( ParseError $parse_error ) {
								$execution_result = $parse_error;
							} catch ( Error $error ) {
								$execution_result = $error;
							} catch ( Exception $exception_error ) {
								$execution_result = $exception_error;
							} catch ( Throwable $throwable_error ) {
								$execution_result = $throwable_error;
							} finally {
								self::pop_attribution_stack();
							}

							if ( ! is_object( $execution_result ) ) {
								self::record_snippet_evaluated_this_request( $post_id );
								$this->wrap_new_shortcode_callbacks_for_attribution( $post_id, $shortcode_tags_before );
								$this->wrap_new_or_changed_hook_callbacks_for_attribution( $post_id, $wp_filter_before );
							}
						} else {
							// Do not execute the code
							$execution_result = 'Code was not executed due to validation error.';
						}
					}

				} else {							
					$execution_result = 'PHP snippet is inactive, so code is not executed.';
				}							
			} else {
				$execution_result = 'Code was not executed due to safe mode being enabled.';
			}
			// if ( $post_id == 1234 ) {
			// 	vi( $execution_result );				
			// }

			// Code has gone through validation and/or execution. Let's check if we have valid error.
			if ( is_array( $validation_result ) ) {

				update_post_meta( $post_id, 'php_snippet_has_error', true );
				update_post_meta( $post_id, 'php_snippet_error_type', 'non-fatal' );
				update_post_meta( $post_id, 'php_snippet_error_code', 'unknown' );

				$message = rtrim( $validation_result['message'], '.' );
				$line    = intval( $validation_result['line'] ) - 1;
				$error_message = $message . ' on line ' . $line;

				update_post_meta( $post_id, 'php_snippet_error_message', '<span class="error-message">' . $error_message . '</span>' );
				update_post_meta( $post_id, 'php_snippet_error_via', 'execute_php_snippet() - $validation_result' );
				update_post_meta( $post_id, 'safe_mode_activation_via', '' );
			}

			if ( is_object( $execution_result ) ) {

				update_post_meta( $post_id, 'php_snippet_has_error', true );
				update_post_meta( $post_id, 'php_snippet_error_type', 'non-fatal' );
				update_post_meta( $post_id, 'php_snippet_error_code', 'unknown' );

				$message = $execution_result->getMessage();
				$line    = $execution_result->getLine();
				$error_message = $message . ' on line ' . $line;

				update_post_meta( $post_id, 'php_snippet_error_message', '<span class="error-message">' . $error_message . '</span>' );
				update_post_meta( $post_id, 'php_snippet_error_via', 'execute_php_snippet() - $execution_result is_object' );
				update_post_meta( $post_id, 'safe_mode_activation_via', '' );
			}

			// After switching from eval() to include: a file with no return yields include() === 1, whereas eval() yielded null for that case. The old logic relied on null === $execution_result to reach maybe_clear(), which skipped hook snippets. Clear on any successful validation + no caught Throwable.
			if ( false === $validation_result && ! is_object( $execution_result ) ) {
				$this->maybe_clear_php_snippet_error_meta_on_success( $post_id );
			}

			if ( 'on_page_load' === $execution_method && 'shortcode' === $execution_location_type ) {
				if ( is_a( $execution_result, 'ParseError' ) ) {
					return '';
				}
				return $execution_result;
			}
			
		}

		/**
		 * Clear stored PHP snippet error meta after a successful run (this snippet only).
		 *
		 * @param int $post_id Snippet post ID.
		 * @return void
		 */
		private function maybe_clear_php_snippet_error_meta_on_success( $post_id ) {
			$post_id = absint( $post_id );
			if ( $post_id < 1 ) {
				return;
			}
			if ( get_post_meta( $post_id, 'php_snippet_has_error', true ) ) {
				update_post_meta( $post_id, 'php_snippet_has_error', false );
				update_post_meta( $post_id, 'php_snippet_error_type', '' );
				update_post_meta( $post_id, 'php_snippet_error_code', '' );
				update_post_meta( $post_id, 'php_snippet_error_message', '' );
				update_post_meta( $post_id, 'php_snippet_error_via', '' );
				update_post_meta( $post_id, 'safe_mode_activation_via', '' );
			}
		}

		/**
		 * Wrap shortcode callbacks registered during this snippet's execution so deferred runs set attribution.
		 *
		 * @param int   $snippet_post_id       Snippet post ID.
		 * @param array $shortcode_tags_before Global $shortcode_tags snapshot before snippet run.
		 * @return void
		 */
		private function wrap_new_shortcode_callbacks_for_attribution( $snippet_post_id, $shortcode_tags_before ) {
			global $shortcode_tags;
			if ( ! is_array( $shortcode_tags ) ) {
				return;
			}
			$before = is_array( $shortcode_tags_before ) ? $shortcode_tags_before : array();
			foreach ( $shortcode_tags as $tag => $callback ) {
				$prev = array_key_exists( $tag, $before ) ? $before[ $tag ] : null;
				if ( $prev !== $callback ) {
					$shortcode_tags[ $tag ] = $this->create_shortcode_attribution_wrapper( $snippet_post_id, $callback );
				}
			}
		}

		/**
		 * Proxy a shortcode callable to push/pop the attribution stack around user code.
		 *
		 * @param int    $snippet_post_id Defining snippet post ID.
		 * @param mixed  $callback        Original shortcode callback.
		 * @return \Closure
		 */
		private function create_shortcode_attribution_wrapper( $snippet_post_id, $callback ) {
			$snippet_post_id = absint( $snippet_post_id );
			$manager         = $this;
			return function ( $atts = null, $content = null, $tag = '' ) use ( $snippet_post_id, $callback, $manager ) {
				$manager->push_attribution_stack_internal( $snippet_post_id );
				try {
					return call_user_func_array( $callback, func_get_args() );
				} finally {
					$manager->pop_attribution_stack_internal();
				}
			};
		}

		/**
		 * Snapshot wp_filter callback identities before snippet execution (tag => priority => idx => identity).
		 *
		 * @return array<string, array<int|string, array<int|string, string>>>
		 */
		private function snapshot_wp_filter_callbacks_for_attribution_diff() {
			global $wp_filter;
			if ( ! is_array( $wp_filter ) ) {
				return array();
			}
			$snap = array();
			foreach ( $wp_filter as $tag => $hook ) {
				if ( ! $hook instanceof WP_Hook ) {
					continue;
				}
				$snap[ $tag ] = array();
				foreach ( $hook->callbacks as $priority => $callbacks ) {
					foreach ( $callbacks as $idx => $data ) {
						if ( ! isset( $data['function'] ) ) {
							continue;
						}
						$snap[ $tag ][ $priority ][ $idx ] = self::callback_identity_for_hook_attribution( $data['function'] );
					}
				}
			}
			return $snap;
		}

		/**
		 * Stable identity string for a hook callback (diff before/after eval).
		 *
		 * @param mixed $callback Hook callback.
		 * @return string
		 */
		private static function callback_identity_for_hook_attribution( $callback ) {
			if ( is_string( $callback ) ) {
				return 's:' . $callback;
			}
			if ( is_array( $callback ) && isset( $callback[0], $callback[1] ) ) {
				$second = $callback[1];
				if ( is_object( $callback[0] ) ) {
					return 'a:' . spl_object_id( $callback[0] ) . ':' . (string) $second;
				}
				return 'a:' . strtolower( (string) $callback[0] ) . ':' . strtolower( (string) $second );
			}
			if ( $callback instanceof Closure ) {
				return 'c:' . spl_object_id( $callback );
			}
			if ( is_object( $callback ) ) {
				return 'o:' . spl_object_id( $callback );
			}
			return 't:' . gettype( $callback );
		}

		/**
		 * Wrap hook callbacks added or replaced during snippet execution for deferred attribution.
		 *
		 * Does not see callbacks registered without add_action/add_filter, or remove_action edge cases (stale proxy).
		 *
		 * @param int   $snippet_post_id Snippet post ID.
		 * @param array $before_snap     From snapshot_wp_filter_callbacks_for_attribution_diff().
		 * @return void
		 */
		private function wrap_new_or_changed_hook_callbacks_for_attribution( $snippet_post_id, $before_snap ) {
			global $wp_filter;
			if ( ! is_array( $wp_filter ) ) {
				return;
			}
			$snippet_post_id = absint( $snippet_post_id );
			if ( $snippet_post_id < 1 ) {
				return;
			}
			$before_snap = is_array( $before_snap ) ? $before_snap : array();
			foreach ( $wp_filter as $tag => $hook ) {
				if ( ! $hook instanceof WP_Hook ) {
					continue;
				}
				$before_tag = isset( $before_snap[ $tag ] ) && is_array( $before_snap[ $tag ] ) ? $before_snap[ $tag ] : array();
				foreach ( $hook->callbacks as $priority => $callbacks ) {
					foreach ( $callbacks as $idx => $data ) {
						if ( ! isset( $data['function'] ) ) {
							continue;
						}
						$fn     = $data['function'];
						$new_id = self::callback_identity_for_hook_attribution( $fn );
						$old_id = null;
						if ( isset( $before_tag[ $priority ][ $idx ] ) ) {
							$old_id = $before_tag[ $priority ][ $idx ];
						}
						if ( $old_id === $new_id ) {
							continue;
						}
						$hook->callbacks[ $priority ][ $idx ]['function'] = $this->create_hook_callback_attribution_wrapper( $snippet_post_id, $fn );
					}
				}
			}
		}

		/**
		 * Proxy a hook callable to push/pop the attribution stack around user code.
		 *
		 * @param int   $snippet_post_id Defining snippet post ID.
		 * @param mixed $callback        Original hook callback.
		 * @return \Closure
		 */
		private function create_hook_callback_attribution_wrapper( $snippet_post_id, $callback ) {
			$snippet_post_id = absint( $snippet_post_id );
			$manager         = $this;
			return function ( ...$args ) use ( $snippet_post_id, $callback, $manager ) {
				$manager->push_attribution_stack_internal( $snippet_post_id );
				try {
					return call_user_func_array( $callback, $args );
				} finally {
					$manager->pop_attribution_stack_internal();
				}
			};
		}

		/**
		 * Instance-level push for use inside Closure (PHP 8+ static closure cannot call protected static easily across versions).
		 *
		 * @param int $post_id Post ID.
		 * @return void
		 */
		private function push_attribution_stack_internal( $post_id ) {
			self::push_attribution_stack( $post_id );
		}

		/**
		 * @return void
		 */
		private function pop_attribution_stack_internal() {
			self::pop_attribution_stack();
		}

	    /**
	     * Check if safe mode is enabled for PHP snippets
	     * 
	     * @since 7.6.5
	     */
	    public function is_safe_mode_enabled() {
			$safe_mode_via_constant = defined( 'CSM_SAFE_MODE' ) ? CSM_SAFE_MODE : false;

			if ( ( isset( $_GET['safemode'] ) && sanitize_text_field( $_GET['safemode'] ) == '1' ) 
				|| ( $safe_mode_via_constant )
				) {
				$safe_mode_is_enabled = true;
			} else {
				$safe_mode_is_enabled = false;							
			}

			return $safe_mode_is_enabled;
	    }
	    
	    /**
	     * Check if snippet is active
	     * 
	     * @since 7.6.5
	     */
	    public function is_snippet_active( $post_id ) {
			$is_snippet_active = ( 'no' != get_post_meta( $post_id, '_active', true ) ) ? true : false;
			
			return $is_snippet_active;
	    }

		/**
		 * Whether include() would output raw bytes (file does not start with <?php after BOM / leading whitespace).
		 *
		 * @param string $file_path Absolute path to snippet PHP file.
		 * @return bool
		 */
		private function php_snippet_file_requires_eval_fallback( $file_path ) {
			$raw = file_get_contents( $file_path );
			if ( false === $raw ) {
				return false;
			}
			$raw  = preg_replace( '/^\xEF\xBB\xBF/', '', $raw );
			$trim = ltrim( $raw, " \t\n\r\0\x0B" );
			return ( 0 !== strncmp( $trim, '<?php', 5 ) );
		}
	    
	    /**
	     * Get PHP code that's been cleaned up
	     * 
	     * @since 7.6.5
	     */
	    public function get_php_code( $file_path ) {
			// Get code and parse it as string
			$php_code = file_get_contents( $file_path );

		    // Clean up for PHP_Validator (body without opening/closing PHP tags).
		    $php_code = trim( $php_code );
		    $php_code = ltrim( $php_code, '<?php' );
		    $php_code = rtrim( $php_code, '?>' );

		    return $php_code;
	    }
		
		/**
		 * Handle fatal and non-fatal PHP errors from CSM snippet code (single handler per request).
		 *
		 * @return void
		 */
		private function csm_shutdown_handler_inner() {
			$error_raw = error_get_last();
			if ( null === $error_raw ) {
				return;
			}

			$file = isset( $error_raw['file'] ) ? $error_raw['file'] : '';
			$code = isset( $error_raw['type'] ) ? $error_raw['type'] : 0;
			// Ref: https://www.php.net/manual/en/errorfunc.constants.php
			$fatal_error_codes = array( 1, 16, 256 );
			$is_fatal          = in_array( intval( $code ), $fatal_error_codes, true );

			$from_csm_path = self::is_shutdown_error_from_csm_snippet( $file );
			$from_heuristic = false;
			if ( $is_fatal ) {
				$from_heuristic = self::is_shutdown_error_likely_deferred_snippet_hook_fatal( $error_raw );
			}
			// Wrapped shortcode/hook proxy may still be active; error file can be eval()'d code or userland, not wp-includes.
			$from_attribution_stack = $is_fatal && self::get_active_attribution_snippet_post_id() > 0;
			$from_saved_php_snippet = $is_fatal && self::get_saved_php_snippet_post_id_this_request() > 0;
			if ( ! $from_csm_path && ! $from_heuristic && ! $from_attribution_stack && ! $from_saved_php_snippet ) {
				return;
			}
			$type                = $is_fatal ? 'fatal' : 'non-fatal';

			$line          = isset( $error_raw['line'] ) ? $error_raw['line'] : 0;
			$message_full  = isset( $error_raw['message'] ) ? $error_raw['message'] : '';
			$message_parts = explode( ' in /', $message_full );
			$message       = $message_parts[0];
			$error_message = $message . ' on line ' . $line;

			$active_id = self::resolve_shutdown_error_attribution_snippet_post_id();
			if ( $active_id < 1 && is_string( $file ) && '' !== $file ) {
				$from_file = self::snippet_post_id_from_canonical_snippet_file_path( $file );
				if ( $from_file > 0 ) {
					$active_id = $from_file;
				}
			}

			$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
			if ( ! is_array( $options_extra ) ) {
				$options_extra = array();
			}
			$snippet_info              = array();
			$execution_method          = 'on_page_load';
			$execution_location_type   = 'hook';
			$active_php_snippet_ids    = isset( $options_extra['code_snippets']['php'] ) ? array_keys( $options_extra['code_snippets']['php'] ) : array();
			if ( $active_id > 0 && isset( $options_extra['code_snippets']['php'][ $active_id ] ) ) {
				$snippet_info            = $options_extra['code_snippets']['php'][ $active_id ];
				$execution_method        = isset( $snippet_info['execution_method'] ) ? $snippet_info['execution_method'] : 'on_page_load';
				$execution_location_type = isset( $snippet_info['execution_location_type'] ) ? $snippet_info['execution_location_type'] : 'hook';
			}

			$message_stack_trace = '';
			if ( false !== strpos( $message_full, 'Stack trace:' ) ) {
				$st_parts = explode( 'Stack trace:', $message_full );
				if ( isset( $st_parts[1] ) ) {
					$message_stack_trace = $st_parts[1];
				}
			}

			if ( 'fatal' === $type ) {
				if ( $active_id > 0 && in_array( $active_id, $active_php_snippet_ids, true ) ) {
					$html_message = '<span class="error-message">' . esc_html( $error_message ) . '</span>';
					if ( '' !== $message_stack_trace ) {
						$html_message .= '<span class="stack-trace">Stack trace:</span>' . ltrim( nl2br( esc_html( str_replace( ABSPATH, '/', $message_stack_trace ) ) ), '<br />' );
					}
					update_post_meta( $active_id, 'php_snippet_has_error', true );
					update_post_meta( $active_id, 'php_snippet_error_type', 'fatal' );
					update_post_meta( $active_id, 'php_snippet_error_code', $code );
					update_post_meta( $active_id, 'php_snippet_error_message', wp_kses_post( $html_message ) );
					update_post_meta( $active_id, 'php_snippet_error_via', 'shutdown' );
					update_post_meta( $active_id, 'safe_mode_activation_via', 'shutdown' );
					update_post_meta( $active_id, '_active', 'no' );

					if ( 'on_page_load' === $execution_method && 'hook' === $execution_location_type ) {
						$wp_config         = new ASENHA\Classes\WP_Config_Transformer();
						$wp_config_options = array(
							'add'       => true,
							'raw'       => true,
							'normalize' => false,
						);
						$wp_config->update( 'constant', 'CSM_SAFE_MODE', 'true', $wp_config_options );
					}
					if ( '' !== self::$recovery_log_prune_token ) {
						self::remove_csm_error_log_entry_by_token( self::$recovery_log_prune_token );
						self::$recovery_log_prune_token = '';
					}
				} else {
					$recovery_token = wp_generate_password( 32, false );
					self::append_csm_error_log_entry(
						array(
							'severity'                    => 'fatal',
							'error_code'                  => intval( $code ),
							'message'                     => wp_strip_all_tags( $error_message ),
							'stack_trace_html'            => $message_stack_trace ? wp_kses_post( self::format_csm_error_log_stack_trace_html( $message_stack_trace ) ) : '',
							'source'                      => 'shutdown',
							'attribution_snippet_post_id' => null,
							'recovery_token'              => $recovery_token,
						)
					);
					$wp_config         = new ASENHA\Classes\WP_Config_Transformer();
					$wp_config_options = array(
						'add'       => true,
						'raw'       => true,
						'normalize' => false,
					);
					$wp_config->update( 'constant', 'CSM_SAFE_MODE', 'true', $wp_config_options );
				}
				return;
			}

			// Non-fatal: record on snippet meta only when attribution is known; otherwise log only.
			if ( $active_id > 0 && in_array( $active_id, $active_php_snippet_ids, true ) ) {
				$html_message = '<span class="error-message">' . esc_html( $error_message ) . '</span>';
				if ( '' !== $message_stack_trace ) {
					$html_message .= '<span class="stack-trace">Stack trace:</span>' . ltrim( nl2br( esc_html( str_replace( ABSPATH, '/', $message_stack_trace ) ) ), '<br />' );
				}
				update_post_meta( $active_id, 'php_snippet_has_error', true );
				update_post_meta( $active_id, 'php_snippet_error_type', 'non-fatal' );
				update_post_meta( $active_id, 'php_snippet_error_code', $code );
				update_post_meta( $active_id, 'php_snippet_error_message', wp_kses_post( $html_message ) );
				update_post_meta( $active_id, 'php_snippet_error_via', 'shutdown' );
				update_post_meta( $active_id, 'safe_mode_activation_via', '' );
				if ( '' !== self::$recovery_log_prune_token ) {
					self::remove_csm_error_log_entry_by_token( self::$recovery_log_prune_token );
					self::$recovery_log_prune_token = '';
				}
			} else {
				self::append_csm_error_log_entry(
					array(
						'severity'                    => 'non-fatal',
						'error_code'                  => intval( $code ),
						'message'                     => wp_strip_all_tags( $error_message ),
						'stack_trace_html'            => $message_stack_trace ? wp_kses_post( self::format_csm_error_log_stack_trace_html( $message_stack_trace ) ) : '',
						'source'                      => 'shutdown',
						'attribution_snippet_post_id' => null,
					)
				);
			}
		}

		/**
		 * Set constants for later use
		 */
		public function set_constants() {
			$dir       = wp_upload_dir();
			$constants = array(
				'CSM_VERSION'     => ASENHA_VERSION,
				'CSM_UPLOAD_DIR'  => $dir['basedir'] . '/code-snippets',
				'CSM_UPLOAD_URL'  => $dir['baseurl'] . '/code-snippets',
				'CSM_PLUGIN_FILE' => __FILE__,
			);
			foreach ( $constants as $_key => $_value ) {
				if ( ! defined( $_key ) ) {
					define( $_key, $_value );
				}
			}
		}

	}

endif;

if ( ! function_exists( 'asenha_csm_get_active_attribution_snippet_post_id__premium_only' ) ) {
	/**
	 * Returns the Code Snippets Manager PHP snippet post ID for error attribution: stack top when set,
	 * else the sole snippet that successfully completed execution this request, else 0.
	 *
	 * @return int Post ID or 0.
	 */
	function asenha_csm_get_active_attribution_snippet_post_id__premium_only() {
		if ( ! class_exists( 'Code_Snippets_Manager' ) ) {
			return 0;
		}
		return Code_Snippets_Manager::resolve_shutdown_error_attribution_snippet_post_id();
	}
}

if ( ! function_exists( 'Code_Snippets_Manager' ) ) {
	/**
	 * Returns the main instance of Code_Snippets_Manager
	 *
	 * @return Code_Snippets_Manager
	 */
	function Code_Snippets_Manager() {
		return Code_Snippets_Manager::instance();
	}

	Code_Snippets_Manager();
}