.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(
' |
',
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;
}
}