This commit is contained in:
Javier Casares 2026-09-23 06:11:14 +00:00
commit bb3beaa353
12 changed files with 465 additions and 147 deletions

View file

@ -36,6 +36,8 @@ class Robotstxt_Manager_Admin {
$menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu';
$loader->add_action( $menu_hook, $this, 'add_menu' );
$loader->add_action( 'admin_post_robotstxt_manager_refresh_catalog', $this, 'handle_refresh' );
$loader->add_action( 'admin_notices', $this, 'security_update_notices' );
$loader->add_action( 'network_admin_notices', $this, 'security_update_notices' );
}
/**
@ -77,9 +79,103 @@ class Robotstxt_Manager_Admin {
$manager_has_api_key = $client->has_api_key();
$manager_store_url = $client->get_store_url();
// Security patches declared for exactly the versions this site runs.
$manager_security_updates = $this->get_security_updates( $catalog, $local );
require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php';
}
/**
* Returns the security patches that apply to the exact versions this
* site runs (Core 1.16.0+ `security_patches` catalog data).
*
* @param list<array<string,mixed>> $catalog Catalog entries.
* @param array<string, array{installed:bool, active:bool, version:string}> $local Local state by slug.
*
* @return list<array{slug:string, name:string, installed:string, patch:string}>
*/
public function get_security_updates( array $catalog, array $local ): array {
$updates = array();
foreach ( $catalog as $entry ) {
$raw_slug = $entry['slug'] ?? '';
$slug = is_string( $raw_slug ) ? $raw_slug : '';
if ( '' === $slug ) {
continue;
}
$state = $local[ $slug ] ?? null;
if ( ! is_array( $state ) || empty( $state['installed'] ) ) {
continue;
}
$raw_version = $state['version'] ?? '';
$version = is_string( $raw_version ) ? $raw_version : '';
$patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $version );
if ( '' === $patch ) {
continue;
}
$raw_name = $entry['name'] ?? '';
$clean_name = is_string( $raw_name ) && '' !== $raw_name ? $raw_name : $slug;
$updates[] = array(
'slug' => $slug,
'name' => $clean_name,
'installed' => $version,
'patch' => $patch,
);
}
return $updates;
}
/**
* Renders the security-update notices on the Plugins screen (not on the
* Manager catalog page, which shows its own block).
*
* @return void
*/
public function security_update_notices(): void {
if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
$base = ( $screen instanceof WP_Screen ) ? (string) $screen->base : '';
if ( ! in_array( $base, array( 'plugins', 'plugins-network' ), true ) ) {
return;
}
$client = Robotstxt_Manager_Core_Client::from_options();
if ( ! $client->is_configured() ) {
return;
}
$catalog = $client->get_catalog();
$security = $this->get_security_updates( $catalog, $this->resolve_local_state( $catalog ) );
foreach ( $security as $update ) {
echo '<div class="notice notice-error"><p>';
echo wp_kses_post(
sprintf(
/* translators: 1: plugin name, 2: installed version, 3: patch version, 4: update URL. */
__( '<strong>Security update available:</strong> %1$s (v%2$s → v%3$s). <a href="%4$s">Update now</a> — this is a security patch for the version this site runs, not a feature update.', 'robotstxt-manager' ),
esc_html( $update['name'] ),
esc_html( $update['installed'] ),
esc_html( $update['patch'] ),
esc_url( self::action_url( 'update', $update['slug'] ) )
)
);
echo '</p></div>';
}
}
/**
* Builds admin notices for subscriptions that need attention.