' . esc_html__( 'Open', 'afb-update-watch' ) . '' );
return $links;
}
/**
* Warns on the dashboard, Plugins and Updates screens when something needs checking.
*
* @return void
*/
public static function notices() {
if ( ! current_user_can( self::capability() ) ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
$ids = array( 'dashboard', 'plugins', 'update-core', 'dashboard-network', 'plugins-network', 'update-core-network' );
if ( ! $screen || ! in_array( $screen->id, $ids, true ) ) {
return;
}
$flagged = Registry::flagged_files();
if ( $flagged ) {
printf(
'
',
esc_html__( 'AFB Update Watch:', 'afb-update-watch' ),
esc_html(
sprintf(
/* translators: %d: number of plugins. */
_n( '%d plugin has changed owner or been closed on WordPress.org.', '%d plugins have changed owner or been closed on WordPress.org.', count( $flagged ), 'afb-update-watch' ),
count( $flagged )
)
),
esc_url( self::page_url() ),
esc_html__( 'Review now', 'afb-update-watch' )
);
}
$woo = Store::woo();
if ( Woo_Guard::enabled() && $woo['version'] && $woo['allowed'] !== $woo['version'] ) {
printf(
'',
esc_html__( 'AFB Update Watch:', 'afb-update-watch' ),
esc_html(
sprintf(
/* translators: 1: WooCommerce version, 2: number of extensions. */
_n( 'WooCommerce %1$s is on hold because %2$d active extension is not tested with it.', 'WooCommerce %1$s is on hold because %2$d active extensions are not tested with it.', count( $woo['untested'] ), 'afb-update-watch' ),
$woo['version'],
count( $woo['untested'] )
)
),
esc_url( self::page_url() ),
esc_html__( 'See which', 'afb-update-watch' )
);
}
}
/**
* Adds a warning row under a flagged plugin on the Plugins screen.
*
* @param string $file Plugin file.
* @return void
*/
public static function plugin_row( $file ) {
if ( ! current_user_can( self::capability() ) ) {
return;
}
$flagged = Registry::flagged_files();
if ( ! isset( $flagged[ $file ] ) ) {
return;
}
global $wp_list_table;
$cols = ( $wp_list_table && method_exists( $wp_list_table, 'get_column_count' ) ) ? (int) $wp_list_table->get_column_count() : 4;
$hold = Holds::enabled() ? ' ' . __( 'Updates are on hold until you review it.', 'afb-update-watch' ) : '';
printf(
' |
',
(int) $cols,
esc_html( Notifier::describe( $flagged[ $file ]['slug'], $flagged[ $file ] ) ),
esc_html( $hold ),
esc_url( self::page_url() ),
esc_html__( 'Review', 'afb-update-watch' )
);
}
/**
* A form that posts one action, with its nonce.
*
* @param string $action Action name without prefix.
* @param string $label Button label.
* @param array $fields Hidden fields.
* @param string $css Button class.
* @return void
*/
private static function button( $action, $label, array $fields = array(), $css = 'button' ) {
echo '';
}
/**
* Renders the screen.
*
* @return void
*/
public static function render() {
if ( ! current_user_can( self::capability() ) ) {
wp_die( esc_html__( 'You do not have permission to view this page.', 'afb-update-watch' ) );
}
$state = Store::state();
$settings = Store::settings();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only, set by our own redirect.
$done = isset( $_GET['afbuw'] ) ? sanitize_key( wp_unslash( $_GET['afbuw'] ) ) : '';
$msgs = array(
'checked' => __( 'Check complete.', 'afb-update-watch' ),
'released' => __( 'Hold released. That plugin can update again and its current owner is the new baseline.', 'afb-update-watch' ),
'allowed' => __( 'That WooCommerce version can now update.', 'afb-update-watch' ),
'saved' => __( 'Settings saved.', 'afb-update-watch' ),
);
echo '' . esc_html__( 'AFB Update Watch', 'afb-update-watch' ) . '
';
if ( isset( $msgs[ $done ] ) ) {
echo '
' . esc_html( $msgs[ $done ] ) . '
';
}
echo '
' . esc_html__( 'Plugins from WordPress.org are checked once a day. If one changes owner or is closed, you are told and its updates are held until you have reviewed it. Plugins bought by a new owner have been used to push malicious updates to every site running them.', 'afb-update-watch' ) . '
';
echo '
' . esc_html(
$state['last_run']
/* translators: %s: how long ago. */
? sprintf( __( 'Last checked %s ago.', 'afb-update-watch' ), human_time_diff( $state['last_run'] ) )
: __( 'Not checked yet.', 'afb-update-watch' )
) . ' ';
self::button( 'check', __( 'Check now', 'afb-update-watch' ), array(), 'button button-primary' );
echo '
';
self::render_woo();
self::render_table( $state['plugins'] );
self::render_settings( $settings );
echo '
';
}
/**
* The WooCommerce section.
*
* @return void
*/
private static function render_woo() {
$installed = Woo_Guard::installed_version();
if ( '' === $installed ) {
return;
}
echo '' . esc_html__( 'WooCommerce updates', 'afb-update-watch' ) . '
';
if ( ! Woo_Guard::enabled() ) {
echo '' . esc_html__( 'The WooCommerce guard is switched off.', 'afb-update-watch' ) . '
';
return;
}
$offered = Woo_Guard::offered_version();
$untested = $offered ? Woo_Guard::untested( $offered ) : array();
$record = Store::woo();
if ( ! $offered ) {
/* translators: %s: WooCommerce version. */
echo '' . esc_html( sprintf( __( 'WooCommerce %s is up to date.', 'afb-update-watch' ), $installed ) ) . '
';
return;
}
if ( ! $untested ) {
/* translators: 1: offered version, 2: installed version. */
echo '' . esc_html( sprintf( __( 'WooCommerce %1$s is available (you have %2$s). Every active extension is marked as tested with it, so it can update.', 'afb-update-watch' ), $offered, $installed ) ) . '
';
return;
}
if ( $record['allowed'] === $offered ) {
/* translators: %s: WooCommerce version. */
echo '' . esc_html( sprintf( __( 'You chose to let WooCommerce %s update even though some extensions are untested.', 'afb-update-watch' ), $offered ) ) . '
';
return;
}
/* translators: 1: offered version, 2: installed version. */
echo '' . esc_html( sprintf( __( 'WooCommerce %1$s will not auto-update (you have %2$s) because these active extensions are not marked as tested with it:', 'afb-update-watch' ), $offered, $installed ) ) . '
';
foreach ( $untested as $ext ) {
echo '- ' . esc_html( $ext['name'] ) . ': ' . esc_html(
'' === $ext['tested']
? __( 'no tested version declared', 'afb-update-watch' )
/* translators: %s: version number. */
: sprintf( __( 'tested up to %s', 'afb-update-watch' ), $ext['tested'] )
) . '
';
}
echo '
' . esc_html__( 'Update those extensions first if updates are available, or test on a staging copy. When you are happy:', 'afb-update-watch' ) . ' ';
/* translators: %s: WooCommerce version. */
self::button( 'allow_woo', sprintf( __( 'Let WooCommerce %s update', 'afb-update-watch' ), $offered ), array( 'version' => $offered ) );
echo '
';
}
/**
* The plugin table.
*
* @param array $plugins Watch entries keyed by slug.
* @return void
*/
private static function render_table( array $plugins ) {
echo '' . esc_html__( 'WordPress.org plugins', 'afb-update-watch' ) . '
';
if ( ! $plugins ) {
echo '' . esc_html__( 'Nothing checked yet. Run a check to take a baseline of every plugin\'s owner.', 'afb-update-watch' ) . '
';
return;
}
echo '| ' . esc_html__( 'Plugin', 'afb-update-watch' ) . ' | ' . esc_html__( 'Status', 'afb-update-watch' ) . ' | ' . esc_html__( 'Owner on WordPress.org', 'afb-update-watch' ) . ' | ' . esc_html__( 'Action', 'afb-update-watch' ) . ' |
';
uasort(
$plugins,
static function ( $a, $b ) {
$rank = array(
'owner_changed' => 0,
'closed' => 1,
'ok' => 2,
);
$ra = isset( $rank[ $a['status'] ] ) ? $rank[ $a['status'] ] : 3;
$rb = isset( $rank[ $b['status'] ] ) ? $rank[ $b['status'] ] : 3;
return $ra <=> $rb;
}
);
foreach ( $plugins as $slug => $entry ) {
$owner = isset( $entry['owner'] ) ? $entry['owner'] : ( isset( $entry['baseline'] ) ? $entry['baseline'] : array() );
$author = ! empty( $owner['author'] ) ? basename( $owner['author'] ) : '';
echo '' . esc_html( isset( $entry['name'] ) ? $entry['name'] : $slug ) . '
' . esc_html( $slug ) . ' | ';
if ( 'owner_changed' === $entry['status'] || 'closed' === $entry['status'] ) {
echo '' . esc_html( 'closed' === $entry['status'] ? __( 'Closed', 'afb-update-watch' ) : __( 'Owner changed', 'afb-update-watch' ) ) . ' ' . esc_html( Notifier::describe( $slug, $entry ) );
} elseif ( ! empty( $entry['last_error'] ) ) {
echo esc_html__( 'Could not check', 'afb-update-watch' ) . ' ' . esc_html( $entry['last_error'] ) . '';
} else {
echo esc_html__( 'OK', 'afb-update-watch' );
}
echo ' | ' . esc_html( $author ? $author : '?' );
if ( ! empty( $owner['committers'] ) ) {
$shown = array_slice( $owner['committers'], 0, 6 );
$more = count( $owner['committers'] ) - count( $shown );
$list = implode( ', ', $shown ) . ( $more > 0
/* translators: %d: number of further committers. */
? ' ' . sprintf( _n( 'and %d more', 'and %d more', $more, 'afb-update-watch' ), $more )
: '' );
echo ' ' . esc_html__( 'Committers:', 'afb-update-watch' ) . ' ' . esc_html( $list ) . '';
}
echo ' | ';
if ( 'owner_changed' === $entry['status'] || 'closed' === $entry['status'] ) {
self::button( 'release', __( 'I have checked it, release the hold', 'afb-update-watch' ), array( 'slug' => $slug ) );
}
echo ' |
';
}
echo '
';
}
/**
* The settings form.
*
* @param array $settings Current settings.
* @return void
*/
private static function render_settings( array $settings ) {
echo '' . esc_html__( 'Settings', 'afb-update-watch' ) . '
';
echo '';
}
/**
* Stops anyone without the capability. Every action also checks its own nonce.
*
* @return void
*/
private static function require_capability() {
if ( ! current_user_can( self::capability() ) ) {
wp_die( esc_html__( 'You do not have permission to do that.', 'afb-update-watch' ), '', array( 'response' => 403 ) );
}
}
/**
* Runs a check now.
*
* @return void
*/
public static function handle_check() {
self::require_capability();
check_admin_referer( 'afbuw_check' );
Plugin::instance()->run_check();
wp_safe_redirect( self::page_url( array( 'afbuw' => 'checked' ) ) );
exit;
}
/**
* Releases a hold.
*
* @return void
*/
public static function handle_release() {
self::require_capability();
check_admin_referer( 'afbuw_release' );
$slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : '';
Registry::release( $slug );
wp_safe_redirect( self::page_url( array( 'afbuw' => 'released' ) ) );
exit;
}
/**
* Lets one WooCommerce version through.
*
* @return void
*/
public static function handle_allow_woo() {
self::require_capability();
check_admin_referer( 'afbuw_allow_woo' );
$version = isset( $_POST['version'] ) ? sanitize_text_field( wp_unslash( $_POST['version'] ) ) : '';
if ( preg_match( '/^\d+(\.\d+)*$/', $version ) ) {
Woo_Guard::allow( $version );
}
wp_safe_redirect( self::page_url( array( 'afbuw' => 'allowed' ) ) );
exit;
}
/**
* Saves settings.
*
* @return void
*/
public static function handle_settings() {
self::require_capability();
check_admin_referer( 'afbuw_settings' );
$input = isset( $_POST['afbuw'] ) && is_array( $_POST['afbuw'] ) ? map_deep( wp_unslash( $_POST['afbuw'] ), 'sanitize_text_field' ) : array();
Store::save_settings( $input );
wp_safe_redirect( self::page_url( array( 'afbuw' => 'saved' ) ) );
exit;
}
}