v1.7.0
This commit is contained in:
parent
4f8ab239b0
commit
c7a8d9efcc
7 changed files with 123 additions and 27 deletions
|
|
@ -527,11 +527,16 @@ class Robotstxt_Manager_Installer {
|
||||||
|
|
||||||
// Free plugins that publish a public download URL in the catalog are
|
// Free plugins that publish a public download URL in the catalog are
|
||||||
// fetched directly (no auth). Everything else goes through Core's
|
// fetched directly (no auth). Everything else goes through Core's
|
||||||
// authenticated download endpoint (account API key as Bearer).
|
// 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 );
|
$use_download_endpoint = ! ( $is_free && '' !== $dl_url );
|
||||||
|
|
||||||
$zip_url = $use_download_endpoint
|
$zip_url = $use_download_endpoint
|
||||||
? $client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download'
|
? add_query_arg(
|
||||||
|
'domain',
|
||||||
|
rawurlencode( $this->site_domain() ),
|
||||||
|
$client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download'
|
||||||
|
)
|
||||||
: $dl_url;
|
: $dl_url;
|
||||||
|
|
||||||
$tmp_file = wp_tempnam( $slug . '.zip' );
|
$tmp_file = wp_tempnam( $slug . '.zip' );
|
||||||
|
|
@ -581,9 +586,21 @@ class Robotstxt_Manager_Installer {
|
||||||
if ( 200 !== $code ) {
|
if ( 200 !== $code ) {
|
||||||
wp_delete_file( $tmp_file );
|
wp_delete_file( $tmp_file );
|
||||||
|
|
||||||
|
// Surface the store's own error message (e.g. the per-domain
|
||||||
|
// license "change the domain in your account" explanation).
|
||||||
|
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||||
|
$detail = is_array( $body ) && isset( $body['message'] ) && is_string( $body['message'] ) ? $body['message'] : '';
|
||||||
|
|
||||||
return new WP_Error(
|
return new WP_Error(
|
||||||
'robotstxt_manager_http',
|
'robotstxt_manager_http',
|
||||||
sprintf(
|
'' !== $detail
|
||||||
|
? sprintf(
|
||||||
|
/* translators: 1: HTTP status code, 2: store error message. */
|
||||||
|
__( 'Download failed (HTTP %1$d): %2$s', 'robotstxt-manager' ),
|
||||||
|
$code,
|
||||||
|
$detail
|
||||||
|
)
|
||||||
|
: sprintf(
|
||||||
/* translators: %d: HTTP status code. */
|
/* translators: %d: HTTP status code. */
|
||||||
__( 'Download failed (HTTP %d).', 'robotstxt-manager' ),
|
__( 'Download failed (HTTP %d).', 'robotstxt-manager' ),
|
||||||
$code
|
$code
|
||||||
|
|
@ -700,6 +717,26 @@ class Robotstxt_Manager_Installer {
|
||||||
*
|
*
|
||||||
* @return bool True when the file looks like a valid ZIP archive.
|
* @return bool True when the file looks like a valid ZIP archive.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Returns the normalised domain of the current site.
|
||||||
|
*
|
||||||
|
* @return string Domain (e.g. 'example.com').
|
||||||
|
*/
|
||||||
|
private function site_domain(): string {
|
||||||
|
$host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
|
||||||
|
|
||||||
|
// Strip the literal "www." prefix (ltrim would eat any leading w/).
|
||||||
|
return (string) preg_replace( '/^www\./', '', $host );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a downloaded archive: must exist, be non-empty, and start
|
||||||
|
* with the ZIP magic bytes "PK".
|
||||||
|
*
|
||||||
|
* @param string $file Absolute path to the downloaded file.
|
||||||
|
*
|
||||||
|
* @return bool True when the file looks like a ZIP archive.
|
||||||
|
*/
|
||||||
private function is_valid_zip( string $file ): bool {
|
private function is_valid_zip( string $file ): bool {
|
||||||
if ( ! file_exists( $file ) ) {
|
if ( ! file_exists( $file ) ) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,17 @@ $compat_warnings = 0;
|
||||||
} elseif ( 'expired' === $sub_status ) {
|
} elseif ( 'expired' === $sub_status ) {
|
||||||
$sub_text = __( 'Expired', 'robotstxt-manager' );
|
$sub_text = __( 'Expired', 'robotstxt-manager' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bound domain (per-domain licenses, Core 1.11.0+):
|
||||||
|
// shown once the license is tied to a site.
|
||||||
|
$raw_bound = $sub_row['bound_domain'] ?? '';
|
||||||
|
$bound_text = is_string( $raw_bound ) ? trim( $raw_bound ) : '';
|
||||||
|
|
||||||
|
if ( '' !== $bound_text && in_array( $sub_status, array( 'active', 'payment_failed' ), true ) ) {
|
||||||
|
$sub_text = '' !== $sub_text
|
||||||
|
? $sub_text . ' @ ' . $bound_text
|
||||||
|
: $bound_text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( '' !== $sub_text ) {
|
if ( '' !== $sub_text ) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,37 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.7.0 =
|
||||||
|
|
||||||
|
_Release date: 2026-08-18_
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
* Per-domain license support (Core 1.11.0+): install/update downloads and the download-token exchange now send this site's normalized domain, so premium licenses bind to this site and the store rejects requests from other domains. Catalog subscription pills show the bound domain when the store reports one (e.g. "Subscribed @ example.com").
|
||||||
|
|
||||||
|
**Changed**
|
||||||
|
|
||||||
|
* Download failures now surface the store's own error message from the JSON body (e.g. the domain-mismatch explanation) instead of only "Download failed (HTTP 403)." — falls back to the bare code when no message is present.
|
||||||
|
|
||||||
|
**Tests**
|
||||||
|
|
||||||
|
* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): pass
|
||||||
|
* PHPStan level 9 + wp-compat: pass
|
||||||
|
* PHPUnit: 149 tests, 391 assertions
|
||||||
|
|
||||||
|
= 1.6.2 =
|
||||||
|
|
||||||
|
_Release date: 2026-08-18_
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
* `ROBOTSTXT_MANAGER_NOTICED` presence constant (guarded with `defined()`, value `true`), defined when Manager loads: ecosystem plugins (Core, Mollie, …) detect an active Manager via `defined( 'ROBOTSTXT_MANAGER_NOTICED' )` instead of scanning the plugin list. The guard keeps a double-load or a conflicting definition from fatalling.
|
||||||
|
|
||||||
|
**Tests**
|
||||||
|
|
||||||
|
* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): pass
|
||||||
|
* PHPStan level 9 + wp-compat: pass
|
||||||
|
* PHPUnit: 147 tests, 386 assertions
|
||||||
|
|
||||||
= 1.6.1 =
|
= 1.6.1 =
|
||||||
|
|
||||||
_Release date: 2026-08-18_
|
_Release date: 2026-08-18_
|
||||||
|
|
|
||||||
|
|
@ -356,10 +356,12 @@ class Robotstxt_Manager_Core_Client {
|
||||||
|
|
||||||
$raw_status = $row['status'] ?? '';
|
$raw_status = $row['status'] ?? '';
|
||||||
$raw_expires_at = $row['expires_at'] ?? '';
|
$raw_expires_at = $row['expires_at'] ?? '';
|
||||||
|
$raw_bound = $row['bound_domain'] ?? '';
|
||||||
|
|
||||||
$rows[ $slug ] = array(
|
$rows[ $slug ] = array(
|
||||||
'status' => is_string( $raw_status ) ? $raw_status : '',
|
'status' => is_string( $raw_status ) ? $raw_status : '',
|
||||||
'expires_at' => is_string( $raw_expires_at ) ? $raw_expires_at : '',
|
'expires_at' => is_string( $raw_expires_at ) ? $raw_expires_at : '',
|
||||||
|
'bound_domain' => is_string( $raw_bound ) ? $raw_bound : '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,6 +385,11 @@ class Robotstxt_Manager_Core_Client {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send this site's domain so per-domain license binding is enforced
|
||||||
|
// at token issuance (Core 1.11.0+); older Core ignores the field.
|
||||||
|
$host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
|
||||||
|
$domain = (string) preg_replace( '/^www\./', '', $host );
|
||||||
|
|
||||||
$response = wp_remote_post(
|
$response = wp_remote_post(
|
||||||
$this->store_url . '/wp-json/' . self::REST_NAMESPACE . '/me/download-token',
|
$this->store_url . '/wp-json/' . self::REST_NAMESPACE . '/me/download-token',
|
||||||
array(
|
array(
|
||||||
|
|
@ -391,7 +398,12 @@ class Robotstxt_Manager_Core_Client {
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
),
|
),
|
||||||
'body' => (string) wp_json_encode( array( 'slug' => $slug ) ),
|
'body' => (string) wp_json_encode(
|
||||||
|
array(
|
||||||
|
'slug' => $slug,
|
||||||
|
'domain' => $domain,
|
||||||
|
)
|
||||||
|
),
|
||||||
'timeout' => self::TIMEOUT,
|
'timeout' => self::TIMEOUT,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# This file is distributed under the GPL-3.0-or-later.
|
# This file is distributed under the GPL-3.0-or-later.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Manager (by ROBOTSTXT) 1.6.1\n"
|
"Project-Id-Version: Manager (by ROBOTSTXT) 1.7.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n"
|
"Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
|
||||||
34
readme.txt
34
readme.txt
|
|
@ -3,9 +3,9 @@ Contributors: robotstxt, javiercasares
|
||||||
Tags: dashboard, catalog, updates, subscriptions, management
|
Tags: dashboard, catalog, updates, subscriptions, management
|
||||||
Requires at least: 4.4
|
Requires at least: 4.4
|
||||||
Tested up to: 7.1
|
Tested up to: 7.1
|
||||||
Stable tag: 1.6.1
|
Stable tag: 1.7.0
|
||||||
Requires PHP: 8.0
|
Requires PHP: 8.0
|
||||||
Version: 1.6.1
|
Version: 1.7.0
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -94,29 +94,25 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres
|
||||||
|
|
||||||
Only the 3 last versions. The full changelog will be at changelog.txt
|
Only the 3 last versions. The full changelog will be at changelog.txt
|
||||||
|
|
||||||
|
= 1.7.0 =
|
||||||
|
|
||||||
|
_Release date: 2026-08-18_
|
||||||
|
|
||||||
|
* New: per-domain license support (Core 1.11.0+) — downloads and download-token exchanges now send this site's domain, so premium licenses bind to this site and the store rejects them from other domains. Catalog subscription pills show the bound domain (e.g. "Subscribed @ example.com").
|
||||||
|
* Improvement: download failures now surface the store's own error message (e.g. the domain-mismatch explanation) instead of a bare HTTP status code.
|
||||||
|
|
||||||
|
= 1.6.2 =
|
||||||
|
|
||||||
|
_Release date: 2026-08-18_
|
||||||
|
|
||||||
|
* Added: `ROBOTSTXT_MANAGER_NOTICED` presence constant (guarded, `true`) — ecosystem plugins detect Manager via a constant check instead of scanning the plugin list.
|
||||||
|
|
||||||
= 1.6.1 =
|
= 1.6.1 =
|
||||||
|
|
||||||
_Release date: 2026-08-18_
|
_Release date: 2026-08-18_
|
||||||
|
|
||||||
* Fixed: "headers already sent" warning after saving the settings — the form handler now runs on `admin_init`, before any output.
|
* Fixed: "headers already sent" warning after saving the settings — the form handler now runs on `admin_init`, before any output.
|
||||||
|
|
||||||
= 1.6.0 =
|
|
||||||
|
|
||||||
_Release date: 2026-08-18_
|
|
||||||
|
|
||||||
* Added: WordPress Multisite compatibility — `Network: true` header forces network-wide activation; menus appear in the network admin on Multisite and in the regular admin on single-site. All options and transients use network-level storage (`get_site_option` / `get_site_transient`).
|
|
||||||
* Fixed: API key not saving when the browser auto-fills the password field with the stored encrypted value.
|
|
||||||
* Fixed: "Test connection" now validates the API key against an authenticated Core endpoint (`/me/subscriptions`) instead of the public catalog — invalid keys are correctly rejected.
|
|
||||||
* Fixed: "Test connection" reads the key from the form field, so a key can be tested before saving.
|
|
||||||
* Added: Validation errors now display on the settings page (missing `settings_errors()` call).
|
|
||||||
* Added: Registration link in the API key field description.
|
|
||||||
|
|
||||||
= 1.5.0 =
|
|
||||||
|
|
||||||
_Release date: 2026-08-18_
|
|
||||||
|
|
||||||
* Fixed: API key saving, test connection, settings error display, registration link.
|
|
||||||
|
|
||||||
= Previous versions =
|
= Previous versions =
|
||||||
|
|
||||||
If you want to see the full changelog, visit the [plugin page](https://www.robotstxt.software/plugins/robotstxt-manager/).
|
If you want to see the full changelog, visit the [plugin page](https://www.robotstxt.software/plugins/robotstxt-manager/).
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: Manager (by ROBOTSTXT)
|
* Plugin Name: Manager (by ROBOTSTXT)
|
||||||
* Plugin URI: https://www.robotstxt.software/plugins/robotstxt-manager/
|
* Plugin URI: https://www.robotstxt.software/plugins/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: 1.6.1
|
* Version: 1.7.0
|
||||||
* Requires at least: 4.4
|
* Requires at least: 4.4
|
||||||
* Requires PHP: 8.0
|
* Requires PHP: 8.0
|
||||||
* Update URI: https://www.robotstxt.software/plugins/robotstxt-manager/
|
* Update URI: https://www.robotstxt.software/plugins/robotstxt-manager/
|
||||||
|
|
@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Plugin version. */
|
/** Plugin version. */
|
||||||
define( 'ROBOTSTXT_MANAGER_VERSION', '1.6.1' );
|
define( 'ROBOTSTXT_MANAGER_VERSION', '1.7.0' );
|
||||||
|
|
||||||
/** 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__ ) );
|
||||||
|
|
@ -34,6 +34,14 @@ define( 'ROBOTSTXT_MANAGER_URL', plugin_dir_url( __FILE__ ) );
|
||||||
/** Plugin basename. */
|
/** Plugin basename. */
|
||||||
define( 'ROBOTSTXT_MANAGER_BASENAME', plugin_basename( __FILE__ ) );
|
define( 'ROBOTSTXT_MANAGER_BASENAME', plugin_basename( __FILE__ ) );
|
||||||
|
|
||||||
|
// Presence flag for the ecosystem: other ROBOTSTXT plugins (Core, Mollie…)
|
||||||
|
// check defined( 'ROBOTSTXT_MANAGER_NOTICED' ) to detect that Manager is
|
||||||
|
// active without scanning the plugin list. Guarded so a double-load or a
|
||||||
|
// conflicting definition elsewhere cannot raise a fatal error.
|
||||||
|
if ( ! defined( 'ROBOTSTXT_MANAGER_NOTICED' ) ) {
|
||||||
|
define( 'ROBOTSTXT_MANAGER_NOTICED', true );
|
||||||
|
}
|
||||||
|
|
||||||
// Load Composer autoloader, with a manual fallback for environments where
|
// Load Composer autoloader, with a manual fallback for environments where
|
||||||
// composer install has not been run.
|
// composer install has not been run.
|
||||||
if ( file_exists( ROBOTSTXT_MANAGER_DIR . 'vendor/autoload.php' ) ) {
|
if ( file_exists( ROBOTSTXT_MANAGER_DIR . 'vendor/autoload.php' ) ) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue