v1.9.1
This commit is contained in:
parent
27ce69c2d2
commit
bb3beaa353
12 changed files with 465 additions and 147 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -379,7 +379,9 @@ class Robotstxt_Manager_Installer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates an installed plugin to the latest catalog version.
|
||||
* Updates an installed plugin to the latest catalog version — or, when a
|
||||
* security patch is declared for the exact installed version (Core
|
||||
* 1.16.0+), to that patch instead of the feature mainline.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
|
@ -394,7 +396,17 @@ class Robotstxt_Manager_Installer {
|
|||
$this->redirect_error( __( 'Plugin is not installed.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
$result = $this->download_and_install( $slug, true );
|
||||
$patch = '';
|
||||
|
||||
$entry = $this->find_catalog_entry( $slug );
|
||||
|
||||
if ( is_array( $entry ) ) {
|
||||
$all = get_plugins();
|
||||
$version = isset( $all[ $file ]['Version'] ) && is_string( $all[ $file ]['Version'] ) ? $all[ $file ]['Version'] : '';
|
||||
$patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $version );
|
||||
}
|
||||
|
||||
$result = $this->download_and_install( $slug, true, $patch );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$this->redirect_error( $result->get_error_message() );
|
||||
|
|
@ -502,10 +514,12 @@ class Robotstxt_Manager_Installer {
|
|||
*
|
||||
* @param string $slug Plugin slug.
|
||||
* @param bool $overwrite Whether to overwrite an existing install (update).
|
||||
* @param string $security_version Patch version to download instead of the
|
||||
* stable mainline (Core 1.16.0+), '' for stable.
|
||||
*
|
||||
* @return true|WP_Error True on success.
|
||||
*/
|
||||
private function download_and_install( string $slug, bool $overwrite = false ) {
|
||||
private function download_and_install( string $slug, bool $overwrite = false, string $security_version = '' ) {
|
||||
$client = Robotstxt_Manager_Core_Client::from_options();
|
||||
|
||||
if ( ! $client->is_configured() ) {
|
||||
|
|
@ -529,15 +543,26 @@ class Robotstxt_Manager_Installer {
|
|||
// fetched directly (no auth). Everything else goes through Core's
|
||||
// authenticated download endpoint (account API key as Bearer), with
|
||||
// this site's domain for per-domain license binding (Core 1.11.0+).
|
||||
$use_download_endpoint = ! ( $is_free && '' !== $dl_url );
|
||||
// Security patches always stream through the endpoint with a version
|
||||
// parameter — the public URL only carries the mainline stable ZIP.
|
||||
$use_download_endpoint = '' !== $security_version || ! ( $is_free && '' !== $dl_url );
|
||||
|
||||
$zip_url = $use_download_endpoint
|
||||
? add_query_arg(
|
||||
'domain',
|
||||
rawurlencode( $this->site_domain() ),
|
||||
if ( $use_download_endpoint ) {
|
||||
$dl_args = array(
|
||||
'domain' => rawurlencode( $this->site_domain() ),
|
||||
);
|
||||
|
||||
if ( '' !== $security_version ) {
|
||||
$dl_args['version'] = rawurlencode( $security_version );
|
||||
}
|
||||
|
||||
$zip_url = add_query_arg(
|
||||
$dl_args,
|
||||
$client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download'
|
||||
)
|
||||
: $dl_url;
|
||||
);
|
||||
} else {
|
||||
$zip_url = $dl_url;
|
||||
}
|
||||
|
||||
$tmp_file = wp_tempnam( $slug . '.zip' );
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* @var array<string, array{installed:bool, active:bool, version:string}> $local
|
||||
* @var array<string, array<string,mixed>> $manager_subscriptions
|
||||
* @var list<array{type:string, message:string}> $manager_notices
|
||||
* @var list<array{slug:string, name:string, installed:string, patch:string}> $manager_security_updates
|
||||
* @var bool $manager_has_api_key
|
||||
* @var string $manager_store_url
|
||||
*/
|
||||
|
|
@ -79,6 +80,25 @@ $compat_warnings = 0;
|
|||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php foreach ( ( $manager_security_updates ?? array() ) as $manager_security ) : ?>
|
||||
<div class="notice notice-error">
|
||||
<p>
|
||||
<?php
|
||||
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( $manager_security['name'] ),
|
||||
esc_html( $manager_security['installed'] ),
|
||||
esc_html( $manager_security['patch'] ),
|
||||
esc_url( Robotstxt_Manager_Admin::action_url( 'update', $manager_security['slug'] ) )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ( empty( $manager_has_api_key ) && '' !== ( $manager_store_url ?? '' ) ) : ?>
|
||||
<div class="notice notice-info">
|
||||
<p>
|
||||
|
|
@ -217,7 +237,15 @@ $compat_warnings = 0;
|
|||
$l_version = is_string( $raw_lv ) ? $raw_lv : '';
|
||||
|
||||
$update_available = $l_installed && '' !== $remote_v && '' !== $l_version
|
||||
&& version_compare( $l_version, $remote_v, '<' );
|
||||
&& version_compare( $l_version, $remote_v, '<' );
|
||||
|
||||
// Security patch declared for exactly the installed version:
|
||||
// the Update action installs the patch, not the mainline.
|
||||
$security_patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $l_version );
|
||||
|
||||
if ( '' !== $security_patch ) {
|
||||
$update_available = true;
|
||||
}
|
||||
|
||||
$row_class = ( ! $wp_ok || ! $php_ok ) ? ' robotstxt-manager-row--incompatible' : '';
|
||||
?>
|
||||
|
|
@ -346,6 +374,15 @@ $compat_warnings = 0;
|
|||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--not-installed">' . esc_html__( 'Not installed', 'robotstxt-manager' ) . '</span>';
|
||||
} elseif ( ! $l_active ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--inactive">' . esc_html__( 'Installed (inactive)', 'robotstxt-manager' ) . '</span>';
|
||||
} elseif ( '' !== $security_patch ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--security">' . esc_html(
|
||||
sprintf(
|
||||
/* translators: 1: installed version, 2: security patch version. */
|
||||
__( 'Security update available (v%1$s → v%2$s)', 'robotstxt-manager' ),
|
||||
$l_version,
|
||||
$security_patch
|
||||
)
|
||||
) . '</span>';
|
||||
} elseif ( $update_available ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--update">' . esc_html(
|
||||
sprintf(
|
||||
|
|
@ -433,6 +470,7 @@ $compat_warnings = 0;
|
|||
.robotstxt-manager-compat--fail { color: #d63638; font-weight: 600; }
|
||||
.robotstxt-manager-compat-warning { cursor: help; }
|
||||
.robotstxt-manager-row--incompatible { background-color: #fef7f0 !important; }
|
||||
.robotstxt-manager-state--security { color: #d63638; font-weight: 600; }
|
||||
|
||||
/* Detail rows (always open): muted, attached to the row above. */
|
||||
.robotstxt-manager-detail-row td { border-top: none !important; padding-top: 0; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue