}|false|null
*/
private static $latest = null;
/**
* Hooks the badges.
*/
public static function register() {
add_filter( 'plugin_row_meta', array( __CLASS__, 'row_meta' ), 10, 3 );
add_action( 'admin_head-plugins.php', array( __CLASS__, 'styles' ) );
}
/**
* Adds the badge to a plugin's row.
*
* @param string[] $meta Row meta links.
* @param string $plugin_file Plugin file, relative to the plugins folder.
* @param array $plugin_data Plugin header data.
* @return string[]
*/
public static function row_meta( $meta, $plugin_file, $plugin_data = array() ) {
$badge = self::badge( (string) $plugin_file, is_array( $plugin_data ) ? $plugin_data : array() );
if ( '' !== $badge ) {
$meta[] = $badge;
}
return $meta;
}
/**
* The badge for one plugin, or '' when there's nothing to show.
*
* @param string $plugin_file Plugin file, relative to the plugins folder.
* @param array $plugin_data Plugin header data (Version).
* @return string HTML.
*/
public static function badge( $plugin_file, array $plugin_data = array() ) {
if ( ! Capabilities::current_user_can_scan() ) {
return '';
}
$latest = self::latest();
if ( ! $latest ) {
return '';
}
$slug = '.' === dirname( $plugin_file ) ? basename( $plugin_file, '.php' ) : dirname( $plugin_file );
$target = $latest['target'];
$url = Page::url() . '#plugin:' . rawurlencode( $slug );
if ( ! isset( $latest['plugins'][ $slug ] ) ) {
// Not in the scan: installed since, left out by the settings, or a must-use plugin.
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$active = is_plugin_active( $plugin_file ) || ( is_multisite() && is_plugin_active_for_network( $plugin_file ) );
if ( ! $active || in_array( 'plugin:' . $slug, (array) Settings::get( 'ignore' ), true ) ) {
return '';
}
return self::html(
'pending',
/* translators: %s: PHP version. */
sprintf( __( 'PHP %s: not checked yet', 'airworthy' ), $target ),
__( 'Airworthy hasn\'t checked this plugin yet: it was added after the last scan. Run a new scan to check it.', 'airworthy' ),
Page::url()
);
}
$row = $latest['plugins'][ $slug ];
$version = isset( $plugin_data['Version'] ) ? (string) $plugin_data['Version'] : '';
if ( '' !== $version && '' !== $row['version'] && $version !== $row['version'] ) {
return self::html(
'pending',
/* translators: %s: PHP version. */
sprintf( __( 'PHP %s: not checked since the update', 'airworthy' ), $target ),
/* translators: %s: version number. */
sprintf( __( 'Airworthy checked version %s. It checks updated plugins again by itself; open Airworthy to see the latest results.', 'airworthy' ), $row['version'] ),
$url
);
}
$labels = self::labels();
if ( ! isset( $labels[ $row['verdict'] ] ) ) {
return ''; // No verdict yet (a stopped scan).
}
list( $label, $explain ) = $labels[ $row['verdict'] ];
return self::html(
$row['verdict'],
/* translators: 1: PHP version, 2: verdict, e.g. "Ready". */
sprintf( __( 'PHP %1$s: %2$s', 'airworthy' ), $target, $label ),
sprintf( $explain, $target ),
$url
);
}
/**
* Badge markup.
*
* @param string $kind Verdict, or "pending".
* @param string $text Badge text.
* @param string $title Longer explanation (tooltip and screen readers).
* @param string $url Link.
* @return string
*/
private static function html( $kind, $text, $title, $url ) {
return sprintf(
'%4$s %5$s',
esc_attr( $kind ),
esc_url( $url ),
esc_attr( $title ),
esc_html__( 'Airworthy:', 'airworthy' ),
esc_html( $text )
);
}
/**
* Short verdict labels and their one-line meaning (%s: PHP version).
*
* @return array
*/
private static function labels() {
return array(
'blocker' => array(
__( 'Blocker', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'Some of this plugin\'s code will not work on PHP %s. See what and why in Airworthy.', 'airworthy' ),
),
'unknown' => array(
__( 'Unknown', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'Some of this plugin\'s files could not be checked for PHP %s.', 'airworthy' ),
),
'suppressed' => array(
__( 'Suppressed by author', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'Its authors marked some code as intentional. Usually safe on PHP %s, but not verified.', 'airworthy' ),
),
'guarded' => array(
__( 'Guarded legacy code', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'Old code that never runs on PHP %s. Safe.', 'airworthy' ),
),
'warnings' => array(
__( 'Warnings', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'Works on PHP %s. Some code will need updating before a later PHP version.', 'airworthy' ),
),
'ready' => array(
__( 'Ready', 'airworthy' ),
/* translators: %s: PHP version. */
__( 'No problems found for PHP %s.', 'airworthy' ),
),
);
}
/**
* The latest finished scan's plugin verdicts, or false when there's none that applies.
*
* @return array{target:string,plugins:array}|false
*/
private static function latest() {
if ( null !== self::$latest ) {
return self::$latest;
}
global $wpdb;
$t = Installer::tables();
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Own tables, one query per page load.
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT s.target_php, c.slug, c.verdict, c.version FROM %i s JOIN %i c ON c.scan_id = s.id WHERE s.id = ( SELECT MAX(id) FROM %i WHERE status = 'complete' ) AND c.type = 'plugin'", $t['scans'], $t['components'], $t['scans'] ) );
if ( ! $rows || version_compare( $rows[0]->target_php, Targets::minor( \Airworthy\Env::php_version() ), '<=' ) ) {
self::$latest = false; // No scan, or the server already runs the version it checked.
return false;
}
$plugins = array();
foreach ( $rows as $r ) {
$plugins[ $r->slug ] = array(
'verdict' => (string) $r->verdict,
'version' => (string) $r->version,
);
}
self::$latest = array(
'target' => (string) $rows[0]->target_php,
'plugins' => $plugins,
);
return self::$latest;
}
/**
* Forgets the cached scan (tests, and after a scan finishes in the same request).
*/
public static function reset() {
self::$latest = null;
}
/**
* Badge styles, on the Plugins screen only.
*/
public static function styles() {
if ( ! Capabilities::current_user_can_scan() ) {
return;
}
?>