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 : '';
|
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' );
|
delete_transient( 'robotstxt_manager_catalog' );
|
||||||
|
|
||||||
return Robotstxt_Manager_Encryption::encrypt( $plain );
|
return Robotstxt_Manager_Encryption::encrypt( $plain );
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,30 @@
|
||||||
== Changelog ==
|
== 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 =
|
= 0.5.0 =
|
||||||
|
|
||||||
_Release date: 2026-08-14_
|
_Release date: 2026-08-14_
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,18 @@ class Robotstxt_Manager_Core_Client {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$body = wp_remote_retrieve_body( $response );
|
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||||
$count = is_array( json_decode( $body, true ) ) ? count( json_decode( $body, true ) ) : 0;
|
|
||||||
|
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(
|
return array(
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
|
|
@ -191,6 +201,12 @@ class Robotstxt_Manager_Core_Client {
|
||||||
return array();
|
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 );
|
$body = wp_remote_retrieve_body( $response );
|
||||||
$data = json_decode( $body, true );
|
$data = json_decode( $body, true );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,13 @@ class Robotstxt_Manager_Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( version_compare( $version, $new_version, '<' ) ) {
|
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 ) ) {
|
if ( property_exists( $transient, 'response' ) && is_array( $transient->response ) ) {
|
||||||
$transient->response[ $plugin_file ] = $this->build_update_object( $slug, (string) $plugin_file, $entry );
|
$transient->response[ $plugin_file ] = $this->build_update_object( $slug, (string) $plugin_file, $entry );
|
||||||
}
|
}
|
||||||
|
|
@ -260,6 +267,18 @@ class Robotstxt_Manager_Updater {
|
||||||
return (object) $data;
|
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.
|
* Builds the download package URL for the native upgrader.
|
||||||
*
|
*
|
||||||
|
|
@ -298,9 +317,10 @@ class Robotstxt_Manager_Updater {
|
||||||
* @return string Domain (e.g. 'example.com').
|
* @return string Domain (e.g. 'example.com').
|
||||||
*/
|
*/
|
||||||
private function site_domain(): string {
|
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) ===
|
=== Manager (by ROBOTSTXT) ===
|
||||||
Contributors: javiercasares, robotstxt
|
Contributors: javiercasares, robotstxt
|
||||||
Tags: dashboard, catalog, updates, subscriptions, management
|
Tags: dashboard, catalog, updates, subscriptions, management
|
||||||
Requires at least: 4.7
|
Requires at least: 4.4
|
||||||
Tested up to: 7.1
|
Tested up to: 7.1
|
||||||
Stable tag: 0.5.0
|
Stable tag: 0.5.1
|
||||||
Requires PHP: 7.4
|
Requires PHP: 8.0
|
||||||
Version: 0.5.0
|
Version: 0.5.1
|
||||||
License: GPL-3.0-or-later
|
License: GPL-3.0-or-later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
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? =
|
= What PHP version is required? =
|
||||||
|
|
||||||
PHP 8.4 or higher.
|
PHP 8.0 or higher.
|
||||||
|
|
||||||
= Do I need Plugins Core installed on my site? =
|
= 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 ==
|
== Compatibility ==
|
||||||
|
|
||||||
* WordPress: 4.7 - 7.1
|
* WordPress: 4.4 - 7.1
|
||||||
* PHP: 7.4 - 8.5
|
* PHP: 8.0 - 8.5
|
||||||
|
|
||||||
== Changelog ==
|
== 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 =
|
= 0.5.0 =
|
||||||
|
|
||||||
_Release date: 2026-08-14_
|
_Release date: 2026-08-14_
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
* Plugin Name: Manager (by ROBOTSTXT)
|
* Plugin Name: Manager (by ROBOTSTXT)
|
||||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager
|
* 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.
|
* 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
|
* Version: 0.5.1
|
||||||
* Requires at least: 4.7
|
* Requires at least: 4.4
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 8.0
|
||||||
* Author: ROBOTSTXT
|
* Author: ROBOTSTXT
|
||||||
* Author URI: https://www.robotstxt.es/
|
* Author URI: https://www.robotstxt.es/
|
||||||
* License: GPL-3.0-or-later
|
* License: GPL-3.0-or-later
|
||||||
|
|
@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Plugin version. */
|
/** 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. */
|
/** Absolute path to the plugin directory, with trailing slash. */
|
||||||
define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ $option_keys = array(
|
||||||
'robotstxt_manager_api_key',
|
'robotstxt_manager_api_key',
|
||||||
'robotstxt_manager_cache_ttl_minutes',
|
'robotstxt_manager_cache_ttl_minutes',
|
||||||
'robotstxt_manager_delete_data_on_uninstall',
|
'robotstxt_manager_delete_data_on_uninstall',
|
||||||
'robotstxt_manager_db_version',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( $option_keys as $key ) {
|
foreach ( $option_keys as $key ) {
|
||||||
|
|
@ -26,3 +25,7 @@ foreach ( $option_keys as $key ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_transient( 'robotstxt_manager_catalog' );
|
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';
|
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
|
// autoload_real.php @generated by Composer
|
||||||
|
|
||||||
class ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef
|
class ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101
|
||||||
{
|
{
|
||||||
private static $loader;
|
private static $loader;
|
||||||
|
|
||||||
|
|
@ -24,12 +24,12 @@ class ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef
|
||||||
|
|
||||||
require __DIR__ . '/platform_check.php';
|
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__));
|
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';
|
require __DIR__ . '/autoload_static.php';
|
||||||
call_user_func(\Composer\Autoload\ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::getInitializer($loader));
|
call_user_func(\Composer\Autoload\ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101::getInitializer($loader));
|
||||||
|
|
||||||
$loader->register(true);
|
$loader->register(true);
|
||||||
|
|
||||||
|
|
|
||||||
4
vendor/composer/autoload_static.php
vendored
4
vendor/composer/autoload_static.php
vendored
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace Composer\Autoload;
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef
|
class ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101
|
||||||
{
|
{
|
||||||
public static $classMap = array (
|
public static $classMap = array (
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
|
|
@ -22,7 +22,7 @@ class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef
|
||||||
public static function getInitializer(ClassLoader $loader)
|
public static function getInitializer(ClassLoader $loader)
|
||||||
{
|
{
|
||||||
return \Closure::bind(function () use ($loader) {
|
return \Closure::bind(function () use ($loader) {
|
||||||
$loader->classMap = ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::$classMap;
|
$loader->classMap = ComposerStaticInit891e1a90cdf8723a2ca296aab35c6101::$classMap;
|
||||||
|
|
||||||
}, null, ClassLoader::class);
|
}, null, ClassLoader::class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
vendor/composer/platform_check.php
vendored
4
vendor/composer/platform_check.php
vendored
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
$issues = array();
|
$issues = array();
|
||||||
|
|
||||||
if (!(PHP_VERSION_ID >= 70400)) {
|
if (!(PHP_VERSION_ID >= 80000)) {
|
||||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
|
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($issues) {
|
if ($issues) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue