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