PluginProbe
Plugin Report / 1.7
Plugin Report v1.7
2.2.4 trunk 1.1 1.2 1.3 1.4 1.5 1.6 1.6.1 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9 1.9.1 1.9.2 1.9.3 2.0.0 2.0.1 2.0.2 2.1 2.1.1 2.2.0 2.2.1 All 27 releases
plugin-report / rt-plugin-report.php

rt-plugin-report.php in Plugin Report 1.7, at rt-plugin-report.php

614 lines 22.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Plugin Report
4 * Plugin URI: https://roytanck.com/?p=277
5 * Description: Provides detailed information about currently installed plugins
6 * Version: 1.7
7 * Requires at least: 4.6
8 * Requires PHP: 5.6
9 * Author: Roy Tanck
10 * Author URI: https://roytanck.com
11 * License: GPLv3
12 * Network: true
13 */
14
15 // If called without WordPress, exit.
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20
21 if ( is_admin() && ! class_exists( 'RT_Plugin_Report' ) ) {
22
23 class RT_Plugin_Report {
24
25 // CSS class constants.
26 const CSS_CLASS_LOW = 'pr-risk-low';
27 const CSS_CLASS_MED = 'pr-risk-medium';
28 const CSS_CLASS_HIGH = 'pr-risk-high';
29
30 // Other class constants.
31 const PLUGIN_VERSION = '1.7';
32 const COLS_PER_ROW = 7;
33 const CACHE_LIFETIME = DAY_IN_SECONDS;
34 const CACHE_LIFETIME_NOREPO = WEEK_IN_SECONDS;
35
36 /**
37 * Constructor
38 */
39 public function __construct() {
40 // Intentionally left blank.
41 }
42
43
44 /**
45 * Set up things like hooks and such
46 */
47 public function init() {
48 // Hook for the admin page.
49 if ( is_multisite() ) {
50 add_action( 'network_admin_menu', array( $this, 'register_settings_page' ) );
51 } else {
52 add_action( 'admin_menu', array( $this, 'register_settings_page' ) );
53 }
54 // Hook for the admin js.
55 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
56 // Add the AJAX hook.
57 add_action( 'wp_ajax_rt_get_plugin_info', array( $this, 'get_plugin_info' ) );
58 // Hook into the WP Upgrader to selectively delete cache items.
59 add_action( 'upgrader_process_complete', array( $this, 'upgrade_delete_cache_items' ), 10, 2 );
60 }
61
62
63 /**
64 * Add a new options page to the network admin
65 */
66 public function register_settings_page() {
67 add_plugins_page(
68 esc_html_x( 'Plugin Report', 'Page and menu title', 'plugin-report' ),
69 esc_html_x( 'Plugin Report', 'Page and menu title', 'plugin-report' ),
70 'manage_options',
71 'plugin_report',
72 array( $this, 'settings_page' )
73 );
74 }
75
76
77 /**
78 * Render the options page
79 */
80 public function settings_page() {
81 // Check user capabilities, just to be sure.
82 if ( ! current_user_can( 'manage_options' ) ) {
83 wp_die();
84 }
85 // Assemble information we'll need.
86 global $wp_version;
87 $plugins = get_plugins();
88
89 // Check wether a core update is available.
90 $wp_latest = $this->check_core_updates();
91
92 // Refresh the cache, but only if this is a fresh timestamp (not if the page has been refreshed with the timestamp still in the URL).
93 if ( isset( $_GET['clear_cache'] ) ) {
94 $new_timestamp = intval( $_GET['clear_cache'] );
95 $last_timestamp = intval( get_site_transient( 'plugin_report_cache_cleared' ) );
96 if ( ! $last_timestamp || $new_timestamp > $last_timestamp ) {
97 $this->clear_cache();
98 set_site_transient( 'plugin_report_cache_cleared', $new_timestamp, self::CACHE_LIFETIME );
99 }
100 }
101
102 // Start the page's output.
103 echo '<div class="wrap">';
104 echo '<h1>' . esc_html_x( 'Plugin Report', 'Page and menu title', 'plugin-report' ) . '</h1>';
105 echo '<p>';
106 $version_temp = '<span class="' . $this->get_version_risk_classname( $wp_version, $wp_latest ) . '">' . $wp_version . '</span>';
107 /* translators: %1$s: Current WordPress version number, %2$s: Current PHP version number */
108 echo sprintf( __( 'Currently running WordPress version %1$s and PHP version %2$s.', 'plugin-report' ), $version_temp, phpversion() );
109 if ( version_compare( $wp_version, $wp_latest, '<' ) ) {
110 /* translators: %s = Available new version number */
111 echo sprintf( ' (' . esc_html__( 'An upgrade to %s is available', 'plugin-report' ) . ')', $wp_latest );
112 }
113 echo '</p>';
114 echo '<p>';
115 // Clear cache and reload.
116 if ( is_multisite() ) {
117 $page_url = 'network/plugins.php?page=plugin_report';
118 } else {
119 $page_url = 'plugins.php?page=plugin_report';
120 }
121 echo '<a href="' . admin_url( $page_url . '&clear_cache=' . current_time( 'timestamp' ) ) . '">' . esc_html__( 'Clear cached plugin data and reload', 'plugin-report' ) . '</a>';
122 echo '</p>';
123 echo '<h3>' . esc_html__( 'Currently installed plugins', 'plugin-report' ) . '</h3>';
124 echo '<p id="plugin-report-progress"></p>';
125 echo '<p>';
126
127 // The report's main table.
128 echo '<table id="plugin-report-table" class="wp-list-table widefat fixed striped">';
129 echo '<thead>';
130 echo '<tr>';
131 echo '<th>' . esc_html__( 'Name', 'plugin-report' ) . '</th>';
132 echo '<th>' . esc_html__( 'Author', 'plugin-report' ) . '</th>';
133 echo '<th>' . esc_html__( 'Activated', 'plugin-report' ) . '</th>';
134 echo '<th data-sort-method="none" class="no-sort">' . esc_html__( 'Installed version', 'plugin-report' ) . '</th>';
135 echo '<th>' . esc_html__( 'Last update', 'plugin-report' ) . '</th>';
136 echo '<th data-sort-method="dotsep">' . esc_html__( 'Tested up to WP version', 'plugin-report' ) . '</th>';
137 echo '<th data-sort-method="number">' . esc_html__( 'Rating', 'plugin-report' ) . '</th>';
138 echo '</tr>';
139 echo '</thead>';
140 echo '<tbody>';
141
142 foreach ( $plugins as $key => $plugin ) {
143 $slug = $this->get_plugin_slug( $key );
144 $cache_key = $this->create_cache_key( $slug );
145 $cache = get_site_transient( $cache_key );
146 if ( $cache ) {
147 // Use the cached report to create a table row.
148 echo $this->render_table_row( $cache );
149 } else {
150 // Render a special table row that's used as a signal to the front-end js that new data is needed.
151 echo '<tr class="plugin-report-row-temp-' . $slug . '"><td colspan="' . self::COLS_PER_ROW . '">' . esc_html__( 'Loading...', 'plugin-report' ) . '</td></tr>';
152 }
153 }
154
155 echo '</tbody>';
156 echo '</table>';
157 echo '</p>';
158
159 echo '<p id="plugin-report-buttons"></p>';
160
161 // Wrap up.
162 echo '</div>';
163 }
164
165
166 /**
167 * Enqueue admin javascript
168 */
169 public function enqueue_assets( $hook ) {
170 // Check if we're on the right screen.
171 if ( 'plugins_page_plugin_report' !== $hook ) {
172 return;
173 }
174 // Register the plugin's admin js, and require jquery.
175 wp_enqueue_script( 'plugin-report-js', plugins_url( '/js/plugin-report.js', __FILE__ ), array( 'jquery', 'plugin-report-tablesort-js' ), self::PLUGIN_VERSION );
176 wp_enqueue_script( 'plugin-report-tablesort-js', plugins_url( '/js/tablesort.min.js', __FILE__ ), array( 'jquery' ), '5.1.0' );
177 wp_enqueue_script( 'plugin-report-tablesort-number-js', plugins_url( '/js/tablesort.number.min.js', __FILE__ ), array( 'plugin-report-tablesort-js' ), '5.1.0' );
178 wp_enqueue_script( 'plugin-report-tablesort-dotsep-js', plugins_url( '/js/tablesort.dotsep.min.js', __FILE__ ), array( 'plugin-report-tablesort-js' ), '5.1.0' );
179 // Add some variables to the page, to be used by the javascript.
180 $slugs = $this->get_plugin_slugs();
181 $slugs_str = implode( ',', $slugs );
182 $vars = array(
183 'plugin_slugs' => $slugs_str,
184 'ajax_nonce' => wp_create_nonce( 'plugin_report_nonce' ),
185 'export_btn' => __( 'Export .csv file', 'plugin-report' ),
186 'plugin_url_header' => __( 'Plugin URL', 'plugin-report' ),
187 'author_url_header' => __( 'Author URL', 'plugin-report' ),
188 );
189 wp_localize_script( 'plugin-report-js', 'plugin_report_vars', $vars );
190 // Enqueue admin CSS file.
191 wp_enqueue_style( 'plugin-report-css', plugin_dir_url( __FILE__ ) . 'css/plugin-report.css', array(), self::PLUGIN_VERSION );
192 }
193
194
195 /**
196 * Get the slugs for all currently installed plugins
197 */
198 private function get_plugin_slugs() {
199 $plugins = get_plugins();
200 $slugs = array();
201 foreach ( $plugins as $key => $plugin ) {
202 $slugs[] = $this->get_plugin_slug( $key );
203 }
204 return $slugs;
205 }
206
207
208 /**
209 * Convert a plugin's file path into its slug
210 */
211 private function get_plugin_slug( $file ) {
212 if ( strpos( $file, '/' ) !== false ) {
213 $parts = explode( '/', $file );
214 } else {
215 $parts = explode( '.', $file );
216 }
217 return sanitize_title( $parts[0] );
218 }
219
220
221 /**
222 * AJAX handler
223 * Returns a full html table row with the plugin's data
224 */
225 public function get_plugin_info() {
226
227 // Check the ajax nonce, display an error if the check fails.
228 if ( ! check_ajax_referer( 'plugin_report_nonce', 'nonce', false ) ) {
229 wp_die();
230 }
231
232 // Check user capabilites, just to be sure.
233 if ( ! current_user_can( 'manage_options' ) ) {
234 wp_die();
235 }
236
237 // Check if get_plugins() function exists.
238 if ( ! function_exists( 'plugins_api' ) ) {
239 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
240 }
241
242 $slug = sanitize_title( $_POST['slug'] );
243
244 $report = $this->assemble_plugin_report( $slug );
245
246 if ( $report ) {
247 $table_row = $this->render_table_row( $report );
248 } else {
249 $table_row = $this->render_error_row( esc_html__( 'No plugin data available.', 'plugin-report' ) );
250 }
251
252 // Formulate a response.
253 $response = array(
254 'html' => $table_row,
255 'message' => 'Success!',
256 );
257 // Return the response.
258 echo wp_json_encode( $response );
259
260 wp_die();
261 }
262
263
264 /**
265 * Gather all the info we can get about a plugin.
266 * Uses transient caching to avoid doing repo API calls on every page visit
267 */
268 private function assemble_plugin_report( $slug ) {
269 if ( ! empty( $slug ) ) {
270 $report = array();
271 $cache_key = $this->create_cache_key( $slug );
272 $cache = get_site_transient( $cache_key );
273 $plugins = get_plugins();
274
275 if ( empty( $cache ) ) {
276
277 // Add the plugin's slug to the report.
278 $report['slug'] = $slug;
279
280 // Get the locally available info, and add it to the report.
281 foreach ( $plugins as $key => $plugin ) {
282 if ( $this->get_plugin_slug( $key ) == $slug ) {
283 $report['local_info'] = $plugin;
284 $report['file_path'] = $key;
285 break;
286 }
287 }
288
289 // Use the wordpress.org repository API to get detailed information.
290 $args = array(
291 'slug' => $slug,
292 'fields' => array(
293 'description' => false,
294 'sections' => false,
295 'tags' => false,
296 'version' => true,
297 'tested' => true,
298 'requires' => true,
299 'requires_php' => true,
300 'compatibility' => true,
301 'author' => true,
302 ),
303 );
304 $returned_object = plugins_api( 'plugin_information', $args );
305
306 // Add the repo info to the report.
307 if ( ! is_wp_error( $returned_object ) ) {
308 $report['repo_info'] = maybe_unserialize( $returned_object );
309 // Cache the report.
310 set_site_transient( $cache_key, $report, self::CACHE_LIFETIME );
311 } else {
312 // Store the error code and message in the report.
313 $report['repo_error_code'] = $returned_object->get_error_code();
314 $report['repo_error_message'] = $returned_object->get_error_message();
315 // Cache for an extra long time when the plugin is not in the repo.
316 set_site_transient( $cache_key, $report, self::CACHE_LIFETIME_NOREPO );
317 }
318 } else {
319 $report = $cache;
320 }
321
322 return $report;
323
324 } else {
325 return null;
326 }
327
328 }
329
330
331 /**
332 * From a report, generate an HTML table row with relevant data for the plugin
333 */
334 private function render_table_row( $report ) {
335 // Get the latest WP release version number.
336 $wp_latest = $this->check_core_updates();
337 // Check if the report is valid.
338 if ( $report == null ) {
339 $html = $this->render_error_row( esc_html__( 'No plugin data available.', 'plugin-report' ) );
340 } else {
341 // Start the new table row.
342 $html = '<tr class="plugin-report-row-' . $report['slug'] . '">';
343 // Name.
344 if ( isset( $report['local_info']['PluginURI'] ) && ! empty( $report['local_info']['PluginURI'] ) ) {
345 $html .= '<td><a href="' . $report['local_info']['PluginURI'] . '"><strong>' . $report['local_info']['Name'] . '</strong></a></td>';
346 } else {
347 $html .= '<td><strong>' . $report['local_info']['Name'] . '</strong></td>';
348 }
349 // Author.
350 if ( isset( $report['local_info']['AuthorURI'] ) && ! empty( $report['local_info']['AuthorURI'] ) ) {
351 $html .= '<td><a href="' . $report['local_info']['AuthorURI'] . '">' . $report['local_info']['Author'] . '</a></td>';
352 } else {
353 $html .= '<td>' . $report['local_info']['Author'] . '</td>';
354 }
355 // Activated.
356 $active = __( 'Please clear cache to update', 'plugin-report' );
357 $css_class = self::CSS_CLASS_MED;
358 if ( is_multisite() ) {
359 $activation_status = $this->get_multisite_activation( $report['file_path'] );
360 if ( true === $activation_status['network'] ) {
361 $css_class = self::CSS_CLASS_LOW;
362 $html .= '<td class="' . $css_class . '">' . __( 'Network activated', 'plugin-report' ) . '</td>';
363 } else {
364 $css_class = ( $activation_status['active'] > 0 ) ? self::CSS_CLASS_LOW : self::CSS_CLASS_HIGH;
365 $html .= '<td class="' . $css_class . '">' . $activation_status['active'] . '/' . $activation_status['sites'] . '</td>';
366 }
367 } else {
368 if ( isset( $report['file_path'] ) ) {
369 $active = is_plugin_active( $report['file_path'] ) ? __( 'Yes', 'plugin-report' ) : __( 'No', 'plugin-report' );
370 $css_class = is_plugin_active( $report['file_path'] ) ? self::CSS_CLASS_LOW : self::CSS_CLASS_HIGH;
371 }
372 $html .= '<td class="' . $css_class . '">' . $active . '</td>';
373 }
374 // Installed / available version.
375 if ( isset( $report['repo_info'] ) ) {
376 $css_class = $this->get_version_risk_classname( $report['local_info']['Version'], $report['repo_info']->version );
377 $html .= '<td class="' . $css_class . '">';
378 $html .= $report['local_info']['Version'];
379 if ( $report['local_info']['Version'] != $report['repo_info']->version ) {
380 // Any platform upgrades needed?
381 global $wp_version;
382 $needs_php_upgrade = isset( $report['repo_info']->requires_php ) ? version_compare( phpversion(), $report['repo_info']->requires_php, '<' ) : false;
383 $needs_wp_upgrade = isset( $report['repo_info']->requires ) ? version_compare( $wp_version, $report['repo_info']->requires, '<' ) : false;
384 // Create the additional message.
385 if ( $needs_wp_upgrade && $needs_php_upgrade ) {
386 /* translators: %1$s: Plugin version number, %2$s: WP version number, %3$s: PHP version number */
387 $html .= ' <span class="pr-additional-info">' . sprintf( esc_html__( '(%1$s available, requires WP %2$s and PHP %3$s)', 'plugin-report'), $report['repo_info']->version, $report['repo_info']->requires, $report['repo_info']->requires_php ) . '</span>';
388 } elseif ( $needs_wp_upgrade ) {
389 /* translators: %1$s: Plugin version number, %2$s: WP version number. */
390 $html .= ' <span class="pr-additional-info">' . sprintf( esc_html__( '(%1$s available, requires WP %2$s)', 'plugin-report'), $report['repo_info']->version, $report['repo_info']->requires ) . '</span>';
391 } elseif ( $needs_php_upgrade ) {
392 /* translators: %1$s: Plugin version number, %2$s: PHP version number. */
393 $html .= ' <span class="pr-additional-info">' . sprintf( esc_html__( '(%1$s available, requires PHP %2$s)', 'plugin-report'), $report['repo_info']->version, $report['repo_info']->requires_php ) . '</span>';
394 } else {
395 /* translators: %s: Plugin version number. */
396 $html .= ' <span class="pr-additional-info">' . sprintf( esc_html__( '(%s available)', 'plugin-report'), $report['repo_info']->version ) . '</span>';
397 }
398 }
399 $html .= '</td>';
400 } else {
401 $html .= '<td>' . $report['local_info']['Version'] . '</td>';
402 }
403 // Last updates.
404 if ( isset( $report['repo_info'] ) ) {
405 $time_update = new DateTime( $report['repo_info']->last_updated );
406 $time_diff = human_time_diff( $time_update->getTimestamp(), current_time( 'timestamp' ) );
407 $css_class = $this->get_timediff_risk_classname( current_time( 'timestamp' ) - $time_update->getTimestamp() );
408 $html .= '<td class="' . $css_class . '" data-sort="' . $time_update->getTimestamp() . '">' . $time_diff . '</td>';
409 } else {
410 $html .= $this->render_error_cell();
411 }
412 // Tested up to.
413 if ( isset( $report['repo_info'] ) ) {
414 $css_class = $this->get_version_risk_classname( $report['repo_info']->tested, $wp_latest );
415 $html .= '<td class="' . $css_class . '">' . $report['repo_info']->tested . '</td>';
416 } else {
417 $html .= $this->render_error_cell();
418 }
419 // Overall user rating.
420 if ( isset( $report['repo_info'] ) ) {
421 $css_class = ( intval( $report['repo_info']->num_ratings ) > 0 ) ? $this->get_percentage_risk_classname( intval( $report['repo_info']->rating ) ) : '';
422 $value_text = ( ( intval( $report['repo_info']->num_ratings ) > 0 ) ? $report['repo_info']->rating . '%' : esc_html__( 'No data available', 'plugin-report' ) );
423 $html .= '<td class="' . $css_class . '">' . $value_text . '</td>';
424 } else {
425 $html .= $this->render_error_cell();
426 }
427 // Close the new table row.
428 $html .= '</tr>';
429 }
430 return $html;
431 }
432
433
434 /**
435 * Format an error message as a table row, so we can return it to javascript
436 */
437 private function render_error_row( $message ) {
438 return '<tr class="pluginreport-row-error"><td colspan="' . self::COLS_PER_ROW . '">' . $message . '</td></tr>';
439 }
440
441
442 /**
443 * Format an error message as a table cell, so we can return it to javascript
444 */
445 private function render_error_cell( $message = null ) {
446 if ( ! $message ) {
447 $message = esc_html__( 'No data available', 'plugin-report' );
448 }
449 return '<td class="pluginreport-cell-error">' . $message . '</td>';
450 }
451
452
453 /**
454 * Figure out what CSS class to use based on current and optimal version numbers
455 */
456 private function get_version_risk_classname( $available, $optimal ) {
457 // If the version is equal or higher, indicate low risk.
458 if ( version_compare( $available, $optimal, '>=' ) ) {
459 return self::CSS_CLASS_LOW;
460 }
461 // Else, indicate high risk.
462 return self::CSS_CLASS_HIGH;
463 }
464
465
466 /**
467 * Assess the risk associated with low ratings or poor compatibility feedback, return corresponding CSS class
468 */
469 private function get_percentage_risk_classname( $perc ) {
470 if ( $perc < 70 ) {
471 return self::CSS_CLASS_HIGH;
472 }
473 if ( $perc < 90 ) {
474 return self::CSS_CLASS_MED;
475 }
476 return self::CSS_CLASS_LOW;
477 }
478
479
480 /**
481 * Assess the risk associated with low ratings or poor compatibility feedback, return corresponding CSS class
482 */
483 private function get_timediff_risk_classname( $time_diff ) {
484 $days = $time_diff / ( DAY_IN_SECONDS );
485 if ( $days > 365 ) {
486 return self::CSS_CLASS_HIGH;
487 }
488 if ( $days > 90 ) {
489 return self::CSS_CLASS_MED;
490 }
491 return self::CSS_CLASS_LOW;
492 }
493
494
495 /**
496 * Get the latest available WordPress version using WP core functions
497 * This way, we don't need to do any API calls. WP check this periodically anyway.
498 */
499 private function check_core_updates() {
500 global $wp_version;
501 $update = get_preferred_from_update_core();
502 // Bail out of no valid response, or false.
503 if ( ! $update || $update == false ) {
504 return $wp_version;
505 }
506 // If latest, return current version number.
507 if ( $update->response == 'latest' ) {
508 return $wp_version;
509 }
510 // Return the preferred update's version number.
511 return $update->version;
512 }
513
514
515 /**
516 * Gather statistics about a plugin's activation on a multisite install
517 */
518 private function get_multisite_activation( $path ) {
519 // Create an array to contain the return values.
520 $activation_status = array(
521 'network' => false,
522 'active' => 0,
523 'sites' => 1,
524 );
525 // Check if the plugin is network activated.
526 $network_plugins = get_site_option( 'active_sitewide_plugins', null );
527 if ( array_key_exists( $path, $network_plugins ) ) {
528 $activation_status['network'] = true;
529 } else {
530 // Get a list of all sites in the multisite install.
531 $args = array(
532 'number' => 9999,
533 'fields' => 'ids',
534 );
535 $sites = get_sites( $args );
536 // Add the total number of sites to the return array.
537 $activation_status['sites'] = count( $sites );
538 // Loop through the sites to find where the plugin is active.
539 foreach ( $sites as $site_id ) {
540 $plugins = get_blog_option( $site_id, 'active_plugins', null );
541 if ( $plugins ) {
542 foreach ( $plugins as $plugin_path ) {
543 if ( $plugin_path === $path ) {
544 $activation_status['active']++;
545 }
546 }
547 }
548 }
549 }
550 // Return the data we gathered.
551 return $activation_status;
552 }
553
554
555 /**
556 * Create a cache key that is unique to the provided plugin slug.
557 */
558 private function create_cache_key( $slug ) {
559 // Create a hash for the plugin slug.
560 $slug_hash = hash( 'sha256', $slug );
561 // Prefix and limit the string to 40 characters to avoid issues with long keys.
562 $cache_key = 'rtpr_' . substr( $slug_hash, 0, 35 );
563 // Return the key.
564 return $cache_key;
565 }
566
567
568 /**
569 * Clear all cached plugin info
570 */
571 private function clear_cache() {
572 // Request a list of all plugins.
573 $plugins = get_plugins();
574 // Loop through the plugins array, and delete cache items.
575 foreach ( $plugins as $key => $plugin ) {
576 $slug = $this->get_plugin_slug( $key );
577 $this->clear_cache_item( $slug );
578 }
579 }
580
581
582 /**
583 * Remove the cache item for a single plugin
584 */
585 private function clear_cache_item( $slug ) {
586 if ( isset( $slug ) ) {
587 $cache_key = $this->create_cache_key( $slug );
588 delete_site_transient( $cache_key );
589 }
590 }
591
592
593 /**
594 * Selectively delete cache for plugins that have been updated.
595 */
596 public function upgrade_delete_cache_items( $upgrader, $data ) {
597 // Check if plugins have been upgraded by WP.
598 if ( isset( $data ) && isset( $data['plugins'] ) && is_array( $data['plugins'] ) ) {
599 // Loop through the plugins, and delete the associated cache items.
600 foreach ( $data['plugins'] as $key => $value ) {
601 $slug = $this->get_plugin_slug( $value );
602 $this->clear_cache_item( $slug );
603 }
604 }
605 }
606
607 }
608
609 // Instantiate the class.
610 $plugin_report_instance = new RT_Plugin_Report();
611 $plugin_report_instance->init();
612
613 }
614