<?php
/**
 * Sensor: Plugins and Themes
 *
 * Plugins and Themes sensor class file.
 *
 * @since      4.5.0
 * @package    wsal
 * @subpackage sensors
 */

declare(strict_types=1);

namespace WSAL\WP_Sensors;

use WSAL\Helpers\Settings_Helper;
use WSAL\Controllers\Alert_Manager;
use WSAL\WP_Sensors\Helpers\WP_Plugins_Themes_Helper;

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

if ( ! class_exists( '\WSAL\WP_Sensors\WP_Plugins_Themes_Sensor' ) ) {
	/**
	 * Plugins & Themes sensor.
	 *
	 * 5000 User installed a plugin
	 * 5001 User activated a WordPress plugin
	 * 5002 User deactivated a WordPress plugin
	 * 5003 User uninstalled a plugin
	 * 5004 User upgraded a plugin
	 * 5005 User installed a theme
	 * 5006 User activated a theme
	 * 5007 User uninstalled a theme
	 * 5030 Plugin failed to update
	 * 5031 User updated a theme
	 * 5034 Updated translations for a plugin
	 * 5035 Updated translations for a theme
	 *
	 * @package    wsal
	 * @subpackage sensors
	 * @since 4.5.0
	 */
	class WP_Plugins_Themes_Sensor {

		/**
		 * List of Themes.
		 *
		 * @var array
		 *
		 * @since 4.5.0
		 */
		private static $old_themes = null;

		/**
		 * List of Plugins.
		 *
		 * @var array
		 *
		 * @since 4.5.0
		 */
		private static $old_plugins = null;

		/**
		 * Litst of all plugins
		 *
		 * @var array
		 *
		 * @since 5.3.4.1
		 */
		private static $plugins_available = null;

		/**
		 * Stores the plugins data for the currently processed plugin
		 *
		 * @var array
		 *
		 * @since 5.3.0
		 */
		private static $plugins_data = array();

		/**
		 * Stores the plugins data for the currently processed plugin
		 *
		 * @var array
		 *
		 * @since 5.4.0
		 */
		private static $reported_plugins = array();


		/**
		 * Inits the main hooks
		 *
		 * @return void
		 *
		 * @since 4.5.0
		 */
		public static function init() {
			$has_permission = ( current_user_can( 'install_plugins' ) || current_user_can( 'activate_plugins' ) ||
				\current_user_can( 'delete_plugins' ) || current_user_can( 'update_plugins' ) || current_user_can( 'install_themes' ) );

			self::event_init();

			\add_action( 'switch_theme', array( __CLASS__, 'event_theme_activated' ) );

			\add_action( 'deleted_theme', array( __CLASS__, 'on_deleted_theme' ), 10, 2 );

			\add_action( 'wp_insert_post', array( __CLASS__, 'plugin_created_post' ), 10, 2 );

			// Log plugin deletions, i.e. when a user click "Delete" in the plugins listing
			// or choose plugin(s) and select Bulk actions -> Delete.
			// Since WordPress 4.4 filters exists that are fired before and after plugin deletion.
			\add_action( 'delete_plugin', array( __CLASS__, 'on_action_delete_plugin' ), 10, 1 );
			\add_action( 'deleted_plugin', array( __CLASS__, 'on_action_deleted_plugin' ), 10, 2 );

			/**
			 * At least the plugin bulk upgrades fires this action before upgrade
			 * We use it to fetch the current version of all plugins, before they are upgraded
			 */
			// \add_filter( 'upgrader_pre_install', array( __CLASS__, 'save_versions_before_update' ), 10, 2 );

			// Fires after the upgrades has done it's thing.
			// Check hook extra for upgrader initiator.
			\add_action( 'upgrader_process_complete', array( __CLASS__, 'on_upgrader_process_complete' ), 10, 2 );
			\add_action( 'upgrader_overwrote_package', array( __CLASS__, 'on_package_overwrite' ), 10, 3 );

			\add_action( 'activated_plugin', array( __CLASS__, 'on_activated_plugin' ), 10, 2 );
			\add_action( 'deactivated_plugin', array( __CLASS__, 'on_deactivated_plugin' ), 10, 1 );

			\add_action( 'update_option_active_plugins', array( __CLASS__, 'on_active_plugins_update' ), 10, 2 );

			\add_action( 'upgrader_process_complete', array( __CLASS__, 'on_plugin_or_theme_update' ), 10, 2 );

			\add_filter( 'upgrader_post_install', array( __CLASS__, 'on_upgrader_post_install' ), 10, 3 );

			\add_action( 'set_site_transient', array( __CLASS__, 'on_available_plugin_update' ), 10, 3 );

			\add_action( 'set_site_transient', array( __CLASS__, 'on_available_theme_update' ), 10, 3 );
		}

		/**
		 * Maybe add thickbox class to the links, this is used by WordPress to trigger the view more information modal.
		 *
		 * @param string $url - the URL of the link to check.
		 *
		 * @return array $extra_css_classes - An array of extra CSS classes to be added to the link.
		 *
		 * @since 5.5.0
		 */
		public static function maybe_add_thickbox_class( $url ) {

			$extra_css_classes = array();

			$is_wp_repo_url = strpos( $url, 'plugin-install.php' );

			if ( $is_wp_repo_url ) {
				// Add thickbox class to the links, this is used by WordPress to trigger the view more information modal.
				$extra_css_classes[] = 'thickbox';
			}

			return $extra_css_classes;
		}

		/**
		 * Build the WordPress repository URL for a plugin.
		 *
		 * @param string $plugin_slug - The plugin slug which is also the directory name of the plugin.
		 *
		 * @return string - The URL to the plugin's page in the WordPress repository.
		 *
		 * @since 5.5.0
		 */
		public static function get_plugin_wp_repo_url( $plugin_slug ) {
			// Get correct url, network_admin_url also checks if we're in a multisite or not.
			return network_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&TB_iframe=true&width=640&height=600' );
		}

		/**
		 * Plugin is deactivated
		 * plugin_name is like admin-menu-tree-page-view/index.php
		 *
		 * @param string $plugin_name Plugin name.
		 *
		 * @since 5.4.0
		 */
		public static function on_deactivated_plugin( $plugin_name ) {
			if ( ! in_array( $plugin_name, self::$reported_plugins, true ) ) {
				$plugin_data = \get_plugin_data( \WP_PLUGIN_DIR . '/' . $plugin_name, true, false );
				$plugin_slug = dirname( $plugin_name );

				Alert_Manager::trigger_event(
					5002,
					array(
						'PluginFile' => \WP_PLUGIN_DIR . '/' . $plugin_name,
						'PluginData' => (object) array(
							'Name'          => $plugin_data['Name'],
							'PluginURI'     => $plugin_data['PluginURI'],
							'PluginRepoUrl' => self::get_plugin_wp_repo_url( $plugin_slug ),
							'Version'       => $plugin_data['Version'],
							'Author'        => $plugin_data['Author'],
							'Network'       => $plugin_data['Network'] ? 'True' : 'False',
						),
					)
				);

				self::$reported_plugins[ $plugin_name ] = $plugin_name;
			}
		}

		/**
		 * Plugin is activated
		 * plugin_name is like admin-menu-tree-page-view/index.php
		 *
		 * @param string $plugin_name Plugin name.
		 * @param bool   $network_wide Network wide.
		 */
		public static function on_activated_plugin( $plugin_name, $network_wide = null ) {
			if ( ! in_array( $plugin_name, self::$reported_plugins, true ) ) {
				$plugin_data = \get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name, true, false );

				$plugin_slug = dirname( $plugin_name );

				Alert_Manager::trigger_event(
					5001,
					array(
						'PluginFile' => WP_PLUGIN_DIR . '/' . $plugin_name,
						'PluginData' => (object) array(
							'Name'          => $plugin_data['Name'],
							'PluginRepoUrl' => self::get_plugin_wp_repo_url( $plugin_slug ),
							'PluginURI'     => $plugin_data['PluginURI'],
							'Version'       => $plugin_data['Version'],
							'Author'        => $plugin_data['Author'],
							'Network'       => $plugin_data['Network'] ? 'True' : 'False',
						),
					)
				);

				self::$reported_plugins[ $plugin_name ] = $plugin_name;
			}
		}

		/**
		 * Called when plugins is updated or installed
		 * Called from class-wp-upgrader.php
		 *
		 * @param \Plugin_Upgrader $plugin_upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function on_upgrader_process_complete( $plugin_upgrader_instance, $arr_data ): void {
			if ( empty( $plugin_upgrader_instance ) || empty( $arr_data ) ) {
				return;
			}

			// Prevent events 5000 and 5005 from being triggered multiple times, triggered in this class on on_upgrader_post_install() before this method.
			if ( Alert_Manager::has_triggered( 5000 ) || Alert_Manager::has_triggered( 5005 ) ) {
				return;
			}

			// Check that required data is set.
			if ( empty( $arr_data['type'] ) || empty( $arr_data['action'] ) ) {
				return;
			}

			if ( isset( $arr_data['type'] ) && 'plugin' === $arr_data['type'] ) {
				self::single_plugin_install( $plugin_upgrader_instance, $arr_data );
				self::single_plugin_update( $plugin_upgrader_instance, $arr_data );
				self::bulk_plugin_update( $plugin_upgrader_instance, $arr_data );
			}

			if ( isset( $arr_data['type'] ) && 'theme' === $arr_data['type'] ) {
				self::theme_install( $plugin_upgrader_instance, $arr_data );
				self::theme_upgrade( $plugin_upgrader_instance, $arr_data );
			}
		}

		/**
		 * Capture event when single plugin is installed
		 *
		 * @param \Plugin_Upgrader $upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function theme_install( $upgrader_instance, $arr_data ): void {

			// Must be type 'theme' and action 'install'.
			if ( 'theme' !== $arr_data['type'] || 'install' !== $arr_data['action'] ) {
				return;
			}

			if ( empty( $upgrader_instance->new_theme_data ) ) {
				return;
			}

			// Install theme.
			$destination_name = $upgrader_instance->result['destination_name'] ?? '';

			if ( empty( $destination_name ) ) {
				return;
			}

			$theme = \wp_get_theme( $destination_name );

			if ( ! $theme->exists() ) {
				return;
			}

			$new_theme_data = $upgrader_instance->new_theme_data;

			Alert_Manager::trigger_event(
				5005,
				array(
					'Theme' => (object) array(
						'Name'                   => $new_theme_data['Name'],
						'ThemeURI'               => $theme->ThemeURI, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Description'            => $theme->get( 'Description' ),
						'Author'                 => $new_theme_data['Author'],
						'Version'                => $new_theme_data['Version'],
						'get_template_directory' => $theme->get_template_directory(),
					),
				)
			);
		}

		/**
		 * Capture event when single plugin is installed
		 *
		 * @param \Plugin_Upgrader $upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function theme_upgrade( $upgrader_instance, $arr_data ): void {

			// Must be type 'theme' and action 'update'.
			if ( 'theme' !== $arr_data['type'] || 'update' !== $arr_data['action'] ) {
				return;
			}

			// If single install make an array so it look like bulk and we can use same code.
			if ( isset( $arr_data['bulk'] ) && $arr_data['bulk'] && isset( $arr_data['themes'] ) ) {
				$arr_themes = (array) $arr_data['themes'];
			} else {
				$arr_themes = array(
					$arr_data['theme'],
				);
			}

			foreach ( $arr_themes as $one_updated_theme ) {
				$theme = \wp_get_theme( $one_updated_theme );

				if ( ! is_a( $theme, 'WP_Theme' ) ) {
					continue;
				}

				$theme_name    = $theme->get( 'Name' );
				$theme_version = $theme->get( 'Version' );

				if ( ! $theme_name || ! $theme_version ) {
					continue;
				}

				Alert_Manager::trigger_event(
					5031,
					array(
						'Theme'       => (object) array(
							'Name'                   => $theme_name,
							'ThemeURI'               => $theme->ThemeURI, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
							'Description'            => $theme->Description, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
							'Author'                 => $theme->Author, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
							'Version'                => $theme_version,
							'get_template_directory' => $theme->get_template_directory(),
						),
						'ThemeStatus' => WP_Plugins_Themes_Helper::get_theme_status_label( $one_updated_theme ),
					)
				);
			}
		}

		/**
		 * Capture event when single plugin is installed
		 *
		 * @param \Plugin_Upgrader $plugin_upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function single_plugin_install( $plugin_upgrader_instance, $arr_data ): void {
			// Bail if not single plugin install data.
			if ( ! isset( $arr_data['action'] ) || 'install' !== $arr_data['action'] || $plugin_upgrader_instance->bulk ) {
				return;
			}

			$upgrader_skin_options = isset( $plugin_upgrader_instance->skin->options ) && is_array( $plugin_upgrader_instance->skin->options ) ? $plugin_upgrader_instance->skin->options : array();
			$upgrader_skin_result  = isset( $plugin_upgrader_instance->skin->result ) && is_array( $plugin_upgrader_instance->skin->result ) ? $plugin_upgrader_instance->skin->result : array();
			$new_plugin_data       = $plugin_upgrader_instance->new_plugin_data ?? array();
			$plugin_slug           = $upgrader_skin_result['destination_name'] ?? '';

			$plugin = $plugin_upgrader_instance->plugin_info();

			$context = array(
				'plugin_slug'         => $plugin_slug,
				'plugin_name'         => $new_plugin_data['Name'] ?? '',
				'plugin_title'        => $new_plugin_data['Title'] ?? '',
				'plugin_url'          => $new_plugin_data['PluginURI'] ?? '',
				'plugin_version'      => $new_plugin_data['Version'] ?? '',
				'plugin_author'       => $new_plugin_data['Author'] ?? '',
				'plugin_requires_wp'  => $new_plugin_data['RequiresWP'] ?? '',
				'plugin_requires_php' => $new_plugin_data['RequiresPHP'] ?? '',
				// Prevent warning Undefined array key "Network" in an edge case when an invalid plugin.zip is uploaded to the site.
				'plugin_network'      => ( $new_plugin_data['Network'] ?? false ) ? \esc_html__( 'True', 'wp-security-audit-log' ) : \esc_html__( 'False', 'wp-security-audit-log' ),
				'plugin_path'         => \WP_PLUGIN_DIR . \DIRECTORY_SEPARATOR . $plugin,
			);

			if ( isset( $new_plugin_data['UpdateURI'] ) ) {
				$context['plugin_update_uri'] = $new_plugin_data['UpdateURI'];
			}

			// Check for premium version being installed / updated.
			if ( in_array( $plugin, array( \WpSecurityAuditLog::PREMIUM_VERSION_WHOLE_PLUGIN_NAME, \WpSecurityAuditLog::FREE_VERSION_WHOLE_PLUGIN_NAME, \WpSecurityAuditLog::NOFS_VERSION_WHOLE_PLUGIN_NAME ), true ) ) {
				return;
			}

			$install_source = 'web';
			if ( isset( $upgrader_skin_options['type'] ) ) {
				$install_source = \strtolower( (string) $upgrader_skin_options['type'] );
			}

			$context['plugin_install_source'] = $install_source;

			// If uploaded plugin store name of ZIP.
			// phpcs:ignore WordPress.Security.NonceVerification.Missing
			if ( 'upload' === $install_source && isset( $_FILES['pluginzip']['name'] ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing
				$plugin_upload_name            = sanitize_text_field( $_FILES['pluginzip']['name'] );
				$context['plugin_upload_name'] = $plugin_upload_name;
			}

			if ( ! is_a( $plugin_upgrader_instance->skin->result, 'WP_Error' ) ) {

				// Check if the plugin is already installed and we are here because of an update via upload.
				$old_plugins = self::get_old_plugins();

				$plugin_slug = dirname( $plugin );

				if ( isset( $old_plugins[ $plugin ] ) ) {
					return;
				} else {
					// No - it is a real update - no need to fire post activate event.
					\remove_action( 'upgrader_overwrote_package', array( __CLASS__, 'on_package_overwrite' ), 10, 3 );
				}

				Alert_Manager::trigger_event(
					5000,
					array(
						'PluginData' => (object) array(
							'Name'            => $context['plugin_name'],
							'PluginURI'       => $context['plugin_url'],
							'PluginRepoUrl'   => self::get_plugin_wp_repo_url( $plugin_slug ),
							'Version'         => $context['plugin_version'],
							'Author'          => $context['plugin_author'],
							'Network'         => $context['plugin_network'],
							'Slug'            => $context['plugin_slug'],
							'Title'           => $context['plugin_title'],
							'plugin_dir_path' => $context['plugin_path'],
						),
					)
				);
			}
		}

		/**
		 * Capture event when single plugin is updated
		 *
		 * @param \Plugin_Upgrader $plugin_upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function single_plugin_update( $plugin_upgrader_instance, $arr_data ): void {
			if ( ! isset( $arr_data['action'] ) || 'update' !== $arr_data['action'] || $plugin_upgrader_instance->bulk ) {
				return;
			}

			if ( is_array( $arr_data['plugin'] ) && isset( $arr_data['plugin'] ) && 'wp-security-audit-log.php' === $arr_data['plugin'] ) {
				return;
			}

			// No plugin info in instance, so get it ourself.
			$plugin_data = array();
			if ( file_exists( \WP_PLUGIN_DIR . '/' . $arr_data['plugin'] ) ) {
				$plugin_data = \get_plugin_data( \WP_PLUGIN_DIR . '/' . $arr_data['plugin'], true, false );
			}

			$plugin_slug = dirname( $arr_data['plugin'] );

			$context = array(
				'plugin_slug'        => $plugin_slug,
				'plugin_name'        => $plugin_data['Name'] ?? '',
				'plugin_title'       => $plugin_data['Title'] ?? '',
				'plugin_description' => $plugin_data['Description'] ?? '',
				'plugin_author'      => $plugin_data['Author'] ?? '',
				'plugin_version'     => $plugin_data['Version'] ?? '',
				'plugin_url'         => $plugin_data['PluginURI'] ?? '',
				'plugin_network'     => ( $plugin_data['Network'] ?? false ) ? \esc_html__( 'True', 'wp-security-audit-log' ) : \esc_html__( 'False', 'wp-security-audit-log' ),
				'plugin_path'        => \WP_PLUGIN_DIR . \DIRECTORY_SEPARATOR . $arr_data['plugin'],
			);

			// Add Update URI if it is set. Available since WP 5.8.
			if ( isset( $plugin_data['UpdateURI'] ) ) {
				$context['plugin_update_uri'] = $plugin_data['UpdateURI'];
			}

			if ( ! \is_wp_error( \validate_plugin( $arr_data['plugin'] ) ) ) {
				$old_plugins = self::get_old_plugins();

				$old_version = ( isset( $old_plugins[ $arr_data['plugin'] ] ) ) ? $old_plugins[ $arr_data['plugin'] ]['Version'] : false;

				if ( $old_version !== $context['plugin_version'] ) {
					Alert_Manager::trigger_event(
						5004,
						array(
							'PluginFile'   => $arr_data['plugin'],
							'PluginData'   => (object) array(
								'Name'            => $context['plugin_name'],
								'PluginURI'       => $context['plugin_url'],
								'PluginRepoUrl'   => self::get_plugin_wp_repo_url( $plugin_slug ),
								'Version'         => $context['plugin_version'],
								'Author'          => $context['plugin_author'],
								'Network'         => $context['plugin_network'],
								'Slug'            => $context['plugin_slug'],
								'Title'           => $context['plugin_title'],
								'plugin_dir_path' => $context['plugin_path'],
							),
							'OldVersion'   => $old_version,
							'PluginStatus' => WP_Plugins_Themes_Helper::get_plugin_status_label( $arr_data['plugin'] ),
						)
					);
				}
			}
		}

		/**
		 * Capture event when single plugin is updated
		 *
		 * @param \Plugin_Upgrader $plugin_upgrader_instance Plugin_Upgrader instance. In other contexts, $this, might
		 *                                                  be a Theme_Upgrader or Core_Upgrade instance.
		 * @param array            $arr_data                 Array of bulk item update data.
		 *
		 * @return void
		 *
		 * @since 5.3.4.1
		 */
		public static function bulk_plugin_update( $plugin_upgrader_instance, $arr_data ): void {
			// Bail if not bulk plugin update.
			if ( ! isset( $arr_data['bulk'] ) || ! $arr_data['bulk'] || ! isset( $arr_data['action'] ) || 'update' !== $arr_data['action'] ) {
				return;
			}

			$plugins_updated = isset( $arr_data['plugins'] ) ? (array) $arr_data['plugins'] : array();

			foreach ( $plugins_updated as $plugin ) {
				$arr_data['plugin'] = $plugin;

				if ( is_array( $arr_data['plugin'] ) && isset( $arr_data['plugin'] ) && 'wp-security-audit-log.php' === $arr_data['plugin'] ) {
					return;
				}

				// No plugin info in instance, so get it ourself.
				$plugin_data = array();
				if ( file_exists( \WP_PLUGIN_DIR . '/' . $arr_data['plugin'] ) ) {
					$plugin_data = \get_plugin_data( \WP_PLUGIN_DIR . '/' . $arr_data['plugin'], true, false );
				}

				$plugin_slug = dirname( $arr_data['plugin'] );

				$context = array(
					'plugin_slug'        => $plugin_slug,
					'plugin_name'        => $plugin_data['Name'] ?? '',
					'plugin_title'       => $plugin_data['Title'] ?? '',
					'plugin_description' => $plugin_data['Description'] ?? '',
					'plugin_author'      => $plugin_data['Author'] ?? '',
					'plugin_version'     => $plugin_data['Version'] ?? '',
					'plugin_url'         => $plugin_data['PluginURI'] ?? '',
					'plugin_network'     => ( $plugin_data['Network'] ?? false ) ? \esc_html__( 'True', 'wp-security-audit-log' ) : \esc_html__( 'False', 'wp-security-audit-log' ),
					'plugin_path'        => \WP_PLUGIN_DIR . \DIRECTORY_SEPARATOR . $arr_data['plugin'],
				);

				// Add Update URI if it is set. Available since WP 5.8.
				if ( isset( $plugin_data['UpdateURI'] ) ) {
					$context['plugin_update_uri'] = $plugin_data['UpdateURI'];
				}

				if ( ! \is_wp_error( \validate_plugin( $arr_data['plugin'], ) ) ) {
					$old_plugins = self::get_old_plugins();

					$old_version = ( isset( $old_plugins[ $arr_data['plugin'] ] ) ) ? $old_plugins[ $arr_data['plugin'] ]['Version'] : false;

					if ( $old_version !== $context['plugin_version'] ) {
						Alert_Manager::trigger_event(
							5004,
							array(
								'PluginFile'   => $arr_data['plugin'],
								'PluginData'   => (object) array(
									'Name'            => $context['plugin_name'],
									'PluginURI'       => $context['plugin_url'],
									'PluginRepoUrl'   => self::get_plugin_wp_repo_url( $plugin_slug ),
									'Version'         => $context['plugin_version'],
									'Author'          => $context['plugin_author'],
									'Network'         => $context['plugin_network'],
									'Slug'            => $context['plugin_slug'],
									'Title'           => $context['plugin_title'],
									'plugin_dir_path' => $context['plugin_path'],
								),
								'OldVersion'   => $old_version,
								'PluginStatus' => WP_Plugins_Themes_Helper::get_plugin_status_label( $arr_data['plugin'] ),
							)
						);
					}
				}
			}
		}

		/**
		 * Fires when the upgrader has successfully overwritten a currently installed
		 * plugin or theme with an uploaded zip package.
		 *
		 * @param string $package          The package file.
		 * @param array  $plugin_data  The new plugin data.
		 * @param string $package_type     The package type (plugin or theme).
		 *
		 * @since 4.5.0
		 */
		public static function on_package_overwrite( $package, $plugin_data, $package_type ) {
			if ( 'plugin' !== $package_type ) {
				return;
			}

			if ( is_array( $plugin_data ) && isset( $plugin_data['TextDomain'] ) && 'wp-security-audit-log' === $plugin_data['TextDomain'] ) {
				return;
			}

			$plugin_slug = self::extract_plugin_filename( $plugin_data['TextDomain'] );

			$context = array(
				'plugin_slug'        => $plugin_slug,
				'plugin_name'        => $plugin_data['Name'],
				'plugin_title'       => $plugin_data['Title'],
				'plugin_description' => $plugin_data['Description'],
				'plugin_author'      => $plugin_data['Author'],
				'plugin_version'     => $plugin_data['Version'],
				'plugin_url'         => $plugin_data['PluginURI'],
				'plugin_network'     => ( $plugin_data['Network'] ?? false ) ? \esc_html__( 'True', 'wp-security-audit-log' ) : \esc_html__( 'False', 'wp-security-audit-log' ),
				'plugin_path'        => \WP_PLUGIN_DIR . \DIRECTORY_SEPARATOR . $plugin_slug,
			);

			if ( ! \is_wp_error( \validate_plugin( $plugin_slug ) ) ) {
				$old_plugins = self::get_old_plugins();

				$old_version = ( isset( $old_plugins[ $plugin_slug ] ) ) ? $old_plugins[ $plugin_slug ]['Version'] : false;

				if ( $old_version !== $context['plugin_version'] ) {
					Alert_Manager::trigger_event(
						5004,
						array(
							'PluginFile'   => $plugin_slug,
							'PluginData'   => (object) array(
								'Name'            => $context['plugin_name'],
								'PluginURI'       => $context['plugin_url'],
								'PluginRepoUrl'   => self::get_plugin_wp_repo_url( $plugin_slug ),
								'Version'         => $context['plugin_version'],
								'Author'          => $context['plugin_author'],
								'Network'         => $context['plugin_network'],
								'Slug'            => $context['plugin_slug'],
								'Title'           => $context['plugin_title'],
								'plugin_dir_path' => $context['plugin_path'],
							),
							'OldVersion'   => $old_version,
							'PluginStatus' => WP_Plugins_Themes_Helper::get_plugin_status_label( $plugin_slug ),
						)
					);
				}
			}
		}

		/**
		 * Extracts plugin filename from the plugin text domain.
		 *
		 * @param string $plugin_textdomain - The text domain of the plugin.
		 *
		 * @return string
		 *
		 * @since 5.3.4.1
		 */
		public static function extract_plugin_filename( $plugin_textdomain ): string {
			foreach ( self::get_all_plugins() as $plugin_file => $plugin ) {
				if ( $plugin['TextDomain'] === $plugin_textdomain ) {
					return $plugin_file;
				}
			}

			return '';
		}

		/**
		 * Store information about a plugin before it gets deleted.
		 * Called from action `deleted_plugin` that is fired just before the plugin will be deleted.
		 *
		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
		 *
		 * @return void
		 *
		 * @since 5.3.0
		 */
		public static function on_action_delete_plugin( $plugin_file ) {
			self::$plugins_data[ $plugin_file ] = \get_plugin_data( \WP_PLUGIN_DIR . '/' . $plugin_file, true, false );
		}


		/**
		 * Log plugin deletion.
		 * Called from action `deleted_plugin` that is fired just after a plugin has been deleted.
		 *
		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
		 * @param bool   $deleted     Whether the plugin deletion was successful.
		 *
		 * @return void
		 *
		 * @since 5.3.0
		 */
		public static function on_action_deleted_plugin( $plugin_file, $deleted ) {
			if ( ! $deleted ) {
				return;
			}

			if ( empty( self::$plugins_data[ $plugin_file ] ) ) {
				return;
			}

			$plugin_file = $plugin_file;
			$plugin_name = \strip_tags( self::$plugins_data[ $plugin_file ]['Title'] );
			$plugin_data = self::$plugins_data[ $plugin_file ];

			$plugin_slug = dirname( $plugin_file );

			Alert_Manager::trigger_event(
				5003,
				array(
					'PluginFile' => $plugin_file,
					'PluginData' => (object) array(
						'Name'          => $plugin_name,
						'Version'       => $plugin_data['Version'],
						'PluginRepoUrl' => self::get_plugin_wp_repo_url( $plugin_slug ),
					),
				)
			);
		}

		/**
		 * Handles a theme deletion attempt.
		 *
		 * @param string $stylesheet Stylesheet of the theme to delete.
		 * @param bool   $deleted    Whether the theme deletion was successful.
		 *
		 * @since 4.5.0
		 */
		public static function on_deleted_theme( $stylesheet, $deleted ) {
			if ( ! $deleted ) {
				return;
			}

			if ( ! array_key_exists( $stylesheet, self::get_old_themes() ) ) {
				return;
			}

			$theme = self::get_old_themes()[ $stylesheet ];
			Alert_Manager::trigger_event(
				5007,
				array(
					'Theme' => (object) array(
						'Name'                   => $theme->Name, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'ThemeURI'               => $theme->ThemeURI, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Description'            => $theme->Description, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Author'                 => $theme->Author, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Version'                => $theme->Version, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'get_template_directory' => $theme->get_template_directory(),
					),
				)
			);
		}

		/**
		 * Triggered when a user accesses the admin area.
		 *
		 * @since 4.5.0
		 */
		public static function event_init() {
			self::get_old_themes();
			self::get_old_plugins();
		}

		/**
		 * Activated a theme.
		 *
		 * @param string $theme_name - Theme name.
		 *
		 * @since 4.5.0
		 */
		public static function event_theme_activated( $theme_name ) {
			$theme = null;
			foreach ( wp_get_themes() as $item ) {
				if ( $theme_name === $item->Name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
					$theme = $item;
					break;
				}
			}
			if ( null === $theme ) {
				Alert_Manager::log_error(
					'Could not locate theme named "' . $theme . '".',
					array(
						'ThemeName' => $theme_name,
						'Themes'    => wp_get_themes(),
					)
				);
				return;
			}
			Alert_Manager::trigger_event(
				5006,
				array(
					'Theme' => (object) array(
						'Name'                   => $theme->Name, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'ThemeURI'               => $theme->ThemeURI, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Description'            => $theme->Description, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Author'                 => $theme->Author, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'Version'                => $theme->Version, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
						'get_template_directory' => $theme->get_template_directory(),
					),
				)
			);
		}

		/**
		 * Get event code by post type.
		 *
		 * @param object $post        - Post object.
		 * @param int    $type_post   - Code for post.
		 * @param int    $type_page   - Code for page.
		 * @param int    $type_custom - Code for custom post type.
		 *
		 * @return false|int
		 *
		 * @since 4.5.0
		 */
		protected static function get_event_type_for_post_type( $post, $type_post, $type_page, $type_custom ) {
			if ( empty( $post ) || ! isset( $post->post_type ) ) {
				return false;
			}

			switch ( $post->post_type ) {
				case 'page':
					return $type_page;
				case 'post':
					return $type_post;
				default:
					return $type_custom;
			}
		}

		/**
		 * Method: Remove the PHP file after `/` in the plugin
		 * directory name.
		 *
		 * For example, it will remove `/akismet.php` from
		 * `akismet/akismet.php`.
		 *
		 * @param string $plugin – Plugin name.
		 *
		 * @return string
		 *
		 * @since 4.5.0
		 */
		public static function get_plugin_dir( $plugin ) {
			$position = strpos( $plugin, '/' );
			if ( false !== $position ) {
				$plugin = substr_replace( $plugin, '', $position );
			}
			return $plugin;
		}

		/**
		 * Method: Return plugin file name.
		 *
		 * @param string $plugin_name - Plugin name.
		 *
		 * @return string
		 *
		 * @since 4.5.0
		 */
		public static function get_plugin_file_name( $plugin_name ) {
			// Verify parameter.
			if ( empty( $plugin_name ) ) {
				return '';
			}

			// Get all plugins.
			$all_plugins = get_plugins();

			$plugin_filename = '';
			if ( ! empty( $all_plugins ) && is_array( $all_plugins ) ) {
				foreach ( $all_plugins as $plugin_file => $plugin_data ) {
					if ( $plugin_name === $plugin_data['Name'] ) {
						$plugin_filename = $plugin_file;
					}
				}
			}

			return $plugin_filename;
		}

		/**
		 * Method: Search and return theme object by name.
		 *
		 * @param string $theme_name - Theme name.
		 *
		 * @return WP_Theme|null
		 *
		 * @since 4.5.0
		 */
		public static function get_theme_by_name( $theme_name ) {
			// Check if $theme_name is empty.
			if ( empty( $theme_name ) ) {
				return null;
			}

			// Get all themes.
			$all_themes = wp_get_themes();

			$theme = null;
			if ( ! empty( $all_themes ) ) {
				foreach ( $all_themes as $theme_slug => $theme_obj ) {
					if ( $theme_name === $theme_slug || $theme_name === $theme_obj->get( 'Name' ) ) {
						$theme = $theme_obj;
						break;
					}
				}
			}
			return $theme;
		}

		/**
		 * Detects posts created by plugin actions.
		 *
		 * @param int      $post_id - The post ID.
		 * @param \WP_Post $post - The post object.
		 *
		 * @return void
		 *
		 * @since 4.5.0
		 * @since 5.6.2 Refactored to better detect posts created by plugin actions.
		 */
		public static function plugin_created_post( $post_id, $post ) {
			if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
				return;
			}

			// Ignore if the request is coming from post editor.
			if ( isset( $_POST['_wp_http_referer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$referrer   = \esc_url_raw( \wp_unslash( $_POST['_wp_http_referer'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$parsed_url = \wp_parse_url( $referrer );

				if ( isset( $parsed_url['path'] ) && 'post' === basename( $parsed_url['path'], '.php' ) ) {
					return;
				}
			}

			$get_action  = isset( $_GET['action'] ) ? \sanitize_text_field( \wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$post_action = isset( $_POST['action'] ) ? \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$plugin_name = isset( $_GET['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_GET['plugin'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

			$wp_actions = array( 'editpost', 'heartbeat', 'inline-save', 'trash', 'untrash', 'vc_save' );

			// Handle GET-based plugin actions (e.g. plugin activation via admin URL).
			if ( $get_action && ! in_array( $get_action, $wp_actions, true ) ) {
				if (
				! in_array( $post->post_type, Alert_Manager::get_ignored_post_types(), true )
				&& ! empty( $post->post_title )
				&& $plugin_name
				&& ! \is_wp_error( \validate_plugin( $plugin_name ) )
				) {
					$plugin_data = \get_plugin_data( \trailingslashit( \WP_PLUGIN_DIR ) . $plugin_name );
					$plugin_slug = dirname( $plugin_name );
					$editor_link = self::get_editor_link( $post );

					Alert_Manager::trigger_event(
						5019,
						array(
							'PluginName'         => ( $plugin_data && isset( $plugin_data['Name'] ) ) ? $plugin_data['Name'] : false,
							'PostID'             => $post->ID,
							'PostType'           => $post->post_type,
							'PostTitle'          => $post->post_title,
							'PostStatus'         => $post->post_status,
							'Username'           => 'Plugins',
							$editor_link['name'] => $editor_link['value'],
							'PluginData'         => (object) array(
								'PluginRepoUrl' => self::get_plugin_wp_repo_url( $plugin_slug ),
							),
						)
					);
				}
			}

			// Handle POST-based plugin actions that include a plugin identifier in the URL.
			if ( $post_action && ! in_array( $post_action, $wp_actions, true ) ) {
				if (
				! in_array( $post->post_type, Alert_Manager::get_ignored_post_types(), true )
				&& ! empty( $post->post_title )
				) {
					// Ignore WooCommerce Bulk Stock Management page.
					$post_page         = isset( $_POST['page'] ) ? \sanitize_text_field( \wp_unslash( $_POST['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
					$is_woo_bulk_stock = 'woocommerce-bulk-stock-management' === $post_page;

					// Ignore MainWP plugin requests.
					$mainwp_actions    = array( 'restore', 'unpublish', 'publish' );
					$is_mainwp_request = isset( $_POST['mainwpsignature'] ) && in_array( $post_action, $mainwp_actions, true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

					if ( ! $is_woo_bulk_stock && ! $is_mainwp_request && $plugin_name && ! \is_wp_error( \validate_plugin( $plugin_name ) ) ) {
						$plugin_data = \get_plugin_data( \trailingslashit( \WP_PLUGIN_DIR ) . $plugin_name );
						$plugin_slug = dirname( $plugin_name );
						$editor_link = self::get_editor_link( $post );

						Alert_Manager::trigger_event(
							5019,
							array(
								'PluginName'         => ( $plugin_data && isset( $plugin_data['Name'] ) ) ? $plugin_data['Name'] : false,
								'PostID'             => $post->ID,
								'PostType'           => $post->post_type,
								'PostTitle'          => $post->post_title,
								'PostStatus'         => $post->post_status,
								'Username'           => 'Plugins',
								$editor_link['name'] => $editor_link['value'],
								'PluginData'         => (object) array(
									'PluginRepoUrl' => self::get_plugin_wp_repo_url( $plugin_slug ),
								),
							)
						);
					}
				}
			}
		}

		/**
		 * Get editor link.
		 *
		 * @param object $post - The post object.
		 * @return array $editor_link name and value link.
		 *
		 * @since 4.5.0
		 */
		private static function get_editor_link( $post ) {
			$name        = 'EditorLink';
			$name       .= ( 'page' === $post->post_type ) ? 'Page' : 'Post';
			$value       = get_edit_post_link( $post->ID );
			$editor_link = array(
				'name'  => $name,
				'value' => $value,
			);

			return $editor_link;
		}

		/**
		 * Get old themes - That must be done BEFORE themes changes apply globally. It stores "image" the themes before changes.
		 *
		 * @return array
		 *
		 * @since 5.3.4.1
		 */
		private static function get_old_themes(): array {
			if ( null === self::$old_themes ) {
				self::$old_themes = \wp_get_themes();
			}

			return self::$old_themes;
		}

		/**
		 * Get old plugins. That must apply before plugins changes apply globally. It stores "image" the plugins before changes.
		 *
		 * @return array
		 *
		 * @since 5.3.4.1
		 */
		private static function get_old_plugins(): array {
			if ( null === self::$old_plugins ) {
				self::$old_plugins = \get_plugins();
			}

			return self::$old_plugins;
		}

		/**
		 * To prevent $silent flag set to true in the WP core we are using this method.
		 *
		 * @param array $old_value - Old value with active plugins.
		 * @param array $new_value - New value with active plugins.
		 *
		 * @return void
		 *
		 * @since 5.4.0
		 */
		public static function on_active_plugins_update( $old_value, $new_value ) {
			if ( \is_array( $new_value ) && \is_array( $old_value ) ) {
				foreach ( $new_value as $plugin ) {
					if ( ! in_array( $plugin, self::$reported_plugins, true ) ) {
						// Plugin is not reported neither as active nor as deactivated.
						// Check if that plugin exists in the old value. If it does, it means that the plugin was deactivated.
						if ( ! in_array( $plugin, $old_value, true ) ) {
							// Plugin was activated.
							self::on_activated_plugin( $plugin );

							self::$reported_plugins[ $plugin ] = $plugin;
						}
					}
				}

				foreach ( $old_value as $plugin ) {
					if ( ! in_array( $plugin, self::$reported_plugins, true ) ) {
						// Plugin is not reported neither as active nor as deactivated.
						// Check if that plugin exists in the old value. If it does, it means that the plugin was deactivated.
						if ( ! in_array( $plugin, $new_value, true ) ) {
							// Plugin was deactivated.
							self::on_deactivated_plugin( $plugin );
							self::$reported_plugins[ $plugin ] = $plugin;
						}
					}
				}
			}
		}

		/**
		 * Get all plugins.
		 *
		 * @return array
		 *
		 * @since 5.3.4.1
		 */
		private static function get_all_plugins(): array {
			if ( null === self::$plugins_available ) {
				self::$plugins_available = \get_plugins();
			}

			return self::$plugins_available;
		}

		/**
		 * Notify when a plugin translation is updated.
		 *
		 * @param \WP_Upgrader|\Language_Pack_Upgrader $upgrader \WP_Upgrader instance. In other contexts this might be a Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
		 * @param array                                $options  Array of bulk item update data.
		 *
		 * @since 5.5.0
		 */
		public static function on_plugin_or_theme_update( $upgrader, $options ) {

			if ( ! isset( $options['type'] ) || ! isset( $options['action'] ) ) {
				return;
			}

			if ( 'translation' !== $options['type'] || 'update' !== $options['action'] ) {
				return;
			}

			$translations = $options['translations'];

			foreach ( $translations as $translation ) {

				// Only proceed if the translation is for a plugin or a theme.
				if ( 'plugin' !== $translation['type'] && 'theme' !== $translation['type'] ) {
					continue;
				}

				/**
				 * Example of $translation for a plugin:
				 * (
				 *     [language] => es_ES
				 *     [type] => plugin
				 *     [slug] => wp-statistics
				 *     [version] => 14.15.3
				 * )
				 */

				/**
				 * Example of $translation for a theme:
				 * (
				 *     [language] => es_ES
				 *     [type] => theme
				 *     [slug] => generatepress
				 *     [version] => 3.6.0
				 * )
				 */

				$name = '';

				if ( method_exists( $upgrader, 'get_name_for_update' ) ) {

					// Name - e.g. "WordPress".
					$name = $upgrader->get_name_for_update( (object) $translation );
				}

				// If name is empty, let's use the slug as a fallback.
				if ( empty( $name ) && ! empty( $translation['slug'] ) ) {
					$name = $translation['slug'];
				}

				if ( 'plugin' === $translation['type'] ) {
						Alert_Manager::trigger_event(
							5034,
							array(
								'plugin_name' => $name,
								'language'    => $translation['language'],
							)
						);
				}

				if ( 'theme' === $translation['type'] ) {
					Alert_Manager::trigger_event(
						5035,
						array(
							'theme_name' => $name,
							'language'   => $translation['language'],
						)
					);
				}
			}
		}

		/**
		 * Fallback method in case events 5000 and 5005 were not triggered.
		 *
		 * This method has been added to handle the case when a plugin or theme is installed via ManageWP,
		 * which is not reported by the on_upgrader_process_complete method.
		 *
		 * @param bool  $response - Installation response.
		 * @param array $hook_extra - Extra arguments passed to hooked filters.
		 * @param array $result - Installation result data.
		 *
		 * @return bool $response - The installation response after the installation has finished
		 *
		 * @since 5.5.0
		 */
		public static function on_upgrader_post_install( $response, $hook_extra, $result ) {

			/**
			 * If $hook_extra['action'] is not set or not equal to 'install', return early.
			 * This works both for themes and plugins.
			 */
			if ( ! isset( $hook_extra['action'] ) || 'install' !== $hook_extra['action'] ) {
				return $response;
			}

			if ( isset( $result['destination_name'] ) ) {
				$folder_name = WP_Plugins_Themes_Helper::trim_folder_name( $result['destination_name'] );

				$is_plugin = WP_Plugins_Themes_Helper::does_dir_or_file_exist( $folder_name, 'plugin' );

				if ( $is_plugin ) {
					$event_plugin_data = WP_Plugins_Themes_Helper::get_plugin_event_info_from_folder( $folder_name );

					if ( is_array( $event_plugin_data ) ) {
						Alert_Manager::trigger_event(
							5000,
							array(
								'PluginData' => (object) $event_plugin_data,
							)
						);
					}
				}

				$is_theme = WP_Plugins_Themes_Helper::does_dir_or_file_exist( $folder_name, 'theme' );
				if ( $is_theme ) {
					$theme_event_data = WP_Plugins_Themes_Helper::get_theme_event_info_from_folder( $folder_name );

					if ( is_array( $theme_event_data ) ) {
						Alert_Manager::trigger_event(
							5005,
							array(
								'Theme' => (object) $theme_event_data,
							)
						);
					}
				}
			}

			return $response;
		}

		/**
		 * Notify when a plugin update is available.
		 *
		 * @param string $transient - The name of the site transient.
		 * @param mixed  $value - Site transient value.
		 * @param int    $expiration - Time until expiration in seconds.
		 *
		 * @since 5.6.0
		 */
		public static function on_available_plugin_update( $transient, $value, $expiration ) {

			// If this is not the update_plugins transient, return early.
			if ( 'update_plugins' !== $transient ) {
				return;
			}

			// If there aren't updates, return early.
			if ( empty( $value->response ) || ! is_array( $value->response ) ) {
				return;
			}

			$notified_updates = Settings_Helper::get_option_value( 'notified_plugin_updates', array() );

			$all_plugins = \get_plugins();

			foreach ( $value->response as $plugin_file => $plugin_data ) {

				// Double check if the plugin file exists.
				if ( ! isset( $all_plugins[ $plugin_file ] ) ) {
					continue;
				}

				$new_available_version = \esc_html( $plugin_data->new_version );
				$last_notified_version = $notified_updates[ $plugin_file ] ?? null;

				// Check if this version was already notified, and in that case, skip the event for this plugin.
				if ( $last_notified_version && version_compare( $last_notified_version, $new_available_version, '>=' ) ) {
					continue;
				}

				$plugin_folder_name = self::get_plugin_dir( $plugin_file );
				$event_plugin_data  = WP_Plugins_Themes_Helper::get_plugin_event_info_from_folder( $plugin_folder_name );

				if ( is_array( $event_plugin_data ) ) {
					$plugin_name            = $event_plugin_data['Name'];
					$plugin_current_version = $event_plugin_data['Version'];
					$update_admin_screen    = \esc_url( \network_admin_url( 'update-core.php' ) );

					$server_address = isset( $_SERVER['SERVER_ADDR'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '127.0.0.1';

					Alert_Manager::trigger_event(
						5032,
						array(
							'CurrentUserID'        => 0,
							'ClientIP'             => $server_address,
							'PluginName'           => $plugin_name,
							'CurrentPluginVersion' => $plugin_current_version,
							'NewPluginVersion'     => $new_available_version,
							'UpdateAdminUrl'       => $update_admin_screen,
						)
					);

					// Mark this plugin update as notified, use the current new version as reference.
					$notified_updates[ $plugin_file ] = $new_available_version;
				}
			}

			// Update the notified updates option.
			Settings_Helper::set_option_value( 'notified_plugin_updates', $notified_updates );
		}

		/**
		 * Notify when a theme update is available.
		 *
		 * @param string $transient - The name of the site transient.
		 * @param mixed  $value - Site transient value.
		 * @param int    $expiration - Time until expiration in seconds.
		 *
		 * @since 5.6.0
		 */
		public static function on_available_theme_update( $transient, $value, $expiration ) {

			// If this is not the update_plugins transient, return early.
			if ( 'update_themes' !== $transient ) {
				return;
			}

			// If there aren't updates, return early.
			if ( empty( $value->response ) || ! is_array( $value->response ) ) {
				return;
			}

			$notified_updates = Settings_Helper::get_option_value( 'notified_theme_updates', array() );

			foreach ( $value->response as $theme_folder => $theme_data ) {
				$new_available_version = \esc_html( $theme_data['new_version'] );
				$last_notified_version = $notified_updates[ $theme_folder ] ?? null;

				// Check if this version was already notified, and in that case, skip the event for this plugin.
				if ( $last_notified_version && version_compare( $last_notified_version, $new_available_version, '>=' ) ) {
					continue;
				}

				$theme_event_data = WP_Plugins_Themes_Helper::get_theme_event_info_from_folder( $theme_folder );

				if ( is_array( $theme_event_data ) ) {
					$theme_name            = $theme_event_data['Name'];
					$theme_current_version = $theme_event_data['Version'];
					$update_admin_screen   = \esc_url( \network_admin_url( 'update-core.php' ) );

					$server_address = isset( $_SERVER['SERVER_ADDR'] ) ? \sanitize_text_field( \wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '127.0.0.1';

					Alert_Manager::trigger_event(
						5033,
						array(
							'CurrentUserID'       => 0,
							'ClientIP'            => $server_address,
							'ThemeName'           => $theme_name,
							'CurrentThemeVersion' => $theme_current_version,
							'NewThemeVersion'     => $new_available_version,
							'UpdateAdminUrl'      => $update_admin_screen,
						)
					);

					// Mark this plugin update as notified, use the current new version as reference.
					$notified_updates[ $theme_folder ] = $new_available_version;
				}
			}

			// Update the notified updates option.
			Settings_Helper::set_option_value( 'notified_theme_updates', $notified_updates );
		}
	}
}
