This commit is contained in:
Javier Casares 2026-08-17 16:05:38 +00:00
commit e3bfd7b6f2
12 changed files with 108 additions and 29 deletions

View file

@ -72,6 +72,8 @@ class Robotstxt_Manager_Admin {
// Admin notices for subscriptions needing attention. // Admin notices for subscriptions needing attention.
$manager_notices = $this->build_subscription_notices( $catalog, $subscriptions ); $manager_notices = $this->build_subscription_notices( $catalog, $subscriptions );
$manager_subscriptions = $subscriptions; $manager_subscriptions = $subscriptions;
$manager_has_api_key = $client->has_api_key();
$manager_store_url = $client->get_store_url();
require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php'; require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php';
} }

View file

@ -263,7 +263,7 @@ class Robotstxt_Manager_Settings {
} }
echo '</p>'; echo '</p>';
} else { } else {
echo '<p class="description">' . esc_html__( 'Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage.', 'robotstxt-manager' ) . '</p>'; echo '<p class="description">' . esc_html__( 'Account-level API key from the ROBOTSTXT store (create your free account there to get one). Optional — the free catalog works without it — but required to link your subscriptions, install premium plugins, and receive their updates. Encrypted before storage.', 'robotstxt-manager' ) . '</p>';
} }
// Action buttons. // Action buttons.

View file

@ -25,6 +25,8 @@ if ( ! defined( 'ABSPATH' ) ) {
* @var array<string, array{installed:bool, active:bool, version:string}> $local * @var array<string, array{installed:bool, active:bool, version:string}> $local
* @var array<string, array<string,mixed>> $manager_subscriptions * @var array<string, array<string,mixed>> $manager_subscriptions
* @var list<array{type:string, message:string}> $manager_notices * @var list<array{type:string, message:string}> $manager_notices
* @var bool $manager_has_api_key
* @var string $manager_store_url
*/ */
$client_configured = Robotstxt_Manager_Core_Client::from_options()->is_configured(); $client_configured = Robotstxt_Manager_Core_Client::from_options()->is_configured();
@ -77,6 +79,22 @@ $compat_warnings = 0;
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php if ( empty( $manager_has_api_key ) && '' !== ( $manager_store_url ?? '' ) ) : ?>
<div class="notice notice-info">
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: %s: store registration URL. */
__( 'Create your free account at the <a href="%s" target="_blank" rel="noopener noreferrer">ROBOTSTXT store</a> to get your personal API key. The key links your subscriptions to this site and unlocks premium plugins and updates.', 'robotstxt-manager' ),
esc_url( $manager_store_url . '/wp-login.php?action=register' )
)
);
?>
</p>
</div>
<?php endif; ?>
<?php if ( '' !== $notice_message && in_array( $notice_result, array( 'success', 'error' ), true ) ) : ?> <?php if ( '' !== $notice_message && in_array( $notice_result, array( 'success', 'error' ), true ) ) : ?>
<div class="notice notice-<?php echo esc_attr( $notice_result ); ?> is-dismissible"><p><?php echo esc_html( $notice_message ); ?></p></div> <div class="notice notice-<?php echo esc_attr( $notice_result ); ?> is-dismissible"><p><?php echo esc_html( $notice_message ); ?></p></div>
<?php endif; ?> <?php endif; ?>
@ -149,6 +167,14 @@ $compat_warnings = 0;
$website_url = '' !== $page_url ? $page_url : $homepage; $website_url = '' !== $page_url ? $page_url : $homepage;
$has_detail = ( '' !== $website_url || '' !== $desc ); $has_detail = ( '' !== $website_url || '' !== $desc );
// Subscription state: premium plugins are only usable
// (install/update) with an active or in-grace subscription.
$is_premium = ( $cost > 0.0 || 'premium' === $kind );
$sub_row = $manager_subscriptions[ $slug ] ?? null;
$raw_status = is_array( $sub_row ) ? ( $sub_row['status'] ?? '' ) : '';
$sub_status = is_string( $raw_status ) ? $raw_status : '';
$subscribed = in_array( $sub_status, array( 'active', 'payment_failed' ), true );
$raw_reqs = $entry['requires_plugins'] ?? ''; $raw_reqs = $entry['requires_plugins'] ?? '';
$req_slugs = array(); $req_slugs = array();
if ( is_string( $raw_reqs ) && '' !== trim( $raw_reqs ) ) { if ( is_string( $raw_reqs ) && '' !== trim( $raw_reqs ) ) {
@ -220,7 +246,7 @@ $compat_warnings = 0;
</td> </td>
<td> <td>
<?php <?php
if ( $cost > 0.0 || 'premium' === $kind ) { if ( $is_premium ) {
echo esc_html( echo esc_html(
sprintf( sprintf(
/* translators: %s: formatted price number. */ /* translators: %s: formatted price number. */
@ -230,13 +256,10 @@ $compat_warnings = 0;
); );
// Subscription pill for premium plugins. // Subscription pill for premium plugins.
$sub_row = $manager_subscriptions[ $slug ] ?? null;
$sub_text = ''; $sub_text = '';
if ( is_array( $sub_row ) ) { if ( is_array( $sub_row ) ) {
$raw_sub_status = $sub_row['status'] ?? '';
$raw_sub_expiry = $sub_row['expires_at'] ?? ''; $raw_sub_expiry = $sub_row['expires_at'] ?? '';
$sub_status = is_string( $raw_sub_status ) ? $raw_sub_status : '';
$sub_expiry = is_string( $raw_sub_expiry ) ? $raw_sub_expiry : ''; $sub_expiry = is_string( $raw_sub_expiry ) ? $raw_sub_expiry : '';
if ( 'active' === $sub_status && '' !== $sub_expiry ) { if ( 'active' === $sub_status && '' !== $sub_expiry ) {
@ -261,11 +284,8 @@ $compat_warnings = 0;
} }
if ( '' !== $sub_text ) { if ( '' !== $sub_text ) {
$raw_pill_status = is_array( $sub_row ) ? ( $sub_row['status'] ?? '' ) : '';
$pill_status = is_string( $raw_pill_status ) ? $raw_pill_status : '';
echo '<br /><span class="robotstxt-manager-subscription robotstxt-manager-subscription--' echo '<br /><span class="robotstxt-manager-subscription robotstxt-manager-subscription--'
. esc_attr( $pill_status ) . esc_attr( $sub_status )
. '">' . esc_html( $sub_text ) . '</span>'; . '">' . esc_html( $sub_text ) . '</span>';
} }
} else { } else {
@ -274,18 +294,16 @@ $compat_warnings = 0;
?> ?>
</td> </td>
<td> <td>
<?php if ( ! $l_installed ) : ?>
<?php if ( ! $wp_ok || ! $php_ok ) : ?> <?php if ( ! $wp_ok || ! $php_ok ) : ?>
<span class="description"><?php esc_html_e( 'Incompatible', 'robotstxt-manager' ); ?></span> <span class="description"><?php esc_html_e( 'Incompatible', 'robotstxt-manager' ); ?></span>
<?php elseif ( $is_premium && ! $subscribed ) : ?>
<?php if ( '' !== $page_url ) : ?>
<a class="button button-primary" href="<?php echo esc_url( $page_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Buy', 'robotstxt-manager' ); ?></a>
<?php else : ?> <?php else : ?>
<?php if ( $cost > 0.0 && '' !== $page_url ) : ?> &mdash;
<a class="button button-secondary" href="<?php echo esc_url( $page_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Buy', 'robotstxt-manager' ); ?></a>
<?php endif; ?>
<?php
$install_class = ( $cost > 0.0 && '' !== $page_url ) ? 'button-secondary' : 'button-primary';
?>
<a class="button <?php echo esc_attr( $install_class ); ?>" href="<?php echo esc_url( Robotstxt_Manager_Admin::action_url( 'install', $slug ) ); ?>"><?php esc_html_e( 'Install', 'robotstxt-manager' ); ?></a>
<?php endif; ?> <?php endif; ?>
<?php elseif ( ! $l_installed ) : ?>
<a class="button button-primary" href="<?php echo esc_url( Robotstxt_Manager_Admin::action_url( 'install', $slug ) ); ?>"><?php esc_html_e( 'Install', 'robotstxt-manager' ); ?></a>
<?php elseif ( ! $l_active ) : ?> <?php elseif ( ! $l_active ) : ?>
<a class="button button-primary" href="<?php echo esc_url( Robotstxt_Manager_Admin::action_url( 'activate', $slug ) ); ?>"><?php esc_html_e( 'Activate', 'robotstxt-manager' ); ?></a> <a class="button button-primary" href="<?php echo esc_url( Robotstxt_Manager_Admin::action_url( 'activate', $slug ) ); ?>"><?php esc_html_e( 'Activate', 'robotstxt-manager' ); ?></a>
<?php elseif ( $update_available ) : ?> <?php elseif ( $update_available ) : ?>

View file

@ -1,5 +1,24 @@
== Changelog == == Changelog ==
= 1.3.0 =
_Release date: 2026-08-17_
**Highlights**
* The catalog now works **without an API key**: only the Store URL is required. The free catalog, prices, and product pages are public on Core, so a fresh install shows the store immediately. An info notice invites visitors to create their free account at the store to get a personal API key (linking subscriptions and unlocking premium plugins and updates).
**Changed**
* Action buttons follow subscription state: premium plugins without an active subscription show **Buy** only (Install/Update hidden — the download would be rejected); with an active or in-grace subscription (and for free plugins) they show **Install / Activate / Update** as usual. Previously premium rows always offered Install alongside Buy.
* The API-key setting description clarifies the key is optional for browsing, required for subscriptions/premium.
* Plugin version 1.2.1 → 1.3.0. 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)
= 1.2.1 = = 1.2.1 =
_Release date: 2026-08-17_ _Release date: 2026-08-17_

View file

@ -91,12 +91,25 @@ class Robotstxt_Manager_Core_Client {
} }
/** /**
* Returns whether the client has both a URL and a key configured. * Returns whether the client can talk to the store.
*
* Only the Store URL is required: the catalog (free plugins, prices,
* product pages) is public on Core. The API key is optional it is
* needed for subscription data and premium downloads, not for listing.
* *
* @return bool * @return bool
*/ */
public function is_configured(): bool { public function is_configured(): bool {
return '' !== $this->store_url && '' !== $this->api_key; return '' !== $this->store_url;
}
/**
* Returns whether an API key is configured.
*
* @return bool
*/
public function has_api_key(): bool {
return '' !== $this->api_key;
} }
/** /**
@ -269,13 +282,16 @@ class Robotstxt_Manager_Core_Client {
private function get( string $endpoint ) { private function get( string $endpoint ) {
$url = $this->store_url . '/wp-json/' . self::REST_NAMESPACE . $endpoint; $url = $this->store_url . '/wp-json/' . self::REST_NAMESPACE . $endpoint;
$headers = array( 'Accept' => 'application/json' );
if ( '' !== $this->api_key ) {
$headers['Authorization'] = 'Bearer ' . $this->api_key;
}
return wp_remote_get( return wp_remote_get(
$url, $url,
array( array(
'headers' => array( 'headers' => $headers,
'Authorization' => 'Bearer ' . $this->api_key,
'Accept' => 'application/json',
),
'timeout' => self::TIMEOUT, 'timeout' => self::TIMEOUT,
) )
); );

Binary file not shown.

View file

@ -293,3 +293,9 @@ msgid "The subscription for %1$s expires in %2$d day."
msgid_plural "The subscription for %1$s expires in %2$d days." msgid_plural "The subscription for %1$s expires in %2$d days."
msgstr[0] "La subscripció de %1$s caduca en %2$d dia." msgstr[0] "La subscripció de %1$s caduca en %2$d dia."
msgstr[1] "La subscripció de %1$s caduca en %2$d dies." msgstr[1] "La subscripció de %1$s caduca en %2$d dies."
msgid "Create your free account at the <a href=\"%s\" target=\"_blank\" rel=\"noopener noreferrer\">ROBOTSTXT store</a> to get your personal API key. The key links your subscriptions to this site and unlocks premium plugins and updates."
msgstr "Crea el teu compte gratuït a <a href=\"%s\" target=\"_blank\" rel=\"noopener noreferrer\">la botiga de ROBOTSTXT</a> per obtenir la teva clau API personal. La clau vincula les teves subscripcions a aquest lloc i desbloqueja els plugins premium i les seves actualitzacions."
msgid "Account-level API key from the ROBOTSTXT store (create your free account there to get one). Optional — the free catalog works without it — but required to link your subscriptions, install premium plugins, and receive their updates. Encrypted before storage."
msgstr "Clau API a nivell de compte de la botiga de ROBOTSTXT (crea allà el teu compte gratuït per obtenir-la). Opcional: el catàleg de plugins gratuïts funciona sense ella, però és necessària per vincular les teves subscripcions, instal·lar plugins premium i rebre les actualitzacions. Es xifra abans de desar-se."

Binary file not shown.

View file

@ -293,3 +293,9 @@ msgid "The subscription for %1$s expires in %2$d day."
msgid_plural "The subscription for %1$s expires in %2$d days." msgid_plural "The subscription for %1$s expires in %2$d days."
msgstr[0] "La suscripción de %1$s caduca en %2$d día." msgstr[0] "La suscripción de %1$s caduca en %2$d día."
msgstr[1] "La suscripción de %1$s caduca en %2$d días." msgstr[1] "La suscripción de %1$s caduca en %2$d días."
msgid "Create your free account at the <a href=\"%s\" target=\"_blank\" rel=\"noopener noreferrer\">ROBOTSTXT store</a> to get your personal API key. The key links your subscriptions to this site and unlocks premium plugins and updates."
msgstr "Crea tu cuenta gratuita en <a href=\"%s\" target=\"_blank\" rel=\"noopener noreferrer\">la tienda de ROBOTSTXT</a> para obtener tu clave API personal. La clave vincula tus suscripciones a este sitio y desbloquea los plugins premium y sus actualizaciones."
msgid "Account-level API key from the ROBOTSTXT store (create your free account there to get one). Optional — the free catalog works without it — but required to link your subscriptions, install premium plugins, and receive their updates. Encrypted before storage."
msgstr "Clave API a nivel de cuenta de la tienda de ROBOTSTXT (crea allí tu cuenta gratuita para obtenerla). Opcional: el catálogo de plugins gratis funciona sin ella, pero es necesaria para vincular tus suscripciones, instalar plugins premium y recibir sus actualizaciones. Se cifra antes de guardarse."

View file

@ -25,3 +25,9 @@ msgstr ""
msgid "Expired" msgid "Expired"
msgstr "" msgstr ""
msgid "Create your free account at the <a href=\"%s\" target=\"_blank\" rel=\"noopener noreferrer\">ROBOTSTXT store</a> to get your personal API key. The key links your subscriptions to this site and unlocks premium plugins and updates."
msgstr ""
msgid "Account-level API key from the ROBOTSTXT store (create your free account there to get one). Optional — the free catalog works without it — but required to link your subscriptions, install premium plugins, and receive their updates. Encrypted before storage."
msgstr ""

View file

@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt
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.2.1 Stable tag: 1.3.0
Requires PHP: 8.0 Requires PHP: 8.0
Version: 1.2.1 Version: 1.3.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
@ -92,6 +92,12 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres
== Changelog == == Changelog ==
= 1.3.0 =
_Release date: 2026-08-17_
* Catalog works without an API key (only the Store URL is needed); free plugins are listed and installable, and an info notice links to the store to register and get a personal key. Premium plugins show Buy only until subscribed; with an active subscription they show Install/Update.
= 1.2.1 = = 1.2.1 =
_Release date: 2026-08-17_ _Release date: 2026-08-17_

View file

@ -3,7 +3,7 @@
* 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: 1.2.1 * Version: 1.3.0
* Requires at least: 4.4 * Requires at least: 4.4
* Requires PHP: 8.0 * Requires PHP: 8.0
* Author: ROBOTSTXT * Author: ROBOTSTXT
@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) {
} }
/** Plugin version. */ /** Plugin version. */
define( 'ROBOTSTXT_MANAGER_VERSION', '1.2.1' ); define( 'ROBOTSTXT_MANAGER_VERSION', '1.3.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__ ) );