v0.5.1
This commit is contained in:
parent
90ac22a845
commit
061329c704
11 changed files with 114 additions and 25 deletions
|
|
@ -333,6 +333,20 @@ class Robotstxt_Manager_Settings {
|
|||
return is_string( $raw ) ? $raw : '';
|
||||
}
|
||||
|
||||
// Account keys are UUIDs issued by the store; reject anything that
|
||||
// cannot be one rather than storing a mangled key that only fails
|
||||
// later at connection time.
|
||||
if ( ! preg_match( '/^[a-f0-9][a-f0-9-]{7,126}$/i', $plain ) ) {
|
||||
add_settings_error(
|
||||
'robotstxt_manager_api_key',
|
||||
'invalid_api_key',
|
||||
esc_html__( 'The API key format is invalid. Copy the full key from your ROBOTSTXT account page.', 'robotstxt-manager' )
|
||||
);
|
||||
|
||||
$raw = get_option( 'robotstxt_manager_api_key', '' );
|
||||
return is_string( $raw ) ? $raw : '';
|
||||
}
|
||||
|
||||
delete_transient( 'robotstxt_manager_catalog' );
|
||||
|
||||
return Robotstxt_Manager_Encryption::encrypt( $plain );
|
||||
|
|
|
|||
|
|
@ -1,5 +1,30 @@
|
|||
== Changelog ==
|
||||
|
||||
= 0.5.1 =
|
||||
|
||||
_Release date: 2026-08-15_
|
||||
|
||||
**Highlights**
|
||||
|
||||
* Stabilization release after a full code + security audit (fresh-context review per AGENTS-deploy.md) and full-range compatibility scans (PHPCompatibility 5.6–8.5, wp-compat laddering). Declared requirements now match the real floors: **WordPress 4.4+** (was 4.7) and **PHP 8.0+** (was 7.4 — the updater's `mixed` type hints and `str_contains()` require 8.0; previously under-declared, which would have been a fatal on 7.4).
|
||||
|
||||
**Fixed**
|
||||
|
||||
* `test_connection()` reported "Connected." on non-200 responses and `get_catalog()` cached auth failures as an empty catalog for the full TTL. Both now check the HTTP status; non-200 responses return an error/empty and are never cached.
|
||||
* `site_domain()` used `ltrim( $host, 'www.' )`, which strips a character set and mangles hosts starting with `w` (e.g. `webdev.example.com` → `ebdev…`, breaking premium package URLs). Now strips only the literal `www.` prefix.
|
||||
* Native-update integration no longer injects premium `response` entries when no decryptable API key exists (the native updater would download into a 403).
|
||||
* Opt-in uninstall: also deletes the `update_plugins` site transient (premium entries embed the API key in the package URL — the plaintext copy must not outlive the plugin) and stops deleting a phantom `robotstxt_manager_db_version` option nothing ever wrote.
|
||||
|
||||
**Changed**
|
||||
|
||||
* API-key setting validates the format (UUID-like, 8–127 chars) before storing, rejecting mangled input at save time instead of failing later at connection time.
|
||||
* Plugin version 0.5.0 → 0.5.1. No database schema changes (no custom tables).
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 4.4 - 7.1 (scan-verified: wp-compat clean from 4.4)
|
||||
* PHP: 8.0 - 8.5 (scan-verified: PHPCompatibility + manual feature audit)
|
||||
|
||||
= 0.5.0 =
|
||||
|
||||
_Release date: 2026-08-14_
|
||||
|
|
|
|||
|
|
@ -146,8 +146,18 @@ class Robotstxt_Manager_Core_Client {
|
|||
);
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$count = is_array( json_decode( $body, true ) ) ? count( json_decode( $body, true ) ) : 0;
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( 200 !== $code ) {
|
||||
return array(
|
||||
'ok' => false,
|
||||
/* translators: %d: HTTP status code. */
|
||||
'message' => sprintf( __( 'The store responded with HTTP %d. Check the Store URL and API key.', 'robotstxt-manager' ), $code ),
|
||||
);
|
||||
}
|
||||
|
||||
$decoded = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$count = is_array( $decoded ) ? count( $decoded ) : 0;
|
||||
|
||||
return array(
|
||||
'ok' => true,
|
||||
|
|
@ -191,6 +201,12 @@ class Robotstxt_Manager_Core_Client {
|
|||
return array();
|
||||
}
|
||||
|
||||
// A non-200 response (auth failure, outage) must not be cached as an
|
||||
// "empty catalog" for the full TTL — return nothing and retry next time.
|
||||
if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,13 @@ class Robotstxt_Manager_Updater {
|
|||
}
|
||||
|
||||
if ( version_compare( $version, $new_version, '<' ) ) {
|
||||
// Premium updates need the account key in the package URL;
|
||||
// without a usable key the native updater would only hit a
|
||||
// 403, so skip injecting the entry.
|
||||
if ( 'premium' === $entry['type'] && ! $this->has_api_key() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( property_exists( $transient, 'response' ) && is_array( $transient->response ) ) {
|
||||
$transient->response[ $plugin_file ] = $this->build_update_object( $slug, (string) $plugin_file, $entry );
|
||||
}
|
||||
|
|
@ -260,6 +267,18 @@ class Robotstxt_Manager_Updater {
|
|||
return (object) $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a decryptable account API key is configured.
|
||||
*
|
||||
* @return bool True when a usable key exists.
|
||||
*/
|
||||
private function has_api_key(): bool {
|
||||
$api_key_raw = get_option( 'robotstxt_manager_api_key', '' );
|
||||
$api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : '';
|
||||
|
||||
return '' !== $api_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the download package URL for the native upgrader.
|
||||
*
|
||||
|
|
@ -298,9 +317,10 @@ class Robotstxt_Manager_Updater {
|
|||
* @return string Domain (e.g. 'example.com').
|
||||
*/
|
||||
private function site_domain(): string {
|
||||
$host = (string) wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
$host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
|
||||
|
||||
return strtolower( ltrim( $host, 'www.' ) );
|
||||
// Strip the literal "www." prefix (ltrim would eat any leading w/.).
|
||||
return (string) preg_replace( '/^www\./', '', $host );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
25
readme.txt
25
readme.txt
|
|
@ -1,11 +1,11 @@
|
|||
=== Manager (by ROBOTSTXT) ===
|
||||
Contributors: javiercasares, robotstxt
|
||||
Tags: dashboard, catalog, updates, subscriptions, management
|
||||
Requires at least: 4.7
|
||||
Requires at least: 4.4
|
||||
Tested up to: 7.1
|
||||
Stable tag: 0.5.0
|
||||
Requires PHP: 7.4
|
||||
Version: 0.5.0
|
||||
Stable tag: 0.5.1
|
||||
Requires PHP: 8.0
|
||||
Version: 0.5.1
|
||||
License: GPL-3.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ No. This plugin is intentionally not compatible with WordPress Multisite, matchi
|
|||
|
||||
= What PHP version is required? =
|
||||
|
||||
PHP 8.4 or higher.
|
||||
PHP 8.0 or higher.
|
||||
|
||||
= Do I need Plugins Core installed on my site? =
|
||||
|
||||
|
|
@ -84,11 +84,22 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres
|
|||
|
||||
== Compatibility ==
|
||||
|
||||
* WordPress: 4.7 - 7.1
|
||||
* PHP: 7.4 - 8.5
|
||||
* WordPress: 4.4 - 7.1
|
||||
* PHP: 8.0 - 8.5
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 0.5.1 =
|
||||
|
||||
_Release date: 2026-08-15_
|
||||
|
||||
* Stabilization release: full code/security audit, compatibility scans, and documentation alignment. Real floors declared: WordPress 4.4+, PHP 8.0+ (previously declared 4.7/7.4).
|
||||
* Connection test and catalog fetch now check the HTTP status code — an unauthorized or failing store reports an error instead of "Connected" / an empty catalog (non-200 responses are no longer cached).
|
||||
* API-key field validates the key format before storing.
|
||||
* Premium plugins are not offered as native updates when no API key is configured (they would only fail with HTTP 403).
|
||||
* Domain normalization for premium package URLs strips only the literal `www.` prefix (hosts starting with "w" were mangled).
|
||||
* Opt-in uninstall now also purges the WordPress update transient (premium entries carry the API key in their package URL) and drops a phantom option.
|
||||
|
||||
= 0.5.0 =
|
||||
|
||||
_Release date: 2026-08-14_
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
* Plugin Name: Manager (by ROBOTSTXT)
|
||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager
|
||||
* Description: Client-side dashboard for the ROBOTSTXT plugin ecosystem. Lists the catalog from a remote Plugins Core install, resolves local install/update state, and installs, activates, and updates plugins directly from the store.
|
||||
* Version: 0.5.0
|
||||
* Requires at least: 4.7
|
||||
* Requires PHP: 7.4
|
||||
* Version: 0.5.1
|
||||
* Requires at least: 4.4
|
||||
* Requires PHP: 8.0
|
||||
* Author: ROBOTSTXT
|
||||
* Author URI: https://www.robotstxt.es/
|
||||
* License: GPL-3.0-or-later
|
||||
|
|
@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
/** Plugin version. */
|
||||
define( 'ROBOTSTXT_MANAGER_VERSION', '0.5.0' );
|
||||
define( 'ROBOTSTXT_MANAGER_VERSION', '0.5.1' );
|
||||
|
||||
/** Absolute path to the plugin directory, with trailing slash. */
|
||||
define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) );
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ $option_keys = array(
|
|||
'robotstxt_manager_api_key',
|
||||
'robotstxt_manager_cache_ttl_minutes',
|
||||
'robotstxt_manager_delete_data_on_uninstall',
|
||||
'robotstxt_manager_db_version',
|
||||
);
|
||||
|
||||
foreach ( $option_keys as $key ) {
|
||||
|
|
@ -26,3 +25,7 @@ foreach ( $option_keys as $key ) {
|
|||
}
|
||||
|
||||
delete_transient( 'robotstxt_manager_catalog' );
|
||||
|
||||
// Purge the WordPress update transient: premium entries carry the API key
|
||||
// in their package URL. WordPress rebuilds it on the next update check.
|
||||
delete_site_transient( 'update_plugins' );
|
||||
|
|
|
|||
2
vendor/autoload.php
vendored
2
vendor/autoload.php
vendored
|
|
@ -19,4 +19,4 @@ if (PHP_VERSION_ID < 50600) {
|
|||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef::getLoader();
|
||||
return ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101::getLoader();
|
||||
|
|
|
|||
8
vendor/composer/autoload_real.php
vendored
8
vendor/composer/autoload_real.php
vendored
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef
|
||||
class ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
|
|
@ -24,12 +24,12 @@ class ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef
|
|||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef', 'loadClassLoader'), true, true);
|
||||
spl_autoload_register(array('ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
|
|
|
|||
4
vendor/composer/autoload_static.php
vendored
4
vendor/composer/autoload_static.php
vendored
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef
|
||||
class ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101
|
||||
{
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
|
|
@ -22,7 +22,7 @@ class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef
|
|||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->classMap = ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::$classMap;
|
||||
$loader->classMap = ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
|
|
|
|||
4
vendor/composer/platform_check.php
vendored
4
vendor/composer/platform_check.php
vendored
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 70400)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
|
||||
if (!(PHP_VERSION_ID >= 80000)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue