.plugins tr:has(+ tr.wps-replaced-row) th, .plugins tr:has(+ tr.wps-replaced-row) td { box-shadow: none; }' . "\n"; } /** * Link that deactivates a replaced plugin on this site and comes back to * the page it was clicked on. * * @param string $file Plugin file. * @return string Unescaped URL. */ public static function deactivate_url($file) { return wp_nonce_url(add_query_arg(array( 'action' => self::DEACTIVATE, 'plugin' => rawurlencode($file), '_wp_http_referer' => rawurlencode(remove_query_arg(array(self::DONE, 'deactivate', 'activate'))), ), admin_url('admin-post.php')), self::DEACTIVATE . '_' . $file); } /** * Core's deactivate link on the Plugins screen, keeping the list's * status, page and search, so it comes back to the same list. * * @param string $file Plugin file. * @return string Unescaped URL. */ private static function plugins_deactivate_url($file) { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- the list being shown. $args = array( 'action' => 'deactivate', 'plugin' => rawurlencode($file), 'plugin_status' => isset($_GET['plugin_status']) ? sanitize_key(wp_unslash($_GET['plugin_status'])) : 'all', 'paged' => isset($_GET['paged']) ? absint($_GET['paged']) : 1, 's' => isset($_GET['s']) ? rawurlencode(sanitize_text_field(wp_unslash($_GET['s']))) : '', ); // phpcs:enable return wp_nonce_url(add_query_arg($args, self_admin_url('plugins.php')), 'deactivate-plugin_' . $file); } /** * admin-post: deactivate a replaced plugin on this site, then go back. */ public static function deactivate() { $file = isset($_GET['plugin']) ? sanitize_text_field(wp_unslash($_GET['plugin'])) : ''; check_admin_referer(self::DEACTIVATE . '_' . $file); if ('' === $file || validate_file($file) || !current_user_can('deactivate_plugin', $file)) { wp_die(esc_html__('You are not allowed to deactivate this plugin.', 'wp-plugin-starter-template'), '', array('response' => 403)); } if (!isset(self::replaced()[dirname($file)])) { wp_die(esc_html__('WP Plugin Starter only deactivates plugins it replaces.', 'wp-plugin-starter-template'), '', array('response' => 400)); } if (!function_exists('is_plugin_active')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $back = wp_get_referer(); $back = $back ? $back : admin_url('plugins.php'); // Network-wide plugins are deactivated in the network admin. if (is_plugin_active($file) && !is_plugin_active_for_network($file)) { deactivate_plugins($file); update_option('recently_activated', array($file => time()) + (array) get_option('recently_activated'), false); $back = add_query_arg(self::DONE, 1, $back); } wp_safe_redirect($back); exit; } /** * "Plugin deactivated." on the page the link came back to. */ public static function deactivated_notice() { echo '

' . esc_html__('Plugin deactivated.', 'wp-plugin-starter-template') . '

'; } /** * A note under a replaced plugin's row on the Plugins screen: which * setting does its job and what to do next. * * @param string $file Plugin file. */ public static function row_note($file) { global $wp_list_table; $states = self::states(); if (!isset($states[$file])) { return; } $item = $states[$file]; $labels = esc_html(implode(', ', array_map(function ($label) { return '‘' . $label . '’'; }, $item['settings']))); $setting = ''; if (WPStarter_Settings::can_change()) { $setting = sprintf(' %2$s', esc_url(WPStarter_Admin_Manager::tab_url(WPStarter_Admin_Manager::SEARCH, array('s' => $item['name']))), esc_html__('Show the setting', 'wp-plugin-starter-template')); } $extras = esc_html(implode(', ', $item['extras'])); switch ($item['step']) { case 'deactivate': /* translators: %s: WP Plugin Starter setting names */ $text = sprintf(esc_html__('WP Plugin Starter makes this plugin redundant: %s is on and takes over once you deactivate this plugin. Then you can delete it.', 'wp-plugin-starter-template'), $labels); break; case 'partial': /* translators: 1: WP Plugin Starter setting names, 2: the plugin's settings WP Plugin Starter does not have */ $text = sprintf(esc_html__('WP Plugin Starter can do this plugin\'s job: %1$s is on and takes over once you deactivate it, except for these, which WP Plugin Starter does not do: %2$s.', 'wp-plugin-starter-template'), $labels, $extras); break; case 'switch_on': /* translators: %s: WP Plugin Starter setting names */ $text = sprintf(esc_html__('WP Plugin Starter makes this plugin redundant: turn on %s in WP Plugin Starter, then deactivate and delete this plugin.', 'wp-plugin-starter-template'), $labels) . $setting; break; case 'switch_on_partial': /* translators: 1: WP Plugin Starter setting names, 2: the plugin's settings WP Plugin Starter does not have */ $text = sprintf(esc_html__('WP Plugin Starter can do part of this plugin\'s job with %1$s. It does not do these, which this plugin has on: %2$s.', 'wp-plugin-starter-template'), $labels, $extras) . $setting; break; case 'switch_on_inactive': /* translators: %s: WP Plugin Starter setting names */ $text = sprintf(esc_html__('WP Plugin Starter makes this plugin redundant: turn on %s in WP Plugin Starter, then you can delete this plugin.', 'wp-plugin-starter-template'), $labels) . $setting; break; default: /* translators: %s: WP Plugin Starter setting names */ $text = sprintf(esc_html__('No longer needed: %s in WP Plugin Starter does this plugin\'s job. You can delete it.', 'wp-plugin-starter-template'), $labels); } $columns = ($wp_list_table instanceof WP_List_Table) ? $wp_list_table->get_column_count() : 4; $active = in_array($item['step'], array('deactivate', 'partial', 'switch_on', 'switch_on_partial'), true) ? ' active' : ' inactive'; printf( '

%3$s

', esc_attr($active), (int) $columns, $text // phpcs:ignore WordPress.Security.EscapeOutput -- built from escaped parts above. ); } /** * Replaced plugins, keyed by folder name, with the settings that replace * them. * * @return array}> slug => name and setting key => label. */ private static function replaced() { $replaced = array(); foreach (WPStarter_Settings::schema() as $key => $field) { if (empty($field['replaces']) || !is_array($field['replaces']) || !empty($field['parent'])) { continue; } foreach ($field['replaces'] as $slug => $name) { if (!isset($replaced[$slug])) { $replaced[$slug] = array('name' => (string) $name, 'settings' => array()); } $replaced[$slug]['settings'][$key] = isset($field['label']) ? (string) $field['label'] : (string) $key; } } return $replaced; } /** * What to show in the notice: one item per installed replaced plugin that * needs a step, leaving out what the person hid. * * @return array}> */ private static function items() { $items = array_filter(self::states(), function ($item) { // Inactive with the setting off: only a note on its row. return 'switch_on_inactive' !== $item['step'] && ('switch_on' !== $item['step'] || WPStarter_Settings::can_change()); }); $hidden = (array) get_user_meta(get_current_user_id(), self::HIDDEN, true); return array_values(array_filter($items, function ($item) use ($hidden) { return !in_array($item['slug'] . ':' . $item['step'], $hidden, true); })); } /** * Every installed replaced plugin that needs a step, keyed by plugin file. * * Steps: deactivate, partial (active, setting on, it does more), * switch_on, switch_on_partial, delete (inactive, setting on) and * switch_on_inactive (inactive, setting off; single sites only). * * @return array,extras:string[]}> */ private static function states() { static $states = null; if (null !== $states) { return $states; } if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed = array(); foreach (array_keys(get_plugins()) as $file) { $slug = dirname($file); if ('.' !== $slug) { $installed[$slug] = $file; } } $network = is_multisite() && is_network_admin(); // Active where this screen can deactivate it: network-wide in the // network admin, on this site otherwise. Stored lists, so plugins // skipped on this screen still count. $here = $network ? array_keys((array) get_site_option('active_sitewide_plugins', array())) : WPStarter_Feature::stored_active_plugins(); $all = WPStarter_Feature::active_plugins(); $items = array(); foreach (self::replaced() as $slug => $plugin) { if (!isset($installed[$slug])) { continue; } $file = $installed[$slug]; $all_on = true; foreach (array_keys($plugin['settings']) as $key) { $all_on = $all_on && (bool) WPStarter_Settings::get($key); } if (in_array($file, $here, true)) { $step = $all_on ? 'deactivate' : 'switch_on'; } elseif (!isset($all[$slug]) && !is_multisite()) { $step = $all_on ? 'delete' : 'switch_on_inactive'; } else { continue; } $extras = array(); if ('deactivate' === $step || 'switch_on' === $step) { /** * What a replaced plugin does on this site that WP Plugin Starter, * as set up, does not. When there is any, the notice lists it * instead of saying the plugin can go. * * @param string[] $extras Plain names. * @param string $slug Plugin folder. */ $extras = array_values(array_filter(array_map('strval', (array) apply_filters('wpstarter_replaced_plugin_extras', array(), (string) $slug)))); } if ($extras) { $step = 'deactivate' === $step ? 'partial' : 'switch_on_partial'; } $items[$file] = array( 'slug' => (string) $slug, 'file' => $file, 'name' => $plugin['name'], 'step' => $step, 'settings' => $plugin['settings'], 'extras' => $extras, ); } $states = $items; return $states; } /** * The notice. */ public static function notice() { $items = self::items(); if (!$items) { return; } $rows = array(); foreach ($items as $item) { $rows[] = self::row($item); } $keys = array_map(function ($item) { return $item['slug'] . ':' . $item['step']; }, $items); $hide = wp_nonce_url(add_query_arg(array('action' => self::HIDE, 'items' => implode(',', $keys)), admin_url('admin-post.php')), self::HIDE); ?>

' . esc_html($item['name']) . ''; $base = self_admin_url('plugins.php'); if ('deactivate' === $item['step']) { /* translators: 1: plugin name, 2: WP Plugin Starter setting names */ $text = sprintf(esc_html__('%1$s: %2$s is on and takes over once you deactivate it. Then you can delete it.', 'wp-plugin-starter-template'), $name, esc_html($labels)); if (current_user_can('deactivate_plugin', $item['file'])) { $url = self::plugins_deactivate_url($item['file']); /* translators: %s: plugin name */ $text .= sprintf(' %2$s', esc_url($url), esc_html(sprintf(__('Deactivate %s', 'wp-plugin-starter-template'), $item['name']))); } return $text; } if ('partial' === $item['step'] || 'switch_on_partial' === $item['step']) { $extras = esc_html(implode(', ', $item['extras'])); if ('partial' === $item['step']) { /* translators: 1: plugin name, 2: WP Plugin Starter setting names, 3: the plugin's settings WP Plugin Starter does not have */ $text = sprintf(esc_html__('%1$s: %2$s is on and takes over once you deactivate it, except for these, which WP Plugin Starter does not do: %3$s. Deactivate it only if you no longer need them.', 'wp-plugin-starter-template'), $name, esc_html($labels), $extras); if (current_user_can('deactivate_plugin', $item['file'])) { $url = self::plugins_deactivate_url($item['file']); /* translators: %s: plugin name */ $text .= sprintf(' %2$s', esc_url($url), esc_html(sprintf(__('Deactivate %s', 'wp-plugin-starter-template'), $item['name']))); } return $text; } /* translators: 1: plugin name, 2: WP Plugin Starter setting names, 3: the plugin's settings WP Plugin Starter does not have */ $text = sprintf(esc_html__('%1$s: %2$s can do part of its job. WP Plugin Starter does not do these, which it has on: %3$s.', 'wp-plugin-starter-template'), $name, esc_html($labels), $extras); $url = WPStarter_Admin_Manager::tab_url(WPStarter_Admin_Manager::SEARCH, array('s' => $item['name'])); return $text . sprintf(' %2$s', esc_url($url), esc_html__('Show the setting', 'wp-plugin-starter-template')); } if ('delete' === $item['step']) { /* translators: 1: plugin name, 2: WP Plugin Starter setting names */ $text = sprintf(esc_html__('%1$s is inactive and no longer needed: %2$s does its job.', 'wp-plugin-starter-template'), $name, esc_html($labels)); if (current_user_can('delete_plugins')) { $url = wp_nonce_url(add_query_arg(array('action' => 'delete-selected', 'checked[]' => $item['file'], 'plugin_status' => 'all'), $base), 'bulk-plugins'); /* translators: %s: plugin name */ $text .= sprintf(' %2$s', esc_url($url), esc_html(sprintf(__('Delete %s', 'wp-plugin-starter-template'), $item['name']))); } return $text; } /* translators: 1: plugin name, 2: WP Plugin Starter setting names */ $text = sprintf(esc_html__('%1$s: switch on %2$s, then deactivate and delete it.', 'wp-plugin-starter-template'), $name, esc_html($labels)); $url = WPStarter_Admin_Manager::tab_url(WPStarter_Admin_Manager::SEARCH, array('s' => $item['name'])); return $text . sprintf(' %2$s', esc_url($url), esc_html__('Show the setting', 'wp-plugin-starter-template')); } /** * admin-post: hide the listed items for this person. */ public static function hide() { check_admin_referer(self::HIDE); if (!current_user_can('activate_plugins')) { wp_die(esc_html__('You are not allowed to manage plugins on this site.', 'wp-plugin-starter-template'), '', array('response' => 403)); } $items = isset($_GET['items']) ? explode(',', sanitize_text_field(wp_unslash($_GET['items']))) : array(); $items = array_filter($items, function ($item) { return (bool) preg_match('/^[A-Za-z0-9._-]+:(deactivate|switch_on|delete|partial|switch_on_partial)$/', $item); }); $hidden = (array) get_user_meta(get_current_user_id(), self::HIDDEN, true); update_user_meta(get_current_user_id(), self::HIDDEN, array_values(array_unique(array_filter(array_merge($hidden, $items))))); $back = wp_get_referer(); wp_safe_redirect($back ? $back : self_admin_url('plugins.php')); exit; } }