diff --git a/class-plugin-report.php b/class-plugin-report.php new file mode 100644 index 0000000..3c2053d --- /dev/null +++ b/class-plugin-report.php @@ -0,0 +1,496 @@ +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); + } + } + + ?> +
+

+

+

+ get_version_risk_classname($wp_version, $wp_latest) . '">' . $wp_version . ''; + 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); + } + ?> +

+

+ + + +

+

+ + + + + + + + + + + + + + + + $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 ''; + } + } + ?> + +
' . __('Loading...', 'multisite-plugin-control') . '
+

+
+ 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 = ''; + if (isset($report['local_info']['PluginURI']) && !empty($report['local_info']['PluginURI'])) { + $html .= '' . $report['local_info']['Name'] . ''; + } else { + $html .= '' . $report['local_info']['Name'] . ''; + } + if (isset($report['local_info']['AuthorURI']) && !empty($report['local_info']['AuthorURI'])) { + $html .= '' . $report['local_info']['Author'] . ''; + } else { + $html .= '' . $report['local_info']['Author'] . ''; + } + 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 .= '' . __('wp.org/wenpai.org, plugin closed', 'multisite-plugin-control') . ''; + } else { + $html .= '' . __('wp.org/wenpai.org, plugin not found', 'multisite-plugin-control') . ''; + } + } else { + $html .= 'wp.org/wenpai.org'; + } + } else { + if ($parsed_repo_url && isset($parsed_repo_url['host'])) { + $html .= '' . $repo_host . ''; + } else { + $html .= '' . __('Updates disabled', 'multisite-plugin-control') . ''; + } + } + } 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 .= '' . __('Network activated', 'multisite-plugin-control') . ''; + } else { + $css_class = ($activation_status['active'] > 0) ? self::CSS_CLASS_LOW : self::CSS_CLASS_HIGH; + $html .= '' . $activation_status['active'] . '/' . $activation_status['sites'] . ''; + } + } 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 .= '' . $active . ''; + } + if (isset($report['repo_info'])) { + $css_class = $this->get_version_risk_classname($report['local_info']['Version'], $report['repo_info']->version); + $html .= ''; + $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 .= ' ' . 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) . ''; + } elseif ($needs_wp_upgrade) { + $html .= ' ' . sprintf(__('(%1$s available, requires WP %2$s)', 'multisite-plugin-control'), $report['repo_info']->version, $report['repo_info']->requires) . ''; + } elseif ($needs_php_upgrade) { + $html .= ' ' . sprintf(__('(%1$s available, requires PHP %2$s)', 'multisite-plugin-control'), $report['repo_info']->version, $report['repo_info']->requires_php) . ''; + } else { + $html .= ' ' . sprintf(__('(%s available)', 'multisite-plugin-control'), $report['repo_info']->version) . ''; + } + } + $html .= ''; + } else { + $html .= '' . $report['local_info']['Version'] . ''; + } + if (version_compare($wp_version, '5.5', '<')) { + $html .= '' . __('Requires WordPress 5.5 or higher', 'multisite-plugin-control') . ''; + } else { + if (isset($report['auto-update']) && $report['auto-update']) { + $html .= '' . __('Enabled', 'multisite-plugin-control') . ''; + } else { + $html .= '' . __('Not enabled', 'multisite-plugin-control') . ''; + } + } + 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 .= '' . $time_diff . ''; + } 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 .= '' . $report['repo_info']->tested . ''; + } 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 .= '' . $value_text . ''; + } else { + $html .= $this->render_error_cell(); + } + $html .= ''; + } + return $html; + } + + private function render_error_row($message) { + return '' . $message . ''; + } + + private function render_error_cell($message = null) { + if (!$message) { + $message = __('No data available', 'multisite-plugin-control'); + } + return '' . $message . ''; + } + + 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); + } + } + } +} \ No newline at end of file diff --git a/css/plugin-report.css b/css/plugin-report.css new file mode 100644 index 0000000..c4fba1c --- /dev/null +++ b/css/plugin-report.css @@ -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; +} diff --git a/js/plugin-report.js b/js/plugin-report.js new file mode 100644 index 0000000..591f4f4 --- /dev/null +++ b/js/plugin-report.js @@ -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( '' ); + } + $( '#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(''); + // 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(); + } + +}); diff --git a/js/tablesort.dotsep.min.js b/js/tablesort.dotsep.min.js new file mode 100644 index 0000000..d33260b --- /dev/null +++ b/js/tablesort.dotsep.min.js @@ -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;ed)return-1;if(c0)if(a.tHead&&a.tHead.rows.length>0){for(e=0;e0&&n.push(m),o++;if(!n)return}for(o=0;o'Multisite Plugin Control','report-msgid-bugs-to'=>'','pot-creation-date'=>'2025-04-15 03:24+0000','po-revision-date'=>'2025-04-15 03:39+0000','last-translator'=>'','language-team'=>'简体中文','language'=>'zh_CN','plural-forms'=>'nplurals=1; plural=0;','mime-version'=>'1.0','content-type'=>'text/plain; charset=UTF-8','content-transfer-encoding'=>'8bit','x-generator'=>'Loco https://localise.biz/','x-loco-version'=>'2.6.11; wp-6.7.2','x-domain'=>'multisite-plugin-control','messages'=>['%s activated on %d sites.'=>'%s 已在 %d 个站点上启用。','%s deactivated on %d sites.'=>'%s 在 %d 个站点上已停用。','%s has been mass activated.'=>'%s 已被批量启用。','%s has been mass deactivated.'=>'%s 已被批量停用。','(%1$s available, requires PHP %2$s)'=>'(%1$s 可用,需要 PHP %2$s)','(%1$s available, requires WP %2$s and PHP %3$s)'=>'(%1$s 可用,需要 WP %2$s 和 PHP %3$s)','(%1$s available, requires WP %2$s)'=>'(%1$s 可用,需要 WP %2$s)','(%s available)'=>'(%s 可用)','Activate All'=>'全部启用','Activate or deactivate plugins across all existing blogs.'=>'在所有现有博客中启用或停用插件。','Activated'=>'启用','Active Sites'=>'活跃站点','All Statuses'=>'所有状态','All Users'=>'所有用户','An error occurred while saving.'=>'保存时发生错误。','Apply'=>'应用','As a %s Pro Site, you now have access to all our premium plugins!'=>'作为 %s 专业网站,您现在可以访问我们所有的高级插件!','Author'=>'作者','Author URL'=>'作者网址','Auto Activation'=>'自动启用','Auto-Activate'=>'自动启用','Auto-Activate (All Users)'=>'自动启用(所有用户)','Auto-update'=>'自动更新','Bulk Actions'=>'批量操作','Checked plugins will override network settings for this site.'=>'已选中的插件将覆盖该站点的网络设置。','Choose who can activate/deactivate plugins: all users, Pro Sites only, or none.'=>'选择谁可以激活/停用插件:所有用户、仅限专业网站或无。','Clear cached plugin data and reload'=>'清除缓存的插件数据并重新加载','Deactivate All'=>'全部停用','Enable'=>'启用','Enable to allow all users to activate/deactivate plugins.'=>'启用以允许所有用户激活/停用插件。','Enabled'=>'已启用','Export .csv file'=>'导出 .csv 文件','Failed to save settings.'=>'无法保存设置。','Failed to select blogs.'=>'无法选择博客。','Help'=>'帮助','https://wpmultisite.com'=>'https://wpmultisite.com','https://wpmultisite.com/plugins/multisite-plugin-control'=>'https://wpmultisite.com/plugins/multisite-plugin-control','Installed Version'=>'安装版本','Invalid nonce.'=>'无效的随机数。','Last Action Time'=>'最后行动时间','Last Update'=>'最后更新','Loading...'=>'加载中...','Manage plugin access permissions across your entire multisite network with enhanced UI and statistics.'=>'通过增强的 UI 和统计数据管理整个多站点网络的插件访问权限。','Manage plugin access permissions across your multisite network.'=>'管理多站点网络中的插件访问权限。','Mass Activate'=>'批量激活','Mass Activation/Deactivation'=>'批量激活/停用','Mass Deactivate'=>'批量停用','Metric'=>'参数','Multisite Plugin Control'=>'多站点插件控制','Name'=>'名称','Network activated'=>'网络已激活','Network too large for mass activation.'=>'网络太大,无法大规模激活。','Network too large for mass deactivation.'=>'网络太大,无法批量停用。','Network too large to calculate usage stats.'=>'网络太大,无法计算使用情况统计数据。','Never'=>'从未','No'=>'否','No data available'=>'无可用数据','No plugin data available.'=>'没有可用的插件数据。','None'=>'无选项','Not enabled'=>'未启用','Only available in WP 5.8+'=>'仅适用于 WP 5.8+','Operation failed.'=>'操作失败。','Permission denied.'=>'无权限。','Please clear cache to update'=>'请清除缓存以进行更新','Please select at least one plugin.'=>'请至少选择一个插件。','Plugin Control'=>'插件控制','Plugin Dashboard'=>'插件仪表板','Plugin Management'=>'插件管理','Plugin Name'=>'插件名称','Plugin Override Options'=>'插件覆盖选项','Plugin Report'=>'插件报告','Plugin URL'=>'插件网址','Plugin Usage'=>'插件用量','Pro Sites'=>'专业网站','Pro Sites Only'=>'仅限专业网站','Processing...'=>'加载中...','Rating'=>'评级','Repository'=>'存储库','Requires WordPress 5.5 or higher'=>'需要 WordPress 5.5 或更高版本','Running WordPress %1$s and PHP %2$s.'=>'运行 WordPress %1$s 和 PHP %2$s。','Save Settings'=>'保存设置','Saving...'=>'保存...','Search plugins by name...'=>'按名称搜索插件...','Server error occurred.'=>'发生服务器错误。','Set to All Users'=>'设置为所有用户','Set to Auto-Activate'=>'设置为自动激活','Set to None'=>'设置为无','Set to Pro Sites'=>'设置为专业网站','Settings saved successfully!'=>'设置保存成功!','Statistics'=>'统计数据','Tested WP Version'=>'已测试WP版本','Total Activations'=>'总启用量','Total Deactivations'=>'总停用量','Total Sites'=>'网站总数','Updates disabled'=>'已禁用更新','Upgrade to %s available'=>'可升级至 %s','User Control'=>'用户控制','Value'=>'数值','Version'=>'版本','View detailed information about installed plugins across the network.'=>'查看整个网络上已安装插件的详细信息。','View plugin control activity across the network.'=>'查看整个网络上的插件控制活动。','View plugin usage across the network.'=>'查看整个网络上的插件使用情况。','When enabled, new blogs will have the plugin activated automatically.'=>'启用后,新博客将自动激活该插件。','wp.org/wenpai.org, plugin closed'=>'wp.​​org/wenpai.org,插件已关闭','wp.org/wenpai.org, plugin not found'=>'wp.​​org/wenpai.org,插件未找到','WPMultisite.com'=>'文派多站点','Yes'=>'是','You do not have sufficient permissions to access this page.'=>'您没有足够的权限访问此页面。']]; diff --git a/languages/multisite-plugin-control-zh_CN.mo b/languages/multisite-plugin-control-zh_CN.mo new file mode 100644 index 0000000..0a16fa3 Binary files /dev/null and b/languages/multisite-plugin-control-zh_CN.mo differ diff --git a/languages/multisite-plugin-control-zh_CN.po b/languages/multisite-plugin-control-zh_CN.po new file mode 100644 index 0000000..870e4be --- /dev/null +++ b/languages/multisite-plugin-control-zh_CN.po @@ -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 "您没有足够的权限访问此页面。" diff --git a/multisite-plugin-control.php b/multisite-plugin-control.php new file mode 100644 index 0000000..facc61b --- /dev/null +++ b/multisite-plugin-control.php @@ -0,0 +1,742 @@ +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'; + ?> +
+

+ + + +

+
+

+ +

+ +
+ + + + +
+ +
+
+
+ + +
+ +
+ + + + + + + + + + + + + + + $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'; + ?> + + + + + + + + + + + +
+ + + + + +
+

+ + + +

+
+
+ +
+

+ get_plugin_usage_stats(); + if (isset($usage_stats['error'])) { + echo '

' . esc_html($usage_stats['error']) . '

'; + } else { + ?> + + + + + + + + + + + + + + + +
+ +
+ +
+ plugin_report->render_report(); ?> +
+ +
+


+

+


+

+


+

+
+
+
+ +
+

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
stats['total_sites']); ?>
stats['total_activations']); ?>
stats['total_deactivations']); ?>
stats['last_action_time'] ?: __('Never', 'multisite-plugin-control')); ?>
+
+ +
+ + + + + __('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 : ['' . __('Pro Sites Only', 'multisite-plugin-control') . '']; + } + } + 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 '

' . sprintf(__('As a %s Pro Site, you now have access to all our premium plugins!', 'multisite-plugin-control'), get_site_option('site_name')) . '

'; + } + } + + 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', []); + ?> + +

+

+ + + + + + + + + + + $p) { + if (is_network_only_plugin($file) || is_plugin_active_for_network($file)) continue; + $checked = in_array($file, $override_plugins) ? 'checked="checked"' : ''; + echo ""; + } + ?> + +
" . esc_html($p['Name']) . "" . esc_html($p['Version']) . "" . wp_kses_post($p['Author']) . "
+ $value) { + $override_plugins[] = sanitize_text_field($plugin); + } + } + update_option('mpc_plugin_override_list', $override_plugins); + } +} + +new MultisitePluginControl(); \ No newline at end of file