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); } } } }