check_server_banned_plugin_lists(); $this->recheck_disabled_list(); $this->banned_list = array_merge( $this->warning_list, $this->disabled_list ); // Full list of Banned Plugins. $this->active_plugins = get_option( 'active_plugins', array() ); global $wp_version; add_action( 'admin_init', array( $this, 'deactivate_disabled_plugins' ), PHP_INT_MAX ); add_action( 'activated_plugin', array( $this, 'deactivate_disabled_plugin' ), PHP_INT_MAX ); add_action( 'admin_print_scripts', array( $this, 'add_plugin_page_scripts' ), PHP_INT_MAX ); add_action( 'admin_print_styles', array( $this, 'add_plugin_page_styles' ), PHP_INT_MAX ); add_filter( 'plugin_install_action_links', array( $this, 'plugin_install_action_links' ), PHP_INT_MAX, 2 ); add_filter( 'install_plugin_complete_actions', array( $this, 'install_plugin_complete_actions' ), PHP_INT_MAX, 3 ); foreach ( $this->disabled_list as $plugin_file ) { add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'disabled_plugin_action_links' ), PHP_INT_MAX ); } foreach ( $this->warning_list as $plugin_file ) { add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'warning_plugin_action_links' ), PHP_INT_MAX ); } if ( self::shall_display_admin_notice() && version_compare( $wp_version, '4.3', '>' ) ) { add_action( 'admin_print_scripts', array( $this, 'add_notice_scripts' ), PHP_INT_MAX ); add_action( 'admin_print_styles', array( $this, 'add_notice_styles' ), PHP_INT_MAX ); add_action( 'admin_notices', array( $this, 'admin_notices' ), PHP_INT_MAX ); add_action( 'wp_ajax_kinsta_dismiss_banned_plugins_nag', array( $this, 'dismiss_banned_plugins_nag' ), PHP_INT_MAX ); } } /** * Retrieve the plugins in the "Warning" list. * * @return array */ public function get_warning_list() { return $this->warning_list; } /** * Retrieve the plugins in the "Disabled" list. * * @return array */ public function get_disabled_list() { return $this->disabled_list; } /** * Retrieve the plugins in the "Banned" list. * * @return array */ public function get_banned_list() { return $this->banned_list; } /** * Disaplay admin notice. * * @return void */ public function admin_notices() { $notice_content = $this->get_the_admin_notice_content(); ?>
disabled_list, true ) ) { deactivate_plugins( $active_plugin ); } } } /** * Deactivate all Disabled Plugins at once * * @return void */ public function deactivate_disabled_plugins() { deactivate_plugins( $this->disabled_list ); // Deactivate all banned plugins at once. } /** * Customize the Action links in the plugin table (e.g. Activate, Deactivate, etc.) * for the plugins in the Warning List. * * @param array $actions An array of plugin action links. By default this can include 'activate', 'deactivate', and 'delete'. * @return array The list of action links modfied. */ public function warning_plugin_action_links( $actions ) { /** * Remove the activate link action. * * We're preventing plugin activation during installation (through WP.org Search or Plugin Uploads), * so it does make more sense to disable the Activate link displayed ont the plugin Table List. */ unset( $actions['activate'] ); $warning_actions = $actions; $banned_action_link = $this->get_banned_plugin_action_link(); if ( is_string( $banned_action_link ) && ! empty( $banned_action_link ) ) { $warning_actions = array_merge( array( 'kinsta_banned' => $banned_action_link ), $warning_actions ); } return $warning_actions; } /** * Customize the Action links in the plugin table (e.g. Activate, Deactivate, etc.), * for the plugins in the Disabled List. * * @param array $actions An array of plugin action links. By default this can include 'activate', 'deactivate', and 'delete'. * @return array The list of action links modfied. */ public function disabled_plugin_action_links( $actions ) { $disabled_actions = array( 'delete' => $actions['delete'], ); $banned_action_link = $this->get_banned_plugin_action_link(); if ( is_string( $banned_action_link ) && ! empty( $banned_action_link ) ) { $disabled_actions = array_merge( array( 'kinsta_banned' => $banned_action_link ), $disabled_actions ); } return $disabled_actions; } /** * Customize the action links for all the plugins in Banned List. * * @param array $action_links An array of plugin action links. Defaults are links to Details and Install Now. * @param array $plugin The plugin currently being listed. * @return array */ public function plugin_install_action_links( $action_links, $plugin ) { foreach ( $this->banned_list as $banned ) { $banned_slug = self::parse_plugin_slug( $banned ); if ( $plugin['slug'] === $banned_slug ) { $action_links = array( '', ); $banned_action_link = $this->get_banned_plugin_install_action_link(); if ( is_string( $banned_action_link ) && ! empty( $banned_action_link ) ) { $action_links = array_merge( $action_links, array( 'kinsta_banned_why' => $banned_action_link ) ); } } } return $action_links; } /** * Customize the action links for all the plugins in Banned List. * * @param array $install_actions Array of plugin action links. * @param object $api Object containing WordPress.org API plugin data. Empty * for non-API installs, such as when a plugin is installed * via upload. * @param string $plugin_file Path to the plugin file relative to the plugins directory. * @return array */ public function install_plugin_complete_actions( $install_actions, $api, $plugin_file ) { $this->recheck_disabled_list(); if ( in_array( $plugin_file, $this->banned_list, true ) ) { if ( isset( $install_actions['activate_plugin'] ) ) { unset( $install_actions['activate_plugin'] ); } if ( isset( $install_actions['network_activate'] ) ) { unset( $install_actions['network_activate'] ); } $install_actions = array( 'kinsta_banned_plugin' => '', ); $banned_action_link = $this->get_banned_plugin_install_action_link(); if ( is_string( $banned_action_link ) && ! empty( $banned_action_link ) ) { $install_actions = array_merge( $install_actions, array( 'kinsta_banned_why' => $banned_action_link ) ); } } return $install_actions; } /** * Dismiss "Banned Plugins" notice * * @return void */ public function dismiss_banned_plugins_nag() { check_ajax_referer( 'kinsta-banned-plugins', 'nonce' ); set_transient( 'kinsta_dismiss_banned_plugins_nag', '1', MONTH_IN_SECONDS ); wp_die(); } /** * Get the notice to disapled in the. * * @return string */ private function get_notifiable_plugins() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed_plugins = get_plugins(); $active_banned_plugins = array_intersect( (array) $this->active_plugins, (array) $this->banned_list ); $notifiable_plugins = array(); foreach ( $active_banned_plugins as $plugin_file ) { if ( array_key_exists( $plugin_file, $installed_plugins ) ) { $notifiable_plugins[ $plugin_file ] = $installed_plugins[ $plugin_file ]; } } return array_column( $notifiable_plugins, 'Name' ); } /** * Retrieve the content message to display in the admin notice. * * @return string */ private function get_the_admin_notice_content() { $notifiable_plugins = $this->get_notifiable_plugins(); $heading = _n( 'Kinsta detected a banned plugin', 'Kinsta detected banned plugins', count( $notifiable_plugins ), 'kinsta-mu-plugins' ); // Translators: 1st %s the "heading" (e.g. Kinsta detected a banned plugin), 2nd %s plugin names. $notice_content = '

' . sprintf( '%s: %s.', $heading, implode( ', ', $notifiable_plugins ) ) . '

'; // Translators: %s "this plugin" if singular, "these plugins" if plural. $notice_content .= '

' . sprintf( __( 'Please deactivate %s as soon as possible. Using a banned plugin can cause performance issues for your site or compatibility issues with our hosting platform.', 'kinsta-mu-plugins' ), _n( 'this plugin', 'these plugins', count( $notifiable_plugins ), 'kinsta-mu-plugins' ) ) . '

'; // The button. $notice_content .= '

' . __( 'Learn more about banned plugins', 'kinsta-mu-plugins' ) . '

'; // Set the default string when KINSTAMU_WHITELABLE is enabled. if ( is_whitelabel_enabled() ) { $heading = _n( 'Banned plugin detected', 'Banned plugins detected', count( $notifiable_plugins ), 'kinsta-mu-plugins' ); // Translators: 1st %s the "heading" (e.g. Kinsta detected a banned plugin), 2nd %s plugin names. $notice_content = '

' . sprintf( '%s: %s.', $heading, implode( ', ', $notifiable_plugins ) ) . '

'; $notice_content .= '

' . __( 'Using a banned plugin can cause performance issues for your site or compatibility issues with the hosting platform.', 'kinsta-mu-plugins' ) . '

'; // Translators: %s "this plugin" if singular, "these plugins" if plural. $notice_content .= '

' . sprintf( __( 'Please deactivate %s as soon as possible.', 'kinsta-mu-plugins' ), _n( 'this plugin', 'these plugins', count( $notifiable_plugins ), 'kinsta-mu-plugins' ) ) . '

'; } return apply_filters( 'kinsta_banned_plugin_notice_content', $notice_content, $notifiable_plugins ); } /** * Retrieve the plugin action link shown on the plugin table list. * * @return string */ public function get_banned_plugin_action_link() { $banned_action = '' . __( 'Banned', 'kinsta-mu-plugins' ) . ' '; if ( is_whitelabel_enabled() ) { $banned_action = '' . __( 'Banned', 'kinsta-mu-plugins' ) . ''; } return apply_filters( 'kinsta_banned_plugin_action_link', $banned_action ); } /** * Retrieve the action link when installing the plugin. * * @return string */ public function get_banned_plugin_install_action_link() { $banned_action = '' . __( 'Why?', 'kinsta-mu-plugins' ) . ''; if ( is_whitelabel_enabled() ) { $banned_action = ''; } return apply_filters( 'kinsta_banned_plugin_install_action_link', $banned_action ); } /** * Get the styles to customize the plugin table list. * * @return string */ private function get_list_tables_styles() { $active_banned_plugins = array_intersect( (array) $this->active_plugins, (array) $this->banned_list ); ob_start(); ?> .plugins .kinsta_banned { color: #999; } .plugins .kinsta-plugin-action--banned .dashicons { float: none; width: 16px; height: 16px; padding: 0; display: inline; position: relative; top: 3px; } .plugins .kinsta-plugin-action--banned .dashicons:before { color: inherit; background: none; box-shadow: none; font-size: 16px; padding: 0; } .kinsta-banned-plugin .kinsta-banned-plugin__title { color: #0073aa; } .plugins .active[data-slug=""] td, .plugins .active[data-slug=""] { background: #fef7f7 !important; } .plugin-update-tr.active[data-slug=""] td, .plugins .active[data-slug=""] { border-left: 4px solid #dc3232; } warning_list as $warning ) { $warning_slugs[] = self::parse_plugin_slug( $warning ); } $warning_data = 'var kinstaWarningPlugins = ' . json_encode( $warning_slugs ) . ';'; $disabled_slugs = array(); foreach ( $this->disabled_list as $disabled ) { $disabled_slugs[] = self::parse_plugin_slug( $disabled ); } $disabled_data = 'var kinstaDisabledPlugins = ' . json_encode( $disabled_slugs ) . ';'; echo ''; } } /** * Print the styles on the Plugins Admin page. * * @return void */ public function add_plugin_page_styles() { if ( self::is_admin_plugin_page() ) { $styles = $this->get_list_tables_styles(); echo ''; } } /** * Print the styles for the dismissable notice. * * @return void */ public function add_notice_styles() { ?> active_plugins, (array) $this->banned_list ); $active_banned_plugin_count = 0; foreach ( $active_banned_plugins as $plugin_file ) { if ( array_key_exists( $plugin_file, $installed_plugins ) ) { $active_banned_plugin_count += 1; } } if ( current_user_can( 'activate_plugins' ) && 0 < $active_banned_plugin_count && 1 !== absint( $dismissed ) ) { $shall = true; } return $shall; } /** * Parse the plugin slug from the plugin basename. * * @param string $plugin_file Path to the plugin file relative to the plugins directory. * @return string */ private static function parse_plugin_slug( $plugin_file ) { $parts = explode( '/', $plugin_file ); return isset( $parts[0] ) ? $parts[0] : ''; } /** * Check if we are on the plugin page. * * @return bool */ private static function is_admin_plugin_page() { $current_screen = function_exists( 'get_current_screen' ) ? \get_current_screen() : null; if ( ! is_object( $current_screen ) ) { return false; } return isset( $current_screen->base ) && ( 'plugins' === $current_screen->base || 'plugin-install' === $current_screen->base ); } /** * Updates the banned plugins list from the server env. * * @return void */ private function check_server_banned_plugin_lists() { if ( isset( $_SERVER ) && isset( $_SERVER['KINSTA_BANNED_PLUGINS'] ) ) { $banned_plugins_array = json_decode( $_SERVER['KINSTA_BANNED_PLUGINS'], $assoc_array = true ); if ( is_array( $banned_plugins_array ) && isset( $banned_plugins_array['disabled'] ) && isset( $banned_plugins_array['warning'] ) ) { if ( is_array( $banned_plugins_array['disabled'] ) ) { $this->disabled_list = $banned_plugins_array['disabled']; } if ( is_array( $banned_plugins_array['warning'] ) ) { $this->warning_list = $banned_plugins_array['warning']; } } } } /** * Check and evaluate disabled list. */ private function recheck_disabled_list(): void { /** * Check for BackWPup installs. */ $backwpup_plugins = [ 'backwpup/backwpup.php' => WP_CONTENT_DIR . '/plugins/backwpup/backwpup.php', 'backwpup-pro/backwpup.php' => WP_CONTENT_DIR . '/plugins/backwpup-pro/backwpup.php', ]; foreach ($backwpup_plugins as $plugin_key => $plugin_path) { if (is_file($plugin_path)) { $plugin_data = get_plugin_data($plugin_path); $plugin_ver = trim((string) ($plugin_data['Version'] ?? '')); // Disable if version is less than 5.6.0, or version info cannot be determined. if (! $plugin_ver || version_compare($plugin_ver, '5.6.0', '<')) { $this->disabled_list = array_merge($this->disabled_list, [$plugin_key]); $this->banned_list = array_unique(array_merge($this->banned_list, [$plugin_key])); } } } } }