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 ); } } }