diff --git a/wp-admin/includes/class-wp-plugins-list-table.php b/wp-admin/includes/class-wp-plugins-list-table.php
index 091709b18c..d4348e250b 100644
--- a/wp-admin/includes/class-wp-plugins-list-table.php
+++ b/wp-admin/includes/class-wp-plugins-list-table.php
@@ -108,8 +108,17 @@ class WP_Plugins_List_Table extends WP_List_Table {
unset( $recently_activated[$key] );
update_option( 'recently_activated', $recently_activated );
}
+
+ $plugin_info = get_site_transient( 'update_plugins' );
foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
+ // Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide.
+ if ( isset( $plugin_info->response[ $plugin_file ] ) ) {
+ $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data );
+ } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) {
+ $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data );
+ }
+
// Filter into individual sections
if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
// On the non-network screen, filter out network-only plugins as long as they're not individually activated
@@ -344,6 +353,7 @@ class WP_Plugins_List_Table extends WP_List_Table {
$actions = array(
'deactivate' => '',
'activate' => '',
+ 'details' => '',
'edit' => '',
'delete' => '',
);
@@ -392,8 +402,18 @@ class WP_Plugins_List_Table extends WP_List_Table {
if ( ! is_multisite() && current_user_can('delete_plugins') )
$actions['delete'] = '' . __('Delete') . '';
} // end if $is_active
+
} // end if $screen->in_admin( 'network' )
+ // Details link using API info, if available
+ if ( ( ! is_multisite() || $screen->in_admin( 'network' ) ) && isset( $plugin_data['slug'] ) ) {
+ $actions['details'] = sprintf( '%s',
+ esc_url( self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
+ '&TB_iframe=true&width=600&height=550' ) ),
+ esc_attr( sprintf( __( 'More information about %s' ), $plugin_data['Name'] ) ),
+ __( 'Details' ) );
+ }
+
if ( ( ! is_multisite() || $screen->in_admin( 'network' ) ) && current_user_can('edit_plugins') && is_writable(WP_PLUGIN_DIR . '/' . $plugin_file) )
$actions['edit'] = '' . __('Edit') . '';
} // end if $context
diff --git a/wp-includes/update.php b/wp-includes/update.php
index 3747ea8fb9..a9e25a8718 100644
--- a/wp-includes/update.php
+++ b/wp-includes/update.php
@@ -277,6 +277,7 @@ function wp_update_plugins( $extra_stats = array() ) {
'plugins' => json_encode( $to_send ),
'translations' => json_encode( $translations ),
'locale' => json_encode( $locales ),
+ 'all' => json_encode( true ),
),
'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' )
);
@@ -303,13 +304,20 @@ function wp_update_plugins( $extra_stats = array() ) {
$plugin = (object) $plugin;
}
unset( $plugin );
+ foreach ( $response['no_update'] as &$plugin ) {
+ $plugin = (object) $plugin;
+ }
+ unset( $plugin );
if ( is_array( $response ) ) {
$new_option->response = $response['plugins'];
$new_option->translations = $response['translations'];
+ // TODO: Perhaps better to store no_update in a separate transient with an expiry?
+ $new_option->no_update = $response['no_update'];
} else {
$new_option->response = array();
$new_option->translations = array();
+ $new_option->no_update = array();
}
set_site_transient( 'update_plugins', $new_option );