mirror of
https://github.com/WPMultisite/multisite-plugin-control.git
synced 2025-08-03 05:33:22 +08:00
v1.9.1 版本发布
This commit is contained in:
parent
00d6c47799
commit
308c0faebd
10 changed files with 1932 additions and 0 deletions
496
class-plugin-report.php
Normal file
496
class-plugin-report.php
Normal file
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class RT_Plugin_Report {
|
||||
const CSS_CLASS_LOW = 'pr-risk-low';
|
||||
const CSS_CLASS_MED = 'pr-risk-medium';
|
||||
const CSS_CLASS_HIGH = 'pr-risk-high';
|
||||
const PLUGIN_VERSION = '2.1.1';
|
||||
const COLS_PER_ROW = 9;
|
||||
const CACHE_LIFETIME = DAY_IN_SECONDS;
|
||||
const CACHE_LIFETIME_NOREPO = WEEK_IN_SECONDS;
|
||||
|
||||
private $parent;
|
||||
|
||||
public function __construct($parent = null) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
|
||||
add_action('wp_ajax_rt_get_plugin_info', [$this, 'get_plugin_info']);
|
||||
add_action('upgrader_process_complete', [$this, 'upgrade_delete_cache_items'], 10, 2);
|
||||
}
|
||||
|
||||
public function render_report() {
|
||||
if (!current_user_can('manage_network_options')) {
|
||||
wp_die(__('You do not have sufficient permissions to access this page.', 'multisite-plugin-control'));
|
||||
}
|
||||
|
||||
global $wp_version;
|
||||
$plugins = get_plugins();
|
||||
$wp_latest = $this->check_core_updates();
|
||||
|
||||
if (isset($_GET['clear_cache'])) {
|
||||
$new_timestamp = intval($_GET['clear_cache']);
|
||||
$last_timestamp = intval(get_site_transient('plugin_report_cache_cleared'));
|
||||
if (!$last_timestamp || $new_timestamp > $last_timestamp) {
|
||||
$this->clear_cache();
|
||||
set_site_transient('plugin_report_cache_cleared', $new_timestamp, self::CACHE_LIFETIME);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="tab-section" data-section="report">
|
||||
<h2><?php _e('Plugin Report', 'multisite-plugin-control'); ?></h2>
|
||||
<p><?php _e('View detailed information about installed plugins across the network.', 'multisite-plugin-control'); ?></p>
|
||||
<p>
|
||||
<?php
|
||||
$version_temp = '<span class="' . $this->get_version_risk_classname($wp_version, $wp_latest) . '">' . $wp_version . '</span>';
|
||||
printf(__('Running WordPress %1$s and PHP %2$s.', 'multisite-plugin-control'), $version_temp, phpversion());
|
||||
if (version_compare($wp_version, $wp_latest, '<')) {
|
||||
printf(' (' . __('Upgrade to %s available', 'multisite-plugin-control') . ')', $wp_latest);
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<a href="<?php echo admin_url('network/plugins.php?page=multisite-plugin-control&tab=report&clear_cache=' . current_time('timestamp')); ?>
|
||||
"class="button" >
|
||||
<?php _e('Clear cached plugin data and reload', 'multisite-plugin-control'); ?>
|
||||
</a>
|
||||
</p>
|
||||
<p id="plugin-report-progress"></p>
|
||||
<table id="plugin-report-table" class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort-default><?php _e('Name', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Author', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Repository', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Activated', 'multisite-plugin-control'); ?></th>
|
||||
<th data-sort-method="none" class="no-sort"><?php _e('Installed Version', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Auto-update', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Last Update', 'multisite-plugin-control'); ?></th>
|
||||
<th data-sort-method="dotsep"><?php _e('Tested WP Version', 'multisite-plugin-control'); ?></th>
|
||||
<th data-sort-method="number"><?php _e('Rating', 'multisite-plugin-control'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
$slug = $this->get_plugin_slug($key);
|
||||
$cache_key = $this->create_cache_key($slug);
|
||||
$cache = get_site_transient($cache_key);
|
||||
if ($cache) {
|
||||
echo $this->render_table_row($cache);
|
||||
} else {
|
||||
echo '<tr class="plugin-report-row-temp-' . $slug . '"><td colspan="' . self::COLS_PER_ROW . '">' . __('Loading...', 'multisite-plugin-control') . '</td></tr>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<p id="plugin-report-buttons"></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function enqueue_assets($hook) {
|
||||
if ('plugins_page_multisite-plugin-control' !== $hook) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_script('plugin-report-js', plugins_url('/js/plugin-report.js', __FILE__), array('jquery', 'plugin-report-tablesort-js'), self::PLUGIN_VERSION);
|
||||
wp_enqueue_script('plugin-report-tablesort-js', plugins_url('/js/tablesort.min.js', __FILE__), array('jquery'), '5.3');
|
||||
wp_enqueue_script('plugin-report-tablesort-number-js', plugins_url('/js/tablesort.number.min.js', __FILE__), array('plugin-report-tablesort-js'), '5.3');
|
||||
wp_enqueue_script('plugin-report-tablesort-dotsep-js', plugins_url('/js/tablesort.dotsep.min.js', __FILE__), array('plugin-report-tablesort-js'), '5.3');
|
||||
$slugs = $this->get_plugin_slugs();
|
||||
$slugs_str = implode(',', $slugs);
|
||||
$vars = array(
|
||||
'plugin_slugs' => $slugs_str,
|
||||
'ajax_nonce' => wp_create_nonce('plugin_report_nonce'),
|
||||
'export_btn' => __('Export .csv file', 'multisite-plugin-control'),
|
||||
'plugin_url_header' => __('Plugin URL', 'multisite-plugin-control'),
|
||||
'author_url_header' => __('Author URL', 'multisite-plugin-control'),
|
||||
);
|
||||
wp_localize_script('plugin-report-js', 'plugin_report_vars', $vars);
|
||||
wp_enqueue_style('plugin-report-css', plugin_dir_url(__FILE__) . 'css/plugin-report.css', array(), self::PLUGIN_VERSION);
|
||||
}
|
||||
|
||||
private function get_plugin_slugs() {
|
||||
$plugins = get_plugins();
|
||||
$slugs = array();
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
$slugs[] = $this->get_plugin_slug($key);
|
||||
}
|
||||
return $slugs;
|
||||
}
|
||||
|
||||
private function get_plugin_slug($file) {
|
||||
if (strpos($file, '/') !== false) {
|
||||
$parts = explode('/', $file);
|
||||
} else {
|
||||
$parts = explode('.', $file);
|
||||
}
|
||||
return sanitize_title($parts[0]);
|
||||
}
|
||||
|
||||
public function get_plugin_info() {
|
||||
if (!check_ajax_referer('plugin_report_nonce', 'nonce', false)) {
|
||||
wp_die();
|
||||
}
|
||||
if (!current_user_can('manage_network_options')) {
|
||||
wp_die();
|
||||
}
|
||||
if (!function_exists('plugins_api')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
}
|
||||
|
||||
$slug = sanitize_title($_POST['slug']);
|
||||
$report = $this->assemble_plugin_report($slug);
|
||||
|
||||
if ($report) {
|
||||
$table_row = $this->render_table_row($report);
|
||||
} else {
|
||||
$table_row = $this->render_error_row(__('No plugin data available.', 'multisite-plugin-control'));
|
||||
}
|
||||
|
||||
$response = array(
|
||||
'html' => $table_row,
|
||||
'message' => 'Success!',
|
||||
);
|
||||
echo wp_json_encode($response);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
private function assemble_plugin_report($slug) {
|
||||
if (!empty($slug)) {
|
||||
$report = array();
|
||||
$cache_key = $this->create_cache_key($slug);
|
||||
$cache = get_site_transient($cache_key);
|
||||
$plugins = get_plugins();
|
||||
$auto_updates = (array) get_site_option('auto_update_plugins', array());
|
||||
|
||||
if (empty($cache)) {
|
||||
$report['slug'] = $slug;
|
||||
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
if ($this->get_plugin_slug($key) === $slug) {
|
||||
$textdomain = $plugin['TextDomain'];
|
||||
if ($textdomain) {
|
||||
if (!is_textdomain_loaded($textdomain)) {
|
||||
if ($plugin['DomainPath']) {
|
||||
load_plugin_textdomain($textdomain, false, dirname($key) . $plugin['DomainPath']);
|
||||
} else {
|
||||
load_plugin_textdomain($textdomain, false, dirname($key));
|
||||
}
|
||||
}
|
||||
} elseif ('hello.php' === basename($key)) {
|
||||
$textdomain = 'default';
|
||||
}
|
||||
if ($textdomain) {
|
||||
foreach (array('Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version') as $field) {
|
||||
$plugin[$field] = translate($plugin[$field], $textdomain);
|
||||
}
|
||||
}
|
||||
$report['local_info'] = $plugin;
|
||||
$report['file_path'] = $key;
|
||||
$report['auto-update'] = in_array($key, $auto_updates);
|
||||
$report['local_info']['Name'] = preg_replace('/\s+/u', ' ', $report['local_info']['Name']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'description' => false,
|
||||
'sections' => false,
|
||||
'tags' => false,
|
||||
'version' => true,
|
||||
'tested' => true,
|
||||
'requires' => true,
|
||||
'requires_php' => true,
|
||||
'compatibility' => true,
|
||||
'author' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$parsed_repo_url = wp_parse_url($report['local_info']['UpdateURI']);
|
||||
$repo_host = isset($parsed_repo_url['host']) ? $parsed_repo_url['host'] : null;
|
||||
if (empty($repo_host) || strtolower($repo_host) === 'w.org' || strtolower($repo_host) === 'wp.org') {
|
||||
$returned_object = plugins_api('plugin_information', $args);
|
||||
}
|
||||
|
||||
if (isset($returned_object)) {
|
||||
if (!is_wp_error($returned_object)) {
|
||||
$report['repo_info'] = maybe_unserialize($returned_object);
|
||||
set_site_transient($cache_key, $report, self::CACHE_LIFETIME);
|
||||
} else {
|
||||
$report['repo_error_code'] = $returned_object->get_error_code();
|
||||
$report['repo_error_message'] = $returned_object->get_error_message();
|
||||
$report['exists_in_svn'] = $this->check_exists_in_svn($slug);
|
||||
set_site_transient($cache_key, $report, self::CACHE_LIFETIME_NOREPO);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$report = $cache;
|
||||
}
|
||||
|
||||
return $report;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function check_exists_in_svn($slug) {
|
||||
$response = wp_remote_get("http://svn.wp-plugins.org/" . $slug . "/");
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
} else {
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
if ('200' == $response_code) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function render_table_row($report) {
|
||||
global $wp_version;
|
||||
$wp_latest = $this->check_core_updates();
|
||||
if (null === $report) {
|
||||
$html = $this->render_error_row(__('No plugin data available.', 'multisite-plugin-control'));
|
||||
} else {
|
||||
$html = '<tr class="plugin-report-row-' . $report['slug'] . '">';
|
||||
if (isset($report['local_info']['PluginURI']) && !empty($report['local_info']['PluginURI'])) {
|
||||
$html .= '<td><a href="' . $report['local_info']['PluginURI'] . '"><strong>' . $report['local_info']['Name'] . '</strong></a></td>';
|
||||
} else {
|
||||
$html .= '<td><strong>' . $report['local_info']['Name'] . '</strong></td>';
|
||||
}
|
||||
if (isset($report['local_info']['AuthorURI']) && !empty($report['local_info']['AuthorURI'])) {
|
||||
$html .= '<td><a href="' . $report['local_info']['AuthorURI'] . '">' . $report['local_info']['Author'] . '</a></td>';
|
||||
} else {
|
||||
$html .= '<td>' . $report['local_info']['Author'] . '</td>';
|
||||
}
|
||||
if (isset($report['local_info']['UpdateURI'])) {
|
||||
$parsed_repo_url = wp_parse_url($report['local_info']['UpdateURI']);
|
||||
$repo_host = isset($parsed_repo_url['host']) ? $parsed_repo_url['host'] : $report['local_info']['UpdateURI'];
|
||||
if (empty($repo_host) || strtolower($repo_host) === 'w.org' || strtolower($repo_host) === 'wordpress.org') {
|
||||
if (isset($report['repo_error_code']) && $report['repo_error_code'] === 'plugins_api_failed') {
|
||||
if (isset($report['exists_in_svn']) && $report['exists_in_svn'] === true) {
|
||||
$html .= '<td class="' . self::CSS_CLASS_HIGH . '">' . __('wp.org/wenpai.org, plugin closed', 'multisite-plugin-control') . '</td>';
|
||||
} else {
|
||||
$html .= '<td class="' . self::CSS_CLASS_HIGH . '">' . __('wp.org/wenpai.org, plugin not found', 'multisite-plugin-control') . '</td>';
|
||||
}
|
||||
} else {
|
||||
$html .= '<td class="' . self::CSS_CLASS_LOW . '">wp.org/wenpai.org</td>';
|
||||
}
|
||||
} else {
|
||||
if ($parsed_repo_url && isset($parsed_repo_url['host'])) {
|
||||
$html .= '<td class="' . self::CSS_CLASS_MED . '">' . $repo_host . '</td>';
|
||||
} else {
|
||||
$html .= '<td class="' . self::CSS_CLASS_MED . '">' . __('Updates disabled', 'multisite-plugin-control') . '</td>';
|
||||
}
|
||||
}
|
||||
} else if (version_compare($wp_version, '5.8', '<')) {
|
||||
$html .= $this->render_error_cell(__('Only available in WP 5.8+', 'multisite-plugin-control'));
|
||||
} else {
|
||||
$html .= $this->render_error_cell();
|
||||
}
|
||||
$active = __('Please clear cache to update', 'multisite-plugin-control');
|
||||
$css_class = self::CSS_CLASS_MED;
|
||||
if (is_multisite()) {
|
||||
$activation_status = $this->get_multisite_activation($report['file_path']);
|
||||
if (true === $activation_status['network']) {
|
||||
$css_class = self::CSS_CLASS_LOW;
|
||||
$html .= '<td class="' . $css_class . '">' . __('Network activated', 'multisite-plugin-control') . '</td>';
|
||||
} else {
|
||||
$css_class = ($activation_status['active'] > 0) ? self::CSS_CLASS_LOW : self::CSS_CLASS_HIGH;
|
||||
$html .= '<td class="' . $css_class . '">' . $activation_status['active'] . '/' . $activation_status['sites'] . '</td>';
|
||||
}
|
||||
} else {
|
||||
if (isset($report['file_path'])) {
|
||||
$active = is_plugin_active($report['file_path']) ? __('Yes', 'multisite-plugin-control') : __('No', 'multisite-plugin-control');
|
||||
$css_class = is_plugin_active($report['file_path']) ? self::CSS_CLASS_LOW : self::CSS_CLASS_HIGH;
|
||||
}
|
||||
$html .= '<td class="' . $css_class . '">' . $active . '</td>';
|
||||
}
|
||||
if (isset($report['repo_info'])) {
|
||||
$css_class = $this->get_version_risk_classname($report['local_info']['Version'], $report['repo_info']->version);
|
||||
$html .= '<td class="' . $css_class . '">';
|
||||
$html .= $report['local_info']['Version'];
|
||||
if ($report['local_info']['Version'] !== $report['repo_info']->version) {
|
||||
$needs_php_upgrade = isset($report['repo_info']->requires_php) ? version_compare(phpversion(), $report['repo_info']->requires_php, '<') : false;
|
||||
$needs_wp_upgrade = isset($report['repo_info']->requires) ? version_compare($wp_version, $report['repo_info']->requires, '<') : false;
|
||||
if ($needs_wp_upgrade && $needs_php_upgrade) {
|
||||
$html .= ' <span class="pr-additional-info">' . sprintf(__('(%1$s available, requires WP %2$s and PHP %3$s)', 'multisite-plugin-control'), $report['repo_info']->version, $report['repo_info']->requires, $report['repo_info']->requires_php) . '</span>';
|
||||
} elseif ($needs_wp_upgrade) {
|
||||
$html .= ' <span class="pr-additional-info">' . sprintf(__('(%1$s available, requires WP %2$s)', 'multisite-plugin-control'), $report['repo_info']->version, $report['repo_info']->requires) . '</span>';
|
||||
} elseif ($needs_php_upgrade) {
|
||||
$html .= ' <span class="pr-additional-info">' . sprintf(__('(%1$s available, requires PHP %2$s)', 'multisite-plugin-control'), $report['repo_info']->version, $report['repo_info']->requires_php) . '</span>';
|
||||
} else {
|
||||
$html .= ' <span class="pr-additional-info">' . sprintf(__('(%s available)', 'multisite-plugin-control'), $report['repo_info']->version) . '</span>';
|
||||
}
|
||||
}
|
||||
$html .= '</td>';
|
||||
} else {
|
||||
$html .= '<td>' . $report['local_info']['Version'] . '</td>';
|
||||
}
|
||||
if (version_compare($wp_version, '5.5', '<')) {
|
||||
$html .= '<td>' . __('Requires WordPress 5.5 or higher', 'multisite-plugin-control') . '</td>';
|
||||
} else {
|
||||
if (isset($report['auto-update']) && $report['auto-update']) {
|
||||
$html .= '<td class="' . self::CSS_CLASS_LOW . '">' . __('Enabled', 'multisite-plugin-control') . '</td>';
|
||||
} else {
|
||||
$html .= '<td>' . __('Not enabled', 'multisite-plugin-control') . '</td>';
|
||||
}
|
||||
}
|
||||
if (isset($report['repo_info']) && isset($report['repo_info']->last_updated)) {
|
||||
$time_update = new DateTime($report['repo_info']->last_updated);
|
||||
$time_diff = human_time_diff($time_update->getTimestamp(), current_time('timestamp'));
|
||||
$css_class = $this->get_timediff_risk_classname(current_time('timestamp') - $time_update->getTimestamp());
|
||||
$html .= '<td class="' . $css_class . '" data-sort="' . $time_update->getTimestamp() . '">' . $time_diff . '</td>';
|
||||
} else {
|
||||
$html .= $this->render_error_cell();
|
||||
}
|
||||
if (isset($report['repo_info']) && isset($report['repo_info']->tested)) {
|
||||
$css_class = $this->get_version_risk_classname($report['repo_info']->tested, $wp_latest, true);
|
||||
$html .= '<td class="' . $css_class . '">' . $report['repo_info']->tested . '</td>';
|
||||
} else {
|
||||
$html .= $this->render_error_cell();
|
||||
}
|
||||
if (isset($report['repo_info']) && isset($report['repo_info']->num_ratings) && isset($report['repo_info']->rating)) {
|
||||
$css_class = (intval($report['repo_info']->num_ratings) > 0) ? $this->get_percentage_risk_classname(intval($report['repo_info']->rating)) : '';
|
||||
$value_text = ((intval($report['repo_info']->num_ratings) > 0) ? $report['repo_info']->rating . '%' : __('No data available', 'multisite-plugin-control'));
|
||||
$html .= '<td class="' . $css_class . '">' . $value_text . '</td>';
|
||||
} else {
|
||||
$html .= $this->render_error_cell();
|
||||
}
|
||||
$html .= '</tr>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function render_error_row($message) {
|
||||
return '<tr class="pluginreport-row-error"><td colspan="' . self::COLS_PER_ROW . '">' . $message . '</td></tr>';
|
||||
}
|
||||
|
||||
private function render_error_cell($message = null) {
|
||||
if (!$message) {
|
||||
$message = __('No data available', 'multisite-plugin-control');
|
||||
}
|
||||
return '<td class="pluginreport-cell-error">' . $message . '</td>';
|
||||
}
|
||||
|
||||
private function get_major_version($version_string) {
|
||||
$parts = explode('.', $version_string);
|
||||
array_splice($parts, 2);
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
private function get_version_risk_classname($available, $optimal, $major_only = false) {
|
||||
if ($major_only) {
|
||||
$available = $this->get_major_version($available);
|
||||
$optimal = $this->get_major_version($optimal);
|
||||
}
|
||||
if (version_compare($available, $optimal, '>=')) {
|
||||
return self::CSS_CLASS_LOW;
|
||||
}
|
||||
return self::CSS_CLASS_HIGH;
|
||||
}
|
||||
|
||||
private function get_percentage_risk_classname($perc) {
|
||||
if ($perc < 70) {
|
||||
return self::CSS_CLASS_HIGH;
|
||||
}
|
||||
if ($perc < 90) {
|
||||
return self::CSS_CLASS_MED;
|
||||
}
|
||||
return self::CSS_CLASS_LOW;
|
||||
}
|
||||
|
||||
private function get_timediff_risk_classname($time_diff) {
|
||||
$days = $time_diff / (DAY_IN_SECONDS);
|
||||
if ($days > 365) {
|
||||
return self::CSS_CLASS_HIGH;
|
||||
}
|
||||
if ($days > 90) {
|
||||
return self::CSS_CLASS_MED;
|
||||
}
|
||||
return self::CSS_CLASS_LOW;
|
||||
}
|
||||
|
||||
private function check_core_updates() {
|
||||
global $wp_version;
|
||||
$update = get_preferred_from_update_core();
|
||||
if (!$update || false === $update) {
|
||||
return $wp_version;
|
||||
}
|
||||
if ('latest' === $update->response) {
|
||||
return $wp_version;
|
||||
}
|
||||
return $update->version;
|
||||
}
|
||||
|
||||
private function get_multisite_activation($path) {
|
||||
$activation_status = array(
|
||||
'network' => false,
|
||||
'active' => 0,
|
||||
'sites' => 1,
|
||||
);
|
||||
$network_plugins = get_site_option('active_sitewide_plugins', null);
|
||||
if (array_key_exists($path, $network_plugins)) {
|
||||
$activation_status['network'] = true;
|
||||
} else {
|
||||
$args = array(
|
||||
'number' => 9999,
|
||||
'fields' => 'ids',
|
||||
);
|
||||
$sites = get_sites($args);
|
||||
$activation_status['sites'] = count($sites);
|
||||
foreach ($sites as $site_id) {
|
||||
$plugins = get_blog_option($site_id, 'active_plugins', null);
|
||||
if ($plugins) {
|
||||
foreach ($plugins as $plugin_path) {
|
||||
if ($plugin_path === $path) {
|
||||
$activation_status['active']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $activation_status;
|
||||
}
|
||||
|
||||
private function create_cache_key($slug) {
|
||||
$slug_hash = hash('sha256', $slug);
|
||||
$cache_key = 'rtpr_' . substr($slug_hash, 0, 35);
|
||||
return $cache_key;
|
||||
}
|
||||
|
||||
private function clear_cache() {
|
||||
$plugins = get_plugins();
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
$slug = $this->get_plugin_slug($key);
|
||||
$this->clear_cache_item($slug);
|
||||
}
|
||||
}
|
||||
|
||||
private function clear_cache_item($slug) {
|
||||
if (isset($slug)) {
|
||||
$cache_key = $this->create_cache_key($slug);
|
||||
delete_site_transient($cache_key);
|
||||
}
|
||||
}
|
||||
|
||||
public function upgrade_delete_cache_items($upgrader, $data) {
|
||||
if (isset($data) && isset($data['plugins']) && is_array($data['plugins'])) {
|
||||
foreach ($data['plugins'] as $key => $value) {
|
||||
$slug = $this->get_plugin_slug($value);
|
||||
$this->clear_cache_item($slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
css/plugin-report.css
Normal file
85
css/plugin-report.css
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* risk indicator classes */
|
||||
|
||||
td.pr-risk-low {
|
||||
background-color: rgba( 0, 255, 0, 0.075 );
|
||||
color: #090 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td.pr-risk-high {
|
||||
background-color: rgba( 255, 0, 0, 0.075 );
|
||||
color: #c00 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td.pr-risk-medium {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* additional info in table cells */
|
||||
|
||||
span.pr-additional-info {
|
||||
font-weight: normal;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* progress bar */
|
||||
|
||||
#plugin-report-progress progress {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 1em;
|
||||
border: 1px solid #e5e5e5;
|
||||
background-color: #fff;
|
||||
color: #3858e9;
|
||||
}
|
||||
|
||||
#plugin-report-progress progress::-webkit-progress-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#plugin-report-progress progress::-webkit-progress-value {
|
||||
background-color: #3858e9;
|
||||
}
|
||||
|
||||
#plugin-report-progress progress::-moz-progress-bar {
|
||||
background-color: #3858e9;
|
||||
}
|
||||
|
||||
/* styling for the tablesort script */
|
||||
|
||||
th[role=columnheader]:not(.no-sort) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th[role=columnheader]:not(.no-sort):after {
|
||||
content: '';
|
||||
float: right;
|
||||
margin-top: 7px;
|
||||
border-width: 0 4px 4px;
|
||||
border-style: solid;
|
||||
border-color: #404040 transparent;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
th[aria-sort=ascending]:not(.no-sort):after {
|
||||
border-bottom: none;
|
||||
border-width: 4px 4px 0;
|
||||
}
|
||||
|
||||
th[aria-sort]:not(.no-sort):after {
|
||||
visibility: visible;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
th[role=columnheader]:not(.no-sort):hover:after {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
120
js/plugin-report.js
Normal file
120
js/plugin-report.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
jQuery(document).ready( function( $ ){
|
||||
|
||||
var rtpr_slugs = plugin_report_vars.plugin_slugs;
|
||||
var rtpr_slugs_array = rtpr_slugs.split(',');
|
||||
var rtpr_nrof_plugins = rtpr_slugs_array.length;
|
||||
var rtpr_progress = 0;
|
||||
|
||||
|
||||
function rtpr_process_next_plugin(){
|
||||
if( rtpr_slugs_array.length > 0 ){
|
||||
var slug = rtpr_slugs_array.shift();
|
||||
if( $( '.plugin-report-row-temp-' + slug ).length ){
|
||||
rtpr_get_plugin_info( slug );
|
||||
} else {
|
||||
rtpr_process_next_plugin();
|
||||
}
|
||||
}
|
||||
// update the progress information on the page
|
||||
var perc = Math.ceil( ( rtpr_progress / rtpr_nrof_plugins ) * 100 );
|
||||
if( perc < 100 ){
|
||||
if( ! $( '#plugin-report-progress' ).find( 'progress' ).length ){
|
||||
$( '#plugin-report-progress' ).html( '<progress max="100" value="0"></progress>' );
|
||||
}
|
||||
$( '#plugin-report-progress progress' ).prop( 'value', perc );
|
||||
rtpr_progress++;
|
||||
} else {
|
||||
// Remove the progress bar.
|
||||
$('#plugin-report-progress').html( '' );
|
||||
// initialize sorting on table
|
||||
new Tablesort(document.getElementById('plugin-report-table'));
|
||||
// Create the export button.
|
||||
$('#plugin-report-buttons').append('<button class="button" href="#" id="plugin-report-export-btn">' + plugin_report_vars.export_btn + '</button>');
|
||||
// Export button event handler.
|
||||
$('#plugin-report-export-btn').click( function( e ){
|
||||
// Call the function that does the exporting.
|
||||
rtpr_export_table();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function rtpr_get_plugin_info( slug ){
|
||||
var data = {
|
||||
'action': 'rt_get_plugin_info',
|
||||
'slug': slug,
|
||||
'nonce': plugin_report_vars.ajax_nonce
|
||||
};
|
||||
|
||||
jQuery.post( ajaxurl, data, function(response) {
|
||||
// parse the response
|
||||
obj = jQuery.parseJSON(response);
|
||||
// replace the temporary table row with the new data
|
||||
$('#plugin-report-table .plugin-report-row-temp-' + slug ).replaceWith( obj.html );
|
||||
// on to the next...
|
||||
rtpr_process_next_plugin();
|
||||
});
|
||||
}
|
||||
|
||||
// kick things off
|
||||
rtpr_process_next_plugin();
|
||||
|
||||
|
||||
// Export CSV file.
|
||||
function rtpr_export_table(){
|
||||
var csv_data = '';
|
||||
var counter = 0;
|
||||
// Loop trough the table header to add the header cells.
|
||||
$('#plugin-report-table thead tr').each(function(){
|
||||
// Use a column counter, because we'll need ot insert two extra columns.
|
||||
counter = 0;
|
||||
// Loop through the header cells.
|
||||
$(this).find('th').each(function(){
|
||||
// Remove any comma's from the cell contents, then add to output.
|
||||
csv_data += $(this).text().replace(/,/g, ".") + ',';
|
||||
// If this is the first column, add the plugin url column
|
||||
if( counter == 0 ){
|
||||
csv_data += plugin_report_vars.plugin_url_header + ',';
|
||||
}
|
||||
// If this is the second column, add the author url column
|
||||
if( counter == 1 ){
|
||||
csv_data += plugin_report_vars.author_url_header + ',';
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
// End of the line.
|
||||
csv_data += "\n";
|
||||
});
|
||||
// Loop through the regular rows to get their data
|
||||
$('#plugin-report-table tbody tr').each(function(){
|
||||
// Use a column counter to insert the two columns.
|
||||
counter = 0;
|
||||
// Loop through all regular cells.
|
||||
$(this).find('td').each(function(){
|
||||
// Remove any comma's from the cell contents, then add to output.
|
||||
csv_data += $(this).text().replace(/,/g, ".") + ',';
|
||||
// If this is one of the first two columns, add a url column.
|
||||
if( counter <2 ){
|
||||
var href = '';
|
||||
$(this).find('a').each(function(){
|
||||
// Get the href attribute, but strip any url vars to keep it short.
|
||||
href = $(this).attr('href').split('#')[0].split('?')[0];
|
||||
});
|
||||
// Add to the output.
|
||||
csv_data += href + ',';
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
// End of the line.
|
||||
csv_data += "\n";
|
||||
});
|
||||
// Create a link element, clickit and remove it.
|
||||
var link = document.createElement( 'a' );
|
||||
var now = new Date();
|
||||
link.download = 'plugin-report-' + now.getFullYear() + '-' + String( '0' + (now.getMonth()+1) ).slice(-2) + '-' + String( '0' + now.getDate() ).slice(-2) + '.csv';
|
||||
link.href = URL.createObjectURL( new Blob( [ "\ufeff", csv_data ], {type: 'text/csv; header=present'} ) );
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
});
|
6
js/tablesort.dotsep.min.js
vendored
Normal file
6
js/tablesort.dotsep.min.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* tablesort v5.2.1 (2021-10-30)
|
||||
* http://tristen.ca/tablesort/demo/
|
||||
* Copyright (c) 2021 ; Licensed MIT
|
||||
*/
|
||||
Tablesort.extend("dotsep",function(a){return/^(\d+\.)+\d+$/.test(a)},function(a,b){a=a.split("."),b=b.split(".");for(var c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(a[e],10),d=parseInt(b[e],10),c!==d){if(c>d)return-1;if(c<d)return 1}return 0});
|
6
js/tablesort.min.js
vendored
Normal file
6
js/tablesort.min.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* tablesort v5.2.1 (2021-10-30)
|
||||
* http://tristen.ca/tablesort/demo/
|
||||
* Copyright (c) 2021 ; Licensed MIT
|
||||
*/
|
||||
!function(){function a(b,c){if(!(this instanceof a))return new a(b,c);if(!b||"TABLE"!==b.tagName)throw new Error("Element must be a table");this.init(b,c||{})}var b=[],c=function(a){var b;return window.CustomEvent&&"function"==typeof window.CustomEvent?b=new CustomEvent(a):(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!1,!1,void 0)),b},d=function(a,b){return a.getAttribute(b.sortAttribute||"data-sort")||a.textContent||a.innerText||""},e=function(a,b){return a=a.trim().toLowerCase(),b=b.trim().toLowerCase(),a===b?0:a<b?1:-1},f=function(a,b){return[].slice.call(a).find(function(a){return a.getAttribute("data-sort-column-key")===b})},g=function(a,b){return function(c,d){var e=a(c.td,d.td);return 0===e?b?d.index-c.index:c.index-d.index:e}};a.extend=function(a,c,d){if("function"!=typeof c||"function"!=typeof d)throw new Error("Pattern and sort must be a function");b.push({name:a,pattern:c,sort:d})},a.prototype={init:function(a,b){var c,d,e,f,g=this;if(g.table=a,g.thead=!1,g.options=b,a.rows&&a.rows.length>0)if(a.tHead&&a.tHead.rows.length>0){for(e=0;e<a.tHead.rows.length;e++)if("thead"===a.tHead.rows[e].getAttribute("data-sort-method")){c=a.tHead.rows[e];break}c||(c=a.tHead.rows[a.tHead.rows.length-1]),g.thead=!0}else c=a.rows[0];if(c){var h=function(){g.current&&g.current!==this&&g.current.removeAttribute("aria-sort"),g.current=this,g.sortTable(this)};for(e=0;e<c.cells.length;e++)f=c.cells[e],f.setAttribute("role","columnheader"),"none"!==f.getAttribute("data-sort-method")&&(f.tabindex=0,f.addEventListener("click",h,!1),null!==f.getAttribute("data-sort-default")&&(d=f));d&&(g.current=d,g.sortTable(d))}},sortTable:function(a,h){var i=this,j=a.getAttribute("data-sort-column-key"),k=a.cellIndex,l=e,m="",n=[],o=i.thead?0:1,p=a.getAttribute("data-sort-method"),q=a.getAttribute("aria-sort");if(i.table.dispatchEvent(c("beforeSort")),h||(q="ascending"===q?"descending":"descending"===q?"ascending":i.options.descending?"descending":"ascending",a.setAttribute("aria-sort",q)),!(i.table.rows.length<2)){if(!p){for(var r;n.length<3&&o<i.table.tBodies[0].rows.length;)r=j?f(i.table.tBodies[0].rows[o].cells,j):i.table.tBodies[0].rows[o].cells[k],m=r?d(r,i.options):"",m=m.trim(),m.length>0&&n.push(m),o++;if(!n)return}for(o=0;o<b.length;o++)if(m=b[o],p){if(m.name===p){l=m.sort;break}}else if(n.every(m.pattern)){l=m.sort;break}for(i.col=k,o=0;o<i.table.tBodies.length;o++){var s,t=[],u={},v=0,w=0;if(!(i.table.tBodies[o].rows.length<2)){for(s=0;s<i.table.tBodies[o].rows.length;s++){var r;m=i.table.tBodies[o].rows[s],"none"===m.getAttribute("data-sort-method")?u[v]=m:(r=j?f(m.cells,j):m.cells[i.col],t.push({tr:m,td:r?d(r,i.options):"",index:v})),v++}for("descending"===q?t.sort(g(l,!0)):(t.sort(g(l,!1)),t.reverse()),s=0;s<v;s++)u[s]?(m=u[s],w++):m=t[s-w].tr,i.table.tBodies[o].appendChild(m)}}i.table.dispatchEvent(c("afterSort"))}},refresh:function(){void 0!==this.current&&this.sortTable(this.current,!0)}},"undefined"!=typeof module&&module.exports?module.exports=a:window.Tablesort=a}();
|
6
js/tablesort.number.min.js
vendored
Normal file
6
js/tablesort.number.min.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* tablesort v5.2.1 (2021-10-30)
|
||||
* http://tristen.ca/tablesort/demo/
|
||||
* Copyright (c) 2021 ; Licensed MIT
|
||||
*/
|
||||
!function(){var a=function(a){return a.replace(/[^\-?0-9.]/g,"")},b=function(a,b){return a=parseFloat(a),b=parseFloat(b),a=isNaN(a)?0:a,b=isNaN(b)?0:b,a-b};Tablesort.extend("number",function(a){return a.match(/^[-+]?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/)||a.match(/^[-+]?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/)||a.match(/^[-+]?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/)},function(c,d){return c=a(c),d=a(d),b(d,c)})}();
|
2
languages/multisite-plugin-control-zh_CN.l10n.php
Normal file
2
languages/multisite-plugin-control-zh_CN.l10n.php
Normal file
File diff suppressed because one or more lines are too long
BIN
languages/multisite-plugin-control-zh_CN.mo
Normal file
BIN
languages/multisite-plugin-control-zh_CN.mo
Normal file
Binary file not shown.
469
languages/multisite-plugin-control-zh_CN.po
Normal file
469
languages/multisite-plugin-control-zh_CN.po
Normal file
|
@ -0,0 +1,469 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Multisite Plugin Control\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-15 03:24+0000\n"
|
||||
"PO-Revision-Date: 2025-04-15 03:39+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: 简体中文\n"
|
||||
"Language: zh_CN\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Loco https://localise.biz/\n"
|
||||
"X-Loco-Version: 2.6.11; wp-6.7.2\n"
|
||||
"X-Domain: multisite-plugin-control"
|
||||
|
||||
#: multisite-plugin-control.php:581
|
||||
#, php-format
|
||||
msgid "%s activated on %d sites."
|
||||
msgstr "%s 已在 %d 个站点上启用。"
|
||||
|
||||
#: multisite-plugin-control.php:609
|
||||
#, php-format
|
||||
msgid "%s deactivated on %d sites."
|
||||
msgstr "%s 在 %d 个站点上已停用。"
|
||||
|
||||
#: multisite-plugin-control.php:547
|
||||
#, php-format
|
||||
msgid "%s has been mass activated."
|
||||
msgstr "%s 已被批量启用。"
|
||||
|
||||
#: multisite-plugin-control.php:556
|
||||
#, php-format
|
||||
msgid "%s has been mass deactivated."
|
||||
msgstr "%s 已被批量停用。"
|
||||
|
||||
#: class-plugin-report.php:332
|
||||
#, php-format
|
||||
msgid "(%1$s available, requires PHP %2$s)"
|
||||
msgstr "(%1$s 可用,需要 PHP %2$s)"
|
||||
|
||||
#: class-plugin-report.php:328
|
||||
#, php-format
|
||||
msgid "(%1$s available, requires WP %2$s and PHP %3$s)"
|
||||
msgstr "(%1$s 可用,需要 WP %2$s 和 PHP %3$s)"
|
||||
|
||||
#: class-plugin-report.php:330
|
||||
#, php-format
|
||||
msgid "(%1$s available, requires WP %2$s)"
|
||||
msgstr "(%1$s 可用,需要 WP %2$s)"
|
||||
|
||||
#: class-plugin-report.php:334
|
||||
#, php-format
|
||||
msgid "(%s available)"
|
||||
msgstr "(%s 可用)"
|
||||
|
||||
#: multisite-plugin-control.php:161
|
||||
msgid "Activate All"
|
||||
msgstr "全部启用"
|
||||
|
||||
#: multisite-plugin-control.php:236
|
||||
msgid "Activate or deactivate plugins across all existing blogs."
|
||||
msgstr "在所有现有博客中启用或停用插件。"
|
||||
|
||||
#: class-plugin-report.php:72
|
||||
msgid "Activated"
|
||||
msgstr "启用"
|
||||
|
||||
#: multisite-plugin-control.php:203
|
||||
msgid "Active Sites"
|
||||
msgstr "活跃站点"
|
||||
|
||||
#: multisite-plugin-control.php:96
|
||||
msgid "All Statuses"
|
||||
msgstr "所有状态"
|
||||
|
||||
#: multisite-plugin-control.php:98 multisite-plugin-control.php:146
|
||||
msgid "All Users"
|
||||
msgstr "所有用户"
|
||||
|
||||
#: multisite-plugin-control.php:368
|
||||
msgid "An error occurred while saving."
|
||||
msgstr "保存时发生错误。"
|
||||
|
||||
#: multisite-plugin-control.php:185
|
||||
msgid "Apply"
|
||||
msgstr "应用"
|
||||
|
||||
#: multisite-plugin-control.php:678
|
||||
#, php-format
|
||||
msgid "As a %s Pro Site, you now have access to all our premium plugins!"
|
||||
msgstr "作为 %s 专业网站,您现在可以访问我们所有的高级插件!"
|
||||
|
||||
#: class-plugin-report.php:70 multisite-plugin-control.php:114
|
||||
#: multisite-plugin-control.php:707
|
||||
msgid "Author"
|
||||
msgstr "作者"
|
||||
|
||||
#: class-plugin-report.php:115
|
||||
msgid "Author URL"
|
||||
msgstr "作者网址"
|
||||
|
||||
#: multisite-plugin-control.php:225
|
||||
msgid "Auto Activation"
|
||||
msgstr "自动启用"
|
||||
|
||||
#: multisite-plugin-control.php:99
|
||||
msgid "Auto-Activate"
|
||||
msgstr "自动启用"
|
||||
|
||||
#: multisite-plugin-control.php:147
|
||||
msgid "Auto-Activate (All Users)"
|
||||
msgstr "自动启用(所有用户)"
|
||||
|
||||
#: class-plugin-report.php:74
|
||||
msgid "Auto-update"
|
||||
msgstr "自动更新"
|
||||
|
||||
#: multisite-plugin-control.php:177
|
||||
msgid "Bulk Actions"
|
||||
msgstr "批量操作"
|
||||
|
||||
#: multisite-plugin-control.php:700
|
||||
msgid "Checked plugins will override network settings for this site."
|
||||
msgstr "已选中的插件将覆盖该站点的网络设置。"
|
||||
|
||||
#: multisite-plugin-control.php:230
|
||||
msgid ""
|
||||
"Choose who can activate/deactivate plugins: all users, Pro Sites only, or "
|
||||
"none."
|
||||
msgstr "选择谁可以激活/停用插件:所有用户、仅限专业网站或无。"
|
||||
|
||||
#: class-plugin-report.php:62
|
||||
msgid "Clear cached plugin data and reload"
|
||||
msgstr "清除缓存的插件数据并重新加载"
|
||||
|
||||
#: multisite-plugin-control.php:166
|
||||
msgid "Deactivate All"
|
||||
msgstr "全部停用"
|
||||
|
||||
#: multisite-plugin-control.php:715
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: multisite-plugin-control.php:232
|
||||
msgid "Enable to allow all users to activate/deactivate plugins."
|
||||
msgstr "启用以允许所有用户激活/停用插件。"
|
||||
|
||||
#: class-plugin-report.php:345
|
||||
msgid "Enabled"
|
||||
msgstr "已启用"
|
||||
|
||||
#: class-plugin-report.php:113
|
||||
msgid "Export .csv file"
|
||||
msgstr "导出 .csv 文件"
|
||||
|
||||
#: multisite-plugin-control.php:359
|
||||
msgid "Failed to save settings."
|
||||
msgstr "无法保存设置。"
|
||||
|
||||
#: multisite-plugin-control.php:583 multisite-plugin-control.php:611
|
||||
msgid "Failed to select blogs."
|
||||
msgstr "无法选择博客。"
|
||||
|
||||
#: multisite-plugin-control.php:88
|
||||
msgid "Help"
|
||||
msgstr "帮助"
|
||||
|
||||
#. Author URI of the plugin
|
||||
msgid "https://wpmultisite.com"
|
||||
msgstr "https://wpmultisite.com"
|
||||
|
||||
#. URI of the plugin
|
||||
msgid "https://wpmultisite.com/plugins/multisite-plugin-control"
|
||||
msgstr "https://wpmultisite.com/plugins/multisite-plugin-control"
|
||||
|
||||
#: class-plugin-report.php:73
|
||||
msgid "Installed Version"
|
||||
msgstr "安装版本"
|
||||
|
||||
#: multisite-plugin-control.php:512 multisite-plugin-control.php:542
|
||||
#: multisite-plugin-control.php:551
|
||||
msgid "Invalid nonce."
|
||||
msgstr "无效的随机数。"
|
||||
|
||||
#: multisite-plugin-control.php:265
|
||||
msgid "Last Action Time"
|
||||
msgstr "最后行动时间"
|
||||
|
||||
#: class-plugin-report.php:75
|
||||
msgid "Last Update"
|
||||
msgstr "最后更新"
|
||||
|
||||
#: class-plugin-report.php:89
|
||||
msgid "Loading..."
|
||||
msgstr "加载中..."
|
||||
|
||||
#. Description of the plugin
|
||||
msgid ""
|
||||
"Manage plugin access permissions across your entire multisite network with "
|
||||
"enhanced UI and statistics."
|
||||
msgstr "通过增强的 UI 和统计数据管理整个多站点网络的插件访问权限。"
|
||||
|
||||
#: multisite-plugin-control.php:82
|
||||
msgid "Manage plugin access permissions across your multisite network."
|
||||
msgstr "管理多站点网络中的插件访问权限。"
|
||||
|
||||
#: multisite-plugin-control.php:116
|
||||
msgid "Mass Activate"
|
||||
msgstr "批量激活"
|
||||
|
||||
#: multisite-plugin-control.php:235
|
||||
msgid "Mass Activation/Deactivation"
|
||||
msgstr "批量激活/停用"
|
||||
|
||||
#: multisite-plugin-control.php:117
|
||||
msgid "Mass Deactivate"
|
||||
msgstr "批量停用"
|
||||
|
||||
#: multisite-plugin-control.php:247
|
||||
msgid "Metric"
|
||||
msgstr "参数"
|
||||
|
||||
#. Name of the plugin
|
||||
#: multisite-plugin-control.php:77
|
||||
msgid "Multisite Plugin Control"
|
||||
msgstr "多站点插件控制"
|
||||
|
||||
#: class-plugin-report.php:69 multisite-plugin-control.php:112
|
||||
#: multisite-plugin-control.php:705
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
#: class-plugin-report.php:308
|
||||
msgid "Network activated"
|
||||
msgstr "网络已激活"
|
||||
|
||||
#: multisite-plugin-control.php:563
|
||||
msgid "Network too large for mass activation."
|
||||
msgstr "网络太大,无法大规模激活。"
|
||||
|
||||
#: multisite-plugin-control.php:591
|
||||
msgid "Network too large for mass deactivation."
|
||||
msgstr "网络太大,无法批量停用。"
|
||||
|
||||
#: multisite-plugin-control.php:487
|
||||
msgid "Network too large to calculate usage stats."
|
||||
msgstr "网络太大,无法计算使用情况统计数据。"
|
||||
|
||||
#: multisite-plugin-control.php:266
|
||||
msgid "Never"
|
||||
msgstr "从未"
|
||||
|
||||
#: class-plugin-report.php:315
|
||||
msgid "No"
|
||||
msgstr "否"
|
||||
|
||||
#: class-plugin-report.php:366 class-plugin-report.php:382
|
||||
msgid "No data available"
|
||||
msgstr "无可用数据"
|
||||
|
||||
#: class-plugin-report.php:156 class-plugin-report.php:264
|
||||
msgid "No plugin data available."
|
||||
msgstr "没有可用的插件数据。"
|
||||
|
||||
#: multisite-plugin-control.php:97 multisite-plugin-control.php:145
|
||||
msgid "None"
|
||||
msgstr "无选项"
|
||||
|
||||
#: class-plugin-report.php:347
|
||||
msgid "Not enabled"
|
||||
msgstr "未启用"
|
||||
|
||||
#: class-plugin-report.php:298
|
||||
msgid "Only available in WP 5.8+"
|
||||
msgstr "仅适用于 WP 5.8+"
|
||||
|
||||
#: multisite-plugin-control.php:408
|
||||
msgid "Operation failed."
|
||||
msgstr "操作失败。"
|
||||
|
||||
#: multisite-plugin-control.php:513 multisite-plugin-control.php:543
|
||||
#: multisite-plugin-control.php:552
|
||||
msgid "Permission denied."
|
||||
msgstr "无权限。"
|
||||
|
||||
#: class-plugin-report.php:302
|
||||
msgid "Please clear cache to update"
|
||||
msgstr "请清除缓存以进行更新"
|
||||
|
||||
#: multisite-plugin-control.php:308
|
||||
msgid "Please select at least one plugin."
|
||||
msgstr "请至少选择一个插件。"
|
||||
|
||||
#: multisite-plugin-control.php:61 multisite-plugin-control.php:62
|
||||
msgid "Plugin Control"
|
||||
msgstr "插件控制"
|
||||
|
||||
#: multisite-plugin-control.php:80
|
||||
msgid "Plugin Dashboard"
|
||||
msgstr "插件仪表板"
|
||||
|
||||
#: multisite-plugin-control.php:85
|
||||
msgid "Plugin Management"
|
||||
msgstr "插件管理"
|
||||
|
||||
#: multisite-plugin-control.php:202
|
||||
msgid "Plugin Name"
|
||||
msgstr "插件名称"
|
||||
|
||||
#: multisite-plugin-control.php:699
|
||||
msgid "Plugin Override Options"
|
||||
msgstr "插件覆盖选项"
|
||||
|
||||
#: class-plugin-report.php:48 multisite-plugin-control.php:87
|
||||
msgid "Plugin Report"
|
||||
msgstr "插件报告"
|
||||
|
||||
#: class-plugin-report.php:114
|
||||
msgid "Plugin URL"
|
||||
msgstr "插件网址"
|
||||
|
||||
#: multisite-plugin-control.php:86
|
||||
msgid "Plugin Usage"
|
||||
msgstr "插件用量"
|
||||
|
||||
#: multisite-plugin-control.php:101 multisite-plugin-control.php:150
|
||||
msgid "Pro Sites"
|
||||
msgstr "专业网站"
|
||||
|
||||
#: multisite-plugin-control.php:664
|
||||
msgid "Pro Sites Only"
|
||||
msgstr "仅限专业网站"
|
||||
|
||||
#: multisite-plugin-control.php:385
|
||||
msgid "Processing..."
|
||||
msgstr "加载中..."
|
||||
|
||||
#: class-plugin-report.php:77
|
||||
msgid "Rating"
|
||||
msgstr "评级"
|
||||
|
||||
#: class-plugin-report.php:71
|
||||
msgid "Repository"
|
||||
msgstr "存储库"
|
||||
|
||||
#: class-plugin-report.php:342
|
||||
msgid "Requires WordPress 5.5 or higher"
|
||||
msgstr "需要 WordPress 5.5 或更高版本"
|
||||
|
||||
#: class-plugin-report.php:53
|
||||
#, php-format
|
||||
msgid "Running WordPress %1$s and PHP %2$s."
|
||||
msgstr "运行 WordPress %1$s 和 PHP %2$s。"
|
||||
|
||||
#: multisite-plugin-control.php:186
|
||||
msgid "Save Settings"
|
||||
msgstr "保存设置"
|
||||
|
||||
#: multisite-plugin-control.php:332
|
||||
msgid "Saving..."
|
||||
msgstr "保存..."
|
||||
|
||||
#: multisite-plugin-control.php:94
|
||||
msgid "Search plugins by name..."
|
||||
msgstr "按名称搜索插件..."
|
||||
|
||||
#: multisite-plugin-control.php:417
|
||||
msgid "Server error occurred."
|
||||
msgstr "发生服务器错误。"
|
||||
|
||||
#: multisite-plugin-control.php:179
|
||||
msgid "Set to All Users"
|
||||
msgstr "设置为所有用户"
|
||||
|
||||
#: multisite-plugin-control.php:180
|
||||
msgid "Set to Auto-Activate"
|
||||
msgstr "设置为自动激活"
|
||||
|
||||
#: multisite-plugin-control.php:178
|
||||
msgid "Set to None"
|
||||
msgstr "设置为无"
|
||||
|
||||
#: multisite-plugin-control.php:182
|
||||
msgid "Set to Pro Sites"
|
||||
msgstr "设置为专业网站"
|
||||
|
||||
#: multisite-plugin-control.php:347 multisite-plugin-control.php:538
|
||||
msgid "Settings saved successfully!"
|
||||
msgstr "设置保存成功!"
|
||||
|
||||
#: multisite-plugin-control.php:242
|
||||
msgid "Statistics"
|
||||
msgstr "统计数据"
|
||||
|
||||
#: class-plugin-report.php:76
|
||||
msgid "Tested WP Version"
|
||||
msgstr "已测试WP版本"
|
||||
|
||||
#: multisite-plugin-control.php:257
|
||||
msgid "Total Activations"
|
||||
msgstr "总启用量"
|
||||
|
||||
#: multisite-plugin-control.php:261
|
||||
msgid "Total Deactivations"
|
||||
msgstr "总停用量"
|
||||
|
||||
#: multisite-plugin-control.php:253
|
||||
msgid "Total Sites"
|
||||
msgstr "网站总数"
|
||||
|
||||
#: class-plugin-report.php:294
|
||||
msgid "Updates disabled"
|
||||
msgstr "已禁用更新"
|
||||
|
||||
#: class-plugin-report.php:55
|
||||
#, php-format
|
||||
msgid "Upgrade to %s available"
|
||||
msgstr "可升级至 %s"
|
||||
|
||||
#: multisite-plugin-control.php:115 multisite-plugin-control.php:227
|
||||
#: multisite-plugin-control.php:704
|
||||
msgid "User Control"
|
||||
msgstr "用户控制"
|
||||
|
||||
#: multisite-plugin-control.php:248
|
||||
msgid "Value"
|
||||
msgstr "数值"
|
||||
|
||||
#: multisite-plugin-control.php:113 multisite-plugin-control.php:706
|
||||
msgid "Version"
|
||||
msgstr "版本"
|
||||
|
||||
#: class-plugin-report.php:49
|
||||
msgid "View detailed information about installed plugins across the network."
|
||||
msgstr "查看整个网络上已安装插件的详细信息。"
|
||||
|
||||
#: multisite-plugin-control.php:243
|
||||
msgid "View plugin control activity across the network."
|
||||
msgstr "查看整个网络上的插件控制活动。"
|
||||
|
||||
#: multisite-plugin-control.php:192
|
||||
msgid "View plugin usage across the network."
|
||||
msgstr "查看整个网络上的插件使用情况。"
|
||||
|
||||
#: multisite-plugin-control.php:226
|
||||
msgid "When enabled, new blogs will have the plugin activated automatically."
|
||||
msgstr "启用后,新博客将自动激活该插件。"
|
||||
|
||||
#: class-plugin-report.php:283
|
||||
msgid "wp.org/wenpai.org, plugin closed"
|
||||
msgstr "wp.org/wenpai.org,插件已关闭"
|
||||
|
||||
#: class-plugin-report.php:285
|
||||
msgid "wp.org/wenpai.org, plugin not found"
|
||||
msgstr "wp.org/wenpai.org,插件未找到"
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "WPMultisite.com"
|
||||
msgstr "文派多站点"
|
||||
|
||||
#: class-plugin-report.php:315
|
||||
msgid "Yes"
|
||||
msgstr "是"
|
||||
|
||||
#: class-plugin-report.php:30 multisite-plugin-control.php:71
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "您没有足够的权限访问此页面。"
|
742
multisite-plugin-control.php
Normal file
742
multisite-plugin-control.php
Normal file
|
@ -0,0 +1,742 @@
|
|||
<?php
|
||||
/*
|
||||
* Plugin Name: Multisite Plugin Control
|
||||
* Plugin URI: https://wpmultisite.com/plugins/multisite-plugin-control
|
||||
* Description: Manage plugin access permissions across your entire WordPress multisite network with enhanced UI and statistics.
|
||||
* Version: 1.9.1
|
||||
* Author: WPMultisite.com
|
||||
* Author URI: https://wpmultisite.com
|
||||
* Network: true
|
||||
* Requires at least: 6.7.2
|
||||
* License: GPL v2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: multisite-plugin-control
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
if (!defined('WPINC')) {
|
||||
die;
|
||||
}
|
||||
|
||||
define('MULTISITE_PLUGIN_CONTROL_VERSION', '1.9.1');
|
||||
define('MULTISITE_PLUGIN_CONTROL_PATH', plugin_dir_path(__FILE__));
|
||||
define('MULTISITE_PLUGIN_CONTROL_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
|
||||
require_once plugin_dir_path(__FILE__) . 'class-plugin-report.php';
|
||||
|
||||
class MultisitePluginControl {
|
||||
private $stats;
|
||||
private $plugin_report;
|
||||
|
||||
public function __construct() {
|
||||
$this->stats = get_site_option('mpc_stats', [
|
||||
'total_sites' => 0,
|
||||
'total_activations' => 0,
|
||||
'total_deactivations' => 0,
|
||||
'last_action_time' => null
|
||||
]);
|
||||
$this->plugin_report = new RT_Plugin_Report($this);
|
||||
$this->plugin_report->init();
|
||||
|
||||
add_action('network_admin_menu', [$this, 'add_menu']);
|
||||
add_action('wpmu_new_blog', [$this, 'new_blog'], 50);
|
||||
if (!(defined('WP_CLI') && WP_CLI)) {
|
||||
add_filter('all_plugins', [$this, 'remove_plugins']);
|
||||
}
|
||||
add_filter('plugin_action_links', [$this, 'action_links'], 10, 4);
|
||||
add_action('admin_notices', [$this, 'supporter_message']);
|
||||
add_action('plugins_loaded', [$this, 'localization']);
|
||||
add_action('wpmueditblogaction', [$this, 'blog_options_form']);
|
||||
add_action('wpmu_update_blog_options', [$this, 'blog_options_form_process']);
|
||||
add_filter('plugin_row_meta', [$this, 'remove_plugin_meta'], 10, 2);
|
||||
add_action('admin_init', [$this, 'remove_plugin_update_row']);
|
||||
add_action('wp_ajax_mpc_save_settings', [$this, 'ajax_save_settings']);
|
||||
add_action('wp_ajax_mpc_mass_activate', [$this, 'ajax_mass_activate']);
|
||||
add_action('wp_ajax_mpc_mass_deactivate', [$this, 'ajax_mass_deactivate']);
|
||||
}
|
||||
|
||||
public function localization() {
|
||||
load_plugin_textdomain('multisite-plugin-control', false, dirname(plugin_basename(__FILE__)) . '/languages/');
|
||||
}
|
||||
|
||||
public function add_menu() {
|
||||
add_submenu_page(
|
||||
'plugins.php',
|
||||
__('Plugin Control', 'multisite-plugin-control'),
|
||||
__('Plugin Control', 'multisite-plugin-control'),
|
||||
'manage_network_options',
|
||||
'multisite-plugin-control',
|
||||
[$this, 'admin_page']
|
||||
);
|
||||
}
|
||||
|
||||
public function admin_page() {
|
||||
if (!current_user_can('manage_network_options')) {
|
||||
wp_die(__('You do not have sufficient permissions to access this page.', 'multisite-plugin-control'));
|
||||
}
|
||||
|
||||
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'management';
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html__('Multisite Plugin Control', 'multisite-plugin-control'); ?>
|
||||
<span style="font-size: 13px; padding-left: 10px;"><?php printf(esc_html__('Version: %s', 'multisite-plugin-control'), esc_html(MULTISITE_PLUGIN_CONTROL_VERSION)); ?></span>
|
||||
<a href="https://wpmultisite.com/document/multisite-plugin-control" target="_blank" class="button button-secondary" style="margin-left: 10px;"><?php esc_html_e('Document', 'multisite-plugin-control'); ?></a>
|
||||
<a href="https://wpmultisite.com/forums/" target="_blank" class="button button-secondary"><?php esc_html_e('Support', 'multisite-plugin-control'); ?></a>
|
||||
</h1>
|
||||
<div class="card">
|
||||
<h2><?php _e('Plugin Dashboard', 'multisite-plugin-control'); ?></h2>
|
||||
<span id="save-status" class="notice" style="display:none; margin-top: 10px;"></span>
|
||||
<p><?php _e('Manage plugin access permissions across your multisite network.', 'multisite-plugin-control'); ?></p>
|
||||
|
||||
<div class="styles-sync-tabs">
|
||||
<button type="button" class="styles-tab <?php echo $active_tab === 'management' ? 'active' : ''; ?>" data-tab="management"><?php _e('Plugin Management', 'multisite-plugin-control'); ?></button>
|
||||
<button type="button" class="styles-tab <?php echo $active_tab === 'usage' ? 'active' : ''; ?>" data-tab="usage"><?php _e('Plugin Usage', 'multisite-plugin-control'); ?></button>
|
||||
<button type="button" class="styles-tab <?php echo $active_tab === 'report' ? 'active' : ''; ?>" data-tab="report"><?php _e('Plugin Report', 'multisite-plugin-control'); ?></button>
|
||||
<button type="button" class="styles-tab <?php echo $active_tab === 'help' ? 'active' : ''; ?>" data-tab="help"><?php _e('Help', 'multisite-plugin-control'); ?></button>
|
||||
</div>
|
||||
|
||||
<div class="styles-sync-content">
|
||||
<div class="tab-section" data-section="management" style="<?php echo $active_tab !== 'management' ? 'display: none;' : ''; ?>">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<input type="text" id="plugin-search" placeholder="<?php _e('Search plugins by name...', 'multisite-plugin-control'); ?>" style="width: 300px;">
|
||||
<select id="plugin-filter" style="margin-left: 10px;">
|
||||
<option value="all"><?php _e('All Statuses', 'multisite-plugin-control'); ?></option>
|
||||
<option value="none"><?php _e('None', 'multisite-plugin-control'); ?></option>
|
||||
<option value="all"><?php _e('All Users', 'multisite-plugin-control'); ?></option>
|
||||
<option value="auto"><?php _e('Auto-Activate', 'multisite-plugin-control'); ?></option>
|
||||
<?php if (function_exists('is_pro_site')): ?>
|
||||
<option value="supporters"><?php _e('Pro Sites', 'multisite-plugin-control'); ?></option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<form id="plugin-control-form" method="post">
|
||||
<?php wp_nonce_field('multisite_plugin_control_nonce', 'control_nonce'); ?>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="select-all-plugins"></th>
|
||||
<th><?php _e('Name', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Version', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Author', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('User Control', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Mass Activate', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Mass Deactivate', 'multisite-plugin-control'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$plugins = get_plugins();
|
||||
$auto_activate = (array) get_site_option('mpc_auto_activate_list', []);
|
||||
$user_control = (array) get_site_option('mpc_user_control_list', []);
|
||||
$supporter_control = (array) get_site_option('mpc_supporter_control_list', []);
|
||||
|
||||
foreach ($plugins as $file => $p) {
|
||||
if (is_network_only_plugin($file) || is_plugin_active_for_network($file)) {
|
||||
continue;
|
||||
}
|
||||
$selected = 'none';
|
||||
if (in_array($file, $user_control)) $selected = 'all';
|
||||
elseif (in_array($file, $auto_activate)) $selected = 'auto';
|
||||
elseif (in_array($file, $supporter_control)) $selected = 'supporters';
|
||||
?>
|
||||
<tr data-status="<?php echo esc_attr($selected); ?>">
|
||||
<td><input type="checkbox" name="selected_plugins[]" value="<?php echo esc_attr($file); ?>" class="plugin-checkbox"></td>
|
||||
<td><?php echo esc_html($p['Name']); ?></td>
|
||||
<td><?php echo esc_html($p['Version']); ?></td>
|
||||
<td><?php echo wp_kses_post($p['Author']); ?></td>
|
||||
<td>
|
||||
<select name="control[<?php echo esc_attr($file); ?>]" class="control-select">
|
||||
<?php
|
||||
$options = [
|
||||
'none' => __('None', 'multisite-plugin-control'),
|
||||
'all' => __('All Users', 'multisite-plugin-control'),
|
||||
'auto' => __('Auto-Activate (All Users)', 'multisite-plugin-control'),
|
||||
];
|
||||
if (function_exists('is_pro_site')) {
|
||||
$options['supporters'] = __('Pro Sites', 'multisite-plugin-control');
|
||||
}
|
||||
foreach ($options as $value => $label) {
|
||||
$is_selected = ($selected === $value) ? ' selected="selected"' : '';
|
||||
echo '<option value="' . esc_attr($value) . '"' . $is_selected . '>' . esc_html($label) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="button mass-activate" data-plugin="<?php echo esc_attr($file); ?>">
|
||||
<?php _e('Activate All', 'multisite-plugin-control'); ?>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="button mass-deactivate" data-plugin="<?php echo esc_attr($file); ?>">
|
||||
<?php _e('Deactivate All', 'multisite-plugin-control'); ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="submit">
|
||||
<select id="bulk-action" style="margin-right: 10px;">
|
||||
<option value=""><?php _e('Bulk Actions', 'multisite-plugin-control'); ?></option>
|
||||
<option value="set-none"><?php _e('Set to None', 'multisite-plugin-control'); ?></option>
|
||||
<option value="set-all"><?php _e('Set to All Users', 'multisite-plugin-control'); ?></option>
|
||||
<option value="set-auto"><?php _e('Set to Auto-Activate', 'multisite-plugin-control'); ?></option>
|
||||
<?php if (function_exists('is_pro_site')): ?>
|
||||
<option value="set-supporters"><?php _e('Set to Pro Sites', 'multisite-plugin-control'); ?></option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<button type="button" id="apply-bulk" class="button"><?php _e('Apply', 'multisite-plugin-control'); ?></button>
|
||||
<button type="button" id="save-settings" class="button button-primary"><?php _e('Save Settings', 'multisite-plugin-control'); ?></button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-section" data-section="usage" style="<?php echo $active_tab !== 'usage' ? 'display: none;' : ''; ?>">
|
||||
<p><?php _e('View plugin usage across the network.', 'multisite-plugin-control'); ?></p>
|
||||
<?php
|
||||
$usage_stats = $this->get_plugin_usage_stats();
|
||||
if (isset($usage_stats['error'])) {
|
||||
echo '<p class="description">' . esc_html($usage_stats['error']) . '</p>';
|
||||
} else {
|
||||
?>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Plugin Name', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Active Sites', 'multisite-plugin-control'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($usage_stats as $stats): ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($stats['name']); ?></td>
|
||||
<td><?php echo esc_html($stats['active_sites']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="tab-section" data-section="report" style="<?php echo $active_tab !== 'report' ? 'display: none;' : ''; ?>">
|
||||
<?php $this->plugin_report->render_report(); ?>
|
||||
</div>
|
||||
|
||||
<div class="tab-section" data-section="help" style="<?php echo $active_tab !== 'help' ? 'display: none;' : ''; ?>">
|
||||
<p><strong><?php _e('Auto Activation', 'multisite-plugin-control'); ?></strong><br>
|
||||
<?php _e('When enabled, new blogs will have the plugin activated automatically.', 'multisite-plugin-control'); ?></p>
|
||||
<p><strong><?php _e('User Control', 'multisite-plugin-control'); ?></strong><br>
|
||||
<?php
|
||||
if (function_exists('is_pro_site')) {
|
||||
_e('Choose who can activate/deactivate plugins: all users, Pro Sites only, or none.', 'multisite-plugin-control');
|
||||
} else {
|
||||
_e('Enable to allow all users to activate/deactivate plugins.', 'multisite-plugin-control');
|
||||
}
|
||||
?></p>
|
||||
<p><strong><?php _e('Mass Activation/Deactivation', 'multisite-plugin-control'); ?></strong><br>
|
||||
<?php _e('Activate or deactivate plugins across all existing blogs.', 'multisite-plugin-control'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2><?php _e('Statistics', 'multisite-plugin-control'); ?></h2>
|
||||
<p><?php _e('View plugin control activity across the network.', 'multisite-plugin-control'); ?></p>
|
||||
<table class="wp-list-table widefat fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Metric', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Value', 'multisite-plugin-control'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><?php _e('Total Sites', 'multisite-plugin-control'); ?></th>
|
||||
<td><?php echo esc_html($this->stats['total_sites']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Total Activations', 'multisite-plugin-control'); ?></th>
|
||||
<td><?php echo esc_html($this->stats['total_activations']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Total Deactivations', 'multisite-plugin-control'); ?></th>
|
||||
<td><?php echo esc_html($this->stats['total_deactivations']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Last Action Time', 'multisite-plugin-control'); ?></th>
|
||||
<td><?php echo esc_html($this->stats['last_action_time'] ?: __('Never', 'multisite-plugin-control')); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
$('.styles-tab').on('click', function() {
|
||||
var tab = $(this).data('tab');
|
||||
window.history.pushState({}, document.title, '<?php echo admin_url('network/plugins.php?page=multisite-plugin-control'); ?>&tab=' + tab);
|
||||
$('.styles-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$('.tab-section').hide();
|
||||
$('.tab-section[data-section="' + tab + '"]').show();
|
||||
});
|
||||
|
||||
function filterPlugins() {
|
||||
var search = $('#plugin-search').val().toLowerCase();
|
||||
var filter = $('#plugin-filter').val();
|
||||
$('.wp-list-table tbody tr').each(function() {
|
||||
var name = $(this).find('td:eq(1)').text().toLowerCase();
|
||||
var status = $(this).data('status');
|
||||
var matchesSearch = search === '' || name.includes(search);
|
||||
var matchesFilter = filter === 'all' || status === filter;
|
||||
$(this).toggle(matchesSearch && matchesFilter);
|
||||
});
|
||||
}
|
||||
$('#plugin-search').on('input', filterPlugins);
|
||||
$('#plugin-filter').on('change', filterPlugins);
|
||||
|
||||
$('#select-all-plugins').on('change', function() {
|
||||
$('.plugin-checkbox').prop('checked', $(this).prop('checked'));
|
||||
});
|
||||
|
||||
$('#apply-bulk').on('click', function() {
|
||||
var action = $('#bulk-action').val();
|
||||
if (!action) return;
|
||||
var selected = $('.plugin-checkbox:checked');
|
||||
if (selected.length === 0) {
|
||||
alert('<?php _e('Please select at least one plugin.', 'multisite-plugin-control'); ?>');
|
||||
return;
|
||||
}
|
||||
selected.each(function() {
|
||||
var plugin = $(this).val();
|
||||
$('select[name="control[' + plugin + ']"]').val(action.replace('set-', ''));
|
||||
});
|
||||
});
|
||||
|
||||
$('#save-settings').on('click', function() {
|
||||
var $button = $(this);
|
||||
var formData = $('#plugin-control-form').serializeArray();
|
||||
var settings = { control: {}, nonce: '' };
|
||||
|
||||
formData.forEach(function(item) {
|
||||
if (item.name.startsWith('control[')) {
|
||||
var plugin = item.name.replace('control[', '').replace(']', '');
|
||||
settings.control[plugin] = item.value;
|
||||
} else if (item.name === 'control_nonce') {
|
||||
settings.nonce = item.value;
|
||||
}
|
||||
});
|
||||
|
||||
$button.prop('disabled', true);
|
||||
$('#save-status').text('<?php _e('Saving...', 'multisite-plugin-control'); ?>').show();
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'mpc_save_settings',
|
||||
settings: settings,
|
||||
_ajax_nonce: settings.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$('#save-status')
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice-success')
|
||||
.text('<?php _e('Settings saved successfully!', 'multisite-plugin-control'); ?>')
|
||||
.show()
|
||||
.delay(3000)
|
||||
.fadeOut();
|
||||
$('.wp-list-table tbody tr').each(function() {
|
||||
var plugin = $(this).find('.plugin-checkbox').val();
|
||||
$(this).data('status', settings.control[plugin]);
|
||||
});
|
||||
} else {
|
||||
$('#save-status')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice-error')
|
||||
.text(response.data || '<?php _e('Failed to save settings.', 'multisite-plugin-control'); ?>')
|
||||
.show();
|
||||
}
|
||||
$button.prop('disabled', false);
|
||||
},
|
||||
error: function() {
|
||||
$('#save-status')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice-error')
|
||||
.text('<?php _e('An error occurred while saving.', 'multisite-plugin-control'); ?>')
|
||||
.show();
|
||||
$button.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.mass-activate, .mass-deactivate').on('click', function() {
|
||||
var $button = $(this);
|
||||
var plugin = $button.data('plugin');
|
||||
var actionType = $button.hasClass('mass-activate') ? 'mass_activate' : 'mass_deactivate';
|
||||
|
||||
if (!confirm('<?php _e('Are you sure you want to '); ?>' + (actionType === 'mass_activate' ? '<?php _e('activate'); ?>' : '<?php _e('deactivate'); ?>') + ' <?php _e('this plugin across all sites?'); ?>')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button.prop('disabled', true);
|
||||
$('#save-status').text('<?php _e('Processing...', 'multisite-plugin-control'); ?>').show();
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'mpc_' + actionType,
|
||||
plugin: plugin,
|
||||
_ajax_nonce: '<?php echo wp_create_nonce('multisite_plugin_control_nonce'); ?>'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$('#save-status')
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice-success')
|
||||
.text(response.data.message)
|
||||
.show()
|
||||
.delay(3000)
|
||||
.fadeOut();
|
||||
} else {
|
||||
$('#save-status')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice-error')
|
||||
.text(response.data || '<?php _e('Operation failed.', 'multisite-plugin-control'); ?>')
|
||||
.show();
|
||||
}
|
||||
$button.prop('disabled', false);
|
||||
},
|
||||
error: function() {
|
||||
$('#save-status')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice-error')
|
||||
.text('<?php _e('Server error occurred.', 'multisite-plugin-control'); ?>')
|
||||
.show();
|
||||
$button.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
max-width: unset;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
.notice {
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.notice-success {
|
||||
background-color: #dff0d8;
|
||||
border-left: 4px solid #46b450;
|
||||
}
|
||||
.notice-error {
|
||||
background-color: #f2dede;
|
||||
border-left: 4px solid #dc3232;
|
||||
}
|
||||
.styles-sync-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.styles-tab {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.styles-tab.active {
|
||||
border-bottom: 2px solid #007cba;
|
||||
font-weight: 600;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
.styles-tab:hover:not(.active) {
|
||||
background: #f0f0f1;
|
||||
border-bottom-color: #dcdcde;
|
||||
}
|
||||
.styles-sync-content {
|
||||
flex: 1;
|
||||
}
|
||||
table.fixed {
|
||||
table-layout: auto;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function get_plugin_usage_stats() {
|
||||
global $wpdb;
|
||||
$plugins = get_plugins();
|
||||
$usage_stats = [];
|
||||
|
||||
if (wp_is_large_network()) {
|
||||
return ['error' => __('Network too large to calculate usage stats.', 'multisite-plugin-control')];
|
||||
}
|
||||
|
||||
$blogs = $wpdb->get_col($wpdb->prepare("SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = %d AND spam = 0", $wpdb->siteid));
|
||||
foreach ($plugins as $file => $p) {
|
||||
if (is_network_only_plugin($file) || is_plugin_active_for_network($file)) {
|
||||
continue;
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($blogs as $blog_id) {
|
||||
switch_to_blog($blog_id);
|
||||
if (is_plugin_active($file)) {
|
||||
$count++;
|
||||
}
|
||||
restore_current_blog();
|
||||
}
|
||||
$usage_stats[$file] = [
|
||||
'name' => $p['Name'],
|
||||
'active_sites' => $count
|
||||
];
|
||||
}
|
||||
return $usage_stats;
|
||||
}
|
||||
|
||||
public function ajax_save_settings() {
|
||||
check_ajax_referer('multisite_plugin_control_nonce', '_ajax_nonce') || wp_send_json_error(__('Invalid nonce.', 'multisite-plugin-control'));
|
||||
if (!current_user_can('manage_network_options')) wp_send_json_error(__('Permission denied.', 'multisite-plugin-control'));
|
||||
|
||||
$settings = isset($_POST['settings']['control']) ? (array)$_POST['settings']['control'] : [];
|
||||
$supporter_control = [];
|
||||
$user_control = [];
|
||||
$auto_activate = [];
|
||||
|
||||
foreach ($settings as $plugin => $value) {
|
||||
switch ($value) {
|
||||
case 'supporters':
|
||||
$supporter_control[] = sanitize_text_field($plugin);
|
||||
break;
|
||||
case 'all':
|
||||
$user_control[] = sanitize_text_field($plugin);
|
||||
break;
|
||||
case 'auto':
|
||||
$auto_activate[] = sanitize_text_field($plugin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('mpc_supporter_control_list', $supporter_control ?: 'EMPTY');
|
||||
update_site_option('mpc_user_control_list', $user_control ?: 'EMPTY');
|
||||
update_site_option('mpc_auto_activate_list', $auto_activate ?: 'EMPTY');
|
||||
|
||||
wp_send_json_success(['message' => __('Settings saved successfully!', 'multisite-plugin-control')]);
|
||||
}
|
||||
|
||||
public function ajax_mass_activate() {
|
||||
check_ajax_referer('multisite_plugin_control_nonce', '_ajax_nonce') || wp_send_json_error(__('Invalid nonce.', 'multisite-plugin-control'));
|
||||
if (!current_user_can('manage_network_options')) wp_send_json_error(__('Permission denied.', 'multisite-plugin-control'));
|
||||
|
||||
$plugin = sanitize_text_field($_POST['plugin']);
|
||||
$this->mass_activate($plugin);
|
||||
wp_send_json_success(['message' => sprintf(__('%s has been mass activated.', 'multisite-plugin-control'), $plugin)]);
|
||||
}
|
||||
|
||||
public function ajax_mass_deactivate() {
|
||||
check_ajax_referer('multisite_plugin_control_nonce', '_ajax_nonce') || wp_send_json_error(__('Invalid nonce.', 'multisite-plugin-control'));
|
||||
if (!current_user_can('manage_network_options')) wp_send_json_error(__('Permission denied.', 'multisite-plugin-control'));
|
||||
|
||||
$plugin = sanitize_text_field($_POST['plugin']);
|
||||
$this->mass_deactivate($plugin);
|
||||
wp_send_json_success(['message' => sprintf(__('%s has been mass deactivated.', 'multisite-plugin-control'), $plugin)]);
|
||||
}
|
||||
|
||||
public function mass_activate($plugin) {
|
||||
global $wpdb;
|
||||
|
||||
if (wp_is_large_network()) {
|
||||
add_settings_error('mpc_messages', 'mass_activate_fail', __('Network too large for mass activation.', 'multisite-plugin-control'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
set_time_limit(120);
|
||||
$blogs = $wpdb->get_col($wpdb->prepare("SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = %d AND spam = 0", $wpdb->siteid));
|
||||
$count = 0;
|
||||
|
||||
if ($blogs) {
|
||||
foreach ($blogs as $blog_id) {
|
||||
switch_to_blog($blog_id);
|
||||
if (!is_plugin_active($plugin)) {
|
||||
activate_plugin($plugin, '', false);
|
||||
$count++;
|
||||
}
|
||||
restore_current_blog();
|
||||
}
|
||||
$this->update_stats('activate', count($blogs), $count);
|
||||
add_settings_error('mpc_messages', 'mass_activate_success', sprintf(__('%s activated on %d sites.', 'multisite-plugin-control'), esc_html($plugin), $count), 'success');
|
||||
} else {
|
||||
add_settings_error('mpc_messages', 'mass_activate_fail', __('Failed to select blogs.', 'multisite-plugin-control'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function mass_deactivate($plugin) {
|
||||
global $wpdb;
|
||||
|
||||
if (wp_is_large_network()) {
|
||||
add_settings_error('mpc_messages', 'mass_deactivate_fail', __('Network too large for mass deactivation.', 'multisite-plugin-control'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
set_time_limit(120);
|
||||
$blogs = $wpdb->get_col($wpdb->prepare("SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = %d AND spam = 0", $wpdb->siteid));
|
||||
$count = 0;
|
||||
|
||||
if ($blogs) {
|
||||
foreach ($blogs as $blog_id) {
|
||||
switch_to_blog($blog_id);
|
||||
if (is_plugin_active($plugin)) {
|
||||
deactivate_plugins($plugin, true);
|
||||
$count++;
|
||||
}
|
||||
restore_current_blog();
|
||||
}
|
||||
$this->update_stats('deactivate', count($blogs), $count);
|
||||
add_settings_error('mpc_messages', 'mass_deactivate_success', sprintf(__('%s deactivated on %d sites.', 'multisite-plugin-control'), esc_html($plugin), $count), 'success');
|
||||
} else {
|
||||
add_settings_error('mpc_messages', 'mass_deactivate_fail', __('Failed to select blogs.', 'multisite-plugin-control'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
private function update_stats($action, $total_sites, $changed) {
|
||||
$this->stats['total_sites'] = $total_sites;
|
||||
if ($action === 'activate') {
|
||||
$this->stats['total_activations'] += $changed;
|
||||
} else {
|
||||
$this->stats['total_deactivations'] += $changed;
|
||||
}
|
||||
$this->stats['last_action_time'] = current_time('mysql');
|
||||
update_site_option('mpc_stats', $this->stats);
|
||||
}
|
||||
|
||||
public function new_blog($blog_id) {
|
||||
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
$auto_activate = (array) get_site_option('mpc_auto_activate_list', []);
|
||||
if ($auto_activate && $auto_activate[0] !== 'EMPTY') {
|
||||
switch_to_blog($blog_id);
|
||||
activate_plugins($auto_activate, '', false);
|
||||
restore_current_blog();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove_plugins($all_plugins) {
|
||||
if (is_super_admin()) return $all_plugins;
|
||||
$auto_activate = (array) get_site_option('mpc_auto_activate_list', []);
|
||||
$user_control = (array) get_site_option('mpc_user_control_list', []);
|
||||
$supporter_control = (array) get_site_option('mpc_supporter_control_list', []);
|
||||
$override_plugins = (array) get_option('mpc_plugin_override_list', []);
|
||||
|
||||
foreach ($all_plugins as $plugin_file => $plugin_data) {
|
||||
if (!in_array($plugin_file, $user_control) && !in_array($plugin_file, $auto_activate) && !in_array($plugin_file, $supporter_control) && !in_array($plugin_file, $override_plugins)) {
|
||||
unset($all_plugins[$plugin_file]);
|
||||
}
|
||||
}
|
||||
return $all_plugins;
|
||||
}
|
||||
|
||||
public function action_links($action_links, $plugin_file, $plugin_data, $context) {
|
||||
global $psts, $blog_id;
|
||||
if (is_network_admin() || is_super_admin()) return $action_links;
|
||||
|
||||
$auto_activate = (array) get_site_option('mpc_auto_activate_list', []);
|
||||
$user_control = (array) get_site_option('mpc_user_control_list', []);
|
||||
$supporter_control = (array) get_site_option('mpc_supporter_control_list', []);
|
||||
$override_plugins = (array) get_option('mpc_plugin_override_list', []);
|
||||
|
||||
if ($context !== 'active') {
|
||||
if (in_array($plugin_file, $user_control) || in_array($plugin_file, $auto_activate) || in_array($plugin_file, $override_plugins)) {
|
||||
return $action_links;
|
||||
} elseif (in_array($plugin_file, $supporter_control) && function_exists('is_pro_site')) {
|
||||
return is_pro_site() ? $action_links : ['<a style="color:red;" href="' . esc_url($psts->checkout_url($blog_id)) . '">' . __('Pro Sites Only', 'multisite-plugin-control') . '</a>'];
|
||||
}
|
||||
}
|
||||
return $action_links;
|
||||
}
|
||||
|
||||
public function supporter_message() {
|
||||
global $pagenow;
|
||||
if (is_super_admin() || $pagenow !== 'plugins.php' || !function_exists('is_pro_site')) return;
|
||||
if (!is_pro_site()) {
|
||||
if (function_exists('supporter_feature_notice')) {
|
||||
supporter_feature_notice();
|
||||
}
|
||||
} else {
|
||||
echo '<div class="error" style="background-color:#F9F9F9;border:0;font-weight:bold;"><p>' . sprintf(__('As a %s Pro Site, you now have access to all our premium plugins!', 'multisite-plugin-control'), get_site_option('site_name')) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
public function remove_plugin_meta($plugin_meta, $plugin_file) {
|
||||
if (is_network_admin() || is_super_admin()) return $plugin_meta;
|
||||
remove_all_actions("after_plugin_row_$plugin_file");
|
||||
return [];
|
||||
}
|
||||
|
||||
public function remove_plugin_update_row() {
|
||||
if (!is_network_admin() && !is_super_admin()) {
|
||||
remove_all_actions('after_plugin_row');
|
||||
}
|
||||
}
|
||||
|
||||
public function blog_options_form($blog_id) {
|
||||
$plugins = get_plugins();
|
||||
$override_plugins = (array) get_blog_option($blog_id, 'mpc_plugin_override_list', []);
|
||||
?>
|
||||
</table>
|
||||
<h3><?php _e('Plugin Override Options', 'multisite-plugin-control'); ?></h3>
|
||||
<p><?php _e('Checked plugins will override network settings for this site.', 'multisite-plugin-control'); ?></p>
|
||||
<table class="widefat" style="margin:10px;width:95%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('User Control', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Name', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Version', 'multisite-plugin-control'); ?></th>
|
||||
<th><?php _e('Author', 'multisite-plugin-control'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($plugins as $file => $p) {
|
||||
if (is_network_only_plugin($file) || is_plugin_active_for_network($file)) continue;
|
||||
$checked = in_array($file, $override_plugins) ? 'checked="checked"' : '';
|
||||
echo "<tr><td><label><input name='plugins[$file]' type='checkbox' value='1' $checked> " . __('Enable', 'multisite-plugin-control') . "</label></td><td>" . esc_html($p['Name']) . "</td><td>" . esc_html($p['Version']) . "</td><td>" . wp_kses_post($p['Author']) . "</td></tr>";
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function blog_options_form_process() {
|
||||
$override_plugins = [];
|
||||
if (isset($_POST['plugins']) && is_array($_POST['plugins'])) {
|
||||
foreach ($_POST['plugins'] as $plugin => $value) {
|
||||
$override_plugins[] = sanitize_text_field($plugin);
|
||||
}
|
||||
}
|
||||
update_option('mpc_plugin_override_list', $override_plugins);
|
||||
}
|
||||
}
|
||||
|
||||
new MultisitePluginControl();
|
Loading…
Add table
Add a link
Reference in a new issue