This commit is contained in:
Javier Casares 2026-08-24 06:42:23 +00:00
commit c7a8d9efcc
7 changed files with 123 additions and 27 deletions

View file

@ -527,11 +527,16 @@ class Robotstxt_Manager_Installer {
// Free plugins that publish a public download URL in the catalog are
// 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 );
$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;
$tmp_file = wp_tempnam( $slug . '.zip' );
@ -581,13 +586,25 @@ class Robotstxt_Manager_Installer {
if ( 200 !== $code ) {
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(
'robotstxt_manager_http',
sprintf(
/* translators: %d: HTTP status code. */
__( 'Download failed (HTTP %d).', 'robotstxt-manager' ),
$code
)
'' !== $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. */
__( 'Download failed (HTTP %d).', 'robotstxt-manager' ),
$code
)
);
}
@ -700,6 +717,26 @@ class Robotstxt_Manager_Installer {
*
* @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 {
if ( ! file_exists( $file ) ) {
return false;

View file

@ -278,7 +278,7 @@ $compat_warnings = 0;
if ( $days_left >= 0 && $days_left <= 14 ) {
$sub_text = sprintf(
/* translators: %d: days remaining. */
/* translators: %d: days remaining. */
__( 'Subscribed — %d days left', 'robotstxt-manager' ),
$days_left
);
@ -292,6 +292,17 @@ $compat_warnings = 0;
} elseif ( 'expired' === $sub_status ) {
$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 ) {