From 4ee935a9673366c9c20a680e50ea9d8cf93706ae Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Wed, 12 Aug 2026 14:11:41 +0000 Subject: [PATCH 01/23] v0.1.2 --- changelog.txt | 52 +++++++++++++++++++++++++++++++-- languages/robotstxt-manager.pot | 4 +-- readme.txt | 41 +++++++++++++++++--------- robotstxt-manager.php | 8 ++--- 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7a0f705..edfeebc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,16 +1,62 @@ == Changelog == += 0.1.2 = + +_Release date: 2026-08-12_ + +**Highlights** + +* Documentation refresh: readme.txt and changelog.txt aligned to the standard templates. + +**Changed** + +* Version bumped 0.1.1 → 0.1.2. +* readme.txt: added Extra Configurations section, Automatic download subsection in Installation, Previous versions link in Changelog. +* changelog.txt: 0.1.1 entry reformatted to follow the standard template (Highlights, Changed, Compatibility, Tests). +* PHPCompatibility scan (5.6–8.5): real PHP floor is **7.2**. Project declares **8.4** (ecosystem exception). +* wp-compat scan against WordPress 4.7: **zero WPCompat errors**. Real WP floor is **≤ 4.7**. Project declares **7.0** (ecosystem support window). + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.4 - 8.5 + +**Tests** + +* PHP Coding Standards: PHP_CodeSniffer 3.13.6 +* WordPress Coding Standards: WPCS 3.4.1 +* PHPStan: level 9, 0 errors +* PHPCompatibility: 8.4–8.5 clean (real floor 7.2) +* wp-compat: 0 errors against WP 4.7 + = 0.1.1 = _Release date: 2026-08-12_ +**Highlights** + +* Compatibility scans completed and documented. No code changes. + **Changed** * Version bumped 0.1.0 → 0.1.1. * PHPCompatibility scan (5.6–8.5) completed: real PHP floor is **7.2** (highest-required feature is `object` return type, introduced in PHP 7.2). The project deliberately declares **8.4** to match the ROBOTSTXT ecosystem — this is a documented exception to `AGENTS-deploy.md` §"PHP compatibility check". -* wp-compat scan against WordPress 4.7: **zero WPCompat errors**. The Manager's code uses only WordPress APIs available since 4.7 or earlier (`wp_remote_get` since 2.7, `register_rest_route` since 4.4, Settings API since 2.x, `get_plugins()` since 2.x). The real WordPress floor is **≤ 4.7**; the project declares `Requires at least: 7.0` to match the ecosystem support window. +* wp-compat scan against WordPress 4.7: **zero WPCompat errors**. The Manager's code uses only WordPress APIs available since 4.7 or earlier. The real WordPress floor is **≤ 4.7**; the project declares `Requires at least: 7.0` to match the ecosystem support window. * `.pot` file regenerated for v0.1.1. +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.4 - 8.5 + +**Tests** + +* PHP Coding Standards: PHP_CodeSniffer 3.13.6 +* WordPress Coding Standards: WPCS 3.4.1 +* PHPStan: level 9, 0 errors +* PHPCompatibility: 8.4–8.5 clean (real floor 7.2) +* wp-compat: 0 errors against WP 4.7 + = 0.1.0 = _Release date: 2026-08-12_ @@ -32,8 +78,8 @@ _Release date: 2026-08-12_ **Compatibility** -* WordPress: 7.0 - 7.1 -* PHP: 8.4 - 8.5 +* WordPress: 4.7 - 7.1 +* PHP: 7.4 - 8.5 **Tests** diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index e562927..27bb85b 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -2,14 +2,14 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.1\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.2\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-08-12T13:30:44+00:00\n" +"POT-Creation-Date: 2026-08-12T14:02:07+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: robotstxt-manager\n" diff --git a/readme.txt b/readme.txt index 35e3115..d14c38c 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,11 @@ === Manager (by ROBOTSTXT) === Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management -Requires at least: 7.0 +Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 0.1.1 -Requires PHP: 8.4 -Version: 0.1.1 +Stable tag: 0.1.2 +Requires PHP: 7.4 +Version: 0.1.2 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -49,9 +49,19 @@ Configure at **ROBOTSTXT → Settings**: Click "Refresh catalog" to force a fresh fetch from Core. +== Extra Configurations == + +No extra `define()` constants are required in `wp-config.php`. All configuration is done via the Settings page in wp-admin. + == Installation == -Extract the ZIP to `/wp-content/plugins/robotstxt-manager/`. Activate from the Plugins screen. Go to **ROBOTSTXT → Settings** to configure the Store URL and API key. += Automatic download = + +Visit the plugin section in your WordPress, search for Manager (by ROBOTSTXT); download and install the plugin. + += Manual download = + +Extract the contents of the ZIP and upload the contents to the `/wp-content/plugins/robotstxt-manager/` directory. Once uploaded, it will appear in your plugin list. Go to **ROBOTSTXT → Settings** to configure the Store URL and API key. == Frequently Asked Questions == @@ -73,11 +83,17 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Compatibility == -* WordPress: 7.0 - 7.1 -* PHP: 8.4 - 8.5 +* WordPress: 4.7 - 7.1 +* PHP: 7.4 - 8.5 == Changelog == += 0.1.2 = + +_Release date: 2026-08-12_ + +* Documentation refresh: readme.txt and changelog.txt aligned to the standard templates. Compatibility scan results documented. + = 0.1.1 = _Release date: 2026-08-12_ @@ -89,12 +105,11 @@ _Release date: 2026-08-12_ _Release date: 2026-08-12_ -* Phase 1 scaffold: plugin header, autoloader, lifecycle hooks, constants. -* Settings page: Store URL, API key (encrypted at rest), cache TTL, data-on-uninstall opt-in. -* Connection test AJAX endpoint with nonce and capability check. -* Main catalog screen: fetches `GET /plugins` from the remote Core install, resolves local install/active/update state, renders a table with status badges. -* "Refresh catalog" admin-post action. -* Per-project tooling: Composer, PHPCS + WPCS + PHPCompatibility, PHPStan level 9 + wp-compat, PHPUnit. `bin/preflight.sh` and `bin/deploy.sh`. +* Phase 1 scaffold. + += Previous versions = + +If you want to see the full changelog, visit the [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager/raw/branch/main/changelog.txt) file. == Compliance == diff --git a/robotstxt-manager.php b/robotstxt-manager.php index fb336c5..5668e8e 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -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 surfaces subscription health. Phase 1: read-only catalog view. - * Version: 0.1.1 - * Requires at least: 7.0 - * Requires PHP: 8.4 + * Version: 0.1.2 + * Requires at least: 4.7 + * Requires PHP: 7.4 * 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.1.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.1.2' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 65a00cf6abc25e2616d8d6fc77c1dbfffbc1a316 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Wed, 12 Aug 2026 14:28:16 +0000 Subject: [PATCH 02/23] v0.1.3 --- changelog.txt | 24 ++++++++++++++++++++++++ languages/robotstxt-manager.pot | 4 ++-- readme.txt | 10 ++++++++-- robotstxt-manager.php | 4 ++-- vendor/autoload.php | 2 +- vendor/composer/autoload_real.php | 8 ++++---- vendor/composer/autoload_static.php | 4 ++-- vendor/composer/platform_check.php | 4 ++-- 8 files changed, 45 insertions(+), 15 deletions(-) diff --git a/changelog.txt b/changelog.txt index edfeebc..c8372af 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,29 @@ == Changelog == += 0.1.3 = + +_Release date: 2026-08-12_ + +**Changed** + +* Version bumped 0.1.2 → 0.1.3. +* `composer.json` PHP requirement updated from `>=8.4` to `>=7.4` to match the real PHPCompatibility scan floor and the plugin header. +* `composer.lock` regenerated. +* `.pot` file regenerated for v0.1.3. + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.4 - 8.5 + +**Tests** + +* PHP Coding Standards: PHP_CodeSniffer 3.13.6 +* WordPress Coding Standards: WPCS 3.4.1 +* PHPStan: level 9, 0 errors +* PHPCompatibility: 7.4–8.5 clean +* wp-compat: 0 errors against WP 4.7 + = 0.1.2 = _Release date: 2026-08-12_ diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index 27bb85b..fb4c7cb 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -2,14 +2,14 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.2\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.3\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-08-12T14:02:07+00:00\n" +"POT-Creation-Date: 2026-08-12T14:17:17+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: robotstxt-manager\n" diff --git a/readme.txt b/readme.txt index d14c38c..dffe146 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 0.1.2 +Stable tag: 0.1.3 Requires PHP: 7.4 -Version: 0.1.2 +Version: 0.1.3 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -88,6 +88,12 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 0.1.3 = + +_Release date: 2026-08-12_ + +* Composer PHP requirement aligned to scan floor (>=7.4). + = 0.1.2 = _Release date: 2026-08-12_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index 5668e8e..03827d5 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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 surfaces subscription health. Phase 1: read-only catalog view. - * Version: 0.1.2 + * Version: 0.1.3 * Requires at least: 4.7 * Requires PHP: 7.4 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.1.2' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.1.3' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/vendor/autoload.php b/vendor/autoload.php index 6732392..30d2faf 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -19,4 +19,4 @@ if (PHP_VERSION_ID < 50600) { require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit524294019242a62eec66c035ad18b361::getLoader(); +return ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef::getLoader(); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 0f93be3..d53f30b 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit524294019242a62eec66c035ad18b361 +class ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef { private static $loader; @@ -24,12 +24,12 @@ class ComposerAutoloaderInit524294019242a62eec66c035ad18b361 require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit524294019242a62eec66c035ad18b361', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); - spl_autoload_unregister(array('ComposerAutoloaderInit524294019242a62eec66c035ad18b361', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit524294019242a62eec66c035ad18b361::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::getInitializer($loader)); $loader->register(true); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 2abf889..a22a3d0 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit524294019242a62eec66c035ad18b361 +class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef { public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', @@ -20,7 +20,7 @@ class ComposerStaticInit524294019242a62eec66c035ad18b361 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->classMap = ComposerStaticInit524294019242a62eec66c035ad18b361::$classMap; + $loader->classMap = ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index 5ece5bc..d2225c7 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 80400)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.4.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { From 681147e8e3ccdb6a8397b0ae36df41480e1786ae Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Fri, 14 Aug 2026 07:55:23 +0000 Subject: [PATCH 03/23] v0.3.1 --- admin/class-robotstxt-manager-admin.php | 23 +- admin/class-robotstxt-manager-installer.php | 432 ++++++++++++++++++ admin/class-robotstxt-manager-settings.php | 8 +- admin/views/page-catalog.php | 50 +- changelog.txt | 56 +++ .../class-robotstxt-manager-activator.php | 2 +- includes/class-robotstxt-manager-plugin.php | 6 +- readme.txt | 6 +- robotstxt-manager.php | 7 +- vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_static.php | 1 + 11 files changed, 567 insertions(+), 25 deletions(-) create mode 100644 admin/class-robotstxt-manager-installer.php diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index b1e8a89..a569212 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -68,7 +68,7 @@ class Robotstxt_Manager_Admin { $catalog = $client->get_catalog(); $local = $this->resolve_local_state( $catalog ); - require_once ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php'; + require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php'; } /** @@ -100,6 +100,27 @@ class Robotstxt_Manager_Admin { exit; } + /** + * Builds a nonce-protected admin-post action URL for a plugin row. + * + * @param string $action One of 'install', 'activate', 'update'. + * @param string $slug Plugin slug. + * + * @return string The action URL. + */ + public static function action_url( string $action, string $slug ): string { + return wp_nonce_url( + add_query_arg( + array( + 'action' => 'robotstxt_manager_' . $action, + 'slug' => $slug, + ), + admin_url( 'admin-post.php' ) + ), + 'robotstxt_manager_' . $action . '_' . $slug + ); + } + /** * Resolves local install state for every catalog entry. * diff --git a/admin/class-robotstxt-manager-installer.php b/admin/class-robotstxt-manager-installer.php new file mode 100644 index 0000000..e21be60 --- /dev/null +++ b/admin/class-robotstxt-manager-installer.php @@ -0,0 +1,432 @@ +add_action( 'admin_post_robotstxt_manager_install', $this, 'handle_install' ); + $loader->add_action( 'admin_post_robotstxt_manager_activate', $this, 'handle_activate' ); + $loader->add_action( 'admin_post_robotstxt_manager_update', $this, 'handle_update' ); + } + + /** + * Installs a plugin from the ROBOTSTXT store. + * + * @return void + */ + public function handle_install(): void { + $slug = $this->authorize( 'install' ); + + $entry = $this->find_catalog_entry( $slug ); + + if ( null === $entry ) { + $this->redirect_error( __( 'Plugin not found in catalog.', 'robotstxt-manager' ) ); + } + + $name = $this->entry_name( $entry, $slug ); + $this->ensure_plugin_functions(); + + $result = $this->download_and_install( $slug ); + + if ( is_wp_error( $result ) ) { + $this->redirect_error( $result->get_error_message() ); + } + + $this->redirect_success( + sprintf( + /* translators: %s: plugin name. */ + __( '%s installed. Activate it from the list below.', 'robotstxt-manager' ), + $name + ) + ); + } + + /** + * Activates an installed plugin. + * + * @return void + */ + public function handle_activate(): void { + $slug = $this->authorize( 'activate' ); + + $this->ensure_plugin_functions(); + + $file = $this->find_plugin_file( $slug ); + + if ( '' === $file ) { + $this->redirect_error( __( 'Plugin is not installed.', 'robotstxt-manager' ) ); + } + + $result = activate_plugins( $file ); + + if ( is_wp_error( $result ) ) { + $this->redirect_error( $result->get_error_message() ); + } + + $this->redirect_success( + sprintf( + /* translators: %s: plugin name (slug). */ + __( '%s activated.', 'robotstxt-manager' ), + $slug + ) + ); + } + + /** + * Updates an installed plugin to the latest catalog version. + * + * @return void + */ + public function handle_update(): void { + $slug = $this->authorize( 'update' ); + + $this->ensure_plugin_functions(); + + $file = $this->find_plugin_file( $slug ); + + if ( '' === $file ) { + $this->redirect_error( __( 'Plugin is not installed.', 'robotstxt-manager' ) ); + } + + $result = $this->download_and_install( $slug, true ); + + if ( is_wp_error( $result ) ) { + $this->redirect_error( $result->get_error_message() ); + } + + $this->redirect_success( + sprintf( + /* translators: %s: plugin name (slug). */ + __( '%s updated.', 'robotstxt-manager' ), + $slug + ) + ); + } + + /** + * Validates the capability and action-specific nonce, then returns the slug. + * + * @param string $action One of 'install', 'activate', 'update'. + * + * @return string The sanitized plugin slug. + */ + private function authorize( string $action ): string { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-manager' ) ); + } + + $raw_slug = ''; + + if ( isset( $_GET['slug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verified below; slug only read after. + $unslashed = wp_unslash( $_GET['slug'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below. + if ( is_string( $unslashed ) ) { + $raw_slug = sanitize_key( $unslashed ); + } + } + + check_admin_referer( 'robotstxt_manager_' . $action . '_' . $raw_slug ); + + return $raw_slug; + } + + /** + * Finds a catalog entry by slug. + * + * @param string $slug Plugin slug. + * + * @return array|null The entry, or null when not found. + */ + private function find_catalog_entry( string $slug ): ?array { + $client = Robotstxt_Manager_Core_Client::from_options(); + + if ( ! $client->is_configured() ) { + return null; + } + + foreach ( $client->get_catalog() as $row ) { + $row_slug = $row['slug'] ?? ''; + if ( is_string( $row_slug ) && $slug === $row_slug ) { + return $row; + } + } + + return null; + } + + /** + * Extracts a display name from a catalog entry. + * + * @param array|null $entry Catalog entry (null tolerated). + * @param string $slug Fallback slug. + * + * @return string The plugin name. + */ + private function entry_name( ?array $entry, string $slug ): string { + $raw_name = ( null !== $entry ) ? ( $entry['name'] ?? '' ) : ''; + $name = is_string( $raw_name ) ? $raw_name : ''; + + return '' !== $name ? $name : $slug; + } + + /** + * Resolves a plugin slug to its installed plugin file. + * + * @param string $slug Plugin slug (directory name). + * + * @return string Plugin file ("slug/file.php") or '' when not installed. + */ + private function find_plugin_file( string $slug ): string { + $all_plugins = get_plugins(); + + foreach ( $all_plugins as $file => $data ) { + $file_slug = dirname( $file ); + if ( '.' === $file_slug ) { + $file_slug = basename( $file, '.php' ); + } + if ( $slug === $file_slug ) { + return $file; + } + } + + return ''; + } + + /** + * Downloads the plugin ZIP from the store and installs it. + * + * @param string $slug Plugin slug. + * @param bool $overwrite Whether to overwrite an existing install (update). + * + * @return true|WP_Error True on success. + */ + private function download_and_install( string $slug, bool $overwrite = false ) { + $client = Robotstxt_Manager_Core_Client::from_options(); + + if ( ! $client->is_configured() ) { + return new WP_Error( 'robotstxt_manager_store', __( 'Store is not configured.', 'robotstxt-manager' ) ); + } + + $entry = $this->find_catalog_entry( $slug ); + + if ( null === $entry ) { + return new WP_Error( 'robotstxt_manager_catalog', __( 'Plugin not found in catalog.', 'robotstxt-manager' ) ); + } + + $raw_kind = $entry['type'] ?? 'free'; + $kind = is_string( $raw_kind ) ? $raw_kind : 'free'; + $raw_dl = $entry['download_url'] ?? ''; + $dl_url = is_string( $raw_dl ) ? $raw_dl : ''; + + $is_free = 'premium' !== $kind; + + // 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). + $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' + : $dl_url; + + $tmp_file = wp_tempnam( $slug . '.zip' ); + + if ( ! $tmp_file ) { + return new WP_Error( 'robotstxt_manager_temp', __( 'Could not create a temporary file for download.', 'robotstxt-manager' ) ); + } + + $headers = array(); + + if ( $use_download_endpoint ) { + $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); + $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + + if ( '' !== $api_key ) { + $headers = array( + 'Authorization' => 'Bearer ' . $api_key, + ); + } + } + + $response = wp_remote_get( + $zip_url, + array( + 'timeout' => 300, + 'stream' => true, + 'filename' => $tmp_file, + 'headers' => $headers, + ) + ); + + if ( is_wp_error( $response ) ) { + wp_delete_file( $tmp_file ); + + return new WP_Error( + 'robotstxt_manager_download', + sprintf( + /* translators: %s: HTTP transport error message. */ + __( 'Download failed: %s', 'robotstxt-manager' ), + $response->get_error_message() + ) + ); + } + + $code = (int) wp_remote_retrieve_response_code( $response ); + + if ( 200 !== $code ) { + wp_delete_file( $tmp_file ); + + return new WP_Error( + 'robotstxt_manager_http', + sprintf( + /* translators: %d: HTTP status code. */ + __( 'Download failed (HTTP %d).', 'robotstxt-manager' ), + $code + ) + ); + } + + if ( ! $this->is_valid_zip( $tmp_file ) ) { + wp_delete_file( $tmp_file ); + + return new WP_Error( 'robotstxt_manager_zip', __( 'The store returned an invalid file.', 'robotstxt-manager' ) ); + } + + $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); + $result = $upgrader->install( + $tmp_file, + array( + 'overwrite' => $overwrite, + ) + ); + + wp_delete_file( $tmp_file ); + + if ( true !== $result ) { + $detail = ( $result instanceof WP_Error ) ? $result->get_error_message() : ''; + + return new WP_Error( + 'robotstxt_manager_install', + '' !== $detail + ? sprintf( + /* translators: %s: upgrader error message. */ + __( 'Installation failed: %s', 'robotstxt-manager' ), + $detail + ) + : __( 'Installation failed.', 'robotstxt-manager' ) + ); + } + + return true; + } + + /** + * Loads the wp-admin plugin/upgrader dependencies. + * + * @return void + */ + private function ensure_plugin_functions(): void { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + if ( ! class_exists( 'Plugin_Upgrader' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + } + } + + /** + * Redirects back to the catalog page with a success notice. + * + * @param string $message Notice text. + * + * @return void + */ + private function redirect_success( string $message ): void { + $this->redirect( 'success', $message ); + } + + /** + * Redirects back to the catalog page with an error notice. + * + * @param string $message Notice text. + * + * @return void + */ + private function redirect_error( string $message ): void { + $this->redirect( 'error', $message ); + } + + /** + * Redirects back to the catalog page carrying a notice. + * + * @param string $result 'success' or 'error'. + * @param string $message Notice text. + * + * @return void + */ + private function redirect( string $result, string $message ): void { + wp_safe_redirect( + add_query_arg( + array( + 'page' => Robotstxt_Manager_Admin::PAGE_SLUG, + 'robotstxt_manager_result' => $result, + 'robotstxt_manager_message' => rawurlencode( $message ), + ), + admin_url( 'admin.php' ) + ) + ); + exit; + } + + /** + * 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 valid ZIP archive. + */ + private function is_valid_zip( string $file ): bool { + if ( ! file_exists( $file ) ) { + return false; + } + + $size = filesize( $file ); + + if ( false === $size || 0 >= $size ) { + return false; + } + + $magic = file_get_contents( $file, false, null, 0, 2 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading 2 bytes of a local temp file. + + return is_string( $magic ) && 'PK' === substr( $magic, 0, 2 ); + } +} diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index aba1c50..1c2d7ac 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -83,7 +83,7 @@ class Robotstxt_Manager_Settings { array( 'type' => 'string', 'sanitize_callback' => 'esc_url_raw', - 'default' => 'https://plugins.robotstxt.es', + 'default' => 'https://www.robotstxt.software', ) ); @@ -216,11 +216,11 @@ class Robotstxt_Manager_Settings { * @return void */ public function render_field_store_url(): void { - $raw = get_option( 'robotstxt_manager_store_url', 'https://plugins.robotstxt.es' ); - $value = is_string( $raw ) ? $raw : 'https://plugins.robotstxt.es'; + $raw = get_option( 'robotstxt_manager_store_url', 'https://www.robotstxt.software' ); + $value = is_string( $raw ) ? $raw : 'https://www.robotstxt.software'; printf( - '', + '', esc_attr( $value ) ); echo '

' . esc_html__( 'Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from.', 'robotstxt-manager' ) . '

'; diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 89738a0..51fc99f 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -31,6 +31,24 @@ $refresh_url = add_query_arg( $refresh_url = wp_nonce_url( $refresh_url, 'robotstxt_manager_refresh_catalog' ); $refreshed = isset( $_GET['refreshed'] ) && '1' === $_GET['refreshed']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended +// Action notice from an install/activate/update redirect. +$notice_result = ''; +$notice_message = ''; + +if ( isset( $_GET['robotstxt_manager_result'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only, set by this plugin's own redirects. + $raw_result = wp_unslash( $_GET['robotstxt_manager_result'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized on the next line. + if ( is_string( $raw_result ) ) { + $notice_result = sanitize_key( $raw_result ); + } +} + +if ( isset( $_GET['robotstxt_manager_message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only, set by this plugin's own redirects. + $raw_message = wp_unslash( $_GET['robotstxt_manager_message'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized on the next line after decoding. + if ( is_string( $raw_message ) ) { + $notice_message = sanitize_text_field( rawurldecode( $raw_message ) ); + } +} + $local_wp_version = (string) get_bloginfo( 'version' ); $local_php_version = (string) PHP_VERSION; $compat_warnings = 0; @@ -43,6 +61,10 @@ $compat_warnings = 0;

+ +

+ +

@@ -186,19 +208,25 @@ $compat_warnings = 0; } ?> - - - - - 0.0 && '' !== $page_url ) : ?> - - - + + + + + + 0.0 && '' !== $page_url ) : ?> + - - + 0.0 && '' !== $page_url ) ? 'button-secondary' : 'button-primary'; + ?> + - + + + + + + diff --git a/changelog.txt b/changelog.txt index c8372af..830c028 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,61 @@ == Changelog == += 0.3.1 = + +_Release date: 2026-08-14_ + +* Default store URL is now `https://www.robotstxt.software` (was `https://plugins.robotstxt.es`). Applies to new activations and fresh installs; existing saved URLs are preserved. The catalog API at the new domain is verified live. +* Plugin version 0.3.0 → 0.3.1. + += 0.3.0 = + +_Release date: 2026-08-13_ + +**Highlights** + +* Classic install/activate/update flow: the catalog actions now run on a full page load (`admin-post.php` with per-action-and-slug nonces) and report the outcome through standard admin notices (green success / red error) at the top of the page, replacing the previous AJAX flow. + +**Added** + +* Activate action — installed-but-inactive plugins show an "Activate" button that calls `activate_plugins()`. +* Update action — plugins with a newer catalog version show an "Update" button that re-downloads the ZIP and runs the `Plugin_Upgrader` with `overwrite` enabled. +* Install/Activate/Update buttons are nonce links (`robotstxt_manager_{action}_{slug}` nonce actions) to `admin-post.php`, all `manage_options`-gated. +* Redirect-based notices: handlers redirect back to the catalog page carrying `robotstxt_manager_result` (success/error) and `robotstxt_manager_message`, rendered as dismissible standard notices. + +**Changed** + +* `Robotstxt_Manager_Installer` rewritten from an AJAX handler to three admin-post handlers (`robotstxt_manager_install`, `robotstxt_manager_activate`, `robotstxt_manager_update`); download logic (Bearer account-key auth, ZIP validation, temp-file cleanup) unchanged. +* Removed the AJAX endpoint, `manager-install.js`, and its script localization. +* Plugin version 0.2.0 → 0.3.0. + +**Compatibility** + +* WordPress: 7.0 - 7.1 (declared floor of the ecosystem; scan-confirmed lower) +* PHP: 8.4 - 8.5 (declared floor of the ecosystem; scan-confirmed lower) + += 0.2.0 = + +_Release date: 2026-08-12_ + +**Highlights** + +* Phase 2: the Install action. Free and premium plugins can be installed directly from the ROBOTSTXT catalog into the local site, authenticated with the account-level API key (requires Plugins Core 1.4.0+). + +**Added** + +* `Robotstxt_Manager_Installer` — AJAX handler (`wp_ajax_robotstxt_manager_install_plugin`, nonce-protected, `manage_options`-gated) that resolves the plugin in the remote catalog, downloads the ZIP (free plugins with a published public `download_url` directly; everything else via Core's authenticated `/download` endpoint with an `Authorization: Bearer ` header), validates the archive (ZIP magic bytes), installs it via `Plugin_Upgrader`, and cleans up the temp file on every path. +* `public/js/manager-install.js` — wires the Install buttons to the AJAX action with in-flight state and result rendering. +* Catalog view: enabled Install buttons for compatible plugins (free and premium). + +**Changed** + +* Plugin version 0.1.3 → 0.2.0. Description updated to reflect Phase 2. + +**Compatibility** + +* WordPress: 7.0 - 7.1 (declared floor of the ecosystem; scan-confirmed lower) +* PHP: 8.4 - 8.5 (declared floor of the ecosystem; scan-confirmed lower) + = 0.1.3 = _Release date: 2026-08-12_ diff --git a/includes/class-robotstxt-manager-activator.php b/includes/class-robotstxt-manager-activator.php index fd4c5fe..c8f5a64 100644 --- a/includes/class-robotstxt-manager-activator.php +++ b/includes/class-robotstxt-manager-activator.php @@ -44,7 +44,7 @@ class Robotstxt_Manager_Activator { */ private static function ensure_defaults(): void { if ( '' === get_option( 'robotstxt_manager_store_url', '' ) ) { - update_option( 'robotstxt_manager_store_url', 'https://plugins.robotstxt.es' ); + update_option( 'robotstxt_manager_store_url', 'https://www.robotstxt.software' ); } if ( '' === get_option( 'robotstxt_manager_cache_ttl_minutes', '' ) ) { diff --git a/includes/class-robotstxt-manager-plugin.php b/includes/class-robotstxt-manager-plugin.php index 9ecf8fc..6252100 100644 --- a/includes/class-robotstxt-manager-plugin.php +++ b/includes/class-robotstxt-manager-plugin.php @@ -59,11 +59,13 @@ final class Robotstxt_Manager_Plugin { private function define_hooks(): void { $this->loader->add_action( 'init', $this, 'load_textdomain' ); - $admin = new Robotstxt_Manager_Admin(); - $settings = new Robotstxt_Manager_Settings(); + $admin = new Robotstxt_Manager_Admin(); + $settings = new Robotstxt_Manager_Settings(); + $installer = new Robotstxt_Manager_Installer(); $admin->register( $this->loader ); $settings->register( $this->loader ); + $installer->register( $this->loader ); } /** diff --git a/readme.txt b/readme.txt index dffe146..9bfe948 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 0.1.3 +Stable tag: 0.3.1 Requires PHP: 7.4 -Version: 0.1.3 +Version: 0.3.1 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -30,7 +30,7 @@ This plugin does **not** re-implement the per-plugin update mechanism. Every ROB Configure at **ROBOTSTXT → Settings**: -* **Store URL** — Base URL of the remote Plugins Core installation (defaults to `https://plugins.robotstxt.es`). +* **Store URL** — Base URL of the remote Plugins Core installation (defaults to `https://www.robotstxt.software`). * **API Key** — Account-level API key issued by the ROBOTSTXT store. Encrypted before storage; masked in the UI (last 4 characters shown). Use the "Test connection" button to verify connectivity. * **Catalog Cache (minutes)** — How long the catalog response is cached in a transient. Default: 60 minutes. * **Data on Uninstall** — Opt-in checkbox. When enabled, all Manager options and transients are deleted on uninstall. Default: off. diff --git a/robotstxt-manager.php b/robotstxt-manager.php index 03827d5..be2818b 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -2,8 +2,8 @@ /** * 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 surfaces subscription health. Phase 1: read-only catalog view. - * Version: 0.1.3 + * 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.3.1 * Requires at least: 4.7 * Requires PHP: 7.4 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.1.3' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.3.1' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); @@ -44,6 +44,7 @@ if ( file_exists( ROBOTSTXT_MANAGER_DIR . 'vendor/autoload.php' ) ) { require_once ROBOTSTXT_MANAGER_DIR . 'includes/class-robotstxt-manager-plugin.php'; require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-admin.php'; require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-settings.php'; + require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-installer.php'; } register_activation_hook( __FILE__, array( 'Robotstxt_Manager_Activator', 'activate' ) ); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index ba41573..1f39cc6 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -11,6 +11,7 @@ return array( 'Robotstxt_Manager_Admin' => $baseDir . '/admin/class-robotstxt-manager-admin.php', 'Robotstxt_Manager_Core_Client' => $baseDir . '/includes/class-robotstxt-manager-core-client.php', 'Robotstxt_Manager_Encryption' => $baseDir . '/includes/class-robotstxt-manager-encryption.php', + 'Robotstxt_Manager_Installer' => $baseDir . '/admin/class-robotstxt-manager-installer.php', 'Robotstxt_Manager_Loader' => $baseDir . '/includes/class-robotstxt-manager-loader.php', 'Robotstxt_Manager_Plugin' => $baseDir . '/includes/class-robotstxt-manager-plugin.php', 'Robotstxt_Manager_Settings' => $baseDir . '/admin/class-robotstxt-manager-settings.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index a22a3d0..9a2f7bf 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -12,6 +12,7 @@ class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef 'Robotstxt_Manager_Admin' => __DIR__ . '/../..' . '/admin/class-robotstxt-manager-admin.php', 'Robotstxt_Manager_Core_Client' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-core-client.php', 'Robotstxt_Manager_Encryption' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-encryption.php', + 'Robotstxt_Manager_Installer' => __DIR__ . '/../..' . '/admin/class-robotstxt-manager-installer.php', 'Robotstxt_Manager_Loader' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-loader.php', 'Robotstxt_Manager_Plugin' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-plugin.php', 'Robotstxt_Manager_Settings' => __DIR__ . '/../..' . '/admin/class-robotstxt-manager-settings.php', From 38fbcf80a54ce5e107abb94ff1acd853eccf3581 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Fri, 14 Aug 2026 16:46:11 +0000 Subject: [PATCH 04/23] v0.4.0 --- admin/views/page-catalog.php | 35 +++++++++++++++++++++++++++++++- changelog.txt | 29 +++++++++++++++++++++++++++ readme.txt | 39 +++++++++++++++++++++++++++++------- robotstxt-manager.php | 4 ++-- 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 51fc99f..366e5ed 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -112,6 +112,8 @@ $compat_warnings = 0; $raw_cost = $entry['price_annual'] ?? 0; $raw_ver = $entry['current_version'] ?? ''; $raw_url = $entry['page_url'] ?? ''; + $raw_home = $entry['homepage'] ?? ''; + $raw_desc = $entry['description'] ?? ''; $raw_rwp = $entry['requires_wp'] ?? ''; $raw_rphp = $entry['requires_php'] ?? ''; @@ -121,9 +123,15 @@ $compat_warnings = 0; $cost = is_numeric( $raw_cost ) ? (float) ( $raw_cost + 0.0 ) : 0.0; $remote_v = is_string( $raw_ver ) ? $raw_ver : ''; $page_url = is_string( $raw_url ) ? $raw_url : ''; + $homepage = is_string( $raw_home ) ? $raw_home : ''; + $desc = is_string( $raw_desc ) ? trim( $raw_desc ) : ''; $req_wp = is_string( $raw_rwp ) ? $raw_rwp : ''; $req_php = is_string( $raw_rphp ) ? $raw_rphp : ''; + $website_url = '' !== $page_url ? $page_url : $homepage; + $expandable = ( '' !== $desc || '' !== $website_url ); + $toggle_id = 'robotstxt-manager-toggle-' . esc_attr( $slug ); + // Compatibility checks. $wp_ok = '' === $req_wp || version_compare( $local_wp_version, $req_wp, '>=' ); $php_ok = '' === $req_php || version_compare( $local_php_version, $req_php, '>=' ); @@ -148,7 +156,11 @@ $compat_warnings = 0; $row_class = ( ! $wp_ok || ! $php_ok ) ? ' robotstxt-manager-row--incompatible' : ''; ?> - + + + + +
@@ -228,6 +240,18 @@ $compat_warnings = 0; + + + + +

+ + + + + + + @@ -265,4 +289,13 @@ $compat_warnings = 0; .robotstxt-manager-compat--fail { color: #d63638; font-weight: 600; } .robotstxt-manager-compat-warning { cursor: help; } .robotstxt-manager-row--incompatible { background-color: #fef7f0 !important; } + +/* Expandable detail rows (CSS-only toggle). */ +.robotstxt-manager-toggle { display: none; } +.robotstxt-manager-toggle-label { cursor: pointer; display: inline-block; width: 1.4em; } +.robotstxt-manager-toggle-arrow::before { content: "\25B8"; /* ▸ */ } +.robotstxt-manager-detail-row { display: none; } +.robotstxt-manager-detail-row td { border-top: none !important; } +tr:has(.robotstxt-manager-toggle:checked) + tr.robotstxt-manager-detail-row { display: table-row; } +tr:has(.robotstxt-manager-toggle:checked) .robotstxt-manager-toggle-arrow::before { content: "\25BE"; /* ▾ */ } diff --git a/changelog.txt b/changelog.txt index 830c028..84a9104 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,12 +1,41 @@ == Changelog == += 0.4.0 = + +_Release date: 2026-08-14_ + +**Highlights** + +* Click-to-expand rows in the catalog table: clicking the ▸ next to a plugin's name reveals a detail row with the plugin's description and a link to its website on the store, so users can understand what each plugin does before installing. Pure CSS toggle (`:has()` + hidden checkbox) — no JavaScript, consistent with the classic-flow philosophy. + +**Added** + +* Detail row under each catalog entry, shown when the entry has a description or a website URL (Core 1.4.2+ exposes `description` in the catalog list; entries without either keep a plain, non-expandable row). +* "Visit website" link using `page_url` (falling back to `homepage`), opened in a new tab with `rel="noopener noreferrer"`. + +**Changed** + +* Plugin version 0.3.1 → 0.4.0. No database schema changes (no custom tables). + +**Compatibility** + +* WordPress: 7.0 - 7.1 (declared floor of the ecosystem; scan-confirmed lower) +* PHP: 8.4 - 8.5 (declared floor of the ecosystem; scan-confirmed lower) + = 0.3.1 = _Release date: 2026-08-14_ +**Changed** + * Default store URL is now `https://www.robotstxt.software` (was `https://plugins.robotstxt.es`). Applies to new activations and fresh installs; existing saved URLs are preserved. The catalog API at the new domain is verified live. * Plugin version 0.3.0 → 0.3.1. +**Compatibility** + +* WordPress: 7.0 - 7.1 (declared floor of the ecosystem; scan-confirmed lower) +* PHP: 8.4 - 8.5 (declared floor of the ecosystem; scan-confirmed lower) + = 0.3.0 = _Release date: 2026-08-13_ diff --git a/readme.txt b/readme.txt index 9bfe948..8993d34 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 0.3.1 +Stable tag: 0.4.0 Requires PHP: 7.4 -Version: 0.3.1 +Version: 0.4.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -17,12 +17,13 @@ Manager (by ROBOTSTXT) is a WordPress plugin that customers install on **their o * Lists the full plugin catalog with name, type (free/premium), price, and current version. * For each catalog entry, shows the **local install state**: not installed, installed (inactive), up to date, or update available. -* One-click actions (Phase 2+): Install free plugins, Buy paid plugins (redirects to checkout on the store site), trigger native WordPress updates. +* Click a plugin's row to expand its description and a link to its website on the store. +* One-click actions: Install, Activate, and Update run on a classic page load with a standard admin notice (green/red) reporting the outcome; Buy redirects to checkout on the store site. * Subscription health dashboard (Phase 4+): license expiring soon, payment failed, expired — surfaced as admin notices so issues are visible before updates silently stop. This plugin does **not** re-implement the per-plugin update mechanism. Every ROBOTSTXT plugin bundles its own copy of the `Robotstxt_Core_Plugin_Updater` SDK, so updates keep working even if Manager is deactivated. Manager's value is visibility and one place to click. -**Phase 1 (current):** Read-only catalog view with local install-state resolution. Settings screen with API key (encrypted at rest), store URL, cache TTL, and connection test. Install/Update/Buy buttons are present but disabled pending Phase 2+. +**Requires Core 1.4.2+** on the store for descriptions in the catalog list (older Core versions still work; rows simply show no description). == Using the plugin == @@ -41,13 +42,13 @@ Configure at **ROBOTSTXT → Settings**: | Column | Description | |---|---| -| Plugin | Name and current version from the store. | +| Plugin | Name and current version from the store. Click ▸ to expand the description and website link. | | Type | Free or Premium. | | Price | Annual price in EUR, or — for free plugins. | | Local state | Not installed / Installed (inactive) / Up to date / Update available (vX → vY). | -| Action | Install / Buy / Update (Phase 2+; disabled in Phase 1). | +| Action | Install / Activate / Update / Buy, depending on local state. | -Click "Refresh catalog" to force a fresh fetch from Core. +Click "Refresh catalog" to force a fresh fetch from Core. Install, Activate, and Update run on a classic page load (no JavaScript) and report the result as a standard admin notice. == Extra Configurations == @@ -88,6 +89,30 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 0.4.0 = + +_Release date: 2026-08-14_ + +* Click-to-expand rows in the catalog: each plugin's description and a link to its website are shown in a detail row under the plugin name (CSS-only toggle, no JavaScript). Requires Core 1.4.2+ on the store. + += 0.3.1 = + +_Release date: 2026-08-14_ + +* Default store URL is now `https://www.robotstxt.software`. Existing saved URLs are preserved. + += 0.3.0 = + +_Release date: 2026-08-13_ + +* Classic install/activate/update flow: actions run on a full page load (`admin-post.php` with per-action-and-slug nonces) and report the outcome through standard admin notices (green success / red error), replacing the previous AJAX flow. + += 0.2.0 = + +_Release date: 2026-08-13_ + +* Install action: free and premium plugins install directly from the catalog, authenticated with the account-level API key against Core's `/download` endpoint (requires Core 1.4.0+). + = 0.1.3 = _Release date: 2026-08-12_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index be2818b..a4d4f84 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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.3.1 + * Version: 0.4.0 * Requires at least: 4.7 * Requires PHP: 7.4 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.3.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.4.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 90ac22a84514f1f6bd17a881e78fc36ff7e88800 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Fri, 14 Aug 2026 20:07:54 +0000 Subject: [PATCH 05/23] v0.5.0 --- admin/class-robotstxt-manager-admin.php | 5 +- changelog.txt | 23 ++ includes/class-robotstxt-manager-plugin.php | 2 + includes/class-robotstxt-manager-updater.php | 337 +++++++++++++++++++ readme.txt | 10 +- robotstxt-manager.php | 5 +- vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_static.php | 1 + 8 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 includes/class-robotstxt-manager-updater.php diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index a569212..3ef11b3 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -74,7 +74,9 @@ class Robotstxt_Manager_Admin { /** * Handles the "Refresh catalog" admin-post action. * - * Clears the cached catalog response and redirects back to the page. + * Clears the cached catalog response, purges WordPress's update_plugins + * transient so native update badges re-evaluate immediately, and + * redirects back to the page. * * @return void */ @@ -87,6 +89,7 @@ class Robotstxt_Manager_Admin { $client = Robotstxt_Manager_Core_Client::from_options(); $client->clear_catalog_cache(); + delete_site_transient( 'update_plugins' ); $redirect = add_query_arg( array( diff --git a/changelog.txt b/changelog.txt index 84a9104..e14081a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,28 @@ == Changelog == += 0.5.0 = + +_Release date: 2026-08-14_ + +**Highlights** + +* Native WordPress update integration: installed catalog plugins feed into WordPress's own update system. The standard "Update available" badge appears on the Plugins screen, and clicking "Update now" runs the normal WordPress updater with the ROBOTSTXT store as the download source — no custom UI. + +**Added** + +* `Robotstxt_Manager_Updater` — injects catalog update data into the `update_plugins` transient (`pre_set_site_transient_update_plugins`): a `response` entry for outdated catalog plugins (package = the store's download proxy; premium entries append the account API key as `api_key`, which Core 1.5.0+ accepts as a query parameter because the native upgrader cannot send headers) and a `no_update` entry for up-to-date ones. Entries injected by a plugin's own bundled SDK are never overwritten. +* `plugins_api` filter — the "View details" modal for catalog slugs is served from catalog data (name, version, requires, tested, homepage, description section, icon, banner). +* "Refresh catalog" now also deletes the `update_plugins` transient so the update badges re-evaluate immediately. + +**Changed** + +* Plugin version 0.4.0 → 0.5.0. No database schema changes (no custom tables). + +**Compatibility** + +* WordPress: 7.0 - 7.1 (declared floor of the ecosystem; scan-confirmed lower) +* PHP: 8.4 - 8.5 (declared floor of the ecosystem; scan-confirmed lower) + = 0.4.0 = _Release date: 2026-08-14_ diff --git a/includes/class-robotstxt-manager-plugin.php b/includes/class-robotstxt-manager-plugin.php index 6252100..5ee0c56 100644 --- a/includes/class-robotstxt-manager-plugin.php +++ b/includes/class-robotstxt-manager-plugin.php @@ -62,10 +62,12 @@ final class Robotstxt_Manager_Plugin { $admin = new Robotstxt_Manager_Admin(); $settings = new Robotstxt_Manager_Settings(); $installer = new Robotstxt_Manager_Installer(); + $updater = new Robotstxt_Manager_Updater(); $admin->register( $this->loader ); $settings->register( $this->loader ); $installer->register( $this->loader ); + $updater->register( $this->loader ); } /** diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php new file mode 100644 index 0000000..bd98198 --- /dev/null +++ b/includes/class-robotstxt-manager-updater.php @@ -0,0 +1,337 @@ +response for + * installed catalog plugins with a newer remote version, and to ->no_update + * for up-to-date ones (prevents false WordPress.org matches). + * - `plugins_api`: serves the "View details" modal from catalog data. + * + * Plugins that bundle their own update SDK already inject their own entries; + * Manager never overwrites an existing response entry. + */ +class Robotstxt_Manager_Updater { + + /** + * Registers all hooks via the loader. + * + * @param Robotstxt_Manager_Loader $loader The plugin hook loader. + * + * @return void + */ + public function register( Robotstxt_Manager_Loader $loader ): void { + $loader->add_filter( 'pre_set_site_transient_update_plugins', $this, 'inject_updates' ); + $loader->add_filter( 'plugins_api', $this, 'plugins_api_filter', 10, 3 ); + } + + /** + * Injects catalog update data into the WordPress update transient. + * + * @param mixed $transient The update_plugins transient object. + * + * @return mixed Modified transient. + */ + public function inject_updates( mixed $transient ): mixed { + if ( ! is_object( $transient ) + || ! property_exists( $transient, 'checked' ) + || ! is_array( $transient->checked ) + || empty( $transient->checked ) + ) { + return $transient; + } + + $client = Robotstxt_Manager_Core_Client::from_options(); + + if ( ! $client->is_configured() ) { + return $transient; + } + + $catalog = $client->get_catalog(); + + if ( empty( $catalog ) ) { + return $transient; + } + + $entries = $this->catalog_by_slug( $catalog ); + + foreach ( $transient->checked as $plugin_file => $raw_version ) { + $version = is_string( $raw_version ) ? $raw_version : ''; + $slug = $this->slug_from_file( (string) $plugin_file ); + $entry = $entries[ $slug ] ?? null; + + if ( null === $entry || '' === $version ) { + continue; + } + + $new_version = $entry['new_version']; + + if ( '' === $new_version ) { + continue; + } + + // Never overwrite an entry injected by the plugin's own SDK. + if ( property_exists( $transient, 'response' ) + && is_array( $transient->response ) + && isset( $transient->response[ $plugin_file ] ) + ) { + continue; + } + + if ( version_compare( $version, $new_version, '<' ) ) { + if ( property_exists( $transient, 'response' ) && is_array( $transient->response ) ) { + $transient->response[ $plugin_file ] = $this->build_update_object( $slug, (string) $plugin_file, $entry ); + } + } elseif ( property_exists( $transient, 'no_update' ) && is_array( $transient->no_update ) ) { + $transient->no_update[ $plugin_file ] = $this->build_update_object( $slug, (string) $plugin_file, $entry, $version ); + } + } + + return $transient; + } + + /** + * Serves the "View details" modal for catalog plugins from catalog data. + * + * @param mixed $result Current filter result. + * @param string $action API action (only 'plugin_information' is handled). + * @param mixed $args Request arguments; expected object with 'slug'. + * + * @return mixed Plugin info object, or the original $result. + */ + public function plugins_api_filter( mixed $result, string $action, mixed $args ): mixed { + if ( 'plugin_information' !== $action || ! is_object( $args ) || empty( $args->slug ) ) { + return $result; + } + + $client = Robotstxt_Manager_Core_Client::from_options(); + + if ( ! $client->is_configured() ) { + return $result; + } + + $catalog = $client->get_catalog(); + + if ( empty( $catalog ) ) { + return $result; + } + + $slug = sanitize_key( (string) $args->slug ); + $entries = $this->catalog_by_slug( $catalog ); + $entry = $entries[ $slug ] ?? null; + + if ( null === $entry ) { + return $result; + } + + $sections = array(); + + if ( '' !== $entry['description'] ) { + $sections['description'] = wp_kses_post( wpautop( $entry['description'] ) ); + } + + if ( empty( $sections ) ) { + $sections['description'] = ''; + } + + $info = array( + 'name' => $entry['name'], + 'slug' => $slug, + 'version' => $entry['new_version'], + 'requires' => $entry['requires_wp'], + 'requires_php' => $entry['requires_php'], + 'tested' => $entry['tested_up_to'], + 'last_updated' => '', + 'homepage' => $entry['page_url'], + 'sections' => $sections, + 'download_link' => '', + ); + + if ( '' !== $entry['icon_url'] ) { + $info['icons'] = array( + '1x' => $entry['icon_url'], + '2x' => $entry['icon_url'], + ); + } + + if ( '' !== $entry['banner_url'] ) { + $info['banners'] = array( + 'low' => $entry['banner_url'], + 'high' => $entry['banner_url'], + ); + } + + return (object) $info; + } + + /** + * Indexes catalog entries by slug with normalised fields. + * + * @param list> $catalog Catalog entries from Core. + * + * @return array> Normalised entries keyed by slug. + */ + private function catalog_by_slug( array $catalog ): array { + $entries = array(); + + foreach ( $catalog as $row ) { + $raw_slug = $row['slug'] ?? ''; + $slug = is_string( $raw_slug ) ? sanitize_key( $raw_slug ) : ''; + + if ( '' === $slug ) { + continue; + } + + $entries[ $slug ] = array( + 'name' => $this->str( $row, 'name', $slug ), + 'type' => $this->str( $row, 'type', 'free' ), + 'new_version' => $this->str( $row, 'current_version', '' ), + 'requires_wp' => $this->str( $row, 'requires_wp', '' ), + 'requires_php' => $this->str( $row, 'requires_php', '' ), + 'tested_up_to' => $this->str( $row, 'tested_up_to', '' ), + 'page_url' => $this->str( $row, 'page_url', '' ), + 'description' => $this->str( $row, 'description', '' ), + 'icon_url' => $this->str( $row, 'icon_url', '' ), + 'banner_url' => $this->str( $row, 'banner_url', '' ), + ); + } + + return $entries; + } + + /** + * Builds the update/no-update object for the WordPress transient. + * + * @param string $slug Plugin slug. + * @param string $plugin_file Plugin basename. + * @param array $entry Normalised catalog entry. + * @param string|null $current_version Installed version; when null an + * update entry is built, otherwise a + * no-update entry pinned to this version. + * + * @return object stdClass for the transient bucket. + */ + private function build_update_object( string $slug, string $plugin_file, array $entry, ?string $current_version = null ): object { + $is_no_update = null !== $current_version; + + $data = array( + 'id' => $plugin_file, + 'slug' => $slug, + 'plugin' => $plugin_file, + 'new_version' => $is_no_update ? $current_version : $entry['new_version'], + 'url' => $entry['page_url'], + 'package' => $is_no_update ? '' : $this->package_url( $slug, $entry['type'] ), + 'requires' => $entry['requires_wp'], + 'requires_php' => $entry['requires_php'], + 'tested' => $entry['tested_up_to'], + 'icons' => array(), + 'banners' => array(), + ); + + if ( ! $is_no_update ) { + if ( '' !== $entry['icon_url'] ) { + $data['icons'] = array( + '1x' => $entry['icon_url'], + '2x' => $entry['icon_url'], + ); + } + + if ( '' !== $entry['banner_url'] ) { + $data['banners'] = array( + 'low' => $entry['banner_url'], + 'high' => $entry['banner_url'], + ); + } + } + + return (object) $data; + } + + /** + * Builds the download package URL for the native upgrader. + * + * Free plugins stream through the proxy without auth (Core 1.4.0+). + * Premium plugins append the account API key as an api_key query parameter + * (accepted by Core 1.5.0+) — the native upgrader cannot send headers. + * + * @param string $slug Plugin slug. + * @param string $type Plugin type ('free' or 'premium'). + * + * @return string Package URL. + */ + private function package_url( string $slug, string $type ): string { + $client = Robotstxt_Manager_Core_Client::from_options(); + + $url = $client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download'; + $args = array( + 'domain' => $this->site_domain(), + ); + + if ( 'premium' === $type ) { + $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); + $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + + if ( '' !== $api_key ) { + $args['api_key'] = $api_key; + } + } + + return add_query_arg( $args, $url ); + } + + /** + * Returns the normalised domain of the current site. + * + * @return string Domain (e.g. 'example.com'). + */ + private function site_domain(): string { + $host = (string) wp_parse_url( home_url(), PHP_URL_HOST ); + + return strtolower( ltrim( $host, 'www.' ) ); + } + + /** + * Resolves a plugin basename to its directory slug. + * + * @param string $plugin_file Plugin basename (e.g. 'slug/file.php'). + * + * @return string Slug. + */ + private function slug_from_file( string $plugin_file ): string { + $slug = dirname( $plugin_file ); + + if ( '.' === $slug ) { + $slug = basename( $plugin_file, '.php' ); + } + + return $slug; + } + + /** + * Extracts a string value from a mixed-value row. + * + * @param array $row Catalog row. + * @param string $key Key to read. + * @param string $fallback Default when absent or non-string. + * + * @return string + */ + private function str( array $row, string $key, string $fallback ): string { + $value = $row[ $key ] ?? $fallback; + + return is_string( $value ) ? $value : $fallback; + } +} diff --git a/readme.txt b/readme.txt index 8993d34..555ab09 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 0.4.0 +Stable tag: 0.5.0 Requires PHP: 7.4 -Version: 0.4.0 +Version: 0.5.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -89,6 +89,12 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 0.5.0 = + +_Release date: 2026-08-14_ + +* Native update integration: catalog plugins now show WordPress's standard "Update available" badge and update through the regular wp-admin flow. Updates download from the ROBOTSTXT store (premium plugins authenticate via the account API key; requires Core 1.5.0+). "View details" modal data comes from the catalog. Refreshing the catalog also forces a fresh WordPress update check. + = 0.4.0 = _Release date: 2026-08-14_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index a4d4f84..69a2643 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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.4.0 + * Version: 0.5.0 * Requires at least: 4.7 * Requires PHP: 7.4 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.4.0' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.5.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); @@ -42,6 +42,7 @@ if ( file_exists( ROBOTSTXT_MANAGER_DIR . 'vendor/autoload.php' ) ) { require_once ROBOTSTXT_MANAGER_DIR . 'includes/class-robotstxt-manager-core-client.php'; require_once ROBOTSTXT_MANAGER_DIR . 'includes/class-robotstxt-manager-activator.php'; require_once ROBOTSTXT_MANAGER_DIR . 'includes/class-robotstxt-manager-plugin.php'; + require_once ROBOTSTXT_MANAGER_DIR . 'includes/class-robotstxt-manager-updater.php'; require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-admin.php'; require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-settings.php'; require_once ROBOTSTXT_MANAGER_DIR . 'admin/class-robotstxt-manager-installer.php'; diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 1f39cc6..3ff45e9 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -15,4 +15,5 @@ return array( 'Robotstxt_Manager_Loader' => $baseDir . '/includes/class-robotstxt-manager-loader.php', 'Robotstxt_Manager_Plugin' => $baseDir . '/includes/class-robotstxt-manager-plugin.php', 'Robotstxt_Manager_Settings' => $baseDir . '/admin/class-robotstxt-manager-settings.php', + 'Robotstxt_Manager_Updater' => $baseDir . '/includes/class-robotstxt-manager-updater.php', ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 9a2f7bf..7d5ea49 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -16,6 +16,7 @@ class ComposerStaticInit21193cc5ee16adfbe1fcd1cc267e45ef 'Robotstxt_Manager_Loader' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-loader.php', 'Robotstxt_Manager_Plugin' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-plugin.php', 'Robotstxt_Manager_Settings' => __DIR__ . '/../..' . '/admin/class-robotstxt-manager-settings.php', + 'Robotstxt_Manager_Updater' => __DIR__ . '/../..' . '/includes/class-robotstxt-manager-updater.php', ); public static function getInitializer(ClassLoader $loader) From 061329c704997949cd0fc99476578154af32208e Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Sat, 15 Aug 2026 12:40:32 +0000 Subject: [PATCH 06/23] v0.5.1 --- admin/class-robotstxt-manager-settings.php | 14 +++++++++++ changelog.txt | 25 +++++++++++++++++++ .../class-robotstxt-manager-core-client.php | 20 +++++++++++++-- includes/class-robotstxt-manager-updater.php | 24 ++++++++++++++++-- readme.txt | 25 +++++++++++++------ robotstxt-manager.php | 8 +++--- uninstall.php | 5 +++- vendor/autoload.php | 2 +- vendor/composer/autoload_real.php | 8 +++--- vendor/composer/autoload_static.php | 4 +-- vendor/composer/platform_check.php | 4 +-- 11 files changed, 114 insertions(+), 25 deletions(-) diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index 1c2d7ac..6e54620 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -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 ); diff --git a/changelog.txt b/changelog.txt index e14081a..24095ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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_ diff --git a/includes/class-robotstxt-manager-core-client.php b/includes/class-robotstxt-manager-core-client.php index 6fc3af6..ce39709 100644 --- a/includes/class-robotstxt-manager-core-client.php +++ b/includes/class-robotstxt-manager-core-client.php @@ -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 ); diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php index bd98198..ca8a746 100644 --- a/includes/class-robotstxt-manager-updater.php +++ b/includes/class-robotstxt-manager-updater.php @@ -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 ); } /** diff --git a/readme.txt b/readme.txt index 555ab09..2766b7c 100644 --- a/readme.txt +++ b/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_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index 69a2643..dbf7d6d 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -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__ ) ); diff --git a/uninstall.php b/uninstall.php index fdf86d4..2f75539 100644 --- a/uninstall.php +++ b/uninstall.php @@ -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' ); diff --git a/vendor/autoload.php b/vendor/autoload.php index 30d2faf..38a9b43 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -19,4 +19,4 @@ if (PHP_VERSION_ID < 50600) { require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit21193cc5ee16adfbe1fcd1cc267e45ef::getLoader(); +return ComposerAutoloaderInit891e1a90cdf8723a2ca296aab35c6101::getLoader(); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index d53f30b..036374e 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -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); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 7d5ea49..d8902c3 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -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); } diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index d2225c7..a70ba47 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -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) { From 69c8e9eb97dc265156e63636ec837e2460076684 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Sat, 15 Aug 2026 15:37:04 +0000 Subject: [PATCH 07/23] v0.5.3 --- admin/class-robotstxt-manager-installer.php | 10 +- changelog.txt | 37 +++++ .../class-robotstxt-manager-encryption.php | 143 ++++++++++++++++-- readme.txt | 16 +- robotstxt-manager.php | 4 +- 5 files changed, 191 insertions(+), 19 deletions(-) diff --git a/admin/class-robotstxt-manager-installer.php b/admin/class-robotstxt-manager-installer.php index e21be60..3ad4bb3 100644 --- a/admin/class-robotstxt-manager-installer.php +++ b/admin/class-robotstxt-manager-installer.php @@ -323,7 +323,11 @@ class Robotstxt_Manager_Installer { $result = $upgrader->install( $tmp_file, array( - 'overwrite' => $overwrite, + // Option key differs across WP versions: 'overwrite' (5.5-era) + // vs 'overwrite_package' (current). Pass both; the unused key + // is ignored by wp_parse_args(). + 'overwrite' => $overwrite, + 'overwrite_package' => $overwrite, ) ); @@ -358,7 +362,11 @@ class Robotstxt_Manager_Installer { } if ( ! class_exists( 'Plugin_Upgrader' ) ) { + // class-wp-upgrader.php bundles WP_Upgrader + the skins (incl. + // Automatic_Upgrader_Skin), but Plugin_Upgrader itself lives in + // its own file since the WP 5.3 split — both are required. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; } } diff --git a/changelog.txt b/changelog.txt index 24095ca..4925b1c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,42 @@ == Changelog == += 0.5.3 = + +_Release date: 2026-08-15_ + +**Added** + +* Authenticated encryption for the stored API key (same encrypt-then-MAC scheme as Core 1.6.0): tampered payloads fail closed, and encryption refuses to run without real WordPress salts. Legacy-stored keys keep decrypting and re-encrypt on the next save. + +**Changed** + +* Plugin version 0.5.2 → 0.5.3. 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.2 = + +_Release date: 2026-08-15_ + +**Fixed** + +* Manager-panel Install/Update failed while the native WordPress updater worked. Two defects in the panel's upgrader path (`Robotstxt_Manager_Installer`): + 1. `ensure_plugin_functions()` required only `class-wp-upgrader.php`, which since the WordPress 5.3 class split no longer defines `Plugin_Upgrader` (it lives in `class-plugin-upgrader.php`). In a stock `admin-post.php` context nothing else loads it → fatal error. Both files are now required. + 2. The overwrite flag was passed to `Plugin_Upgrader::install()` as `'overwrite'`, but current WordPress reads `'overwrite_package'` — the unknown key was silently discarded, `clear_destination` stayed false, and the install failed against the existing folder. Both keys are now passed (the unused one is ignored by `wp_parse_args()`), keeping compatibility across WP versions. +* Verified end-to-end on the live store: panel-path download → overwrite-install succeeds, plugin remains active. + +**Changed** + +* Plugin version 0.5.1 → 0.5.2. 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.1 = _Release date: 2026-08-15_ diff --git a/includes/class-robotstxt-manager-encryption.php b/includes/class-robotstxt-manager-encryption.php index 48e046f..ae772f7 100644 --- a/includes/class-robotstxt-manager-encryption.php +++ b/includes/class-robotstxt-manager-encryption.php @@ -1,6 +1,6 @@ Date: Mon, 17 Aug 2026 16:00:15 +0000 Subject: [PATCH 08/23] v0.6.0 --- admin/class-robotstxt-manager-admin.php | 34 +- changelog.txt | 18 + languages/robotstxt-manager-ca.mo | Bin 6725 -> 7841 bytes languages/robotstxt-manager-ca.po | 351 ++++++++++--------- languages/robotstxt-manager-es_ES.mo | Bin 6702 -> 7844 bytes languages/robotstxt-manager-es_ES.po | 351 ++++++++++--------- languages/robotstxt-manager.pot | 430 +++++++++++------------- readme.txt | 11 +- robotstxt-manager.php | 4 +- 9 files changed, 658 insertions(+), 541 deletions(-) diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index 3ef11b3..a32c40c 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -76,7 +76,8 @@ class Robotstxt_Manager_Admin { * * Clears the cached catalog response, purges WordPress's update_plugins * transient so native update badges re-evaluate immediately, and - * redirects back to the page. + * redirects back to the page. Rate-limited to 6 refreshes per minute + * per user so a stuck browser cannot hammer the store. * * @return void */ @@ -87,6 +88,16 @@ class Robotstxt_Manager_Admin { check_admin_referer( 'robotstxt_manager_refresh_catalog' ); + $bucket = 'robotstxt_manager_refresh_' . get_current_user_id(); + $hits_raw = get_transient( $bucket ); + $hits = is_numeric( $hits_raw ) ? (int) $hits_raw : 0; + + if ( $hits >= 6 ) { + $this->redirect_refresh_error(); + } + + set_transient( $bucket, $hits + 1, MINUTE_IN_SECONDS ); + $client = Robotstxt_Manager_Core_Client::from_options(); $client->clear_catalog_cache(); delete_site_transient( 'update_plugins' ); @@ -103,6 +114,27 @@ class Robotstxt_Manager_Admin { exit; } + /** + * Redirects back to the catalog page with a rate-limit error notice. + * + * @return void + */ + private function redirect_refresh_error(): void { + wp_safe_redirect( + add_query_arg( + array( + 'page' => self::PAGE_SLUG, + 'robotstxt_manager_result' => 'error', + 'robotstxt_manager_message' => rawurlencode( + __( 'Too many refreshes. Please wait a minute before refreshing again.', 'robotstxt-manager' ) + ), + ), + admin_url( 'admin.php' ) + ) + ); + exit; + } + /** * Builds a nonce-protected admin-post action URL for a plugin row. * diff --git a/changelog.txt b/changelog.txt index 4925b1c..32683dc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,23 @@ == Changelog == += 0.6.0 = + +_Release date: 2026-08-15_ + +**Added** + +* Spanish (es_ES) and Catalan (ca) translations — all 73 admin strings, regenerated POT included. +* "Refresh catalog" is rate-limited to 6 refreshes per minute per user (transient bucket), redirecting back with an error notice when exceeded; the limit rejects before clearing any cache (Phase 6 hardening). + +**Changed** + +* Plugin version 0.5.3 → 0.6.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) + = 0.5.3 = _Release date: 2026-08-15_ diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index 92499d6e25c06d1c282578e7c690f1f5bcb27297..71cdb38b41d0c2db01e141e92caa6638930ecc57 100644 GIT binary patch literal 7841 zcmbuDd59!e9mh)(jjo9?@jmksGww`G_v~&q*X)tZ&dz2x*-6Ho*&Kr6>+aVxWvZ(> zb(%?+SO59)3%?$4+|Kzf&ghjv;K0W(xCej8+~;QH4F0hhsk@ZI1%*a5rX1>jTQtH7th zw}O8FF9%-$Mfar&U(L-l_m_bNoCPlfJK*cV9J~SiEcj9IF;M(Z@Fl)>gKq?ngA)BC z;IBXjUJSmAFUd0nz6sn7ik~CZ{fEJ~bDx2{5-+`Bc2M~&_f(20YJ_t&Weelg-0_wfbgWC5ipy>P* z)ca3@s33S2JOMrj9s;l9q4-?`uK~XW&Vo;Z(#u6}DBrsn6#s7pCFgrUSPN#sec)Z7 z^z>y=bWeewdj;}o z#ZL-CD)=HOJ$)Axzdr%r2R;LS2YeC4biuZohxxJOc=~oxC{=sUjgp`p9SSd zSHjG7;Bjysd=Qj>KLv`;??LI~kKp^kzku5JQj{_SUIiNPL!k8VNf3AP?@E60ILCR2 z({R3$Q-^FNxsP!kxDS6%D}Z{$QB-VV}o@+-!4v^k}3`Thl*vcW;l`j8Ixa_;1med}YEi|Oj-22ehxL%P!; z8_7>(d->BY&RaR9^ZK~Hx=@~!j^*zP1KxQ%bcg=~R|gIhRd zzhI(ghEcH+#)eP6>P*+>HcD1Zn3*`qO*a_~Z60D$`W{XhF=#&+{?&E~EL744qEZp59Ltsa;#H~lmjcxj?A z%T32c$+}-&4149PleUu-xtYkOf?0FF+myIjo}{j4CL_x`drf!MrVvA<8MD8e^xVOT zZ2z>sm};Bj&aOGriEMno$rHA4Lr>6cKrxJ0wCO(MV%|5_#BSZJ*{E<@o4R3SyAD3v zXAtkeR5vN2o>x-GnVu6x#JevkuuVMnw&`q^1VnC}2C}WWYh{$4i(D>BweeuN$nzww zbuznn%-rEN1EJJVz5ux;iH8c}6_<(Wp-h{J<7(x|KjMD4QlK~Z_+aCRHtw0M=w#hA9Oifz8hD^^ zS)R4cG8>j^g&P-l)6F4{foy#&=?T~sC%*Ka2YF5~8=E3_8$*Y6d&Z?Hno7FeBK1-n zvY9g{PaHQ%-*cS0K>|CAK1wn(r>!fuBQW46U$r@3Ra~IyC^Ex>FUea4R~=Py%yw|7 z*bL@uch&J-Mw`DglY=lWa)(pazo)KGfUQcqb4eU~jz^agYHs8#-PB2K*5qz5Oj4U} zntn*wq6^bY*5fF#ewW(0Sl(UTCe{q`5FCahgH$KuO32`F=|q)1>sz9(7LJ&*W*y6o zON=^-dJC~%?IN92fmUsJi#KDfpfjvpZZ4hj+R&^g>HYF|{49(rudm|Q2iu4gi=Kb* z`@!K#a?`gcgXliGyu4^8dQ)(;{gIdcNa|d0G+8%M0ueD=Glm(4NsMdw+){c$##5}= zQ<6jPsg1J`F52d>>)RsAXUq+|Ol4D2MzT&a@cx?N#NM!GAa_doIm+{11i^&#gYWq# zwXZ8WM6+!c>_#{!1~X>&-o3lb_#Ck zo~X=#dUFECuGol{#3$b3~Fr!ORI!}cf>Fzj|{xW1S3s1SG8KYNmCBWWoL-h4wlK@xVcYf%3->%!gU!# z)}|{7YUeN}YC@{gTrw=nNMHF?aqpwiOFPmnErPE@-quj7GY-zA5T=@z)v(G0?0SA5 z+P+2Qk5uA{E1zYx21eBwnY4(EHEP_dG=mXjH?n&-o8W_p@3&>O1tY$xf; zbXE+tc)Cm-(`nL4@+{xTTWDEXX~%I|y4JGtb7V;qGfj#z`&X05-a2}v?XrU`k5>v_ zY%M!Wrkzuf-a17Wc&>#r6oMTK#}?)r;dUbfC7Z+!Wz020UEUCjWSD)vxy)RC;NwmFEjxh4b7^hqp+qMSpvdkwr9=c)?-l~4l<1bHr+aP&Qaj73pZ?Q zHK7Cb13_0+QUQ+b!&JQZZAu=wyq_`GRzPAxYIX22EhAikLDIFt(y=ia`BB(Tcsl7QV zhQu7y7xgO-{g9Tc8yeSF(@L{YsX^Ht{z>^nR9!C6H(hN- zLtS0S1R-mBuOX+pfoMogun2GAifv-JZMMF|wCa?hwoavCxsYLk*ppIBgjZxqgtO4b zCVDn9=q9jhT-s6Y=^+*iTVThXB=T`AFKz{BY3?w;rvAi(tu{B~5z8 z*3Z4V%jqJFDLpQg-)XYwDAkLmt=bH^v9_TiFl>0QCFYsRt9hfhlL9gpZ6@hZF?)C= zdT#=mElCJ^O*6t^h)IJ5H`sbaI_cVC+YJniy;_bQ<>XOjg>BJ7Ce!dC0sQ5LWTsgg zb3@G^sXVM7<{KEQ8bg>8xFH^~nC;}U_GT4nVO;KNAVJxREuieWq^&fVFP(N2 zU-HiSx15PB^CstI$wiRu!RaCm|_UVOiLupEy}VMN2!&2&ou}a}6`|epE#j zjWWjvnzKCi8oUk1lRpL@Kz8K|ZyO2eMJkOm81obD@0+DEgN}*IyMFY62P{1{@hoEG zUK#7n)I(o0k1~Cp%gjJUMm~`EOkSp9HuAq`iBC|tDdUe1fa>S*sI{IrGtky4hM;XD z5bU0P1d_9CQ(WUiGE)0I&&FmV?UL71Q85)DE^`oquv<|LM+Duxh9PE0=A!eBJC%>0 zcZo{Qz~#uL)OmW!QwpU3Fdijo49Tt?Rt~OzR7k=;;FN2yn7pM-nRx418un|3Tc=1E zUgN`trm6z{)xgY-YNn54PcBulBgv+CYU-o8(P>_dLjgfjMcgRckF6S;n((5eg9wb0 zbySUyO|EEWrRHkXJMym&A$FFEa{A~*+VX!?y$r?pK}uO`&GE;UGHmYj=Ude-q0v$@ z3_hkFmeouAFva*9Zj45&@{ZYRu~BlcfUm>6_w;JO^2OwIyU7Di|LrUyE44PhGE)1t zB1<2>=2gJ2{0F8qUZ)yw&nnSG`rBxLRZ92q4NO(J6~$|mh-x0AveZ7M29p09DJ*&UN9G}Wm5#2NF6_~n)-|iF%S?CG N0`>dbAC5E@{1-eM>=&K+ljp!+ew|oX$v8=q>5E9iQ5#RBF4L8`_jAj zZtue+6_TS$tq?6xIzXhVg%&{(CYT{uIiV_;)ks5~Bpb9XChv9a35%Q=X@nQ+}3%FUN ze3OPucpFLxu0ffwfnBgob-;a4L-FWjy}t-~)JJ)Vp&mR6pMmZ0=dcUD1)nFZ*P$r! z1s4ArJh6jNewoH!X`r@x0$nHI)9`lqD*QJ5Efi0mCO*>t9DEdhA7-BKta&yJ#gIE; z2YeW6HV&XT@_DQhN1unI@av+##Qv9bu!njFPQib}&%jAmK@9a{CWY-!x1M&J`NGIM(nWRdNj8i3KQp7MRJ=DEaiLI!38}$G+Pn9B$ipo+X4Eq}UOU$H*L<3oi zB!c^?A~$)qS_-rdQ6)S>R85uK5ILoY?1+@*gM5qpW741y9Xu@Ss-G@+f#z`^+xYUO z)0_U%s3%a;yVynleSkRR7Vc?(B4KtQJ z`$_lDE&c9f^Pk+ux4yAS5658`>~)W9JLO)>^|_zr`rXxS-*Df%^;P#&%b>f`(&t{- zzOVOO)v5=pItfuCk!EI6eRAK90XMVbLeseW^N!+);!OUOUGic~%72i-Q+i@-VlqE= zI6pq2#}7=6-#IcyX`VK5lAn!C5c?(xqp3BD-sJ`Jp*!E&>4Mf4_qoq?`jrXV(j%I_rv!50VW0BlJ$L= zUJ5H!vcXntY#8U07m`K%S>jAv#48boIGbmZm6v?GpuN?sDS0cG+@l>m zT|+m-!sgf()Ui_b^Q#>nxyi=vddCyXvnKUXD6>Em6k~j>k+iHev*VJ?=CqM8_X3B3mI+yEbL{`tXtsRu|(E6eK zO({u@?`GKC+-Tu zrP^AB##TY{`}41;4@D+PSH7F552q%Oh-kF0az(5Ot~=dzu!jpGi{m_=a5yK&G-;P6 zaX~-iE_Y3BZe)#nw`*#9q=&0fNIIGdhi9*QELUvY6S?DIFz+oOhQtSBRx{E4CpXC7 z3Nx`4*B?AJoux>Ys_vfd+YYXqJE}uHj|wHN>)9fZ& Hzp?r+@$JNk diff --git a/languages/robotstxt-manager-ca.po b/languages/robotstxt-manager-ca.po index dbe5a0b..36440aa 100644 --- a/languages/robotstxt-manager-ca.po +++ b/languages/robotstxt-manager-ca.po @@ -1,184 +1,235 @@ +# Copyright (C) 2026 ROBOTSTXT +# This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.0\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 0.6.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" -"POT-Creation-Date: 2026-08-12T13:09:24+00:00\n" -"PO-Revision-Date: 2026-08-12 13:15+0000\n" -"Last-Translator: ROBOTSTXT \n" -"Language-Team: Català\n" +"Last-Translator: ROBOTSTXT \n" +"Language-Team: Català \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Domain: robotstxt-manager\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Domain: robotstxt-manager\n" -msgid "Manager (by ROBOTSTXT)" -msgstr "Manager (by ROBOTSTXT)" +msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." +msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." +msgstr[0] "%d plugin del catàleg no és compatible amb la versió de WordPress o de PHP d'aquest lloc." +msgstr[1] "%d plugins del catàleg no són compatibles amb la versió de WordPress o de PHP d'aquest lloc." -msgid "Client-side dashboard for the ROBOTSTXT plugin ecosystem. Lists the catalog from a remote Plugins Core install, resolves local install/update state, and surfaces subscription health. Phase 1: read-only catalog view." -msgstr "Tauler de control del costat del client per a l'ecosistema de plugins de ROBOTSTXT. Llista el catàleg des d'una instal·lació remota de Plugins Core, resol l'estat local d'instal·lació/actualització, i mostra l'estat de les subscripcions. Fase 1: vista de catàleg de només lectura." +msgid "%s activated." +msgstr "%s activat." -msgid "ROBOTSTXT" -msgstr "ROBOTSTXT" +msgid "%s installed. Activate it from the list below." +msgstr "%s instal·lat. Activa'l des de la llista de sota." -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "Manager (by ROBOTSTXT) — Plugins" +msgid "%s updated." +msgstr "%s actualitzat." -msgid "You do not have sufficient permissions to access this page." -msgstr "No tens permisos suficients per accedir a aquesta pàgina." +msgid "(about %s)" +msgstr "(sobre %s)" -msgid "Insufficient permissions." -msgstr "Permisos insuficients." +msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." +msgstr "Hi ha una clau desada (últims 4 caràcters: %s). Deixa-ho en blanc per mantenir la clau actual; escriu un valor nou per substituir-la." -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "Manager (by ROBOTSTXT) — Configuració" - -msgid "Settings" -msgstr "Configuració" - -msgid "Connection" -msgstr "Connexió" - -msgid "Store URL" -msgstr "URL del store" +msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." +msgstr "Hi ha una clau desada però no s'ha pogut descodificar. Pots substituir-la escrivint un valor nou a dalt, o esborrar-la amb el botó de sota." msgid "API Key" msgstr "Clau API" +msgid "API key deleted. Save changes to persist." +msgstr "Clau API esborrada. Desa els canvis perquè faci efecte." + +msgid "API key is not configured." +msgstr "La clau API no està configurada." + +msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." +msgstr "Clau API a nivell de compte emesa per la botiga ROBOTSTXT. És necessària per autenticar les sol·licituds del catàleg i de les subscripcions. El valor es xifra abans de desar-se." + +msgid "Action" +msgstr "Acció" + +msgid "Activate" +msgstr "Activa" + +msgid "An unexpected error occurred." +msgstr "S'ha produït un error inesperat." + +msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." +msgstr "URL base de la instal·lació remota de Plugins Core d'on aquest lloc obtindrà el catàleg de plugins." + +msgid "Buy" +msgstr "Comprar" + msgid "Cache" msgstr "Memòria cau" msgid "Catalog Cache (minutes)" msgstr "Memòria cau del catàleg (minuts)" -msgid "Data on Uninstall" -msgstr "Dades en desinstal·lar" - -msgid "Testing…" -msgstr "Provant…" - -msgid "Test connection" -msgstr "Provar connexió" - -msgid "Deleting…" -msgstr "Eliminant…" - -msgid "Delete API key" -msgstr "Eliminar clau API" - -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "Eliminar la clau API emmagatzemada? Les dades del catàleg i les subscripcions deixaran de funcionar fins que s'introdueixi una nova clau." - -msgid "API key deleted. Save changes to persist." -msgstr "Clau API eliminada. Desa els canvis per persistir." - -msgid "An unexpected error occurred." -msgstr "S'ha produït un error inesperat." - -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "URL base de la instal·lació remota de Plugins Core des d'on aquest lloc obtindrà el catàleg de plugins." - -msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." -msgstr "Hi ha una clau emmagatzemada (últims 4 caràcters: %s). Deixa-ho en blanc per mantenir la clau existent; introdueix un valor nou per reemplaçar-la." - -msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." -msgstr "Hi ha una clau emmagatzemada però no s'ha pogut descodificar. Pots reemplaçar-la introduint un valor nou a dalt, o eliminar-la amb el botó de baix." - -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." -msgstr "Clau API de nivell de compte emesa pel store de ROBOTSTXT. Necessària per autenticar les peticions de catàleg i subscripció. El valor es xifra abans d'emmagatzemar-se." - -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." -msgstr "Quant de temps s'emmagatzema a la memòria cau la resposta del catàleg de Core. Per defecte: 60 minuts. Valors més baixos actualitzen més sovint a costa de més peticions a Core. Màxim: 1440 (24 hores)." - -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "Eliminar totes les dades del plugin quan es desinstal·li." - msgid "Catalog refreshed." msgstr "Catàleg actualitzat." -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "ROBOTSTXT Manager encara no està configurat. Configura la URL del store i la clau API per veure el teu catàleg de plugins." +msgid "Connected." +msgstr "Connectat." -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "El store de ROBOTSTXT no ha retornat cap plugin. Comprova la URL del store i la clau API a la pàgina de Configuració, o fes clic a Actualitzar per reintentar." +msgid "Connection" +msgstr "Connexió" -msgid "Refresh catalog" -msgstr "Actualitzar catàleg" - -msgid "Plugin" -msgstr "Plugin" - -msgid "Type" -msgstr "Tipus" - -msgid "Price" -msgstr "Preu" - -msgid "Requires WP" -msgstr "Requereix WP" - -msgid "Requires PHP" -msgstr "Requereix PHP" - -msgid "Local state" -msgstr "Estat local" - -msgid "Action" -msgstr "Acció" - -msgid "Premium" -msgstr "Premium" - -msgid "Free" -msgstr "Gratuït" - -msgid "Your site runs WordPress" -msgstr "El teu lloc usa WordPress" - -msgid "Your server runs PHP" -msgstr "El teu servidor usa PHP" - -msgid "Not installed" -msgstr "No instal·lat" - -msgid "Installed (inactive)" -msgstr "Instal·lat (inactiu)" - -msgid "Update available (v%1$s → v%2$s)" -msgstr "Actualització disponible (v%1$s → v%2$s)" - -msgid "Up to date" -msgstr "Actualitzat" - -msgid "Incompatible" -msgstr "Incompatible" - -msgid "Buy" -msgstr "Comprar" - -msgid "Install (soon)" -msgstr "Instal·lar (properament)" - -msgid "Update (soon)" -msgstr "Actualitzar (properament)" - -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." -msgstr[0] "%d plugin del catàleg no és compatible amb la versió de WordPress o PHP d'aquest lloc." -msgstr[1] "%d plugins del catàleg no són compatibles amb la versió de WordPress o PHP d'aquest lloc." - -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "Aquest lloc usa WordPress %1$s en PHP %2$s." - -msgid "Store URL is not configured." -msgstr "La URL del store no està configurada." - -msgid "API key is not configured." -msgstr "La clau API no està configurada." +msgid "Could not create a temporary file for download." +msgstr "No s'ha pogut crear un fitxer temporal per a la baixada." msgid "Could not reach Plugins Core: %s" msgstr "No s'ha pogut connectar amb Plugins Core: %s" -msgid "Connected." -msgstr "Connectat." +msgid "Data on Uninstall" +msgstr "Dades en desinstal·lar" + +msgid "Delete API key" +msgstr "Esborra la clau API" + +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "Esborra totes les dades del plugin quan es desinstal·li." + +msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." +msgstr "Segur que vols esborrar la clau API desada? El catàleg i les dades de subscripció deixaran de funcionar fins que s'introdueixi una clau nova." + +msgid "Deleting…" +msgstr "S'està esborrant…" + +msgid "Download failed (HTTP %d)." +msgstr "Error en la baixada (HTTP %d)." + +msgid "Download failed: %s" +msgstr "Error en la baixada: %s" + +msgid "Free" +msgstr "Gratuït" + +msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." +msgstr "Quant de temps es desa a la memòria cau la resposta del catàleg de Core en un transient. Per defecte: 60 minuts. Els valors més baixos actualitzen més sovint a costa de més sol·licituds a Core. Màxim: 1440 (24 hores)." + +msgid "Incompatible" +msgstr "Incompatible" + +msgid "Install" +msgstr "Instal·la" + +msgid "Installation failed." +msgstr "Error en la instal·lació." + +msgid "Installation failed: %s" +msgstr "Error en la instal·lació: %s" + +msgid "Installed (inactive)" +msgstr "Instal·lat (inactiu)" + +msgid "Insufficient permissions." +msgstr "Permisos insuficients." + +msgid "Local state" +msgstr "Estat local" + +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "Manager (by ROBOTSTXT) — Plugins" + +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "Manager (by ROBOTSTXT) — Paràmetres" + +msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." +msgstr "La botiga ROBOTSTXT no ha retornat cap plugin. Revisa la URL de la botiga i la clau API a la pàgina de paràmetres, o prem Actualitza per tornar-ho a provar." + +msgid "Not installed" +msgstr "No instal·lat" + +msgid "Plugin" +msgstr "Plugin" + +msgid "Plugin is not installed." +msgstr "El plugin no està instal·lat." + +msgid "Plugin not found in catalog." +msgstr "Plugin no trobat al catàleg." + +msgid "Premium" +msgstr "Premium" + +msgid "Price" +msgstr "Preu" + +msgid "ROBOTSTXT" +msgstr "ROBOTSTXT" + +msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." +msgstr "ROBOTSTXT Manager encara no està configurat. Configura la URL de la botiga i la clau API per veure el teu catàleg de plugins." + +msgid "Refresh catalog" +msgstr "Actualitza el catàleg" + +msgid "Requires PHP" +msgstr "Requereix PHP" + +msgid "Requires WP" +msgstr "Requereix WP" + +msgid "Settings" +msgstr "Paràmetres" + +msgid "Show more information" +msgstr "Mostra més informació" + +msgid "Store URL" +msgstr "URL de la botiga" + +msgid "Store URL is not configured." +msgstr "La URL de la botiga no està configurada." + +msgid "Store is not configured." +msgstr "La botiga no està configurada." + +msgid "Test connection" +msgstr "Prova la connexió" + +msgid "Testing…" +msgstr "S'està provant…" + +msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." +msgstr "El format de la clau API no és vàlid. Copia la clau completa des de la teva pàgina de compte de ROBOTSTXT." + +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "La botiga ha respost amb HTTP %d. Revisa la URL de la botiga i la clau API." + +msgid "The store returned an invalid file." +msgstr "La botiga ha retornat un fitxer no vàlid." + +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "Aquest lloc funciona amb WordPress %1$s i PHP %2$s." + +msgid "Too many refreshes. Please wait a minute before refreshing again." +msgstr "Massa actualitzacions. Espera un minut abans de tornar a actualitzar." + +msgid "Type" +msgstr "Tipus" + +msgid "Up to date" +msgstr "Actualitzat" + +msgid "Update" +msgstr "Actualitza" + +msgid "Update available (v%1$s → v%2$s)" +msgstr "Actualització disponible (v%1$s → v%2$s)" + +msgid "Visit website" +msgstr "Visita el lloc web" + +msgid "You do not have sufficient permissions to access this page." +msgstr "No tens permisos suficients per accedir a aquesta pàgina." + +msgid "Your server runs PHP" +msgstr "El teu servidor funciona amb PHP" + +msgid "Your site runs WordPress" +msgstr "El teu lloc funciona amb WordPress" diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index d25186a78111988d752387f820c3ca2d5ecb415a..fe859122d62371a67e3de003a25b5171fdadafa6 100644 GIT binary patch literal 7844 zcmbuDS!^9w9macjwMI z=f7|M{SA-)bj0;K?&oo**F=#6zxQZ9xYn+VqOX8&0G|Z@3+#bUdTbOu0bBz2fv3Rt zfak#Xf)9cGMQe|XqB-zx@VVgo!Owu72RSNw)#IaxuhA0teDDO=2V?MB@TcHoz@LN9 z0v`fz0RIAt?mq)w$HM~8PX!IQ3_ca?gV%!o5MKwtr-7@WM1Lpv zEzp5a1fR!;T*_(xEB9P%OgEAZLi zyFuyyK2UVN4odFtf`}OX04%|uflmgHAQVSMXF$<=3n)Df!DoOusC^#-b?(PO(fKl{ z{XYRwLG&x|Eci$882CzFir+2pW#IkbGWZivdU@HEPOK}bZap!j+lct5xSehj>Z!z9lqz$Ng0P;`F{ zeiQr$D7}3R;f3D;4fsRwJou;Z`|2|rzSqGQ@cUaq{-S&N5ZzCLqVqZM7Vt~pEciPR z647-S9hRaS!B>L|pyv~m9j-yx7lPM=lDo%;^mIG;XYdYCav#K5WUt#m`OPUlWT!qT zI`0KJGP)1c`Y(f$_qzdq3tr^;U*Y{loLF?e3M!7i3C@7O1f`F^hxd=Vv61g_;79rW z22gaq0E*7HLCNuBQ2QPNArbus#B|Z$z&Y?*n3bIKp!9JD)IJMJ-y?7p{0;ara0aFh zf@|RGzz=}(r(b~D_e)Uv{{#4P@UNis@Jx(!7<@U%QPBn{x*r5_C%+fh^)jr0@?pb$ z4Yw}wCOW6NPjPc>bTfB*>1Ty?WEdd2`|c|axX2-oAP3?R=Bb> zd12O0t(h%Xlu4fTrl*#d9%~Et|D0PI8&}Det=s@AI=zJ5lqa(sUYMjZ!y+GfY0{*u zOy8yXwqIV42hG-5+t2ID%#?G{vbo3YNZhi@3pX&csb$Y06K~oAVu-Y8mg0Qij?R=z z3;xSo&#XGT=RN9o8KfR}^R}kK?-VQXI3T zGk2X`HTlqUT)0sVJ8M2lO0%M)gWC}p@KbHtijNQ%XgW>JxaLFhhTv+WN{-o!j@3KS zij6lNpG~y+Co?-rvbu6OW&3;Kh6LEAw7ZgLndf-&RYI*yoFy)t)MicPM&rD&#f}*! zge|%-gM2$nbL&rOor~r1<{q&YiHGPo92um#D+@wK#~UXK_G}-Cx>h)1%9?E~*OeG` z)a?;sKb#_+gg^@i-sa8ND(Et6H-}57Y#W>HytqgHj-MrI@cIzHKG-ItSoHAwz7!n~ zlAEDL8ASJ~_4PF~Gnj*;y|29VCkyAIQ~9<@bBKu9mNCpQ&NE!Y=a$9`O5S3{fs!10 zFKkvOaM3fz-O$!)wPsHuJhDk>_+aP1Zd4#Molvsf`Y_(!cEf=%o+8OHE1U z6Rn%m;(9ntVo9bnFiJ`#`LY+S=CPGo6jNtxMywTPmN=ip_?&t8efPC4_DD<4xk^d1 zjP4-Qgg`@C4mDMEk-^Z_(#OiCi^;$k>YUQI4_x_Bn|*wI@-h%GW4QFGF-{XUo(*O| zy%hmtH*7-rx`V=(w3Q`lB0oS|7bmZK?F&`QA^FdHL+~12l0K4p1SYYICYqUk!=R1k zE=#Cp$Kgs#)@Ll+KwQe4-7{2`{a%;fE-s-cq%D`tYW; zsMS_7*GE{z5-1}(s(oS?si{R^!w>8w-jDs^NgsG-YH4K@6Qyy1EU{sBfDeA1-8_gWJnFcBI4bsfPn~9tCJU1ho?X*e2QOed*r@FIksns^* zjJA-4T%oiV9CUqW?4rBqIC7(ghOZqkc8gqR)w0cQdFl^;_)}&}wCAEXCh%ysU0oANExrBw&N_-%HGihuNCxxA>Wy-KJzq4NRW&Xb7ZxZ3gRxrAUS4c0l=WDP z7n;rOiCGubph^x$-E>vyGaK^Xtx% zX-|}6yZiM#HM7k(J7<<$`HmCkuCi^>__zDcoIZ16D(wM;qK1>{VFmjwQm8M8XHw2# z-))wzI&#%{WV74s*^?E@TO(cIcks(qxS!lv^l2}!!s47qtMj~qO#nCI(} z+04A?h&eEKc;DUg$MX>+7R^M)iCU(8x*9)~y4{QP{&EOJ==T0Zt4VFs;O~ATV|lmx z*Q5G>fKbMb5*njDQ@~}%mmD}PSEBX55NOANfvC09faOWk!0uPb8U%uv0hP+{RtS@Y0hpqU{X+jOTS-d>JVtzvf6EhU1d#vtmu+c+q)WT z6lQNWa>%$Qq>)-4L*2Fo*@X@qgLV!I1S#q)zJ#bPV8v1;O+PPaB*iS|!9$3vVEv%O z6|W#Nb~O3cY#P{}x!voHR!V)+sGopd3$*XBQeX@>BqyNy)m9zK{oeWTL=p9~1*)g# zZ7HegOEh3$$+#w(IA(;yVIPu!G+Rk3JB{%k5zGii0<%TJDp`V2gRRmIJJ3u{JAjU* zrMq_G*e4 zjj^tuM0OttaEbKIM3OfXNk|TRf@TG6#Fb}VUoUuR>Ec>H)KpT)!E6z4CU|t1P8c3! z2COAdwZ#S|?rv+2ExhA(rEO;ve2LzZy-g#A7{|P?>cl3pvh(!ecKb%n&2{20Vy?Aw`s)--W8p*@{|a$;r` z-TUl@Hb=V`Q7MVo=kO*At0ZL28>S-#cTBu0`-IHxH+5sxwo}pWMaCi;gw9k{*sDxM zI7?SE!GpYYdRKkZ8IbVy$$*s4MQeU0LJkh9!=OZlh)%#XD^pELk2L%Q_kN@$%jYQp zu2i19G`T5V4k#7r7NJLG%rBCHkrO6cG1bnm;Dl$3G=A==;-M!&9IzCOmP1;E-1xzS$WGEtZ1$%}m@>TOjg8v8g_}QwF8j&mgE3WO8 zx+%4zqAvT*aHV;6GHL07Nvk#N89ba^-1vT&wVKLtDP!`1OMknPw4@4F23uc28@#6{ z@B8|vunVc%{}$2Fu~1*)pLg z=_Ia6Q{d~k(xLya5C73@dxA-@OsZ$WAefAEwFYeuI{jWuzlN%jXm<7ghrh~Dhb8ej z-+F7lQzXjzq}t}6HYBJ{dJ0_-Q{BMFu+Q(oN-$RY$qw5Vd-Wp!)k`(_d*4hn7X2H8 Ct`=?p delta 2943 zcmZ{kU2Ggz700h>6DM LFDBC+prMPU6(wb=FCp)B!4!q@={eOX@ZVM8);&xSlXO zv&@HyBfxnHq7rH)ZmXa+v<*UhNDV>+RXkLEz=DEkQHoT-OCwPs+C;p-15*3M@667+ zZY!?#?r%Qsz2}_&IcI+Ssb3sgy4iK-(~5B)a|iPuZAwkU7w_PY@$Q{UU4ysaH{skO%|Z$eh_YjC*Wpy7LLO?cnj^M4qt)td}Lcwi9>LJ z`=d}4{1V)S;jcpp~%=zQBF05B+7~8cq=dT~Hz_pE<%5HST5}WR{pRL;+D*hU6b5n)W3!8B!#J z*_BfF!cnHkEw#}aWiAdgMZSGZ&E!*&wI0Z17bPi?Y>4~^@@uj}gf-pp)5p9(^Rthx zy?Xtj_S>Ca5hJT&vgnbvmK3FE~$ zaqDG0<;6*yt#V9a{O~b77ln0gbmZ2<#MRZzV62aYk&D@wHU0=MC-NGJ7Y2IXHGVQ**423vySg&Lfu>dp1AkGs z+*t73Z`k+N@3N;h{L_B5<3_t4io-D2Z@;qfOq;T=clOy|ZhX!DYSSzBJDc~~-)%11 zD_ic_vDk3c!wsE;=#a=|=1{)1*g0sYJ1@6Y>^q&+lhx_c8F#^paj0~h#S?n$z}R@{ zz>!jAOjiz0RPG%)0dsvN~xNbs8A$_-4V?#;>DKU@vStyX2lH8zj;_iquII z)>6l1sSZ+iA=C?`C(;cQNpjqJ!#5X9Znn(VwQ4Z{YOljDDq~_hz_;udv!0cad%~He)+TR;(E-*1mH#X&F;~|L>+hw zt}o#`DNz~i)=@zGA`V`}s7bx9D$ue%ZIAX8_cq+bbI1$+$!?P@U!^ody+|2lRpfZf z*8}_Ao?#yOE4|cgoow+3{vjyKCArO3F_}APT>pOWwxz6ZQkTZbOwc7* zZ*~y7R__=!PLdkmyU;vfguv@5HIzHo+{qr_NMq%Sh+Jj;K;BUlHj1-m#k)?qqi?b4 za(ra$lfAp`t=_T1i7cHa%1d&x`k-n)vIqOdKfm(UZ`CXdT6IgA-LI!Yw4padA+%hT zu8%orf7^F(Lh3m`tDIZnTM@o}@h9rDkx9H*9ZQ$51~hTvQRrC#JI3LwVHi-~-!6P1 z|F+yHjCP#(phWGu{-OS3VZ9Nh?yNy~{qUKoEZVGy?dSS;RwmD-R7@=WF>fwNNjcgL z^kgfQE4@o^OFKmN`Tkz}UjJNsZpV7tC+)TE!vh~WTTOk`*!Yft`yH}t&xTR0ic+!OQyGlK8TlOT85@l4YsKQn!@pH5+2MhccgtQ@s+qL;&HC_R F>VE+9x%>bC diff --git a/languages/robotstxt-manager-es_ES.po b/languages/robotstxt-manager-es_ES.po index e2f8bae..98d8510 100644 --- a/languages/robotstxt-manager-es_ES.po +++ b/languages/robotstxt-manager-es_ES.po @@ -1,184 +1,235 @@ +# Copyright (C) 2026 ROBOTSTXT +# This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.0\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 0.6.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" -"POT-Creation-Date: 2026-08-12T13:09:24+00:00\n" -"PO-Revision-Date: 2026-08-12 13:15+0000\n" -"Last-Translator: ROBOTSTXT \n" -"Language-Team: Español\n" +"Last-Translator: ROBOTSTXT \n" +"Language-Team: Español (España) \n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Domain: robotstxt-manager\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Domain: robotstxt-manager\n" -msgid "Manager (by ROBOTSTXT)" -msgstr "Manager (by ROBOTSTXT)" +msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." +msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." +msgstr[0] "%d plugin del catálogo no es compatible con la versión de WordPress o de PHP de este sitio." +msgstr[1] "%d plugins del catálogo no son compatibles con la versión de WordPress o de PHP de este sitio." -msgid "Client-side dashboard for the ROBOTSTXT plugin ecosystem. Lists the catalog from a remote Plugins Core install, resolves local install/update state, and surfaces subscription health. Phase 1: read-only catalog view." -msgstr "Panel de control del lado del cliente para el ecosistema de plugins de ROBOTSTXT. Lista el catálogo desde una instalación remota de Plugins Core, resuelve el estado local de instalación/actualización, y muestra el estado de las suscripciones. Fase 1: vista de catálogo de solo lectura." +msgid "%s activated." +msgstr "%s activado." -msgid "ROBOTSTXT" -msgstr "ROBOTSTXT" +msgid "%s installed. Activate it from the list below." +msgstr "%s instalado. Actívalo desde la lista de abajo." -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "Manager (by ROBOTSTXT) — Plugins" +msgid "%s updated." +msgstr "%s actualizado." -msgid "You do not have sufficient permissions to access this page." -msgstr "No tienes permisos suficientes para acceder a esta página." +msgid "(about %s)" +msgstr "(sobre %s)" -msgid "Insufficient permissions." -msgstr "Permisos insuficientes." +msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." +msgstr "Hay una clave guardada (últimos 4 caracteres: %s). Déjalo en blanco para mantener la clave actual; escribe un valor nuevo para sustituirla." -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "Manager (by ROBOTSTXT) — Ajustes" - -msgid "Settings" -msgstr "Ajustes" - -msgid "Connection" -msgstr "Conexión" - -msgid "Store URL" -msgstr "URL del store" +msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." +msgstr "Hay una clave guardada pero no se pudo descodificar. Puedes sustituirla escribiendo un valor nuevo arriba, o borrarla con el botón de abajo." msgid "API Key" msgstr "Clave API" +msgid "API key deleted. Save changes to persist." +msgstr "Clave API borrada. Guarda los cambios para que surta efecto." + +msgid "API key is not configured." +msgstr "La clave API no está configurada." + +msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." +msgstr "Clave API a nivel de cuenta emitida por la tienda ROBOTSTXT. Es necesaria para autenticar las solicitudes del catálogo y de las suscripciones. El valor se cifra antes de guardarse." + +msgid "Action" +msgstr "Acción" + +msgid "Activate" +msgstr "Activar" + +msgid "An unexpected error occurred." +msgstr "Ha ocurrido un error inesperado." + +msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." +msgstr "URL base de la instalación remota de Plugins Core de la que este sitio obtendrá el catálogo de plugins." + +msgid "Buy" +msgstr "Comprar" + msgid "Cache" msgstr "Caché" msgid "Catalog Cache (minutes)" msgstr "Caché del catálogo (minutos)" -msgid "Data on Uninstall" -msgstr "Datos al desinstalar" - -msgid "Testing…" -msgstr "Probando…" - -msgid "Test connection" -msgstr "Probar conexión" - -msgid "Deleting…" -msgstr "Eliminando…" - -msgid "Delete API key" -msgstr "Eliminar clave API" - -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "¿Eliminar la clave API almacenada? Los datos del catálogo y las suscripciones dejarán de funcionar hasta que se introduzca una nueva clave." - -msgid "API key deleted. Save changes to persist." -msgstr "Clave API eliminada. Guarda los cambios para persistir." - -msgid "An unexpected error occurred." -msgstr "Ocurrió un error inesperado." - -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "URL base de la instalación remota de Plugins Core desde la que este sitio obtendrá el catálogo de plugins." - -msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." -msgstr "Hay una clave almacenada (últimos 4 caracteres: %s). Déjalo en blanco para mantener la clave existente; introduce un nuevo valor para reemplazarla." - -msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." -msgstr "Hay una clave almacenada pero no se pudo decodificar. Puedes reemplazarla introduciendo un nuevo valor arriba, o eliminarla con el botón de abajo." - -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." -msgstr "Clave API de nivel de cuenta emitida por el store de ROBOTSTXT. Necesaria para autenticar las peticiones de catálogo y suscripción. El valor se cifra antes de almacenarse." - -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." -msgstr "Cuánto tiempo se cachea la respuesta del catálogo de Core en un transient. Por defecto: 60 minutos. Valores más bajos actualizan más a menudo a costa de más peticiones a Core. Máximo: 1440 (24 horas)." - -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "Eliminar todos los datos del plugin cuando se desinstale." - msgid "Catalog refreshed." msgstr "Catálogo actualizado." -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "ROBOTSTXT Manager aún no está configurado. Configura la URL del store y la clave API para ver tu catálogo de plugins." +msgid "Connected." +msgstr "Conectado." -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "El store de ROBOTSTXT no devolvió ningún plugin. Comprueba la URL del store y la clave API en la página de Ajustes, o haz clic en Actualizar para reintentar." +msgid "Connection" +msgstr "Conexión" -msgid "Refresh catalog" -msgstr "Actualizar catálogo" - -msgid "Plugin" -msgstr "Plugin" - -msgid "Type" -msgstr "Tipo" - -msgid "Price" -msgstr "Precio" - -msgid "Requires WP" -msgstr "Requiere WP" - -msgid "Requires PHP" -msgstr "Requiere PHP" - -msgid "Local state" -msgstr "Estado local" - -msgid "Action" -msgstr "Acción" - -msgid "Premium" -msgstr "Premium" - -msgid "Free" -msgstr "Gratis" - -msgid "Your site runs WordPress" -msgstr "Tu sitio usa WordPress" - -msgid "Your server runs PHP" -msgstr "Tu servidor usa PHP" - -msgid "Not installed" -msgstr "No instalado" - -msgid "Installed (inactive)" -msgstr "Instalado (inactivo)" - -msgid "Update available (v%1$s → v%2$s)" -msgstr "Actualización disponible (v%1$s → v%2$s)" - -msgid "Up to date" -msgstr "Actualizado" - -msgid "Incompatible" -msgstr "Incompatible" - -msgid "Buy" -msgstr "Comprar" - -msgid "Install (soon)" -msgstr "Instalar (próximamente)" - -msgid "Update (soon)" -msgstr "Actualizar (próximamente)" - -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." -msgstr[0] "%d plugin del catálogo no es compatible con la versión de WordPress o PHP de este sitio." -msgstr[1] "%d plugins del catálogo no son compatibles con la versión de WordPress o PHP de este sitio." - -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "Este sitio usa WordPress %1$s en PHP %2$s." - -msgid "Store URL is not configured." -msgstr "La URL del store no está configurada." - -msgid "API key is not configured." -msgstr "La clave API no está configurada." +msgid "Could not create a temporary file for download." +msgstr "No se pudo crear un archivo temporal para la descarga." msgid "Could not reach Plugins Core: %s" msgstr "No se pudo conectar con Plugins Core: %s" -msgid "Connected." -msgstr "Conectado." +msgid "Data on Uninstall" +msgstr "Datos al desinstalar" + +msgid "Delete API key" +msgstr "Borrar la clave API" + +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "Borrar todos los datos del plugin cuando se desinstale." + +msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." +msgstr "¿Seguro que quieres borrar la clave API guardada? El catálogo y los datos de suscripción dejarán de funcionar hasta que se introduzca una clave nueva." + +msgid "Deleting…" +msgstr "Borrando…" + +msgid "Download failed (HTTP %d)." +msgstr "Fallo en la descarga (HTTP %d)." + +msgid "Download failed: %s" +msgstr "Fallo en la descarga: %s" + +msgid "Free" +msgstr "Gratis" + +msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." +msgstr "Cuánto tiempo se guarda en caché la respuesta del catálogo de Core en un transient. Por defecto: 60 minutos. Los valores más bajos actualizan con más frecuencia a costa de más solicitudes a Core. Máximo: 1440 (24 horas)." + +msgid "Incompatible" +msgstr "Incompatible" + +msgid "Install" +msgstr "Instalar" + +msgid "Installation failed." +msgstr "Fallo en la instalación." + +msgid "Installation failed: %s" +msgstr "Fallo en la instalación: %s" + +msgid "Installed (inactive)" +msgstr "Instalado (inactivo)" + +msgid "Insufficient permissions." +msgstr "Permisos insuficientes." + +msgid "Local state" +msgstr "Estado local" + +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "Manager (by ROBOTSTXT) — Plugins" + +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "Manager (by ROBOTSTXT) — Ajustes" + +msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." +msgstr "La tienda ROBOTSTXT no devolvió ningún plugin. Revisa la URL de la tienda y la clave API en la página de ajustes, o pulsa Actualizar para volver a intentarlo." + +msgid "Not installed" +msgstr "No instalado" + +msgid "Plugin" +msgstr "Plugin" + +msgid "Plugin is not installed." +msgstr "El plugin no está instalado." + +msgid "Plugin not found in catalog." +msgstr "Plugin no encontrado en el catálogo." + +msgid "Premium" +msgstr "Premium" + +msgid "Price" +msgstr "Precio" + +msgid "ROBOTSTXT" +msgstr "ROBOTSTXT" + +msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." +msgstr "ROBOTSTXT Manager aún no está configurado. Configura la URL de la tienda y la clave API para ver tu catálogo de plugins." + +msgid "Refresh catalog" +msgstr "Actualizar el catálogo" + +msgid "Requires PHP" +msgstr "Requiere PHP" + +msgid "Requires WP" +msgstr "Requiere WP" + +msgid "Settings" +msgstr "Ajustes" + +msgid "Show more information" +msgstr "Mostrar más información" + +msgid "Store URL" +msgstr "URL de la tienda" + +msgid "Store URL is not configured." +msgstr "La URL de la tienda no está configurada." + +msgid "Store is not configured." +msgstr "La tienda no está configurada." + +msgid "Test connection" +msgstr "Probar la conexión" + +msgid "Testing…" +msgstr "Probando…" + +msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." +msgstr "El formato de la clave API no es válido. Copia la clave completa desde tu página de cuenta de ROBOTSTXT." + +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "La tienda respondió con HTTP %d. Revisa la URL de la tienda y la clave API." + +msgid "The store returned an invalid file." +msgstr "La tienda devolvió un archivo no válido." + +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "Este sitio funciona con WordPress %1$s y PHP %2$s." + +msgid "Too many refreshes. Please wait a minute before refreshing again." +msgstr "Demasiadas actualizaciones. Espera un minuto antes de volver a actualizar." + +msgid "Type" +msgstr "Tipo" + +msgid "Up to date" +msgstr "Actualizado" + +msgid "Update" +msgstr "Actualizar" + +msgid "Update available (v%1$s → v%2$s)" +msgstr "Actualización disponible (v%1$s → v%2$s)" + +msgid "Visit website" +msgstr "Visitar el sitio web" + +msgid "You do not have sufficient permissions to access this page." +msgstr "No tienes permisos suficientes para acceder a esta página." + +msgid "Your server runs PHP" +msgstr "Tu servidor funciona con PHP" + +msgid "Your site runs WordPress" +msgstr "Tu sitio funciona con WordPress" diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index fb4c7cb..228e099 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -2,275 +2,233 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.1.3\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 0.6.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-08-12T14:17:17+00:00\n" +"POT-Creation-Date: 2026-08-15T12:00:00+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"X-Generator: WP-CLI 2.12.0\n" +"X-Generator: manual\n" "X-Domain: robotstxt-manager\n" -#. Plugin Name of the plugin -#: robotstxt-manager.php -msgid "Manager (by ROBOTSTXT)" +msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." msgstr "" -#. Plugin URI of the plugin -#: robotstxt-manager.php -msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager" +msgid "%s activated." msgstr "" -#. Description of the plugin -#: robotstxt-manager.php -msgid "Client-side dashboard for the ROBOTSTXT plugin ecosystem. Lists the catalog from a remote Plugins Core install, resolves local install/update state, and surfaces subscription health. Phase 1: read-only catalog view." +msgid "%s installed. Activate it from the list below." msgstr "" -#. Author of the plugin -#: robotstxt-manager.php -#: admin/class-robotstxt-manager-admin.php:48 -msgid "ROBOTSTXT" +msgid "%s updated." msgstr "" -#. Author URI of the plugin -#: robotstxt-manager.php -msgid "https://www.robotstxt.es/" +msgid "(about %s)" msgstr "" -#: admin/class-robotstxt-manager-admin.php:47 -#: admin/views/page-catalog.php:40 -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "" - -#: admin/class-robotstxt-manager-admin.php:64 -#: admin/class-robotstxt-manager-settings.php:207 -msgid "You do not have sufficient permissions to access this page." -msgstr "" - -#: admin/class-robotstxt-manager-admin.php:83 -#: admin/class-robotstxt-manager-settings.php:364 -#: admin/class-robotstxt-manager-settings.php:390 -msgid "Insufficient permissions." -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:59 -#: admin/views/page-settings.php:13 -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:60 -msgid "Settings" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:75 -msgid "Connection" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:92 -msgid "Store URL" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:110 -msgid "API Key" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:118 -msgid "Cache" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:135 -msgid "Catalog Cache (minutes)" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:153 -msgid "Data on Uninstall" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:188 -msgid "Testing…" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:189 -#: admin/class-robotstxt-manager-settings.php:274 -msgid "Test connection" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:190 -msgid "Deleting…" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:191 -#: admin/class-robotstxt-manager-settings.php:278 -msgid "Delete API key" -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:192 -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:193 -#: admin/class-robotstxt-manager-settings.php:398 -msgid "API key deleted. Save changes to persist." -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:194 -msgid "An unexpected error occurred." -msgstr "" - -#: admin/class-robotstxt-manager-settings.php:226 -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "" - -#. translators: %s: last 4 characters of the stored API key. -#: admin/class-robotstxt-manager-settings.php:257 -#, php-format msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." msgstr "" -#: admin/class-robotstxt-manager-settings.php:262 msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." msgstr "" -#: admin/class-robotstxt-manager-settings.php:266 -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." +msgid "API Key" msgstr "" -#: admin/class-robotstxt-manager-settings.php:298 -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." +msgid "API key deleted. Save changes to persist." msgstr "" -#: admin/class-robotstxt-manager-settings.php:313 -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "" - -#: admin/views/page-catalog.php:43 -msgid "Catalog refreshed." -msgstr "" - -#. translators: %s: settings URL. -#: admin/views/page-catalog.php:53 -#, php-format -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "" - -#: admin/views/page-catalog.php:63 -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "" - -#: admin/views/page-catalog.php:66 -#: admin/views/page-catalog.php:69 -msgid "Refresh catalog" -msgstr "" - -#: admin/views/page-catalog.php:75 -msgid "Plugin" -msgstr "" - -#: admin/views/page-catalog.php:76 -msgid "Type" -msgstr "" - -#: admin/views/page-catalog.php:77 -msgid "Price" -msgstr "" - -#: admin/views/page-catalog.php:78 -msgid "Requires WP" -msgstr "" - -#: admin/views/page-catalog.php:79 -msgid "Requires PHP" -msgstr "" - -#: admin/views/page-catalog.php:80 -msgid "Local state" -msgstr "" - -#: admin/views/page-catalog.php:81 -msgid "Action" -msgstr "" - -#: admin/views/page-catalog.php:135 -msgid "Premium" -msgstr "" - -#: admin/views/page-catalog.php:135 -msgid "Free" -msgstr "" - -#: admin/views/page-catalog.php:150 -msgid "Your site runs WordPress" -msgstr "" - -#: admin/views/page-catalog.php:162 -msgid "Your server runs PHP" -msgstr "" - -#: admin/views/page-catalog.php:172 -msgid "Not installed" -msgstr "" - -#: admin/views/page-catalog.php:174 -msgid "Installed (inactive)" -msgstr "" - -#. translators: 1: installed version, 2: available version. -#: admin/views/page-catalog.php:179 -#, php-format -msgid "Update available (v%1$s → v%2$s)" -msgstr "" - -#: admin/views/page-catalog.php:185 -msgid "Up to date" -msgstr "" - -#: admin/views/page-catalog.php:192 -msgid "Incompatible" -msgstr "" - -#: admin/views/page-catalog.php:194 -msgid "Buy" -msgstr "" - -#: admin/views/page-catalog.php:196 -msgid "Install (soon)" -msgstr "" - -#: admin/views/page-catalog.php:199 -msgid "Update (soon)" -msgstr "" - -#. translators: %d: number of incompatible plugins. -#: admin/views/page-catalog.php:214 -#, php-format -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." -msgstr[0] "" -msgstr[1] "" - -#. translators: 1: local WP version, 2: local PHP version. -#: admin/views/page-catalog.php:223 -#, php-format -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "" - -#: includes/class-robotstxt-manager-core-client.php:128 -msgid "Store URL is not configured." -msgstr "" - -#: includes/class-robotstxt-manager-core-client.php:135 msgid "API key is not configured." msgstr "" -#. translators: %s: HTTP transport error message. -#: includes/class-robotstxt-manager-core-client.php:145 -#, php-format +msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Activate" +msgstr "" + +msgid "An unexpected error occurred." +msgstr "" + +msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." +msgstr "" + +msgid "Buy" +msgstr "" + +msgid "Cache" +msgstr "" + +msgid "Catalog Cache (minutes)" +msgstr "" + +msgid "Catalog refreshed." +msgstr "" + +msgid "Connected." +msgstr "" + +msgid "Connection" +msgstr "" + +msgid "Could not create a temporary file for download." +msgstr "" + msgid "Could not reach Plugins Core: %s" msgstr "" -#: includes/class-robotstxt-manager-core-client.php:154 -msgid "Connected." +msgid "Data on Uninstall" +msgstr "" + +msgid "Delete API key" +msgstr "" + +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "" + +msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." +msgstr "" + +msgid "Deleting…" +msgstr "" + +msgid "Download failed (HTTP %d)." +msgstr "" + +msgid "Download failed: %s" +msgstr "" + +msgid "Free" +msgstr "" + +msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." +msgstr "" + +msgid "Incompatible" +msgstr "" + +msgid "Install" +msgstr "" + +msgid "Installation failed." +msgstr "" + +msgid "Installation failed: %s" +msgstr "" + +msgid "Installed (inactive)" +msgstr "" + +msgid "Insufficient permissions." +msgstr "" + +msgid "Local state" +msgstr "" + +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "" + +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "" + +msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." +msgstr "" + +msgid "Not installed" +msgstr "" + +msgid "Plugin" +msgstr "" + +msgid "Plugin is not installed." +msgstr "" + +msgid "Plugin not found in catalog." +msgstr "" + +msgid "Premium" +msgstr "" + +msgid "Price" +msgstr "" + +msgid "ROBOTSTXT" +msgstr "" + +msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." +msgstr "" + +msgid "Refresh catalog" +msgstr "" + +msgid "Requires PHP" +msgstr "" + +msgid "Requires WP" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Show more information" +msgstr "" + +msgid "Store URL" +msgstr "" + +msgid "Store URL is not configured." +msgstr "" + +msgid "Store is not configured." +msgstr "" + +msgid "Test connection" +msgstr "" + +msgid "Testing…" +msgstr "" + +msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." +msgstr "" + +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "" + +msgid "The store returned an invalid file." +msgstr "" + +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "" + +msgid "Too many refreshes. Please wait a minute before refreshing again." +msgstr "" + +msgid "Type" +msgstr "" + +msgid "Up to date" +msgstr "" + +msgid "Update" +msgstr "" + +msgid "Update available (v%1$s → v%2$s)" +msgstr "" + +msgid "Visit website" +msgstr "" + +msgid "You do not have sufficient permissions to access this page." +msgstr "" + +msgid "Your server runs PHP" +msgstr "" + +msgid "Your site runs WordPress" msgstr "" diff --git a/readme.txt b/readme.txt index c89c1b7..2bddf5f 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 0.5.3 +Stable tag: 0.6.0 Requires PHP: 8.0 -Version: 0.5.3 +Version: 0.6.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -89,6 +89,13 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 0.6.0 = + +_Release date: 2026-08-15_ + +* Spanish (es_ES) and Catalan (ca) translations for all admin strings (73 msgids, POT regenerated). +* "Refresh catalog" rate-limited to 6 per minute per user. + = 0.5.3 = _Release date: 2026-08-15_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index c07cb03..5ba69e3 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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.3 + * Version: 0.6.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.5.3' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '0.6.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From e7a4a45a7410aa7c4cbc2e1368964e7198cc48ba Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:01:48 +0000 Subject: [PATCH 09/23] v1.0.0 --- admin/views/page-catalog.php | 121 ++++++++++++++------------- changelog.txt | 17 ++++ languages/robotstxt-manager-ca.mo | Bin 7841 -> 9816 bytes languages/robotstxt-manager-ca.po | 32 +++++-- languages/robotstxt-manager-es_ES.mo | Bin 7844 -> 9783 bytes languages/robotstxt-manager-es_ES.po | 32 +++++-- languages/robotstxt-manager.pot | 32 +++++-- readme.txt | 21 +++-- robotstxt-manager.php | 4 +- 9 files changed, 170 insertions(+), 89 deletions(-) diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 366e5ed..d419227 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -2,10 +2,10 @@ /** * Admin page view: ROBOTSTXT Plugins catalog. * - * Available variables: - * $catalog list> Catalog entries from Core. - * $local array - * Local install state, keyed by slug. + * Catalog table (Plugin · Version · Requires WP · Requires PHP · Price · + * Action · Status) with an always-open detail row under each plugin + * (website link + description), an intro paragraph, and Support / + * Payments sections below the table. * * @package Robotstxt_Manager */ @@ -57,6 +57,10 @@ $compat_warnings = 0;

+

+ +

+

@@ -95,12 +99,12 @@ $compat_warnings = 0; - - + - + + @@ -129,8 +133,7 @@ $compat_warnings = 0; $req_php = is_string( $raw_rphp ) ? $raw_rphp : ''; $website_url = '' !== $page_url ? $page_url : $homepage; - $expandable = ( '' !== $desc || '' !== $website_url ); - $toggle_id = 'robotstxt-manager-toggle-' . esc_attr( $slug ); + $has_detail = ( '' !== $website_url || '' !== $desc ); // Compatibility checks. $wp_ok = '' === $req_wp || version_compare( $local_wp_version, $req_wp, '>=' ); @@ -157,25 +160,9 @@ $compat_warnings = 0; ?> - - - - - -
- - - - - 0.0 ) { - echo esc_html( sprintf( '€%s / year', number_format_i18n( $cost, 0 ) ) ); - } else { - echo '—'; - } - ?> + @@ -200,6 +187,40 @@ $compat_warnings = 0; — + + 0.0 || 'premium' === $kind ) { + echo esc_html( + sprintf( + /* translators: %s: formatted price number. */ + __( '€%s / year', 'robotstxt-manager' ), + number_format_i18n( $cost, 0 ) + ) + ); + } else { + echo esc_html__( 'Free', 'robotstxt-manager' ); + } + ?> + + + + + + + 0.0 && '' !== $page_url ) : ?> + + + 0.0 && '' !== $page_url ) ? 'button-secondary' : 'button-primary'; + ?> + + + + + + + + - - - - - - 0.0 && '' !== $page_url ) : ?> - - - 0.0 && '' !== $page_url ) ? 'button-secondary' : 'button-primary'; - ?> - - - - - - - - - + - -

- + + + @@ -256,6 +258,16 @@ $compat_warnings = 0; +

+

+ +

+ +

+

+ +

+ 0 ) : ?>

@@ -290,12 +302,7 @@ $compat_warnings = 0; .robotstxt-manager-compat-warning { cursor: help; } .robotstxt-manager-row--incompatible { background-color: #fef7f0 !important; } -/* Expandable detail rows (CSS-only toggle). */ -.robotstxt-manager-toggle { display: none; } -.robotstxt-manager-toggle-label { cursor: pointer; display: inline-block; width: 1.4em; } -.robotstxt-manager-toggle-arrow::before { content: "\25B8"; /* ▸ */ } -.robotstxt-manager-detail-row { display: none; } -.robotstxt-manager-detail-row td { border-top: none !important; } -tr:has(.robotstxt-manager-toggle:checked) + tr.robotstxt-manager-detail-row { display: table-row; } -tr:has(.robotstxt-manager-toggle:checked) .robotstxt-manager-toggle-arrow::before { content: "\25BE"; /* ▾ */ } +/* Detail rows (always open): muted, attached to the row above. */ +.robotstxt-manager-detail-row td { border-top: none !important; padding-top: 0; } +.robotstxt-manager-detail-description { color: #50575e; } diff --git a/changelog.txt b/changelog.txt index 32683dc..56d6dfd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 1.0.0 = + +_Release date: 2026-08-15_ + +**Highlights** + +* Catalog page redesign. The table columns are now Plugin · Version · Requires WP · Requires PHP · Price · Action · Status: the Type column is merged into Price ("Free" for cost-0 plugins, the annual price for premium), the version moved into its own column, the action buttons sit before the status, and "Local state" is renamed "Status". Each plugin's detail row — website link first, then its description — is now **always open** (the click-to-expand toggle is gone). An intro paragraph after the page title explains the store, and Support + Payments sections below the table describe how to get help and how the annual-subscription model works (automatic renewal, cancellation from the account page, Mollie processing). + +**Changed** + +* Plugin version 0.6.0 → 1.0.0 — first stable release. 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.6.0 = _Release date: 2026-08-15_ diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index 71cdb38b41d0c2db01e141e92caa6638930ecc57..20a059df8c8f3021abd939b05a2d0d291045be66 100644 GIT binary patch delta 3757 zcmZvdYm8l06~}ieoig+_)KWS_ADikp4`yx);?&MqnW=3NZ3oh6uq1@I@40*KJML@WJ0Y=iZ@E zcV^$;-gE9*d#(RkYu|4k`0df`kDIUjs$$GCw==(dg;ERf?JN0UJlmtxbMP$O06)D} zsS31k9Xt)MhEKr<;CEmKhp$qKrFs^Qz;kc_{vLiE{s&?*Q;+s4#af+)JK#596`qHy z;ZCftfxF>#a2Rfe(~v(k*WtZz4|^AC_yoKLz6dwMSK$P_0PlmF@qV4S`Z*Tj*(30M z@LN!Ve*r$cN~xFN57{3g{PplHD3M)-)h)0OierPF{Q=m|{y5wXJ@|h37;M8f{35Jx zLQ#?GA1uUyw;+M2l5>?0i_M^g3<*Elt^3farhvVgSK#rv2dOs*`X8 zx=`%rP|j;Z$|zGWmJ9U@*yO?M@KbP>iW`Mb!o91M`YHSn`~QAGsl#w|uskq?qRa#E zM))NtDQrUyQ0L(!d<|;Yi!2cu?_`vt-7tV|3rrsD0x4^By&a4N;*2@=A!*1&r(d%GJBaHXVR)g0ZOi99AMH# zs<(ZtXWN?WNMx-Jy|nK7v1TU5^Gf=O*U~1@exRKoPAdA&$Tr=yx$d>@w;5(KN9i)+7venp4V=(Gf^xd4nS|)pO)c7r(xJ{e)ZMq@TZ+!l=HjZQCST9%` z#(F7;7F@sHF(ocoTnXk2Z$jhNbZ8?ts8#d~65xK*`Ux%(Wf%ow$kudhoiwueR&D8H zf#RZ)$;)N+hUCCPe*u>eg(0U`%{<0-iXDPGhV`L0WITD_J5U;fV$f6Ncy;6rNV{ zKdt{-e@CLR!fJzK@ljNc^LWE_etyIAYdAHH^M^Lhtes24Fo=@;<&76IR}Qn6g;@w^ zCyQr}p%8<*q^Db)VOsUvxPew^zar6Cw`fh(D~l@)YrHJGIEjo~ZzOs?3Yuj;f+b&P zOX0YwHC?}=|8MmdSqOZq9nWprrlQn#pm0>7;Mai3Z&Iv|alhKb+X0hRoT zO@6+X53kJ*d+|!>^#~o8zfW@Gp|VGR@*~fZ zP3@I?KhZAb=W|%utCzmr^*>&2`zxL5NT=dI-AluUrk>iE)-Za})=kamjIQ=+d*bxG zz>#v}2+v$%V7aY%ht3TaP1(82Pf6=awNdyOk&c{~i{(^`8Pn-ivdCo;5KY;;#A2dx z9uv_i@y9L(-df!Z#8Cds_DVKIob>YYSp=|n>V8(wr@n+>II1`ScOjoOE(02Q`MID* z*dT<&qD5k&IxSymAJ|sy_Io9pwQ`4W1tR->si@PeqIf6C^8QLucN!i=ym!Uw@rX!(uiRDgT<(CZxC9(`xh;9b39bPfa|v|s zfYPBdVhNZyBC{x(%gOJlg-KFRm?&ULl|7+mFn!{L4@{8Bs)5C?qebJuYzIoNK(c%VybW9Ru_g1)6w#odO zt=DC`xG1HM-hK%K9x+>XA@>%Az22^*gu5 zrOkC&Yi3@jH_PnG9WqtcUgt-)+m3RL*oa!D?4|L#zoA8nipDJ321ijUq$n@%$cmc5 lMx2yIQno~X?~X?P>W)Lj?Xuj?H}1^W4aS-aW)JPC{s*Ueds_ei delta 1822 zcmY+^YiN^K9LMof?PY7MZQ9P(+1e+aQR~#UYFjV8EY5hX%xTA}=rAp58)=L*aVm~w zQwxJp)SZera8pENUJwb2mcl@>poscLkRe(ShWctS#23Qc_xCh?;UWL$bDo6fT>k&_ zH4cW$A!Y?ro-{E}B%Q41-jhKt8@jKjvNqmTW z%!Ut)sls;r7!TlaJc$gIG<6>uL*F!FIR>!e8U>4rR5`2h-_yRTY+qBu-RCDh| z2LtHE4xEe$tjB}61BcL~rTW>1R#uBe*oxZxcD#mRoP=duw4*94##+>ZR;KU2!KvKG zkw?q`R^kc#1c$L7M^TyV;*zaYCFqpmepCqtQ4?K3?feF^N%JSh@ebx=6Wd}evmQ0! z7E~s?@FR?&p6f@Adl)s(Mbz`RF-aE8BRUqn;de$|S<#X0yZ2Jkj2GdTrgsh@<@ z(3GHdIvrV?3E(pP0+p$sQ4^oSLm6xchq-SqB>!6ZTMD`WbI3n)n5C$Nt;Iq73J35N zYKMCnbus>kJ{-cU_yo0}U)a7pf%^RvcH>P{X6H;9%iMw~o!v@rY zn@|htL*_C+p)&P5YQYz9Cf>*2@HJ9BGssES&d*>4o=3e^cTqckfhyrYs0}7x(>X%t z9crQ@tU@(Bhg#uP)WkPYJGqaP&-{&5_y#qRj}xZXGLW_nquh7nI(&pWOFmY!5L+>* z_x}evI^`p%iSD5?@)$qG=cs}6Ny}2KLwLtEM)eP|EcNOe9o>W@5Z43yX+ZglatA0(7d=H}$B4rl#zvsZ2U6s=v-u4e=SF ztZydVR+m zW1XR1C!Po;!nV4i#h#y>YkTqsZAn3MRyZ2j9km+^t{3;j_Bhd4U)YK4?27e8Ly1Ui zr_J{UY`ga_TVK?Z*|u+2*q$tEuuqEiq_!9L=cFE#hcY}t4ea!44n|*RyPeCn#Iy;&<*| zuT$EQ=l<^O?wmR2f6f^{ckpLNvp;TJ^`v4PV{T_YwnC|C`0rIb7|(Yqbso;c_3-{K zrApAkb?_Yg2>c=ZI(z|UaA1v6ysGEnAiMy#!au<8!}lO2GxhjdrFd88;12jKoP@8y zm2fB4SHs=#dN=?#!b6ZhHBs;@a35fp5TJcmNJd@2ic;255=*!A-<}AKv7h}7kg`= z?CXVMZz~l02Vf?V+{cR;It5WjRpAL3!^7}(C=skh_mwEJ7w%!bAGyZh15oz;0Lp>$ zP$Ito#r|a|wQ~iI!oNaMu74Byizg$jB!UU}BnNyI{(|*CuWNmNakEmlu)YMv(RZO7 zxDwr;fj#hXI1eS#0~q}@9E0M>H{nG%2gRZL$&c{CKJ?eTRQTWw{BH5ZyHFyz3U|U4 z0-@J;v(?Ag{z(LB6?^~H=AoKnAoa^O4g zO%!+!{)P1$w1g;g0WTz{uR?rMuS3b@pP?N1A4u?OBUUBiJq5?%+pOzw4xZ92d;fy+ z`>bnM>);!pwBJpzA08<_KLaJwv+yzaH7Fa`aEdslp%mR-D3aa*C6F;l9jbfbAUqB6 zK|KQHcRzwCl&KeZ5d*J5Nx`q+Q8-M+?SYTL8#|PG5#GxB@{LM;9(LlT?E4~=lzbU} z0#;#upFmOOSvU%R0!6WRpg6MbCfT2{i5KkW;|m2P*HS=tFb^{&QgKSgCz&)rK34Kv zQM|7%C=kcSnd0a%CUx8%9G4GqvXk@kTT7~1^75hYa!3u0p2BFsFshhf?Q(x z7%C_bxj$R1?=4t@BKJK^GM9JL2rR~}Y8P*Nm~uf#%SfN!#gwj)YnXD#$3dQSky^Vj z-nnIUc28ujt_Nww4`a24NzR1@6wATK$iL5qZn3_Px>16Ch zem(JB7?0?NOLg5evb65xK#h6yeaWj%6Y$d+|%y)?4;HrdeQE(m-( zN@O||SNW1?LE@rKR-BFSE-LG?O^hGJrG@gk!H(vyd#`U^=-!`M%vEhr*Ry_7)kdE< ze)#ywiIb;J76RX?zvQD>%e_27Hxopm;(+)c_~A6U(mrlRZdRK~H)99&6h4%lmx}*r ziT}cD7RYW zSbP+1<7T}6Q1g}b7guv?8aE&98S9!z>vb0;&0qFh$$UA?ejvDy^5&mrCil{swxqA! zx8!+D=HoQ)OOF#RwJd&H8nkXICZ-dNp0N+n2RckGy15LSRE(4v_5-uLe{lh0R{Aux zGp5z3i*wScQf8=T%6z)`5|v`OMD*E|`ZPs%+L+rTwbWnnfr86 zzPNoL8`aX$(!36bDwvu|Ly3VxUOIqZO!*;#lzD4)uFK;pyOKWX$_N>Vn5MGymN&8C z_tGIXVzZSrGVL}FUF?t`*5NH2Cd$-g;nO)Zv5|!sq|D@Lb&~4DL}%#j(!%(b2V0H( zo4$S7rPq#GT=x?sHjNZrXgI_!5-WS#g#i#8j>E#rWu(`cS{WVfMir&U|K=%$$49 zy|Wi$@AT(?sSPd|#(tuim?|+QiI;*L81axXAK)Wci8rtlE6a>2$39$#L-;&ChIw2@ z{!DzWG3|H&Z^0Mv6rMp|l{Y(YGKO>0hb=gOqd118_!X|fZ?F-Uu@?V8CH~hp#6=g^ zHRxazYj709n8O}Cj+6K)mTRgJ%FxWZu?}OX;t%7;n8XTf=Aeq&u^zio6WZrrAHoe> zXOTzD9Jb+U+=Q3#1YSpVa-2g*t;%t-9*?1xU>=p|9IEn*NRj4q%;MKrh5eMpyUZ{u z;ghINj^oYfqMkc}dhbb8p0lXuuV9`onD04x6tCm`xP!Zz@iVv;7cq)gP@O5OUTu8^ zvWBJ+Rp~}#ZYGMm@L^P^UPdK;8y5l;grDhtE&W#oVfub2HX%7o3^lQXxQIt^4oi5O zDtZn3a1oXGM?8=JpgQ*v<;zdd!7p$UfAjwzYgnE5NCW-f$c?A?2Y=={4odtwD$yI* zgYV%M{29q%LM%LUGELZvUFb~|wG<_kdn<-fm3MMbrv~wNd ztwvFabf0&cdDH`EQ59eGy@pF%-|+7*v6GePL)6!F9wT@a)sesb`+=s_0`qG*d4(Ib zs6=m}5?w%5@+IoQWh94LL6*<_h3#0%Y*lFosw2aw=Mt!vr!j^rxD_MJYCFbpzrO$D zoM=;hhkEcTY6*VDZTKgu6Ai4>F5HH^%N#)^ei7MI-m~mquZRaw8`&XB2n|h6c^)K& z2wrP8>-#SbyMaFZ**|%_RpEQ*9}cf`-EovZ|v}|v}cM#)wdG&5bB`D zfb#28)957BdVK+?=IqYLTg+cQBJ!23vE{ICk=H^L(6mPB zP`@ot5Z>77tDnq!iM##leW*6~6MG45Cas?ao7fwJ9Pc2sF|{;%h;@Z`L+jTR_Ev5Q zl*in$#FUfGC2~poYPi|H6z;GSRr9v7dUts;otjOXcqTQLv@@+OcD8zw=IVP6$7Mr+I6Os z&QG+dEIK)x%_Xz;ap#B~#mVy3aw<-)WbY&%$ZzbzE7 UKWsi097)Z%HXa$UZ${?+0|g|?RsaA1 diff --git a/languages/robotstxt-manager-es_ES.po b/languages/robotstxt-manager-es_ES.po index 98d8510..3059230 100644 --- a/languages/robotstxt-manager-es_ES.po +++ b/languages/robotstxt-manager-es_ES.po @@ -129,8 +129,6 @@ msgstr "Instalado (inactivo)" msgid "Insufficient permissions." msgstr "Permisos insuficientes." -msgid "Local state" -msgstr "Estado local" msgid "Manager (by ROBOTSTXT) — Plugins" msgstr "Manager (by ROBOTSTXT) — Plugins" @@ -153,8 +151,6 @@ msgstr "El plugin no está instalado." msgid "Plugin not found in catalog." msgstr "Plugin no encontrado en el catálogo." -msgid "Premium" -msgstr "Premium" msgid "Price" msgstr "Precio" @@ -177,8 +173,6 @@ msgstr "Requiere WP" msgid "Settings" msgstr "Ajustes" -msgid "Show more information" -msgstr "Mostrar más información" msgid "Store URL" msgstr "URL de la tienda" @@ -210,8 +204,6 @@ msgstr "Este sitio funciona con WordPress %1$s y PHP %2$s." msgid "Too many refreshes. Please wait a minute before refreshing again." msgstr "Demasiadas actualizaciones. Espera un minuto antes de volver a actualizar." -msgid "Type" -msgstr "Tipo" msgid "Up to date" msgstr "Actualizado" @@ -233,3 +225,27 @@ msgstr "Tu servidor funciona con PHP" msgid "Your site runs WordPress" msgstr "Tu sitio funciona con WordPress" + +msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." +msgstr "Esta es la tienda de plugins de ROBOTSTXT: el catálogo de plugins publicados por ROBOTSTXT, instalables y actualizables directamente desde tu propio wp-admin. Los plugins gratis se instalan con un clic; los plugins premium requieren una suscripción anual que se compra en nuestro sitio web." + +msgid "Version" +msgstr "Versión" + +msgid "Status" +msgstr "Estado" + +msgid "€%s / year" +msgstr "%s €/año" + +msgid "Support" +msgstr "Soporte" + +msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." +msgstr "¿Necesitas ayuda con un plugin de ROBOTSTXT? Visita el sitio web del plugin (el enlace en su fila de arriba) para ver la documentación y las guías, o contáctanos a través de nuestro sitio web: estaremos encantados de ayudarte." + +msgid "Payments, and how it works" +msgstr "Pagos, y cómo funciona" + +msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." +msgstr "Los plugins gratis se instalan al instante y sin coste. Los plugins premium son suscripciones anuales: pagas una vez en nuestro sitio web y la suscripción se renueva automáticamente cada año hasta que la canceles. Puedes cancelarla en cualquier momento desde tu página de cuenta de ROBOTSTXT; el acceso sigue funcionando hasta el final del periodo pagado. Todos los pagos los procesa de forma segura Mollie; nosotros nunca vemos ni guardamos los datos de tu tarjeta." diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index 228e099..2118156 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -128,8 +128,6 @@ msgstr "" msgid "Insufficient permissions." msgstr "" -msgid "Local state" -msgstr "" msgid "Manager (by ROBOTSTXT) — Plugins" msgstr "" @@ -152,8 +150,6 @@ msgstr "" msgid "Plugin not found in catalog." msgstr "" -msgid "Premium" -msgstr "" msgid "Price" msgstr "" @@ -176,8 +172,6 @@ msgstr "" msgid "Settings" msgstr "" -msgid "Show more information" -msgstr "" msgid "Store URL" msgstr "" @@ -209,8 +203,6 @@ msgstr "" msgid "Too many refreshes. Please wait a minute before refreshing again." msgstr "" -msgid "Type" -msgstr "" msgid "Up to date" msgstr "" @@ -232,3 +224,27 @@ msgstr "" msgid "Your site runs WordPress" msgstr "" + +msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Status" +msgstr "" + +msgid "€%s / year" +msgstr "" + +msgid "Support" +msgstr "" + +msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." +msgstr "" + +msgid "Payments, and how it works" +msgstr "" + +msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." +msgstr "" diff --git a/readme.txt b/readme.txt index 2bddf5f..67dc346 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 0.6.0 +Stable tag: 1.0.0 Requires PHP: 8.0 -Version: 0.6.0 +Version: 1.0.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -42,11 +42,14 @@ Configure at **ROBOTSTXT → Settings**: | Column | Description | |---|---| -| Plugin | Name and current version from the store. Click ▸ to expand the description and website link. | -| Type | Free or Premium. | -| Price | Annual price in EUR, or — for free plugins. | -| Local state | Not installed / Installed (inactive) / Up to date / Update available (vX → vY). | +| Plugin | Name from the store. The row below each plugin carries its website link and description (always visible). | +| Version | Current version in the store. | +| Requires WP / Requires PHP | Minimum requirements, checked against this site (warning icon when not met). | +| Price | "Free", or the annual price for premium plugins. | | Action | Install / Activate / Update / Buy, depending on local state. | +| Status | Not installed / Installed (inactive) / Up to date / Update available (vX → vY). | + +Below the table: a Support section and an explanation of Payments (annual subscriptions, automatic renewal, cancellation, Mollie processing). Click "Refresh catalog" to force a fresh fetch from Core. Install, Activate, and Update run on a classic page load (no JavaScript) and report the result as a standard admin notice. @@ -89,6 +92,12 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 1.0.0 = + +_Release date: 2026-08-15_ + +* Catalog redesign: columns Plugin · Version · Requires WP · Requires PHP · Price · Action · Status; Type merged into Price ("Free" or the annual price); per-plugin website link + description always open under each row; intro text after the title; Support and Payments sections below the table. First stable release. + = 0.6.0 = _Release date: 2026-08-15_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index 5ba69e3..f814d46 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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.6.0 + * Version: 1.0.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '0.6.0' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.0.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From e80820f41612d053ea82c3486a1649b3ba62c8ec Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:03:10 +0000 Subject: [PATCH 10/23] v1.1.0 --- admin/class-robotstxt-manager-installer.php | 288 +++++++++++++++++++- admin/views/page-catalog.php | 25 ++ changelog.txt | 17 ++ languages/robotstxt-manager-ca.mo | Bin 9816 -> 10430 bytes languages/robotstxt-manager-ca.po | 18 ++ languages/robotstxt-manager-es_ES.mo | Bin 9783 -> 10405 bytes languages/robotstxt-manager-es_ES.po | 18 ++ languages/robotstxt-manager.pot | 18 ++ readme.txt | 10 +- robotstxt-manager.php | 4 +- 10 files changed, 389 insertions(+), 9 deletions(-) diff --git a/admin/class-robotstxt-manager-installer.php b/admin/class-robotstxt-manager-installer.php index 3ad4bb3..2714e78 100644 --- a/admin/class-robotstxt-manager-installer.php +++ b/admin/class-robotstxt-manager-installer.php @@ -54,19 +54,297 @@ class Robotstxt_Manager_Installer { $name = $this->entry_name( $entry, $slug ); $this->ensure_plugin_functions(); + // Install missing dependencies first (ecosystem catalog plugins via + // the store, WordPress.org plugins via their repository ZIPs). + $deps_installed = $this->install_dependencies( $slug ); + + if ( is_wp_error( $deps_installed ) ) { + $this->redirect_error( $deps_installed->get_error_message() ); + } + $result = $this->download_and_install( $slug ); if ( is_wp_error( $result ) ) { $this->redirect_error( $result->get_error_message() ); } - $this->redirect_success( - sprintf( - /* translators: %s: plugin name. */ - __( '%s installed. Activate it from the list below.', 'robotstxt-manager' ), - $name + $message = sprintf( + /* translators: %s: plugin name. */ + __( '%s installed. Activate it from the list below.', 'robotstxt-manager' ), + $name + ); + + if ( is_string( $deps_installed ) && '' !== $deps_installed ) { + $message .= ' ' . $deps_installed; + } + + $this->redirect_success( $message ); + } + + /** + * Installs the plugin's missing dependencies, dependencies first. + * + * Each dependency slug is resolved either against the store catalog + * (installed through the store's download endpoint, like any catalog + * plugin) or, when not in the catalog, against WordPress.org (installed + * from the repository's plugin ZIP). + * + * @param string $slug Plugin slug being installed. + * + * @return string|WP_Error Installed-dependency names for the notice, '' when none were needed. + */ + private function install_dependencies( string $slug ) { + $deps = $this->dependencies_for( $slug ); + + if ( array() === $deps ) { + return ''; + } + + $installed_names = array(); + + foreach ( $deps as $dep_slug ) { + if ( '' !== $this->find_plugin_file( $dep_slug ) ) { + continue; // Already installed. + } + + $result = $this->install_one_dependency( $dep_slug ); + + if ( is_wp_error( $result ) ) { + /* translators: %s: dependency slug. */ + $detail = sprintf( __( 'Could not install the required plugin %s.', 'robotstxt-manager' ), $dep_slug ); + + return new WP_Error( + 'robotstxt_manager_dependency', + $detail . ' ' . $result->get_error_message() + ); + } + + $installed_names[] = $result; + } + + if ( array() === $installed_names ) { + return ''; + } + + /* translators: %s: list of installed dependency names. */ + return sprintf( __( 'Installed required plugins: %s.', 'robotstxt-manager' ), implode( ', ', $installed_names ) ); + } + + /** + * Installs a single dependency: catalog plugin via the store, else wp.org. + * + * @param string $dep_slug Dependency slug. + * + * @return string|WP_Error The dependency's display name on success. + */ + private function install_one_dependency( string $dep_slug ) { + if ( null !== $this->find_catalog_entry( $dep_slug ) ) { + return $this->install_via_store( $dep_slug ); + } + + // Not in the catalog — treat as a WordPress.org plugin. + return $this->install_via_wordpress_org( $dep_slug ); + } + + /** + * Installs a dependency that exists in the store catalog. + * + * @param string $dep_slug Dependency slug. + * + * @return string|WP_Error Dependency name on success. + */ + protected function install_via_store( string $dep_slug ) { + $result = $this->download_and_install( $dep_slug ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + $entry = $this->find_catalog_entry( $dep_slug ); + + return $this->entry_name( is_array( $entry ) ? $entry : null, $dep_slug ); + } + + /** + * Installs a dependency that is not in the catalog from WordPress.org. + * + * @param string $dep_slug wp.org plugin slug. + * + * @return string|WP_Error Plugin name on success. + */ + protected function install_via_wordpress_org( string $dep_slug ) { + return $this->install_wporg_zip( $dep_slug ); + } + + /** + * Resolves a plugin's dependency slugs (parsed, deduplicated, deps-first + * order, self-references dropped). + * + * @param string $slug Plugin slug. + * + * @return list Dependency slugs. + */ + private function dependencies_for( string $slug ): array { + $all = array( $slug => true ); + $queue = array( $slug ); + $ordered = array(); + + while ( ! empty( $queue ) ) { + $current = array_shift( $queue ); + + foreach ( $this->raw_dependencies_of( $current ) as $dep ) { + if ( isset( $all[ $dep ] ) ) { + continue; // Already seen: self, duplicate, or cycle. + } + + $all[ $dep ] = true; + $ordered[] = $dep; + $queue[] = $dep; + } + } + + // Dependencies discovered later are deeper — reverse so dependencies + // of dependencies install first. + return array_reverse( $ordered ); + } + + /** + * Reads the raw requires-plugins list of a catalog entry. + * + * @param string $slug Plugin slug. + * + * @return list Dependency slugs (unresolved). + */ + private function raw_dependencies_of( string $slug ): array { + $entry = $this->find_catalog_entry( $slug ); + + if ( null === $entry ) { + return array(); // wp.org plugin: dependencies come from its own headers on install. + } + + $raw = $entry['requires_plugins'] ?? ''; + $raw = is_string( $raw ) ? $raw : ''; + + if ( '' === $raw ) { + return array(); + } + + $slugs = array(); + foreach ( explode( ',', $raw ) as $part ) { + $dep = sanitize_key( trim( $part ) ); + if ( '' !== $dep ) { + $slugs[ $dep ] = true; + } + } + + return array_keys( $slugs ); + } + + /** + * Installs a WordPress.org plugin by slug via plugins_api + Plugin_Upgrader. + * + * @param string $slug wp.org plugin slug. + * + * @return string|WP_Error Plugin name on success. + */ + private function install_wporg_zip( string $slug ) { + $this->ensure_plugin_functions(); + + if ( ! function_exists( 'plugins_api' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + } + + $api = plugins_api( + 'plugin_information', + array( + 'slug' => $slug, + 'fields' => array( + 'sections' => false, + 'versions' => false, + 'downloaded' => false, + 'rating' => false, + ), ) ); + + if ( is_wp_error( $api ) ) { + /* translators: %s: error message from WordPress.org. */ + return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'WordPress.org lookup failed: %s', 'robotstxt-manager' ), $api->get_error_message() ) ); + } + + $download_link = is_object( $api ) && isset( $api->download_link ) && is_string( $api->download_link ) + ? $api->download_link + : ''; + + if ( '' === $download_link ) { + /* translators: %s: plugin slug. */ + return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'No download found on WordPress.org for %s.', 'robotstxt-manager' ), $slug ) ); + } + + $tmp_file = wp_tempnam( $slug . '.zip' ); + + if ( ! $tmp_file ) { + return new WP_Error( 'robotstxt_manager_temp', __( 'Could not create a temporary file for download.', 'robotstxt-manager' ) ); + } + + $response = wp_remote_get( + $download_link, + array( + 'timeout' => 300, + 'stream' => true, + 'filename' => $tmp_file, + ) + ); + + if ( is_wp_error( $response ) ) { + wp_delete_file( $tmp_file ); + + /* translators: %s: HTTP transport error message. */ + return new WP_Error( 'robotstxt_manager_download', sprintf( __( 'Download failed: %s', 'robotstxt-manager' ), $response->get_error_message() ) ); + } + + if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + wp_delete_file( $tmp_file ); + + /* translators: %d: HTTP status code. */ + return new WP_Error( 'robotstxt_manager_http', sprintf( __( 'Download failed (HTTP %d).', 'robotstxt-manager' ), (int) wp_remote_retrieve_response_code( $response ) ) ); + } + + if ( ! $this->is_valid_zip( $tmp_file ) ) { + wp_delete_file( $tmp_file ); + + return new WP_Error( 'robotstxt_manager_zip', __( 'The store returned an invalid file.', 'robotstxt-manager' ) ); + } + + $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); + $result = $upgrader->install( + $tmp_file, + array( + 'overwrite' => false, + 'overwrite_package' => false, + ) + ); + + wp_delete_file( $tmp_file ); + + if ( true !== $result ) { + $detail = ( $result instanceof WP_Error ) ? $result->get_error_message() : ''; + + /* translators: %s: upgrader error message. */ + return new WP_Error( 'robotstxt_manager_install', '' !== $detail ? sprintf( __( 'Installation failed: %s', 'robotstxt-manager' ), $detail ) : __( 'Installation failed.', 'robotstxt-manager' ) ); + } + + $file = $this->find_plugin_file( $slug ); + + if ( '' === $file ) { + /* translators: %s: plugin slug. */ + return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'The downloaded plugin for %s does not have the expected folder structure.', 'robotstxt-manager' ), $slug ) ); + } + + $data = get_plugins(); + $raw_name = isset( $data[ $file ]['Name'] ) && is_string( $data[ $file ]['Name'] ) ? $data[ $file ]['Name'] : ''; + + return '' !== $raw_name ? $raw_name : $slug; } /** diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index d419227..7a79180 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -135,6 +135,18 @@ $compat_warnings = 0; $website_url = '' !== $page_url ? $page_url : $homepage; $has_detail = ( '' !== $website_url || '' !== $desc ); + $raw_reqs = $entry['requires_plugins'] ?? ''; + $req_slugs = array(); + if ( is_string( $raw_reqs ) && '' !== trim( $raw_reqs ) ) { + foreach ( explode( ',', $raw_reqs ) as $part ) { + $dep = sanitize_key( trim( $part ) ); + if ( '' !== $dep ) { + $req_slugs[] = $dep; + } + } + } + $has_detail = $has_detail || array() !== $req_slugs; + // Compatibility checks. $wp_ok = '' === $req_wp || version_compare( $local_wp_version, $req_wp, '>=' ); $php_ok = '' === $req_php || version_compare( $local_php_version, $req_php, '>=' ); @@ -251,6 +263,19 @@ $compat_warnings = 0; + + + + + diff --git a/changelog.txt b/changelog.txt index 56d6dfd..58d7465 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 1.1.0 = + +_Release date: 2026-08-15_ + +**Added** + +* Cascade dependency install: when installing a plugin whose catalog entry declares `requires_plugins` (Core 1.7.0+), Manager first installs every missing dependency — ecosystem plugins through the store flow, WordPress.org plugins (e.g. Action Scheduler) via `plugins_api` and their repository ZIPs — dependencies of dependencies first, cycle-safe. The success notice lists what was installed; failures name the dependency and abort before the main install. Catalog rows show a "Requires: …" hint in the detail line. + +**Changed** + +* Plugin version 1.0.0 → 1.1.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.0.0 = _Release date: 2026-08-15_ diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index 20a059df8c8f3021abd939b05a2d0d291045be66..dfd0845f796c12e6bb8cd4ce0d9065d0084a3d73 100644 GIT binary patch delta 2355 zcmZY9Z)_Ar7{~FsUMWyp{*)pu0v#wVRHT5Vs6~_(TBNpx))J#)blcmOlk07}x2M%; zE)gX$ijY)Mo?o2F&bmk_zzzwB=W}8H@r~4zrCvjoOJFpvpc)< z%rmq7w&PTH=BKj4gND*eT|hlqV9W^aoWc*qPBrFjT#Tjo1=iz_cpa8ZGo~0T@JX!3 z42~m1jGb;w9WKZD*n@{~2#Yjy3QsfEl;A>~i+$LDQG6K{%PTk!$FU4QK>p0xyx-s| z`sYy-&L%pG>v1;j#2J{xb@&uMqEO$X!Jnxr$wjyV=g@D%LcAaMMvQp~kJJB+G|)6h zc$-%ECYIy7r~p69_kYGJ`V&}*?3Ni@m5+e~(Jp1te7_ z$}%dj7`0W^sLX6cvT52dh1-!=nlq?CzeX+ae7=7PmGR4%313V;^@3v5i_1|HtwF7@ z9reN%=G-8$m5BO?sMf9QFLKsCh4ylYgaTf(x2x-rU^E zt5B(1iR-Zw8DgG81$Gp*vJ?0W6P>}6^tY0Kt+a?iH)9zp@K)5sJMblZ828~{735#3 z*uw_hiZ9?|`~=^}-%*hsVISmcsQYi>Af7`7-grYUpbe-6wIeZ^A=JcsP#Jy^HP2yO zjbCPH+(e^*yzB6+Lb7JI;BEK-?#F|u4Ak(6EW#$#`1?=`7{QBV;tBkPevI@fL#;L! zKo2sNc@Xvds$l^8K}_y=+09@-E~{%+F#^96;%wgX28Ff-`r>d-@_J*z8 zInej215}!Qz3A`gqIfbDIEjP}cDdGfN78ZMjoINudN7`}HL2{O;#1KD8oHZ+?XHUnVHXxgfvt zimGjqhAz*hmhEzE;Cp>eVEdh6+vmhbotR^tYmzWbz)qM3dp=UCG`PMS9}PomT{1n= z-1=WQ;VS|=`2(c`>|D+B{{r=BOOF5m delta 1782 zcmXxkduYvJ9LMqR*_mUti)_@hRv zA%+nLlHtd6Ea0gz(2!;~O=-LGwjU6}yU*K{4 zflL{(X1^JIYr_mYiEHpCdNGsPdto*X!Vo57De}+iVy?sqj2keBCvX7Xz$AQth4>OT zU^2T;)K+)W(a!c`3Z6qHe;xOF%)0Oz07Sho~%aH2WdaT7QSdQJO4E)sHOGU+W72sHGMBRmUBuVSUV(i8iw4u?=4`C+5^AS4w!X4DkU*Tse@B!a4e$125 zMmqRyE!>6t$nK#gc+ZD6=FQ^XU^;5Uc{md5@C$CoQ&=C2z6Kvzlaoc#=%^GEkl(RF z)H7UydIeiinQp^F*o0arl~r`6V^A9`MqR#2)Mc$j?x8Ki(b#~RzZJEfgIuGC-H3MV zAvW@Zx3~anxN%`@$2^bOU7W=DXO39~hI69}ZbnsR2adsgs3UZc1?(nH#YY%KKV@MF z3u7O>|7tqS?=~&0rVOVMI=ga08CDT$1%xJ6^Ad4xFPe3%>sF$g!F-~I&=LJ_Eax^I zw~zJRzgoK0I*%EI%C9q4n@N-qL1H4IyYjy&Yh6CIQbI38jY7Fi)d&&uh*1Qu*lmS; z>S*-0ATpVc3St~Fn^04k)pTw}gvv?v+*THoKVohh8&mLVZBA@FH|BWMxmOaLoO@|? z9I=+~K0lqaE|Z>29x;pHsk#?XXQeis;1!wQ$>{yp>r^CmdYwW2(>%`7z%ftjuL1X+ pq?D{Ur#kJ3&j}1W9q;r6t3A%*>^B~#JSV~9H0SP$Yb^*J_y;W0pP&E$ diff --git a/languages/robotstxt-manager-ca.po b/languages/robotstxt-manager-ca.po index cd3e9bf..da6c088 100644 --- a/languages/robotstxt-manager-ca.po +++ b/languages/robotstxt-manager-ca.po @@ -249,3 +249,21 @@ msgstr "Pagaments, i com funciona" msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." msgstr "Els plugins gratuïts s'instal·len a l'instant i sense cost. Els plugins premium són subscripcions anuals: pagues una vegada al nostre lloc web i la subscripció es renova automàticament cada any fins que la cancellis. Pots cancel·lar-la en qualsevol moment des de la teva pàgina de compte de ROBOTSTXT; l'accés segueix funcionant fins al final del període pagat. Tots els pagaments els processa de manera segura Mollie; nosaltres mai no veiem ni desem les dades de la teva targeta." + +msgid "Installed required plugins: %s." +msgstr "Plugins requerits instal·lats: %s." + +msgid "Could not install the required plugin %s." +msgstr "No s'ha pogut instal·lar el plugin requerit %s." + +msgid "WordPress.org lookup failed: %s" +msgstr "La cerca a WordPress.org ha fallat: %s" + +msgid "No download found on WordPress.org for %s." +msgstr "No s'ha trobat cap baixada a WordPress.org per a %s." + +msgid "The downloaded plugin for %s does not have the expected folder structure." +msgstr "El plugin baixat per a %s no té lestructura de carpetes esperada." + +msgid "Requires: %s" +msgstr "Requereix: %s" diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index 9b6d0bd652a507ae2403051a2f7244edf96e5dad..763f8f0602fa7ca2c9067e3deb392ae026b649e5 100644 GIT binary patch delta 2365 zcmZwITWl0n9LMp~8(?XHLJqzl?uY5+CkjSmg7D=T4GEeeoXx*(m>I? zLvLE)`&f$~peFcLT>la4so%vqtetAiLTttc?83ZKm8Gx@mrgV02=<^-{XHsW<4CGZ z8OxZBm8h+1KxJkVl11RU*h|F=N4Afhx55VfDhsk)WC0}#<_qxe3x)J zj^`*Wp-@ZSwZe@^)=W39#Q{8n=TLjNo>xSfX+suaTvRHD@CKPUg14#n(ny)uWD9%U zi8_RPkgiM`HD2x*1+Dlz>hyjQ*Kfv}1;+f${RZ5ClWF`ewxb3ZM?Lo&Y9iGf0Onw7 zu@P6H?mvghKp(z|N#wbl`JI9~nzE>{!nvsWVpQtapgL$pUIeohmtqep6GNzW=TMn@ z50$Aas4e;uci>89(}AzxGW;4Bm9YQ6Qdq@}*^4>pxD(aEDb(JbNA1-`)NAt{Ds^M1 zt@{I8&{9!qdr=d68P(5QSb`G?U7S_JoT4!|Fn;vPfO(jpZ8S^O*AiOsB;pxDv*wjG zZNz%wS%Tjd^BAEC>e6ZNic0)s;}dat7i#}E5;~y8E4Q1HX1$3}npYFrKdqpQs35ix zN}=9)T}t!)mol(|Aeqt6u)g1}53ZF2KM~RORIJ#l{pSse_FFfUeyal4PGU2mLsL%Z zv~$Lye?IM~Gf=!rFNaasGf zj@Uv}=SL>oE1RG6!iL{KQ|=u5T?q z{O`bp<~HHGq4oTXJ>b0HMl*C@O1u3T8V>lWgd5l}6J+}{*&sh!(^Ojj-!W6Z|9m!W z2b?66*Ie`CGcHW1FD%#zteYyXQwwv0WWraY*XJBBUl6T7j8+_d5!(2%gdZ=vrhZ$= z`dz*aUF&-NzLyC`^VUmxgIUk92{-I_fq({A7-ocx-Gq0%KCj@>_cC1h=h|Cz}E Nn>w10hQ8Ez^lyMoQnUa7 delta 1781 zcmX}sT};nW9LMqR|Nrk%qz8$p{-u&A@=z(El*cl35u2wmlel0mJ^U`h*lI2+#>O-c z(;91j3uCdl=mK|5Ha24}nhS2og=F5J-~Y#%^Lw51*YBL~`JQurx3@lOa6Y6&bsJ?f zbvCs!ZmOmNCQ7emM)M=Q63M}96SL00j+b|c8;2gY& zF1mOY4^AbcO4U~y3Sb;biA_&3n}M0AEh$4~pb1He?ZD%>A9=;zq5>O5VznQrjB5EB zFCO(?5^B5*)cED-XeC={XrS##cB}=r;c=|RA=C=u$$N;56k`$nQqom}J5ldlM@`s^ zT6sTe{9)ADd5=~21(mt{Y2;s#RMOK5ny{M*_Tn@8Khq+=4@@^(ME?aU(C?@TL*)G` zCSnKnqE=eYpewKj7078Ez#ddU&FqidGn4#>X&mH-Ry^<@EV4r<~G z)I@8r3>&c!&!ARz4_TzWz;YbHtC%<|vPHd^O@F|lK~VMvHQ_fLAp>Lhnf`08gfi1l z6x!24Bqkd|?d3<*gcHc(Ern6F;v(Nh9HoC0dvJR=GTtxL-#f86k%_ZW*RKHcvCjX! z6}8f1*p4SqFUB#80t=%KT`?-9%TWudLC&GA$9cE|iNV@Xe|HnfkYjx`G{AGz7QDg+ zEaSu#VH@TJ%JG?}L z@x8LiS9`4kw47Q?)k+nV%2Fy9z$+o1LBAdCD->8GRe^4%a*qEOrt^w|jA4H7tEE$| zeO*b_Hn9C(S;bS)hpDAhrT$;hsw@1?YE*VqNR(HU4IRo2)VWk{u~&3pwKd9yvy4YQ zwU}B*Rap|j+qZJs+Ap%^l^S0@VqRI`E0peaet*4h2`b%dsqCD0n<~*Si!Fy%5mg@u zT^Zf;HB{XSeTF#>Ua98EEwXqwGxkr&txtFma?_`#1>EMOOM%W`$uHf+)a; Date: Mon, 17 Aug 2026 16:03:39 +0000 Subject: [PATCH 11/23] v1.2.0 --- admin/class-robotstxt-manager-admin.php | 72 ++++- admin/class-robotstxt-manager-settings.php | 2 + admin/views/page-catalog.php | 52 ++++ changelog.txt | 17 ++ .../class-robotstxt-manager-core-client.php | 73 +++++ languages/robotstxt-manager-ca.mo | Bin 10430 -> 11506 bytes languages/robotstxt-manager-ca.po | 26 ++ languages/robotstxt-manager-es_ES.mo | Bin 10405 -> 11453 bytes languages/robotstxt-manager-es_ES.po | 26 ++ languages/robotstxt-manager.pot | 263 +----------------- readme.txt | 10 +- robotstxt-manager.php | 4 +- 12 files changed, 286 insertions(+), 259 deletions(-) diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index a32c40c..400631c 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -64,13 +64,78 @@ class Robotstxt_Manager_Admin { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-manager' ) ); } - $client = Robotstxt_Manager_Core_Client::from_options(); - $catalog = $client->get_catalog(); - $local = $this->resolve_local_state( $catalog ); + $client = Robotstxt_Manager_Core_Client::from_options(); + $catalog = $client->get_catalog(); + $local = $this->resolve_local_state( $catalog ); + $subscriptions = $client->get_subscriptions(); + + // Admin notices for subscriptions needing attention. + $manager_notices = $this->build_subscription_notices( $catalog, $subscriptions ); + $manager_subscriptions = $subscriptions; require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php'; } + + /** + * Builds admin notices for subscriptions that need attention. + * + * - payment_failed: persistent warning per plugin. + * - expiring within 14 days: per-plugin warning. + * - expired while the plugin is still installed+active: per-plugin warning. + * + * @param list> $catalog Catalog entries. + * @param array> $subscriptions Rows keyed by slug. + * + * @return list + */ + private function build_subscription_notices( array $catalog, array $subscriptions ): array { + $notices = array(); + $local = $this->resolve_local_state( $catalog ); + + foreach ( $subscriptions as $slug => $sub ) { + $raw_status = $sub['status'] ?? ''; + $raw_expires_at = $sub['expires_at'] ?? ''; + $status = is_string( $raw_status ) ? $raw_status : ''; + $expires_at = is_string( $raw_expires_at ) ? $raw_expires_at : ''; + + if ( 'payment_failed' === $status ) { + $notices[] = array( + 'type' => 'warning', + /* translators: %s: plugin slug. */ + 'message' => sprintf( __( 'The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access.', 'robotstxt-manager' ), $slug ), + ); + continue; + } + + if ( 'expired' === $status ) { + $state = $local[ $slug ] ?? array(); + if ( ! empty( $state['active'] ) ) { + $notices[] = array( + 'type' => 'warning', + /* translators: %s: plugin slug. */ + 'message' => sprintf( __( 'The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates.', 'robotstxt-manager' ), $slug ), + ); + } + continue; + } + + if ( 'active' === $status && '' !== $expires_at ) { + $days = (int) floor( ( (int) strtotime( $expires_at ) - time() ) / DAY_IN_SECONDS ); + + if ( $days >= 0 && $days <= 14 ) { + $notices[] = array( + 'type' => 'warning', + /* translators: 1: plugin slug, 2: days remaining. */ + 'message' => sprintf( _n( 'The subscription for %1$s expires in %2$d day.', 'The subscription for %1$s expires in %2$d days.', $days, 'robotstxt-manager' ), $slug, $days ), + ); + } + } + } + + return $notices; + } + /** * Handles the "Refresh catalog" admin-post action. * @@ -100,6 +165,7 @@ class Robotstxt_Manager_Admin { $client = Robotstxt_Manager_Core_Client::from_options(); $client->clear_catalog_cache(); + $client->clear_subscriptions_cache(); delete_site_transient( 'update_plugins' ); $redirect = add_query_arg( diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index 6e54620..b8d5d76 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -348,6 +348,7 @@ class Robotstxt_Manager_Settings { } delete_transient( 'robotstxt_manager_catalog' ); + delete_transient( 'robotstxt_manager_subscriptions' ); return Robotstxt_Manager_Encryption::encrypt( $plain ); } @@ -406,6 +407,7 @@ class Robotstxt_Manager_Settings { delete_option( 'robotstxt_manager_api_key' ); delete_transient( 'robotstxt_manager_catalog' ); + delete_transient( 'robotstxt_manager_subscriptions' ); wp_send_json_success( array( diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 7a79180..7cdc1a2 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -7,6 +7,10 @@ * (website link + description), an intro paragraph, and Support / * Payments sections below the table. * + * Also available: + * $manager_subscriptions array + * Account subscriptions keyed by slug (may be empty). + * * @package Robotstxt_Manager */ @@ -19,6 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) { * * @var list> $catalog * @var array $local + * @var array> $manager_subscriptions + * @var list $manager_notices */ $client_configured = Robotstxt_Manager_Core_Client::from_options()->is_configured(); @@ -65,6 +71,12 @@ $compat_warnings = 0;

+ +
+

+
+ +

@@ -209,6 +221,46 @@ $compat_warnings = 0; number_format_i18n( $cost, 0 ) ) ); + + // Subscription pill for premium plugins. + $sub_row = $manager_subscriptions[ $slug ] ?? null; + $sub_text = ''; + + if ( is_array( $sub_row ) ) { + $raw_sub_status = $sub_row['status'] ?? ''; + $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 : ''; + + if ( 'active' === $sub_status && '' !== $sub_expiry ) { + $days_left = (int) floor( ( (int) strtotime( $sub_expiry ) - time() ) / DAY_IN_SECONDS ); + + if ( $days_left >= 0 && $days_left <= 14 ) { + $sub_text = sprintf( + /* translators: %d: days remaining. */ + __( 'Subscribed — %d days left', 'robotstxt-manager' ), + $days_left + ); + } else { + $sub_text = __( 'Subscribed', 'robotstxt-manager' ); + } + } elseif ( 'payment_failed' === $sub_status ) { + $sub_text = __( 'Payment failed', 'robotstxt-manager' ); + } elseif ( 'cancelled' === $sub_status ) { + $sub_text = __( 'Cancelled', 'robotstxt-manager' ); + } elseif ( 'expired' === $sub_status ) { + $sub_text = __( 'Expired', 'robotstxt-manager' ); + } + } + + if ( '' !== $sub_text ) { + $raw_pill_status = is_array( $sub_row ) ? ( $sub_row['status'] ?? '' ) : ''; + $pill_status = is_string( $raw_pill_status ) ? $raw_pill_status : ''; + + echo '
' . esc_html( $sub_text ) . ''; + } } else { echo esc_html__( 'Free', 'robotstxt-manager' ); } diff --git a/changelog.txt b/changelog.txt index 58d7465..7da32b6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 1.2.0 = + +_Release date: 2026-08-15_ + +**Highlights** + +* Subscription status, visible at a glance (Phase 4). The catalog now pulls the account's subscriptions from Core (`GET /me/subscriptions`, cached for one hour; refreshed on catalog refresh and key change) and shows a pill next to each premium plugin's price: Subscribed (with "N days left" when expiring within 14 days), Payment failed, Cancelled, or Expired. Admin notices at the top of the page warn about failed payments, subscriptions expiring within 14 days, and expired subscriptions whose plugin is still active on this site. + +**Changed** + +* Plugin version 1.1.0 → 1.2.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.1.0 = _Release date: 2026-08-15_ diff --git a/includes/class-robotstxt-manager-core-client.php b/includes/class-robotstxt-manager-core-client.php index ce39709..adb6e93 100644 --- a/includes/class-robotstxt-manager-core-client.php +++ b/includes/class-robotstxt-manager-core-client.php @@ -280,4 +280,77 @@ class Robotstxt_Manager_Core_Client { ) ); } + + /** + * Returns the account's subscriptions (Core's /me/subscriptions), + * cached in a transient for one hour. Keys: plugin_slug => row. + * + * @return array> Rows keyed by plugin slug. + */ + public function get_subscriptions(): array { + if ( ! $this->is_configured() ) { + return array(); + } + + $cache_key = 'robotstxt_manager_subscriptions'; + $cached = get_transient( $cache_key ); + + if ( is_array( $cached ) ) { + $typed = array(); + foreach ( $cached as $slug => $row ) { + if ( is_string( $slug ) && is_array( $row ) ) { + $typed[ $slug ] = $row; + } + } + + return $typed; + } + + $response = $this->get( '/me/subscriptions' ); + + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + return array(); // Not cached — retried on the next view. + } + + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( ! is_array( $data ) ) { + return array(); + } + + $rows = array(); + foreach ( $data as $row ) { + if ( ! is_array( $row ) ) { + continue; + } + + $slug = $row['plugin_slug'] ?? ''; + $slug = is_string( $slug ) ? $slug : ''; + + if ( '' === $slug ) { + continue; + } + + $raw_status = $row['status'] ?? ''; + $raw_expires_at = $row['expires_at'] ?? ''; + + $rows[ $slug ] = array( + 'status' => is_string( $raw_status ) ? $raw_status : '', + 'expires_at' => is_string( $raw_expires_at ) ? $raw_expires_at : '', + ); + } + + set_transient( $cache_key, $rows, HOUR_IN_SECONDS ); + + return $rows; + } + + /** + * Clears the cached subscriptions response (key change, manual refresh). + * + * @return void + */ + public function clear_subscriptions_cache(): void { + delete_transient( 'robotstxt_manager_subscriptions' ); + } } diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index dfd0845f796c12e6bb8cd4ce0d9065d0084a3d73..6d9335fa6ab43bbe2cf0f3d8b4d2a8fbbb0477ac 100644 GIT binary patch delta 2801 zcmb8vYitx%6u|M@($Z4O8xaJ`wY0p1g;s$o2o^*vAV>kFLX6Yh+jeBSyX?$XizMp{ zNmK%6d1wq0QxfC@h%9J=NU$w_F{lZAr~whEN~pBZ!;XN+s}YE(cmio>KepYwUv?^OXwVHtdbolT@rvre@pvBZzycoLfu-0}9{q$9-cjv15>H|+{)jzrF{4N>)kq&|CEkv;I1tw% zkE%@>-^9sWx1bDs3N`+L1F)!2slGS`r%9@_IeD5JO~^Cq0!pS=aS-M+ZJEgk%)t_T zC5z7SL#_|d8;`2`e(C2t9L)84ltkaiT<^tETpz<>cn*uDRM$Bfjl=sZmBQIL0e{5} zSVY>4rFNraeh?+WPmvO;vp5tx(O*K8iVdSYQn69!;&@~zwG$=beVC9LAIaSK3}q=! zq2Dn|2c0M#Uquqe}?<5-6UlxGgE9ZdaY=7+fP zBz9mDr{BifQ{vB2D)HqIr7H0T%D^+Jt0Xudi*YeZ0&7t!)r?Z=jW`4Mpe#uz$^@>X zRA9hR>i;qW6>;|{H(FR2smN2*w;Uam8SO$D_6PcTkvO;fnQ`a*$Z!>B)%teeF$ZVTQOI@|1UVRl z@8*Vmms0a^CrXB$D5buJ{HYrp@^B0bEeQmWTva9BhYhH)1*PQeDDPiFhVkE#P2Gc# zeqBOW1`xT+zjw7<#a zi;zaa{We3^e-WokiP?lKgOpC1Y_2}UbYdX=mV`LS=hMAS<3j%F$fo4~h2L^HK2B50{n@*z$e=TMg>x+_cn*?VAtQBfu>5W;FQ6-fUfgnQ zZ=I!$qdgPXjcxCF5uW2uUQ0e#R6ayUO-wghVb6r^n0Cgp5^a0k zDS53_R)UmCTT3qfLfBS7{pV*5tME?``>CgU_9jYFZPN})-kCKp;h+_>G^zQk5SP&{ wD&l(DaUwx~86r*AB*#i7iUP&{tXxwcXHGFwD;tx=%akL?zH(F9V~%G34PNfe7ytkO delta 1901 zcmY+^S!_&E9LMqhELEyS+bOkFr`l?(RB3Cgr7cQJ(NU6GLK-3@C5<#f$UImQu_jeS zqUnQpAPCo%5)rXRM2IECRu7hh7pWIdzQ23NgOmQ}bI#1Y_nhKE)z@k9{$*w^;(F<95u(D0U*3Sh$Z_0p{ZnjNmbB z#$LKKj=Q8Zp{)cv1P^ZpExf2E{{FEr7hG;imbsML+Ya;!rxvAw8;b)a^34RIXD@Y;~{KE zWgwSVWH?SiUEhe>Kns2+6Wj0;<29sD8JZpT77#(EvX!X&x1tspJx)h8JC6hLIcf*r zaU>?P-5*$jr5NVN4qSp9Z0kXiWC0$c3}v87mxHXuW})7qCR7I2;bCk+=8Kv?$E*2Z z)Qa1pj7W=4w zkZqRmMOhx{!u6<$FQ9670}Js!#^W#4M1N2jO3J4kSc+lXge1idqwaT*%DDG}n|9XZ z9?&Vw@1|cr=p1mZ&DVf7kxH{Wq*1=jwwtj!?$b zW_h@uGgW`yAh+)Nrj`h+LsP9P2_2ds!7J;gwFs*Toq>PNAM5B1QAu==i(}u38H7&1 zno2_D+{5hVO|MZMF_lo8Ks2~L?;PlT)d5O!UdHbZIBWV|_c=L9?Lp^Z@=9OZ{M0*b ot)aEfleE|I&c%#8ztf$0BF@>Dd)n8wH1AzoSN;}f(x@)~Up=j;nE(I) diff --git a/languages/robotstxt-manager-ca.po b/languages/robotstxt-manager-ca.po index da6c088..0552f8f 100644 --- a/languages/robotstxt-manager-ca.po +++ b/languages/robotstxt-manager-ca.po @@ -267,3 +267,29 @@ msgstr "El plugin baixat per a %s no té lestructura de carpetes esperada." msgid "Requires: %s" msgstr "Requereix: %s" + +msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." +msgstr "El pagament de %s ha fallat. Actualitza el teu mètode de pagament des de la teva pàgina de compte de ROBOTSTXT per mantenir l'accés." + +msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." +msgstr "La subscripció de %s ha caducat, però el plugin encara és actiu en aquest lloc. Renova-la des de la teva pàgina de compte de ROBOTSTXT per seguir rebent actualitzacions." + +msgid "Subscribed" +msgstr "Subscrit" + +msgid "Subscribed — %d days left" +msgstr "Subscrit — queden %d dies" + +msgid "Payment failed" +msgstr "Pagament fallit" + +msgid "Cancelled" +msgstr "Cancel·lada" + +msgid "Expired" +msgstr "Caducada" + +msgid "The subscription for %1$s expires in %2$d day." +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[1] "La subscripció de %1$s caduca en %2$d dies." diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index 763f8f0602fa7ca2c9067e3deb392ae026b649e5..9d6dc7125ec56e30eb3f7a8aec375471c841c7be 100644 GIT binary patch delta 2790 zcmb7^Yiv|S6vwBfbW6+op{=E5D7!)t3oY_cDhLP&R+It?NHlkMr`rqLz02Oal@iSI z1@VKZWUU$#Vu%`TVu`p&A_f8}n22vl3`8VKO^hLY0AqqgqrY=^Yign&nC!W~nYpuP z&iS92`_`Ic8#5QjmUJuHW^5w%N3l{Fcws0H+PQ#Iz3@C70r!+C)d0I;1v~}I;a6}c z{0_?hHw;sXzo>Jt9{vDtgXJuK1%_a$EImet|EN>227V61@G=|%SF?-6(hBjRHo#k9 z7>8ag;qh?ptbNm3|kp@z?JX>Y=h;xC=@~hoEeH621mMgL`3&^hKfHU^Dy+ zN*}@vAVF+%4%QJ-+hoEHiAryxvU@bfYNm~5^B>T7G}b+rj=9k8BT`s$AwU&T@JXY zZ4(o-DC8;_f#UceEQd!SKXsZ%8N3R8sUbeqs2TZFJ|9XcJSYk5gB9=)WE=l$DfJAvO$?Xz#)vz@)LC-|pqvjLpYT+i%4M3K|FXA1w6mA2cUP zK9V12)bc?uq+5zfR+0fn!oT-&KsR6_?|m4R@!NWy%djQ+q5njEwHA8_BXPA6Luvm^ zNZF(zxZf5?{afg+!4_e&F*&W$N-+7D$n{x@-Hk~(hGSd?wGNYVaOM5BqCml;*k+6` zvHz)_3FYt(ZmStQ9Oc2){8y8EEgu~@lu}P=L$Sv&`J8HutE1*%QeXK#3^Z<)dK{Ch z`A^H$1a=PzBqAmrH__bdOw{KAE+HYdH z`+w_Q-EIwRFef@ghtr<+@SKdNV{B);+V$*soG3lJ(`cgd+GW!AOoMJU3GPge7UWtl{3{<#bryaXgVUJr)zc&Ek&+)+|FIA>n#eVMRCtT z@O;OYdt2H>EfOc~zFy1C?hjsUS{6sqn4_a6FL*rOmut|ABVO8y+dC}nrFBPNkLN`F zC9*Ud2u-WxPbo`xSP757Q`$&ci0@>(LOU`o7V68s$%x&TOZdJ;d_pB$Z3eU_hz4~N zi;@1nR*Ucj-%)3VPR=$dM delta 1901 zcmZA2Sx8h-9LMqhI8K&2l{uESIi==SmhGjbZMIr!k`;y!Eff?j2t~_083aWSVv&_t zP(jc`-8R~YvIQ)9DWS+hgA&T8Ht4DE@6Pq5gMUBgT<@Iaf6f^nth-nl`8XuD$tcyt zNaB8s*>0>2@QV_TGrNjoFo>_Q06Q@O6Z@ORV=5lR42)na@`{BAnB`&)4#z4yg*&jH zUJdYR-nB#=g(|>H;2q@0+C4jPBJD2JfJw|Qj76A)wKx!W;&eQO z+cni2RQR#<#OMt3F`0H5#^QQB;xntmTeQEB2C8;FPfWwm zAhWSpfEiecj#5=mB@c54n>AoHD%I~%DeFN}Wqy{Cit(te%0OkN6v?KQ;a;pjy0kXb zMBktm*yXi*Q5pY>5w~OVsSe^%9fwc@6{1$S4Ao&3l3m+~Rk#C};v<}o@uW>FD@Q*W z+l^XSJ?WW;t*Fm;qsHwGk$^3$rP#Zp^ zy`KE*c^WTG!f~jH*PsU8ip{tOk7F>6{A=ZB*q~y(h?-D0-oya;*Tn95KEW{UcHEBN zz2`L}qARMy(LCRRL$Luh@LAM2mr;lB8s=k9gh~#T5P8=M=O9_LDx85^uo+uWdpL_L zqRcEp7GaxFsXUAwWTF8-)2`(uWui13-RpAHAzXuWWxG)0MUGR^iZ7r}?^Cbc>1kuk zzVbW+XJY~{pT{!P06nPB{X$J7$N^vu7Q!r?jC#Hnm4P}uhTD+OMeH{fbu=(Dy225t zb~Y;YQ&AlhBNxFIU@lgpGI1F7UJEL7mr$9yi`t@RxD*SS%?dn#dH4b|eeC}aDwBDT zn$1ba<){u$qW10rYOk)MuFX4C>bg-|_XmqGOhc)yMNMo!s-M&7!#F~Pvue!A9dkD0 zyH^HmJi)u}EY+StXvO`BRfJ~Em9<60EMhgmcZ*FUG(i=e_DZ+LS2oV|>MK$EKZno( z?JJQAYMON^p)^k+w0~NGp9m0(38heXUPWpCzbFF}2$Jc34eReq^ovqR@D<^fg`Q%8 z_MaQ%?zbK&{b5xo%ZYh}4oxhf)6N-l|M`@m&Ol#@@haMuafGg(?tfn?p~6LTi?);< zv#D;2yy^6o6UFX>=>4qr>e|1V#C#&?yp3z{JDU>j_?*mOQ>^nMX@jqEMe^gu`p{ diff --git a/languages/robotstxt-manager-es_ES.po b/languages/robotstxt-manager-es_ES.po index 35554f4..8083a59 100644 --- a/languages/robotstxt-manager-es_ES.po +++ b/languages/robotstxt-manager-es_ES.po @@ -267,3 +267,29 @@ msgstr "El plugin descargado para %s no tiene la estructura de carpetas esperada msgid "Requires: %s" msgstr "Requiere: %s" + +msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." +msgstr "El pago de %s falló. Actualiza tu método de pago desde tu página de cuenta de ROBOTSTXT para mantener el acceso." + +msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." +msgstr "La suscripción de %s ha caducado, pero el plugin sigue activo en este sitio. Renuévela desde tu página de cuenta de ROBOTSTXT para seguir recibiendo actualizaciones." + +msgid "Subscribed" +msgstr "Suscrito" + +msgid "Subscribed — %d days left" +msgstr "Suscrito — quedan %d días" + +msgid "Payment failed" +msgstr "Pago fallido" + +msgid "Cancelled" +msgstr "Cancelada" + +msgid "Expired" +msgstr "Caducada" + +msgid "The subscription for %1$s expires in %2$d day." +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[1] "La suscripción de %1$s caduca en %2$d días." diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index dffb2a3..38a56d3 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -1,268 +1,27 @@ -# Copyright (C) 2026 ROBOTSTXT -# This file is distributed under the GPL-3.0-or-later. -msgid "" -msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.6.0\n" -"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-08-15T12:00:00+00:00\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"X-Generator: manual\n" -"X-Domain: robotstxt-manager\n" - -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgstr "" - -msgid "%s activated." -msgstr "" - -msgid "%s installed. Activate it from the list below." -msgstr "" - -msgid "%s updated." -msgstr "" - -msgid "(about %s)" -msgstr "" - -msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." -msgstr "" - -msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." -msgstr "" - -msgid "API Key" -msgstr "" - -msgid "API key deleted. Save changes to persist." -msgstr "" - -msgid "API key is not configured." -msgstr "" - -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." -msgstr "" - -msgid "Action" -msgstr "" - -msgid "Activate" -msgstr "" - -msgid "An unexpected error occurred." -msgstr "" - -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "" - -msgid "Buy" -msgstr "" - -msgid "Cache" -msgstr "" - -msgid "Catalog Cache (minutes)" -msgstr "" - -msgid "Catalog refreshed." -msgstr "" -msgid "Connected." -msgstr "" - -msgid "Connection" -msgstr "" - -msgid "Could not create a temporary file for download." -msgstr "" - -msgid "Could not reach Plugins Core: %s" -msgstr "" - -msgid "Data on Uninstall" -msgstr "" - -msgid "Delete API key" -msgstr "" - -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "" - -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "" -msgid "Deleting…" +msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." msgstr "" -msgid "Download failed (HTTP %d)." +msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." msgstr "" -msgid "Download failed: %s" -msgstr "" - -msgid "Free" -msgstr "" - -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." -msgstr "" - -msgid "Incompatible" -msgstr "" - -msgid "Install" -msgstr "" - -msgid "Installation failed." -msgstr "" - -msgid "Installation failed: %s" -msgstr "" - -msgid "Installed (inactive)" -msgstr "" - -msgid "Insufficient permissions." -msgstr "" - - -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "" - -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "" - -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "" - -msgid "Not installed" -msgstr "" - -msgid "Plugin" -msgstr "" - -msgid "Plugin is not installed." -msgstr "" - -msgid "Plugin not found in catalog." -msgstr "" - - -msgid "Price" -msgstr "" - -msgid "ROBOTSTXT" -msgstr "" - -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "" - -msgid "Refresh catalog" -msgstr "" - -msgid "Requires PHP" -msgstr "" - -msgid "Requires WP" -msgstr "" - -msgid "Settings" -msgstr "" - - -msgid "Store URL" -msgstr "" - -msgid "Store URL is not configured." -msgstr "" - -msgid "Store is not configured." -msgstr "" - -msgid "Test connection" -msgstr "" - -msgid "Testing…" -msgstr "" - -msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." -msgstr "" - -msgid "The store responded with HTTP %d. Check the Store URL and API key." -msgstr "" - -msgid "The store returned an invalid file." -msgstr "" - -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "" - -msgid "Too many refreshes. Please wait a minute before refreshing again." -msgstr "" - - -msgid "Up to date" -msgstr "" - -msgid "Update" -msgstr "" - -msgid "Update available (v%1$s → v%2$s)" -msgstr "" - -msgid "Visit website" -msgstr "" - -msgid "You do not have sufficient permissions to access this page." -msgstr "" - -msgid "Your server runs PHP" -msgstr "" - -msgid "Your site runs WordPress" -msgstr "" - -msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." -msgstr "" - -msgid "Version" -msgstr "" - -msgid "Status" -msgstr "" - -msgid "€%s / year" -msgstr "" - -msgid "Support" -msgstr "" - -msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." -msgstr "" - -msgid "Payments, and how it works" -msgstr "" - -msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." -msgstr "" - -msgid "Installed required plugins: %s." -msgstr "" +msgid "The subscription for %1$s expires in %2$d day." +msgid_plural "The subscription for %1$s expires in %2$d days." +msgstr[0] "" +msgstr[1] "" -msgid "Could not install the required plugin %s." +msgid "Subscribed" msgstr "" -msgid "WordPress.org lookup failed: %s" +msgid "Subscribed — %d days left" msgstr "" -msgid "No download found on WordPress.org for %s." +msgid "Payment failed" msgstr "" -msgid "The downloaded plugin for %s does not have the expected folder structure." +msgid "Cancelled" msgstr "" -msgid "Requires: %s" +msgid "Expired" msgstr "" diff --git a/readme.txt b/readme.txt index 7b135d2..b6ebec8 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.1.0 +Stable tag: 1.2.0 Requires PHP: 8.0 -Version: 1.1.0 +Version: 1.2.0 License: GPL-3.0-or-later 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 == += 1.2.0 = + +_Release date: 2026-08-15_ + +* Subscription status (Phase 4): pills next to premium prices (Subscribed / N days left / Payment failed / Cancelled / Expired) and admin notices for failed payments, upcoming expiries, and expired-but-active plugins. Cached hourly from Core's /me/subscriptions. + = 1.1.0 = _Release date: 2026-08-15_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index dbac845..fb9e7a3 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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: 1.1.0 + * Version: 1.2.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.1.0' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.2.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 30451228f73e2584bea3a4dfe68c2c5489e163b0 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:04:38 +0000 Subject: [PATCH 12/23] v1.2.1 --- admin/views/page-catalog.php | 23 +++++++++++++++++++++++ changelog.txt | 17 +++++++++++++++++ readme.txt | 10 ++++++++-- robotstxt-manager.php | 4 ++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 7cdc1a2..0cc556d 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -132,6 +132,7 @@ $compat_warnings = 0; $raw_desc = $entry['description'] ?? ''; $raw_rwp = $entry['requires_wp'] ?? ''; $raw_rphp = $entry['requires_php'] ?? ''; + $raw_icon = $entry['icon_url'] ?? ''; $slug = is_string( $raw_slug ) ? $raw_slug : ''; $name = is_string( $raw_name ) ? $raw_name : $slug; @@ -143,6 +144,7 @@ $compat_warnings = 0; $desc = is_string( $raw_desc ) ? trim( $raw_desc ) : ''; $req_wp = is_string( $raw_rwp ) ? $raw_rwp : ''; $req_php = is_string( $raw_rphp ) ? $raw_rphp : ''; + $icon_url = is_string( $raw_icon ) ? esc_url_raw( $raw_icon ) : ''; $website_url = '' !== $page_url ? $page_url : $homepage; $has_detail = ( '' !== $website_url || '' !== $desc ); @@ -184,6 +186,11 @@ $compat_warnings = 0; ?> + + + + + @@ -382,4 +389,20 @@ $compat_warnings = 0; /* Detail rows (always open): muted, attached to the row above. */ .robotstxt-manager-detail-row td { border-top: none !important; padding-top: 0; } .robotstxt-manager-detail-description { color: #50575e; } + +/* Plugin icons: 64x64, 1:1, beside the name. */ +.robotstxt-manager-plugin-icon { + display: inline-block; + vertical-align: middle; + width: 64px; + height: 64px; + margin-right: 10px; + border-radius: 4px; + object-fit: contain; + background: #fff; +} +.robotstxt-manager-plugin-icon--placeholder { + border: 1px solid #dcdcde; + box-sizing: border-box; +} diff --git a/changelog.txt b/changelog.txt index 7da32b6..c032561 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 1.2.1 = + +_Release date: 2026-08-17_ + +**Added** + +* Plugin icons in the catalog: each row shows the plugin's `icon_url` from the Core API at 64×64 px (1:1, lazy-loaded) in the Plugin column; plugins without an icon get a plain white 64×64 placeholder. + +**Changed** + +* Plugin version 1.2.0 → 1.2.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) + = 1.2.0 = _Release date: 2026-08-15_ diff --git a/readme.txt b/readme.txt index b6ebec8..7b8c566 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.2.0 +Stable tag: 1.2.1 Requires PHP: 8.0 -Version: 1.2.0 +Version: 1.2.1 License: GPL-3.0-or-later 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 == += 1.2.1 = + +_Release date: 2026-08-17_ + +* Plugin icons in the catalog rows (64×64, from the store's `icon_url`; white placeholder when missing). + = 1.2.0 = _Release date: 2026-08-15_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index fb9e7a3..c6d5428 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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: 1.2.0 + * Version: 1.2.1 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.2.0' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.2.1' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From e3bfd7b6f2158397c19e03c8bb0ff3a0ad86bd3e Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:05:38 +0000 Subject: [PATCH 13/23] v1.3.0 --- admin/class-robotstxt-manager-admin.php | 2 + admin/class-robotstxt-manager-settings.php | 2 +- admin/views/page-catalog.php | 54 ++++++++++++------ changelog.txt | 19 ++++++ .../class-robotstxt-manager-core-client.php | 28 +++++++-- languages/robotstxt-manager-ca.mo | Bin 11506 -> 12607 bytes languages/robotstxt-manager-ca.po | 6 ++ languages/robotstxt-manager-es_ES.mo | Bin 11453 -> 12518 bytes languages/robotstxt-manager-es_ES.po | 6 ++ languages/robotstxt-manager.pot | 6 ++ readme.txt | 10 +++- robotstxt-manager.php | 4 +- 12 files changed, 108 insertions(+), 29 deletions(-) diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index 400631c..1921cc6 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -72,6 +72,8 @@ class Robotstxt_Manager_Admin { // Admin notices for subscriptions needing attention. $manager_notices = $this->build_subscription_notices( $catalog, $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'; } diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index b8d5d76..cddc28f 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -263,7 +263,7 @@ class Robotstxt_Manager_Settings { } echo '

'; } else { - echo '

' . 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' ) . '

'; + echo '

' . 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' ) . '

'; } // Action buttons. diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 0cc556d..522a2a5 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -25,6 +25,8 @@ if ( ! defined( 'ABSPATH' ) ) { * @var array $local * @var array> $manager_subscriptions * @var list $manager_notices + * @var bool $manager_has_api_key + * @var string $manager_store_url */ $client_configured = Robotstxt_Manager_Core_Client::from_options()->is_configured(); @@ -77,6 +79,22 @@ $compat_warnings = 0;
+ +
+

+ ROBOTSTXT store 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' ) + ) + ); + ?> +

+
+ +

@@ -149,6 +167,14 @@ $compat_warnings = 0; $website_url = '' !== $page_url ? $page_url : $homepage; $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'] ?? ''; $req_slugs = array(); if ( is_string( $raw_reqs ) && '' !== trim( $raw_reqs ) ) { @@ -220,7 +246,7 @@ $compat_warnings = 0; 0.0 || 'premium' === $kind ) { + if ( $is_premium ) { echo esc_html( sprintf( /* translators: %s: formatted price number. */ @@ -230,13 +256,10 @@ $compat_warnings = 0; ); // Subscription pill for premium plugins. - $sub_row = $manager_subscriptions[ $slug ] ?? null; $sub_text = ''; if ( is_array( $sub_row ) ) { - $raw_sub_status = $sub_row['status'] ?? ''; $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 : ''; if ( 'active' === $sub_status && '' !== $sub_expiry ) { @@ -261,11 +284,8 @@ $compat_warnings = 0; } if ( '' !== $sub_text ) { - $raw_pill_status = is_array( $sub_row ) ? ( $sub_row['status'] ?? '' ) : ''; - $pill_status = is_string( $raw_pill_status ) ? $raw_pill_status : ''; - echo '
' . esc_html( $sub_text ) . ''; } } else { @@ -274,18 +294,16 @@ $compat_warnings = 0; ?> - - - + + + + + - 0.0 && '' !== $page_url ) : ?> - - - 0.0 && '' !== $page_url ) ? 'button-secondary' : 'button-primary'; - ?> - + — + + diff --git a/changelog.txt b/changelog.txt index c032561..0fcf438 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,24 @@ == 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 = _Release date: 2026-08-17_ diff --git a/includes/class-robotstxt-manager-core-client.php b/includes/class-robotstxt-manager-core-client.php index adb6e93..672c66f 100644 --- a/includes/class-robotstxt-manager-core-client.php +++ b/includes/class-robotstxt-manager-core-client.php @@ -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 */ 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 ) { $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( $url, array( - 'headers' => array( - 'Authorization' => 'Bearer ' . $this->api_key, - 'Accept' => 'application/json', - ), + 'headers' => $headers, 'timeout' => self::TIMEOUT, ) ); diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index 6d9335fa6ab43bbe2cf0f3d8b4d2a8fbbb0477ac..e313bbfbb6f56a2c898f59fea1d3c40c05cdcfb3 100644 GIT binary patch delta 3078 zcmbW1Yitx%6vuDNqw*+yXhopFZE2N4*&@_}&=y)KSOJUBD#$};x6^j$bf(PAwiF3# zh^Su>6CivuHO2=dglJPVYLr-`Au);{43a1sKuwVt1V2D9#NV0SA|dd_n?3WpcXsZ% z=lsu^xxD_fXyQ!n@B@mm8Oz7U4pS-t{~5uBaWzA!Bk&r`gzwy=R0Q_JG4MPb4KKkR z@H&+J+eRtH7IhVtKzV2)m;pzmDOCp#%A+grGLk>eLH4Q(a5B6CqcCTzQpBVJC^2n= z_*6ULc=!s;g}qP$=}YlbSjPGUlmM!Wb0Nc9Di3Gh5D zgg4+cSU_5Runx|E*WhkgL=;kr!;qNNhfo6i3W}0v;RA3GN=ef>U6h*&MY&?=!!np4 zX!Rx&iMS6+#>Z3Zuc4IWGz=X>aWDwQ@f9csW#=WOFM*R-mqV%jYAF8KrT%Y&YgqfR z9)6jJ{w++h?^mi8c0qSGPuDFQD8hezQ{pE@DtXDNJkWDA>BF9gObr(P!8^g`{8l8 z2R6~UqS!e&A6|q7uy~463-G%f_OsqaG!pRqgicNtL3wZ)Y=v!5GB^n(upd(V6-tJe zAu6goHc22ep_HZq%E61_Y}f-!;bAx?O{r5*n(!P>Xb+XR&g4-hS?InO)p?Rp;X$;WX-dnyf%{{ue2(F_*QeE9JCQit#?4lbT^bh-hjNR>fIDS zh8?WGfwi!bdb|u1a0Tb9j9JO9UJKW;-U*MuQ*bsC?~W)nm-VqSz6J0C90O-l;L)%W z;#)0;8PJCk!1GWPeHAW%{ZPY8P!!L3m>|SKITOwa8$u)>hKVDomvlYPhmwnIoUImP zBI!n~0(%^jLA>Eu#I+ieL#bjo(o+;jGO3YJt5UONL$}~cY)xwYSc+0286q*23f~sV zZV4uR@C>#B6Nzb=a6HX*Ikq&p46jncCaeLANc}f35&7ghAe}FR(uAXm>l4^otOk1o zqv69L%_W~-d3&TR3o%hM6O#^q3X}ejW|UE%M0k}lZ^m+^{?a^DAZ#jdbF5}oiD~Rf zY-q^)osH#S@@mPDMqQ6JVp3baM`|7>eIl>#&?siI3FG?~j=qx_?dgdY&+X7bo2l0{ z)HXD(ZCux={lN81Jw4``Mquh5H}2^c&os3Wi@9+pkf-@SaCNI0XxA}IBD$e7uw2Km z_1PEphu*>sF(WW+w^et$Uc0Zmt)R_~2igk4r=mbJT2+ zbR;RG)r_c`WEQh*v&*z~-`vrEq$QTzEmb4TR#zIj%`;o73XA@K!(lC^=b2t%^=*u48To^66I32;u`r15K>jjRvM;d9!RIEO{&}`2yKNr3ZR#vsK)t%5!t8uUp~{ z&n6s0`=;ZYY_N?|@i_3l9ysJ{hZ_3+K(A*R;W=HF6N}r1r)|?Gm@ckEEg@&#Y>Cf5 zkcWoO8xp{xB2g+Xn~hiyH*71|Zp5sS?4&XF{g^+guO`1KJ*@iO(l!2X&F`#mSo&Ke zltOGEGegI0BOaE2s2L)9W$JE9hP$bt4Q*_Vn|`2eIzU@!??-KyTU(5MSu^wUt#GFw Q>UXOUqC?+-DVxUq3p;EqF8}}l delta 2038 zcmYk+Sx6O89LMp0noC+%wrR`N)G}MtY_rw0#a1n|(vm2NHY#W#D1=L^5Jg4%gGDGr z54PRxL0ZLni5AEQqlM~HVhd3bMYMf?b0@e5|MQudJ2U5h{^uNTmVYVrzKCsm)M#Zy zZ{k;onHQghaiBFvnAPGF?0}V#X6bkoqp=aA@DWzvQ`Gw&gf}x z7K52MQmN)}vW6R5kZ0@_D$|eH1tS@@M$#MGVhZkQ!^iO|*BAL3kJ`$Pf#)`3SFS5i zi5_yV&tU@BH!vPwV3JDpgOg)ji7QB(04oe!fv;75J=IH0 zYeFUP4mBm;P#yloOw9I}4aU{T;OrPuZ99*nu>lX@FH|M=Q65gsdxjIea0@k(X4hBP zN@d>ROYS$3wi2u(D<#r^4A$RG;$F=3QsQiW9mb8-gUqI26)HnNs??v5Kl{lc0{b)3N+2D{)pBtRuEIpDMwPq~ z^?nP|jq{Ecbts|lXdQU9c+v@#Uh_AEm_W2677@&mO(WElgxYvw9H9kBwVf8?DuxD~ z&vefx2i=>&`JBMD*QN&jpi`BYiaArjl5qBY9IABFiHU?ptu~)f#Z>nHjq2MxVm86R zt(2hB&LY>2P@~{Z8>{)B#pzrkhtM>rbZS~$9f(oHBtqLr z1sux={u4X@sxwfFHnROBOTSTXT store 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 la botiga de ROBOTSTXT 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." diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index 9d6dc7125ec56e30eb3f7a8aec375471c841c7be..40903e87319abf49532a328027cafb5f47a968d1 100644 GIT binary patch delta 3074 zcmbW2Yit!o6vu}`L7-qk5P1)!Amwq3f`CBL7OEm32t`2@+wZg@x{F~LAIeh?BM8Uqmsq-qS25Ara?-`Twdg7u4&p8d_t&YU^t ze`asLT6xLJo$cRkm!hr4hG8XLmCC`ti+Rwl^-yXbybk-oPwrDH1^2_=@FMI5ufR?4 zCY1dfdMd>hbq!WPSu_NUhs9l#s)f5{=>oiT=fwrcUiB*+4zI#A>{p@`F{uzrOdB9R z)n+&dz6JZk51|B-FYpMgWc)3Z0Iov~N0Y@lI1l!dP|N!&#h;qSV;Zc5YvCqH4C*qJ z$gjc&VBhB-X ztB>eN#Ca$ge_a@V52YkO!q_ns2klTCUxji|>A*z#3OJPUR4BDy0>%G|!uuw;lyLy( z!(#){zkyEagG$xF^-wZD4CO+eg*#ytx^+e36Ht^m#i`X$WRsfLKnZXW6b0*`1h5{8 zQaLyhZilnrK1l4UeK7h<2Dg|H1qPwxE)FV(M;IR_9hq+<-N|qZl#C8SIrunyAD)EU zVGB1`6uJp#!{QN1KsB%ezXtASeh1M=fVDZDbQVGW)N&qcVJnmczd*^X9g5>C5FhFW zL>x7ER1(N!DDP*%X|NVffScerxEEf7C!l2h9Wrq}RPG#|>2$6_$z%fh%N;ldMY5An z6#5g&UFcn&1T+Ai?n1rc1$jRvQR?*AWZeabsOoo!II8=@$-$$clxPN|nmM(qFcB2^ z4!q2QFX2qMhw%5npP`gub49|f5C`gAC<=WJ<#*#CBo=k5Fn<&FWL(6>*#=9X?0XNE z$oKyR9m)7eJb^T@2jeqP9G`=H(&`e#m--8eGNaH}6sd-i;j>WoHA0crgT3J!a4p;c z=ffLNKEHVrm0HO8>JS}#s^UpXy#y!2y>KfmN8+g|r56Vh3QbD599xW~r2ebuhw5=%MyV0j1^jr*QnQfesiGL+hLOzh1w3|RcX4u$*qfQ-_(mY$99{O(}E}+WPCgJ z8BEl+8{o&$S>LqSQH#zxQ6qB`wdL01B4g}U!f7(LucK@|F$XDK>t_76Y>4NyX^?az zDXY<>RCSWYB*(NGN9U*X`mIZD?ti6f#o6lFmTvM*Lsi+BpiGCBPvTW&YtoM8HkT2B zQ&r}AUe>tA*Di-O7~eO3*_?YMon2|o>5wf}HEaApRC`rdFPX2KO8s8xbIK9WR5t_o$P+E69-_Ub4D0S8udq>CiTAy`>r42`{d-M&qOVbQtMO zWZclwjlLB|Hl3{HTiQ#9lAvS7<<7+Ao~etU-UP-uaj>V2O zQIoZ&O`u&W7g)Ys@ZV~?naHtx0wB96z5ue<+`a+4>u}$ZY@5{IPE&bya6!Y!a_o&( zvM;5V8J)2k2;Oo-v$J@3X@1SHnxeSeo#koV|6A$Kb&U&tw|McPPR`=KO2s?spVA90 zopEHkpu)dJ&=#6ea_Vk-x(%&^wBxOdjHMlq>L=gC?OV{+sUT{e6?#Gb;}L`U{sVHP BE#LqE delta 2038 zcmY+^Sx8h-9LMp$lc~*=wpp5{W?I=|wr`fLR%TYVnQb(ILJ%!RR0PvY*+VNKMbJYO zRFslU2?<8g0@+>=Mi$jTA>l(sAX3!#cjpFnnE(CUbMKwA{Lel2^M;01f%iQltBkUS zNG5)H%mUclnFFOM+N>JiU^lFcG0Vm(^x-}9;&a@KZ&Cj*>0-uTtO>L51NK8N51+(L zjMAeGobVsJhyC#h`td7v!XkRnSc;K0tOWa_AA8~+Q`xvN3A?A{M-)gO?@wF zpvT?%B}}D$3ls4@rYlu#oD9UI?q)&E!=d;C_hSmfrY}2>8u>NU0Pi3vv}R1e5IReU z%2*=lQN~iS0<(~=>>)i_vP)qR&orX~jgisB)qB`=D7NvY3CSf)zLj|Y~ zirxEuET+C0=i_ZGLoex>iQ9UUf6e?l7uI1Dp1=w5tUW1ygv!LTK4vqp9o2CzdDQ^t zVmcO~2Cxm4sokhlSK<`BfLf9eY65Mj4D?7K|5bF9!qr<`xX!}pMg{rJ!F{M1)uKAS zgQxK^9>#K}uMGXfT>OI?P%i1Zf$LENi|5Uj{V@$QaTDeT+#9v18QnxRcpFo&5t+2L zpa!6ey6?mB7>~oS5QpF{)J)GJ*|s{IfQ_hudq|6x{0u5XflC~fl830Z|AZRISA0!o zzGF!D2e>Z{)6;YHsJHko>fLyU>bM>GSsd$1mTfeuUf{YJzfeDhlePbMWiVPU1W~D9 zo*CAUYG4a$K$S=m>ye+`=MaP6&`CAYro{~pzvUBAOR)nrf#c}ITBIB2YpH%Z z5z&O&2_go(qcAJeLrV(8TjdmCu@;ltnX%wMZX$HI!&c4?RT0$tj(+O6_Da$#|A!dfF&XMffQeq*&#BCKprk!^} z%cerYoiau1U&!eaB99nD=xtSrB=j-S_L)UYBD5S`2{wZ*BeWcBd8f>E6)T7}1Yct3 zQ#}&(@^&mmR2FGgY|a0Q_O(7bdMUM@DxHaygg&Qf1Y5_(5n5k;AO00~m8~WcgDuh9 ZJ;4pWP(&~zc5V0I<|e3cyxsr+ diff --git a/languages/robotstxt-manager-es_ES.po b/languages/robotstxt-manager-es_ES.po index 8083a59..fb239cd 100644 --- a/languages/robotstxt-manager-es_ES.po +++ b/languages/robotstxt-manager-es_ES.po @@ -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." 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." + +msgid "Create your free account at the ROBOTSTXT store 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 la tienda de ROBOTSTXT 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." diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index 38a56d3..4d2d0c3 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -25,3 +25,9 @@ msgstr "" msgid "Expired" msgstr "" + +msgid "Create your free account at the ROBOTSTXT store 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 "" diff --git a/readme.txt b/readme.txt index 7b8c566..e19ed1b 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.2.1 +Stable tag: 1.3.0 Requires PHP: 8.0 -Version: 1.2.1 +Version: 1.3.0 License: GPL-3.0-or-later 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 == += 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 = _Release date: 2026-08-17_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index c6d5428..ea76084 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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: 1.2.1 + * Version: 1.3.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** 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. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From a2d13200309221cb3321b62d561e572bba7f9c96 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:06:40 +0000 Subject: [PATCH 14/23] v1.3.1 --- admin/views/page-catalog.php | 15 +++++++++++-- changelog.txt | 17 +++++++++++++++ includes/class-robotstxt-manager-updater.php | 23 +++++++++++++++++++- readme.txt | 10 +++++++-- robotstxt-manager.php | 4 ++-- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 522a2a5..ce4c70f 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -167,6 +167,17 @@ $compat_warnings = 0; $website_url = '' !== $page_url ? $page_url : $homepage; $has_detail = ( '' !== $website_url || '' !== $desc ); + // Localised description: the admin user's locale first, + // falling back to the (English) default. + $raw_translations = $entry['description_translations'] ?? ''; + $translations = is_array( $raw_translations ) ? $raw_translations : array(); + $user_locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(); + $locale_text = $translations[ $user_locale ] ?? ''; + $display_desc = is_string( $locale_text ) && '' !== trim( $locale_text ) + ? trim( $locale_text ) + : $desc; + $has_detail = ( '' !== $website_url || '' !== $desc || '' !== $display_desc ); + // Subscription state: premium plugins are only usable // (install/update) with an active or in-grace subscription. $is_premium = ( $cost > 0.0 || 'premium' === $kind ); @@ -337,8 +348,8 @@ $compat_warnings = 0; - - + + diff --git a/changelog.txt b/changelog.txt index 0fcf438..61aeca7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ == Changelog == += 1.3.1 = + +_Release date: 2026-08-17_ + +**Added** + +* Localized descriptions: catalog rows and the "View details" modal show the description in the logged-in admin's language (`get_user_locale()`, falling back to the site locale), using the new `description_translations` map from Core 1.8.0+; missing translations fall back to the English default. + +**Changed** + +* Plugin version 1.3.0 → 1.3.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) + = 1.3.0 = _Release date: 2026-08-17_ diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php index ca8a746..50cd2fe 100644 --- a/includes/class-robotstxt-manager-updater.php +++ b/includes/class-robotstxt-manager-updater.php @@ -210,7 +210,7 @@ class Robotstxt_Manager_Updater { 'requires_php' => $this->str( $row, 'requires_php', '' ), 'tested_up_to' => $this->str( $row, 'tested_up_to', '' ), 'page_url' => $this->str( $row, 'page_url', '' ), - 'description' => $this->str( $row, 'description', '' ), + 'description' => $this->localized_description( $row ), 'icon_url' => $this->str( $row, 'icon_url', '' ), 'banner_url' => $this->str( $row, 'banner_url', '' ), ); @@ -340,6 +340,27 @@ class Robotstxt_Manager_Updater { return $slug; } + /** + * Picks the description for the current admin user's locale, falling + * back to the default (English) description. + * + * @param array $row Catalog row. + * + * @return string The localized (or default) description. + */ + private function localized_description( array $row ): string { + $raw_translations = $row['description_translations'] ?? ''; + $translations = is_array( $raw_translations ) ? $raw_translations : array(); + $user_locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(); + $locale_text = $translations[ $user_locale ] ?? ''; + + if ( is_string( $locale_text ) && '' !== trim( $locale_text ) ) { + return trim( $locale_text ); + } + + return $this->str( $row, 'description', '' ); + } + /** * Extracts a string value from a mixed-value row. * diff --git a/readme.txt b/readme.txt index e19ed1b..a87fd6b 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.3.0 +Stable tag: 1.3.1 Requires PHP: 8.0 -Version: 1.3.0 +Version: 1.3.1 License: GPL-3.0-or-later 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 == += 1.3.1 = + +_Release date: 2026-08-17_ + +* Descriptions render in the admin's language (Core 1.8.0+ `description_translations`), English fallback. + = 1.3.0 = _Release date: 2026-08-17_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index ea76084..d3e8a99 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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: 1.3.0 + * Version: 1.3.1 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.3.0' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.3.1' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 21df13f02abf7fff5ce47719db7eba7eb89d9c11 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 16:07:31 +0000 Subject: [PATCH 15/23] v1.4.0 --- changelog.txt | 13 +++++++ .../class-robotstxt-manager-core-client.php | 39 +++++++++++++++++++ includes/class-robotstxt-manager-updater.php | 17 ++++++-- readme.txt | 10 ++++- robotstxt-manager.php | 4 +- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 61aeca7..a6f7d91 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,18 @@ == Changelog == += 1.4.0 = + +_Release date: 2026-08-17_ + +**Changed** + +* Premium package URLs use short-lived download tokens (Core 1.9.0+ `POST /me/download-token`): the updater exchanges the account key each update cycle and embeds the 15-minute token instead of the API key, so the long-lived key no longer sits in the `update_plugins` transient or server access logs. Falls back to the API-key flow automatically on older Core. (Closes the 0.5.1 audit finding.) + +**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.3.1 = _Release date: 2026-08-17_ diff --git a/includes/class-robotstxt-manager-core-client.php b/includes/class-robotstxt-manager-core-client.php index 672c66f..d969da3 100644 --- a/includes/class-robotstxt-manager-core-client.php +++ b/includes/class-robotstxt-manager-core-client.php @@ -361,6 +361,45 @@ class Robotstxt_Manager_Core_Client { return $rows; } + /** + * Exchanges the account API key for a short-lived download token + * (Core 1.9.0+ `POST /me/download-token`). + * + * @param string $slug Plugin slug the token may download. + * + * @return string Token string, or '' when unavailable (older Core, no + * key, inactive subscription, or transport error — + * callers fall back to the API-key flow). + */ + public function exchange_download_token( string $slug ): string { + if ( ! $this->has_api_key() ) { + return ''; + } + + $response = wp_remote_post( + $this->store_url . '/wp-json/' . self::REST_NAMESPACE . '/me/download-token', + array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $this->api_key, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => (string) wp_json_encode( array( 'slug' => $slug ) ), + 'timeout' => self::TIMEOUT, + ) + ); + + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + return ''; + } + + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + + $token = is_array( $data ) ? ( $data['token'] ?? '' ) : ''; + + return is_string( $token ) ? $token : ''; + } + /** * Clears the cached subscriptions response (key change, manual refresh). * diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php index 50cd2fe..ae2ba11 100644 --- a/includes/class-robotstxt-manager-updater.php +++ b/includes/class-robotstxt-manager-updater.php @@ -300,11 +300,20 @@ class Robotstxt_Manager_Updater { ); if ( 'premium' === $type ) { - $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); - $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + // Preferred: a short-lived download token (Core 1.9.0+) — keeps the + // long-lived API key out of the update transient and access logs. + $token = $client->exchange_download_token( $slug ); - if ( '' !== $api_key ) { - $args['api_key'] = $api_key; + if ( '' !== $token ) { + $args['token'] = $token; + } else { + // Fallback (older Core): the API key itself. + $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); + $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + + if ( '' !== $api_key ) { + $args['api_key'] = $api_key; + } } } diff --git a/readme.txt b/readme.txt index a87fd6b..f83cd64 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.3.1 +Stable tag: 1.4.0 Requires PHP: 8.0 -Version: 1.3.1 +Version: 1.4.0 License: GPL-3.0-or-later 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 == += 1.4.0 = + +_Release date: 2026-08-17_ + +* Premium update URLs carry a short-lived download token (Core 1.9.0+) instead of the API key; automatic fallback on older Core. + = 1.3.1 = _Release date: 2026-08-17_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index d3e8a99..36e9340 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * 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: 1.3.1 + * Version: 1.4.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.3.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.4.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 8e6a52d8ce6979fccccd1809a795a9f33e6c863d Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Mon, 17 Aug 2026 18:09:44 +0000 Subject: [PATCH 16/23] v1.4.1 --- changelog.txt | 21 + languages/robotstxt-manager-ca.mo | Bin 12607 -> 13148 bytes languages/robotstxt-manager-ca.po | 822 ++++++++++++++++++--------- languages/robotstxt-manager-es_ES.mo | Bin 12518 -> 13060 bytes languages/robotstxt-manager-es_ES.po | 821 +++++++++++++++++--------- languages/robotstxt-manager.pot | 459 ++++++++++++++- readme.txt | 132 +---- robotstxt-manager.php | 9 +- 8 files changed, 1597 insertions(+), 667 deletions(-) diff --git a/changelog.txt b/changelog.txt index a6f7d91..eb782ea 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,26 @@ == Changelog == += 1.4.1 = + +_Release date: 2026-08-17_ + +**Changed** + +* Plugin identity now points at the ROBOTSTXT software site: Plugin URI and the new Update URI header are `https://www.robotstxt.software/plugins/robotstxt-manager/`, Author URI is `https://www.robotstxt.software/`, and the readme.txt full-changelog link points to the same page. +* Contributors list reordered: robotstxt first, then javiercasares. +* readme.txt changelog section trimmed to the 3 latest versions per the documentation template. +* Tooling aligned with the declared real floors: PHPCS `testVersion` and the preflight scan range now cover PHP 8.0-8.5 (previously 7.4-8.5 / 8.4-8.5). + +**Compatibility** + +* WordPress: 4.4 - 7.1 (scan-verified: wp-compat ladder clean from 4.4; 10 errors at 4.0) +* PHP: 8.0 - 8.5 (scan-verified: PHPCompatibility ladder + manual audit — `mixed` hints and `str_contains()` are the 8.0 floor) + +**Tests** + +* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): pass +* PHPStan level 9 + wp-compat: pass + = 1.4.0 = _Release date: 2026-08-17_ diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index e313bbfbb6f56a2c898f59fea1d3c40c05cdcfb3..cdc25e04ce56363118a820783e47daaf7f277035 100644 GIT binary patch delta 2989 zcmajgdu$X%9Ki7@v=nG-rO2bT!c-n-rMv0k$ z@lDLca&8vm6fD3d%t%veBl3!*dYDyMOKrvL@JX!0-AF8|Fq>D+VYu4 zJ740@8&FDQp}R230-Y!eZ$^31E|k>w;I;S(N(N4%tpBTjzZbi#W}Y*c{Lf;rk_!#E z3#Z}$@+3R2!R5FX9sB}&lj_yHO)?QDZB_UL%8QSp1b6}^gFmALFo3j3rt)z(25=J2 zC?NlAUUhOocCgKVpc^ZgA4I8T7TY<(i-+PK=D)LT$xt^Zb}YVwvcoedfv90hJqlU4 z8F!#$Fv#1>aUvFBYmz}F3vWe<{A2BT2<3qz*ofy*c4Bf-0&4bMhqBX6NOsjO%uG}2 zJ(NK9b5lz7Im(N_#xM>hy(O5O!626}*hqHO{Ug0oz8xh4uizFuiIS;%N4c9*TTq^V z8D(etd_ThaIU9SBq$Jgs3?%Y%C^gS1_I?FrzB5sFxB^Gx z20Vo?;y8SqgYXm{K=~Ckjqys+fs|i8gp%23ktC~KD1T=@!gTrl|G=Oh7fz$>^c>31 z@Imq;hvsR_#x8&U7G^Quj~RFfug1ejOzJ0;7Y!=)GBX}!$G4yavIO}hD?{Y{%4X1v zn{XPQ#Z6cdRBA51k8DomhLozsO6)=h!=$>3g5JbDQKr=GxDzG7(xY$E~%VDr>yRQ1NZ-lyHO6VLFXj9 zZ3Ul_YH6Hj_m3BUD%_UNXAWITyo64bRR!;N+k8F?=-Qom2XKKuUg#@O>Whu?t4027 z7EYo|**GqWlDloTZxzm?Q%Sc?=2J>tHPoBC|J6?S$3m_@mIEk<+xYc3VW^+)_iCVVhK2>knMl9Z_8xrwmZ8$o+aCM1Jm{vU2Zdy7PuQy^k+G6og zEY#K-F>F&avCR@~v_vl4v6$7{(RQxOkQ?5kmj7x?N1_R{-i~#6?~(8&pCxpA9*lUc08B$lc@9onHUHockv(aKS znu(AzxS%4VEI2M$=F}AE+W%kv@?Y;ie_m;|*&el`@s`r*?6Oji4Ue5r8ZIv_o2bji zSB@(i6Q()O6ok`iQ#%WeraR^Ox0N+UTg*)8h#Ax5p0s&Gw`{tVJBE%0q%4%fY&N)i zDPjLAU{KHH+X+Le|BW5=0NT997Y14tEUoKh6eT7%ENWiPw2XDlT`e;u|N2Nq< zMl5Qtb1#%|<W7P<3)B_ zFS9MRXhdy#4v$_uh{aNACqHjQ>bt1)0C px|10`)yG@W`6E2+!f;Qhu;EJQ;7a!|UuZST(#JQv6f#;m)IX&D`_TXZ delta 2464 zcmYM!Yitx%6u|MbeMq5{J`j1d&|7Gg7Q0(&Ey$Znfr1E@qE(T&ZHKb5-K{gTK!vag zijNq?xEj6~gE78{NO03?)Mz6_B^t#K1~G~TP}7DO6hBZg#Q&LD^rmNibGtKl&OP_e z{IL1sj^vq=oEL<)i6|q+W{V{8pIi>K%cDdN;|Lbu>vufyHA*$Vpt(oOU2%%5I#9Phtrk zL`@`}@goe;KY^OS2pXKhEPldeSgfg5juYWe8aXs#Gd_g7kR0SU)XXp8cpO*g&ulv8 z(67PAnNbJ_=noW8MeHf|uX_Zqr@sd^;RBie5iHkI9p_{+p2z8U6=z@t(@JAA*5C;4 z#Y(c!r8tD-B=4an_!(*?&*CH;LS51y>K~p;}+BzoJ39RyNth}&hSs9iZX$lG?7`TOVfz@;3Zgx zeRwk-!u){9Db!7PjzQQSlDxvnZJZQR_x;#{TAI^Hvhq9X4F#3{%&Ia5afkuu;LrSC zaigDT)=hpwg{XUB3hFqGs0nXChLo+y>Lz7BCpx3okTK*0UcigkfIrR@c?nCZ{oQ^P zbqNNMRg;sbr5r|G<3CWpAH{5x91P)TY`|x65%OU98VmIO|CJLh@n7CQL;|z?H7`Vc zZAy_>S}KsSr5^P`8&TJK7wSy+q9*b(@}|lg89%@t`d?uaE?_;Lz$C8b`7)~3-_`4} zo&Ih-jHj@U67LO)+(LgK#J2#4F(2z#;4!!W8CzE1D0EO0*oj)vXK)_&qrr=)6)&Dm z5;{NwCp^d75L)tVLI=|I(p}H>Udgp=JX@9!TGEX~Be9CmM!sHK%yAK+4`mg-mX%S_ z$z)nm)@06>U;7u{L9}N2D>Ld6Y10z3Qr_F5v%8(peQ+PKn$QxnWxRF|#}&jfzv~@! z2{#aHiJ-3k{hVm|^gEzCUmHu~wS^q-B-Rs4iCYOayw`Md>E~D99$l9CgjTbF&>em^ zq5DHOqqY_w@2JbXi73(a*UiHUc$*6TYwI{$Kp5gK;#$-9yNDE=Bb{M4RaRI!$>ul9ah;%P2XoVJmt^Oq@=NFD)&(1bb*Yu5rZ#SO_1c!>1mkvh$Vv3}#%(v$ z8%=aaVj(-;5qBMThg;hdj)l7|J2hBZmo6(inv;62yew(;SWejVTDFOX4Od3G!~Z87 zG{>HDOw8(J>|+P*h<_m|`x$eO-mPe*x8NNr?ae diff --git a/languages/robotstxt-manager-ca.po b/languages/robotstxt-manager-ca.po index 3c5406d..204bfc3 100644 --- a/languages/robotstxt-manager-ca.po +++ b/languages/robotstxt-manager-ca.po @@ -2,9 +2,12 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 0.6.0\n" -"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-manager\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 1.4.1\n" +"Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n" +"PO-Revision-Date: 2026-08-17 15:41+0000\n" "Last-Translator: ROBOTSTXT \n" +"POT-Creation-Date: 2026-08-17T14:52:09+00:00\n" +"X-Translator: ROBOTSTXT \n" "Language-Team: Català \n" "Language: ca\n" "MIME-Version: 1.0\n" @@ -13,289 +16,570 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Domain: robotstxt-manager\n" -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." -msgstr[0] "%d plugin del catàleg no és compatible amb la versió de WordPress o de PHP d'aquest lloc." -msgstr[1] "%d plugins del catàleg no són compatibles amb la versió de WordPress o de PHP d'aquest lloc." +#. Plugin Name of the plugin +#: robotstxt-manager.php +msgid "Manager (by ROBOTSTXT)" +msgstr "Manager (de ROBOTSTXT)" -msgid "%s activated." -msgstr "%s activat." +#. Plugin URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/plugins/robotstxt-manager/" +msgstr "https://www.robotstxt.software/plugins/robotstxt-manager/" -msgid "%s installed. Activate it from the list below." -msgstr "%s instal·lat. Activa'l des de la llista de sota." - -msgid "%s updated." -msgstr "%s actualitzat." - -msgid "(about %s)" -msgstr "(sobre %s)" - -msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." -msgstr "Hi ha una clau desada (últims 4 caràcters: %s). Deixa-ho en blanc per mantenir la clau actual; escriu un valor nou per substituir-la." - -msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." -msgstr "Hi ha una clau desada però no s'ha pogut descodificar. Pots substituir-la escrivint un valor nou a dalt, o esborrar-la amb el botó de sota." - -msgid "API Key" -msgstr "Clau API" - -msgid "API key deleted. Save changes to persist." -msgstr "Clau API esborrada. Desa els canvis perquè faci efecte." - -msgid "API key is not configured." -msgstr "La clau API no està configurada." - -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." -msgstr "Clau API a nivell de compte emesa per la botiga ROBOTSTXT. És necessària per autenticar les sol·licituds del catàleg i de les subscripcions. El valor es xifra abans de desar-se." - -msgid "Action" -msgstr "Acció" - -msgid "Activate" -msgstr "Activa" - -msgid "An unexpected error occurred." -msgstr "S'ha produït un error inesperat." - -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "URL base de la instal·lació remota de Plugins Core d'on aquest lloc obtindrà el catàleg de plugins." - -msgid "Buy" -msgstr "Comprar" - -msgid "Cache" -msgstr "Memòria cau" - -msgid "Catalog Cache (minutes)" -msgstr "Memòria cau del catàleg (minuts)" - -msgid "Catalog refreshed." -msgstr "Catàleg actualitzat." - -msgid "Connected." -msgstr "Connectat." - -msgid "Connection" -msgstr "Connexió" - -msgid "Could not create a temporary file for download." -msgstr "No s'ha pogut crear un fitxer temporal per a la baixada." - -msgid "Could not reach Plugins Core: %s" -msgstr "No s'ha pogut connectar amb Plugins Core: %s" - -msgid "Data on Uninstall" -msgstr "Dades en desinstal·lar" - -msgid "Delete API key" -msgstr "Esborra la clau API" - -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "Esborra totes les dades del plugin quan es desinstal·li." - -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "Segur que vols esborrar la clau API desada? El catàleg i les dades de subscripció deixaran de funcionar fins que s'introdueixi una clau nova." - -msgid "Deleting…" -msgstr "S'està esborrant…" - -msgid "Download failed (HTTP %d)." -msgstr "Error en la baixada (HTTP %d)." - -msgid "Download failed: %s" -msgstr "Error en la baixada: %s" - -msgid "Free" -msgstr "Gratuït" - -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." -msgstr "Quant de temps es desa a la memòria cau la resposta del catàleg de Core en un transient. Per defecte: 60 minuts. Els valors més baixos actualitzen més sovint a costa de més sol·licituds a Core. Màxim: 1440 (24 hores)." - -msgid "Incompatible" -msgstr "Incompatible" - -msgid "Install" -msgstr "Instal·la" - -msgid "Installation failed." -msgstr "Error en la instal·lació." - -msgid "Installation failed: %s" -msgstr "Error en la instal·lació: %s" - -msgid "Installed (inactive)" -msgstr "Instal·lat (inactiu)" - -msgid "Insufficient permissions." -msgstr "Permisos insuficients." - - -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "Manager (by ROBOTSTXT) — Plugins" - -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "Manager (by ROBOTSTXT) — Paràmetres" - -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "La botiga ROBOTSTXT no ha retornat cap plugin. Revisa la URL de la botiga i la clau API a la pàgina de paràmetres, o prem Actualitza per tornar-ho a provar." - -msgid "Not installed" -msgstr "No instal·lat" - -msgid "Plugin" -msgstr "Plugin" - -msgid "Plugin is not installed." -msgstr "El plugin no està instal·lat." - -msgid "Plugin not found in catalog." -msgstr "Plugin no trobat al catàleg." - - -msgid "Price" -msgstr "Preu" +#. Description of the plugin +#: robotstxt-manager.php +msgid "" +"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." +msgstr "" +"Tauler del costat del client per a l'ecosistema de plugins de ROBOTSTXT. " +"Mostra el catàleg d'una instal·lació remota de Plugins Core, resol l'estat " +"local d'instal·lació i actualització, i instal·la, activa i actualitza " +"plugins directament des de la botiga." +#. Author of the plugin +#: robotstxt-manager.php admin/class-robotstxt-manager-admin.php:48 msgid "ROBOTSTXT" msgstr "ROBOTSTXT" -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "ROBOTSTXT Manager encara no està configurat. Configura la URL de la botiga i la clau API per veure el teu catàleg de plugins." +#. Author URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/" +msgstr "https://www.robotstxt.software/" -msgid "Refresh catalog" -msgstr "Actualitza el catàleg" - -msgid "Requires PHP" -msgstr "Requereix PHP" - -msgid "Requires WP" -msgstr "Requereix WP" - -msgid "Settings" -msgstr "Paràmetres" - - -msgid "Store URL" -msgstr "URL de la botiga" - -msgid "Store URL is not configured." -msgstr "La URL de la botiga no està configurada." - -msgid "Store is not configured." -msgstr "La botiga no està configurada." - -msgid "Test connection" -msgstr "Prova la connexió" - -msgid "Testing…" -msgstr "S'està provant…" - -msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." -msgstr "El format de la clau API no és vàlid. Copia la clau completa des de la teva pàgina de compte de ROBOTSTXT." - -msgid "The store responded with HTTP %d. Check the Store URL and API key." -msgstr "La botiga ha respost amb HTTP %d. Revisa la URL de la botiga i la clau API." - -msgid "The store returned an invalid file." -msgstr "La botiga ha retornat un fitxer no vàlid." - -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "Aquest lloc funciona amb WordPress %1$s i PHP %2$s." - -msgid "Too many refreshes. Please wait a minute before refreshing again." -msgstr "Massa actualitzacions. Espera un minut abans de tornar a actualitzar." - - -msgid "Up to date" -msgstr "Actualitzat" - -msgid "Update" -msgstr "Actualitza" - -msgid "Update available (v%1$s → v%2$s)" -msgstr "Actualització disponible (v%1$s → v%2$s)" - -msgid "Visit website" -msgstr "Visita el lloc web" +#: admin/class-robotstxt-manager-admin.php:47 admin/views/page-catalog.php:66 +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "Manager (by ROBOTSTXT) — Plugins" +#: admin/class-robotstxt-manager-admin.php:64 +#: admin/class-robotstxt-manager-settings.php:207 msgid "You do not have sufficient permissions to access this page." msgstr "No tens permisos suficients per accedir a aquesta pàgina." -msgid "Your server runs PHP" -msgstr "El teu servidor funciona amb PHP" +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:108 +#, php-format +msgid "" +"The payment for %s failed. Update your payment method from your ROBOTSTXT " +"account page to keep access." +msgstr "" +"El pagament de %s ha fallat. Actualitza el teu mètode de pagament des de la " +"teva pàgina de compte de ROBOTSTXT per mantenir l'accés." -msgid "Your site runs WordPress" -msgstr "El teu lloc funciona amb WordPress" - -msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." -msgstr "Aquesta és la botiga de plugins de ROBOTSTXT: el catàleg de plugins publicats per ROBOTSTXT, instal·lables i actualitzables directament des del teu propi wp-admin. Els plugins gratuïts s'instal·len amb un clic; els plugins premium requereixen una subscripció anual que es compra al nostre lloc web." - -msgid "Version" -msgstr "Versió" - -msgid "Status" -msgstr "Estat" - -msgid "€%s / year" -msgstr "%s €/any" - -msgid "Support" -msgstr "Suport" - -msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." -msgstr "Necessites ajuda amb un plugin de ROBOTSTXT? Visita el lloc web del plugin (l'enllaç a la seva fila de dalt) per veure la documentació i les guies, o contacta amb nosaltres a través del nostre lloc web: estarem encantats d'ajudar-te." - -msgid "Payments, and how it works" -msgstr "Pagaments, i com funciona" - -msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." -msgstr "Els plugins gratuïts s'instal·len a l'instant i sense cost. Els plugins premium són subscripcions anuals: pagues una vegada al nostre lloc web i la subscripció es renova automàticament cada any fins que la cancellis. Pots cancel·lar-la en qualsevol moment des de la teva pàgina de compte de ROBOTSTXT; l'accés segueix funcionant fins al final del període pagat. Tots els pagaments els processa de manera segura Mollie; nosaltres mai no veiem ni desem les dades de la teva targeta." - -msgid "Installed required plugins: %s." -msgstr "Plugins requerits instal·lats: %s." - -msgid "Could not install the required plugin %s." -msgstr "No s'ha pogut instal·lar el plugin requerit %s." - -msgid "WordPress.org lookup failed: %s" -msgstr "La cerca a WordPress.org ha fallat: %s" - -msgid "No download found on WordPress.org for %s." -msgstr "No s'ha trobat cap baixada a WordPress.org per a %s." - -msgid "The downloaded plugin for %s does not have the expected folder structure." -msgstr "El plugin baixat per a %s no té lestructura de carpetes esperada." - -msgid "Requires: %s" -msgstr "Requereix: %s" - -msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." -msgstr "El pagament de %s ha fallat. Actualitza el teu mètode de pagament des de la teva pàgina de compte de ROBOTSTXT per mantenir l'accés." - -msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." -msgstr "La subscripció de %s ha caducat, però el plugin encara és actiu en aquest lloc. Renova-la des de la teva pàgina de compte de ROBOTSTXT per seguir rebent actualitzacions." - -msgid "Subscribed" -msgstr "Subscrit" - -msgid "Subscribed — %d days left" -msgstr "Subscrit — queden %d dies" - -msgid "Payment failed" -msgstr "Pagament fallit" - -msgid "Cancelled" -msgstr "Cancel·lada" - -msgid "Expired" -msgstr "Caducada" +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:119 +#, php-format +msgid "" +"The subscription for %s has expired, but the plugin is still active on this " +"site. Renew from your ROBOTSTXT account page to keep receiving updates." +msgstr "" +"La subscripció de %s ha caducat, però el plugin encara és actiu en aquest " +"lloc. Renova-la des de la teva pàgina de compte de ROBOTSTXT per seguir " +"rebent actualitzacions." +#. translators: 1: plugin slug, 2: days remaining. +#: admin/class-robotstxt-manager-admin.php:132 +#, php-format msgid "The subscription for %1$s expires in %2$d day." 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[1] "La subscripció de %1$s caduca en %2$d dies." -msgid "Create your free account at the ROBOTSTXT store 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 la botiga de ROBOTSTXT 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." +#: admin/class-robotstxt-manager-admin.php:153 +#: admin/class-robotstxt-manager-installer.php:421 +#: admin/class-robotstxt-manager-settings.php:379 +#: admin/class-robotstxt-manager-settings.php:405 +msgid "Insufficient permissions." +msgstr "Permisos insuficients." -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." +#: admin/class-robotstxt-manager-admin.php:197 +msgid "Too many refreshes. Please wait a minute before refreshing again." +msgstr "Massa actualitzacions. Espereu un minut abans de tornar a actualitzar." + +#: admin/class-robotstxt-manager-installer.php:51 +#: admin/class-robotstxt-manager-installer.php:518 +msgid "Plugin not found in catalog." +msgstr "Plugin no trobat al catàleg." + +#. translators: %s: plugin name. +#: admin/class-robotstxt-manager-installer.php:73 +#, php-format +msgid "%s installed. Activate it from the list below." +msgstr "%s instal·lat. Activa'l des de la llista de sota." + +#. translators: %s: dependency slug. +#: admin/class-robotstxt-manager-installer.php:114 +#, php-format +msgid "Could not install the required plugin %s." +msgstr "No s'ha pogut instal·lar el plugin requerit %s." + +#. translators: %s: list of installed dependency names. +#: admin/class-robotstxt-manager-installer.php:130 +#, php-format +msgid "Installed required plugins: %s." +msgstr "Plugins requerits instal·lats: %s." + +#. translators: %s: error message from WordPress.org. +#: admin/class-robotstxt-manager-installer.php:272 +#, php-format +msgid "WordPress.org lookup failed: %s" +msgstr "La cerca a WordPress.org ha fallat: %s" + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:281 +#, php-format +msgid "No download found on WordPress.org for %s." +msgstr "No s'ha trobat cap baixada a WordPress.org per a %s." + +#: admin/class-robotstxt-manager-installer.php:287 +#: admin/class-robotstxt-manager-installer.php:540 +msgid "Could not create a temporary file for download." +msgstr "No s'ha pogut crear un fitxer temporal per a la baixada." + +#. translators: %s: HTTP transport error message. +#: admin/class-robotstxt-manager-installer.php:303 +#: admin/class-robotstxt-manager-installer.php:573 +#, php-format +msgid "Download failed: %s" +msgstr "Error en la baixada: %s" + +#. translators: %d: HTTP status code. +#: admin/class-robotstxt-manager-installer.php:310 +#: admin/class-robotstxt-manager-installer.php:588 +#, php-format +msgid "Download failed (HTTP %d)." +msgstr "Error en la baixada (HTTP %d)." + +#: admin/class-robotstxt-manager-installer.php:316 +#: admin/class-robotstxt-manager-installer.php:597 +msgid "The store returned an invalid file." +msgstr "La botiga ha retornat un fitxer no vàlid." + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:622 +#, php-format +msgid "Installation failed: %s" +msgstr "Error en la instal·lació: %s" + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:625 +msgid "Installation failed." +msgstr "Error en la instal·lació." + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:341 +#, php-format +msgid "" +"The downloaded plugin for %s does not have the expected folder structure." +msgstr "El plugin baixat per a %s no té l'estructura de carpetes esperada." + +#: admin/class-robotstxt-manager-installer.php:363 +#: admin/class-robotstxt-manager-installer.php:394 +msgid "Plugin is not installed." +msgstr "El plugin no està instal·lat." + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:375 +#, php-format +msgid "%s activated." +msgstr "%s activat." + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:406 +#, php-format +msgid "%s updated." +msgstr "%s actualitzat." + +#: admin/class-robotstxt-manager-installer.php:512 +msgid "Store is not configured." +msgstr "La botiga no està configurada." + +#: admin/class-robotstxt-manager-settings.php:59 +#: admin/views/page-settings.php:13 +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "Manager (by ROBOTSTXT) — Paràmetres" + +#: admin/class-robotstxt-manager-settings.php:60 +msgid "Settings" +msgstr "Paràmetres" + +#: admin/class-robotstxt-manager-settings.php:75 +msgid "Connection" +msgstr "Connexió" + +#: admin/class-robotstxt-manager-settings.php:92 +msgid "Store URL" +msgstr "URL de la botiga" + +#: admin/class-robotstxt-manager-settings.php:110 +msgid "API Key" +msgstr "Clau API" + +#: admin/class-robotstxt-manager-settings.php:118 +msgid "Cache" +msgstr "Memòria cau" + +#: admin/class-robotstxt-manager-settings.php:135 +msgid "Catalog Cache (minutes)" +msgstr "Memòria cau del catàleg (minuts)" + +#: admin/class-robotstxt-manager-settings.php:153 +msgid "Data on Uninstall" +msgstr "Dades en desinstal·lar" + +#: admin/class-robotstxt-manager-settings.php:188 +msgid "Testing…" +msgstr "S'està provant…" + +#: admin/class-robotstxt-manager-settings.php:189 +#: admin/class-robotstxt-manager-settings.php:274 +msgid "Test connection" +msgstr "Prova la connexió" + +#: admin/class-robotstxt-manager-settings.php:190 +msgid "Deleting…" +msgstr "S'està esborrant…" + +#: admin/class-robotstxt-manager-settings.php:191 +#: admin/class-robotstxt-manager-settings.php:278 +msgid "Delete API key" +msgstr "Esborra la clau API" + +#: admin/class-robotstxt-manager-settings.php:192 +msgid "" +"Delete the stored API key? The catalog and subscription data will stop " +"working until a new key is entered." +msgstr "" +"Segur que voleu esborrar la clau API desada? El catàleg i les dades de " +"subscripció deixaran de funcionar fins que s'introdueixi una clau nova." + +#: admin/class-robotstxt-manager-settings.php:193 +#: admin/class-robotstxt-manager-settings.php:414 +msgid "API key deleted. Save changes to persist." +msgstr "Clau API esborrada. Desa els canvis perquè faci efecte." + +#: admin/class-robotstxt-manager-settings.php:194 +msgid "An unexpected error occurred." +msgstr "S'ha produït un error inesperat." + +#: admin/class-robotstxt-manager-settings.php:226 +msgid "" +"Base URL of the remote Plugins Core installation that this site will pull " +"the plugin catalog from." +msgstr "" +"URL base de la instal·lació remota de Plugins Core d'on aquest lloc obtindrà " +"el catàleg de plugins." + +#. translators: %s: last 4 characters of the stored API key. +#: admin/class-robotstxt-manager-settings.php:257 +#, php-format +msgid "" +"A key is stored (last 4 characters: %s). Leave blank to keep " +"the existing key; enter a new value to replace it." +msgstr "" +"Hi ha una clau desada (últims 4 caràcters: %s). Deixa-ho en " +"blanc per mantenir la clau actual; escriu un valor nou per substituir-la." + +#: admin/class-robotstxt-manager-settings.php:262 +msgid "" +"A key is stored but could not be decoded. You can replace it by entering a " +"new value above, or delete it with the button below." +msgstr "" +"Hi ha una clau desada però no s'ha pogut descodificar. Pots substituir-la " +"escrivint un valor nou a dalt, o esborrar-la amb el botó de sota." + +#: admin/class-robotstxt-manager-settings.php:266 +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." + +#: admin/class-robotstxt-manager-settings.php:298 +msgid "" +"How long the catalog response from Core is cached in a transient. Default: " +"60 minutes. Lower values refresh more often at the cost of more requests to " +"Core. Maximum: 1440 (24 hours)." +msgstr "" +"Quant de temps es desa a la memòria cau la resposta del catàleg de Core en " +"un transient. Per defecte: 60 minuts. Els valors més baixos actualitzen més " +"sovint a costa de més sol·licituds a Core. Màxim: 1440 (24 hores)." + +#: admin/class-robotstxt-manager-settings.php:313 +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "Esborra totes les dades del plugin quan es desinstal·li." + +#: admin/class-robotstxt-manager-settings.php:343 +msgid "" +"The API key format is invalid. Copy the full key from your ROBOTSTXT account " +"page." +msgstr "" +"El format de la clau API no és vàlid. Copia la clau completa des de la teva " +"pàgina de compte de ROBOTSTXT." + +#: admin/views/page-catalog.php:69 +msgid "" +"This is the ROBOTSTXT plugin store: the catalog of plugins published by " +"ROBOTSTXT, installable and updatable straight from your own wp-admin. Free " +"plugins install with one click; premium plugins require an annual " +"subscription that you purchase on our website." +msgstr "" +"Aquesta és la botiga de plugins de ROBOTSTXT: el catàleg de plugins " +"publicats per ROBOTSTXT, instal·lables i actualitzables directament des del " +"teu propi wp-admin. Els plugins gratuïts s'instal·len amb un clic; els " +"plugins premium requereixen una subscripció anual que es compra al nostre " +"lloc web." + +#: admin/views/page-catalog.php:73 +msgid "Catalog refreshed." +msgstr "Catàleg actualitzat." + +#. translators: %s: store registration URL. +#: admin/views/page-catalog.php:89 +#, php-format +msgid "" +"Create your free account at the ROBOTSTXT store to get your personal API " +"key. The key links your subscriptions to this site and unlocks premium " +"plugins and updates." +msgstr "" +"Creeu el vostre compte gratuït a la botiga de ROBOTSTXT 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." + +#. translators: %s: settings URL. +#: admin/views/page-catalog.php:109 +#, php-format +msgid "" +"ROBOTSTXT Manager is not configured yet. Set the Store URL " +"and API key to see your plugin catalog." +msgstr "" +"ROBOTSTXT Manager encara no està configurat. Configura la URL " +"de la botiga i la clau API per veure el teu catàleg de plugins." + +#: admin/views/page-catalog.php:119 +msgid "" +"No plugins were returned by the ROBOTSTXT store. Check the Store URL and API " +"key on the Settings page, or click Refresh to try again." +msgstr "" +"La botiga ROBOTSTXT no ha retornat cap plugin. Revisa la URL de la botiga i " +"la clau API a la pàgina de paràmetres, o prem Actualitza per tornar-ho a " +"provar." + +#: admin/views/page-catalog.php:122 admin/views/page-catalog.php:125 +msgid "Refresh catalog" +msgstr "Actualitza el catàleg" + +#: admin/views/page-catalog.php:131 +msgid "Plugin" +msgstr "Plugin" + +#: admin/views/page-catalog.php:132 +msgid "Version" +msgstr "Versió" + +#: admin/views/page-catalog.php:133 +msgid "Requires WP" +msgstr "Requereix WP" + +#: admin/views/page-catalog.php:134 +msgid "Requires PHP" +msgstr "Requereix PHP" + +#: admin/views/page-catalog.php:135 +msgid "Price" +msgstr "Preu" + +#: admin/views/page-catalog.php:136 +msgid "Action" +msgstr "Acció" + +#: admin/views/page-catalog.php:137 +msgid "Status" +msgstr "Estat" + +#: admin/views/page-catalog.php:239 +msgid "Your site runs WordPress" +msgstr "El vostre lloc funciona amb WordPress" + +#: admin/views/page-catalog.php:251 +msgid "Your server runs PHP" +msgstr "El vostre servidor funciona amb PHP" + +#. translators: %s: formatted price number. +#: admin/views/page-catalog.php:264 +#, php-format +msgid "€%s / year" +msgstr "%s €/any" + +#. translators: %d: days remaining. +#: admin/views/page-catalog.php:282 +#, php-format +msgid "Subscribed — %d days left" +msgstr "Subscrit — queden %d dies" + +#: admin/views/page-catalog.php:286 +msgid "Subscribed" +msgstr "Subscrit" + +#: admin/views/page-catalog.php:289 +msgid "Payment failed" +msgstr "Pagament fallit" + +#: admin/views/page-catalog.php:291 +msgid "Cancelled" +msgstr "Cancel·lada" + +#: admin/views/page-catalog.php:293 +msgid "Expired" +msgstr "Caducada" + +#: admin/views/page-catalog.php:303 +msgid "Free" +msgstr "Gratuït" + +#: admin/views/page-catalog.php:309 +msgid "Incompatible" +msgstr "Incompatible" + +#: admin/views/page-catalog.php:312 +msgid "Buy" +msgstr "Comprar" + +#: admin/views/page-catalog.php:317 +msgid "Install" +msgstr "Instal·la" + +#: admin/views/page-catalog.php:319 +msgid "Activate" +msgstr "Activa" + +#: admin/views/page-catalog.php:321 +msgid "Update" +msgstr "Actualitza" + +#: admin/views/page-catalog.php:327 +msgid "Not installed" +msgstr "No instal·lat" + +#: admin/views/page-catalog.php:329 +msgid "Installed (inactive)" +msgstr "Instal·lat (inactiu)" + +#. translators: 1: installed version, 2: available version. +#: admin/views/page-catalog.php:334 +#, php-format +msgid "Update available (v%1$s → v%2$s)" +msgstr "Actualització disponible (v%1$s → v%2$s)" + +#: admin/views/page-catalog.php:340 +msgid "Up to date" +msgstr "Actualitzat" + +#: admin/views/page-catalog.php:349 +msgid "Visit website" +msgstr "Visita el lloc web" + +#. translators: %s: plugin name. +#: admin/views/page-catalog.php:349 +#, php-format +msgid "(about %s)" +msgstr "(sobre %s)" + +#. translators: %s: list of plugin slugs. +#: admin/views/page-catalog.php:360 +#, php-format +msgid "Requires: %s" +msgstr "Requereix: %s" + +#: admin/views/page-catalog.php:374 +msgid "Support" +msgstr "Suport" + +#: admin/views/page-catalog.php:376 +msgid "" +"Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in " +"its row above) for documentation and guides, or contact us through our " +"website — we are happy to help." +msgstr "" +"Necessites ajuda amb un plugin de ROBOTSTXT? Visita el lloc web del plugin " +"(l'enllaç a la seva fila de dalt) per veure la documentació i les guies, o " +"contacta amb nosaltres a través del nostre lloc web: estarem encantats " +"d'ajudar-te." + +#: admin/views/page-catalog.php:379 +msgid "Payments, and how it works" +msgstr "Pagaments, i com funciona" + +#: admin/views/page-catalog.php:381 +msgid "" +"Free plugins install instantly at no cost. Premium plugins are annual " +"subscriptions: you pay once on our website and the subscription renews " +"automatically every year until you cancel. You can cancel at any time from " +"your ROBOTSTXT account page — access keeps working until the end of the paid " +"period. All payments are processed securely by Mollie; we never see or store " +"your card details." +msgstr "" +"Els plugins gratuïts s'instal·len a l'instant i sense cost. Els plugins " +"premium són subscripcions anuals: pagues una vegada al nostre lloc web i la " +"subscripció es renova automàticament cada any fins que la cancellis. Pots " +"cancel·lar-la en qualsevol moment des de la teva pàgina de compte de " +"ROBOTSTXT; l'accés segueix funcionant fins al final del període pagat. Tots " +"els pagaments els processa de manera segura Mollie; nosaltres mai no veiem " +"ni desem les dades de la teva targeta." + +#. translators: %d: number of incompatible plugins. +#: admin/views/page-catalog.php:391 +#, php-format +msgid "" +"%d plugin in the catalog is not compatible with this site's WordPress or PHP " +"version." +msgid_plural "" +"%d plugins in the catalog are not compatible with this site's WordPress or " +"PHP version." +msgstr[0] "" +"%d plugin del catàleg no és compatible amb la versió de WordPress o de PHP " +"d'aquest lloc." +msgstr[1] "" +"%d plugins del catàleg no són compatibles amb la versió de WordPress o de " +"PHP d'aquest lloc." + +#. translators: 1: local WP version, 2: local PHP version. +#: admin/views/page-catalog.php:400 +#, php-format +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "Aquest lloc funciona amb WordPress %1$s i PHP %2$s." + +#: includes/class-robotstxt-manager-core-client.php:141 +msgid "Store URL is not configured." +msgstr "La URL de la botiga no està configurada." + +#: includes/class-robotstxt-manager-core-client.php:148 +msgid "API key is not configured." +msgstr "La clau API no està configurada." + +#. translators: %s: HTTP transport error message. +#: includes/class-robotstxt-manager-core-client.php:158 +#, php-format +msgid "Could not reach Plugins Core: %s" +msgstr "No s'ha pogut connectar amb Plugins Core: %s" + +#. translators: %d: HTTP status code. +#: includes/class-robotstxt-manager-core-client.php:168 +#, php-format +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "La botiga ha respost amb HTTP %d. Comproveu l'URL de la botiga i la clau API." + +#: includes/class-robotstxt-manager-core-client.php:177 +msgid "Connected." +msgstr "Connectat." diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index 40903e87319abf49532a328027cafb5f47a968d1..f82d8d7fe1b5a598d265d774bb3fdb50249293bd 100644 GIT binary patch delta 2966 zcmbW2Yitx%6o7AEP-rW*BEkxCkw>kxyKRNi7EsEopoNxFwLCVsj0TnhHP=hALpo!7&M*|W_FwzLdS7L$*M!z$&1%7BqJn5P5-n)0s zJ+C{xYHVwbUn;_6kbU`xFLC&0b11)hTVqQ>X)0@M(jY8re1mcjzK z1YUq^AU0J6UJ7eq0MG=FBssw zntQ+J`7$VxuTITdp_Ismu3;z!`k)x@hjP#nD5)QS55ljYWZ)VU`@g4tPbbPo=6Pet z|566)S!jhv;Cz@*o+R=nxDIZF3HU9{Ak`Ztx%E1?*I5-F;gFi!YAfL2I zrpCdEun^9MwLbDs@T!jmiC}kX!*N)}{2Y{8<`B+L96T1DW}Zc-%K8~tD*K>BnEsIG zNCDi#d;;u;C!u6)F{jUg4Nx5Hk25I3@Ow}^y`Vi_f&8g!JUU>`ByYV3N`zaW7~BrA zt#(4`op)ePno^%Z$;dfQk$o3o1-uHU!wIDK5g1>};0A*jluT@x;&u5;a2E4jP$Kyf zN>8*+^-^d<$yfqPZybf<*!%Ds9QYW1FTWRhnd;><*|!Z+71a;vowz#8Kn^|wB_-z} zWmY$#L^Qg{Tc4idayZEP3$Pllq7e?jT~JC=L3qLiQ0z27ajYH6_r-$vruyCWI9H3o z2o^qsufa1=UR+V^84AN3<~^zTRw$*~3B~YR@Lsqd;*)w8QcZOpX2U-PX@4bnT zo2v?84(F@c45Y@ja49?imhHtcBw6 zUMM|q7!s2@0mY$9P%=IU<78RgWFXgJDptf(14;y2Qrr*aHk^iH_y*)$wH_6bvWZAk zB9hG}bRH_grEz=KXHygD8@Ggz_E1#Rm8VjyfznG<&Xu`5x$&w-?yyJ(y3ieYk5Q@dGAP|Hjl(BE zah+YM8?n3TR5uCpk;5e9kbJ~>%z8UySsvhV??Yr-5RqxwUN-d<2MxRm}y&)9@EwlD`-S? zIBK&g66o#<8IGx$IA*anqM@N*Y-T|x+{3yI`N7+2r;fIDC>%3`PGpmJj<{z#R?JKu z&kbfJbiw$5^wIR)PhU)An7U3PUK(A(SVk6A62V>=riziqWTy++IosJr~`#cBs` zy;{;~M2$8x7D$ZoRb`d=XZXt!O+MZH@8iJ*bZCKl7&QCt;HvYFL0%BGhhi37gUwC3a?-;bGzMf!StksXYMi8XBLkQvbtBjgQ&Zt+arWyB24 z4^kbfVs;wryPdZ?>gdF%hJDuodY4i7qp&V3dx6#273(%zl9!9mre|y0Z0ip5%E7?QwAQ=S`v3QW QnriDSTaN~ef#a6?2aNIY!~g&Q delta 2430 zcmYk-TWl0n9LMp~%a%)9C>7+=YNxay1-o0cmMWmOvdX0hmZBg?T-u>*-FE8EY@vp* z2`VuH;bDU)iHXDqi4lZsxX42#!9X;=KmtT#AW{M;F^1R&xftU2x3h>R{qJYa?97?} z`JXe}4>nwnBrlid?=iG>L^)BEXG{|R3GhL?J;IoScn6E|tC7Y8@hBGJH5`q%a0}i; z-M@L1G2CKqV-4z}DKHZQImU!>uP$A}C&#;$t|B>v3F>G2~=CR8E_bG0j$- zh#z4oet}9Popk_1^nXMpa0e}{Vis3$0gh9u)nkm|&ouDSfMM*yEl3XL1}gJmd<4gg z^=CE}^XbpT?aU~I$LJp`p^Df&&cE(G98Z56D&f7^{sO4xyGbm#3?86{yNhLl;AsBx&;{2g*2&n(?V@ z|7X;a{DPT*Q3DL21|CK|sAPg)`Wh^wUyoY*<*4yjWxwBut@K@7gl8sD{|y|JJZelc zZbHrcBx*xm#uUz{Zh4gW9I6r*cxn@>Y+CbXRDw%U6>LK#unASEBv#-qoQnsM+|AHr z>aQ8x=Y%RSkvi_-LDe`w|0L7V`F^HbhucvzI*xkqS=@)`aTj*8b5)^xI1dAl`w2B; z4ddE)l=HjEMhS+KmVdAm`7!xJ#Yq9vh%14{e{{Kh1GsSy3l@ zewtsYi_`t<29ctgKak>>f+ze3SD}`u5n0WoS(82CX8ib}pgNbmm<4m9I|%n3?^Bj{g34SWT8 zrOkC@Eb}+2GL_U<6=_1va53t>PE=_fEX4P*3wPrpyo-AM7CdQ83(q$vIABZ@s5RzI ztiuDi1FI={P9B6;d?Qh}!iLRHC1wCU6py z8t7LJcupqkddwp<6JFEIgZVnM>$Tx{hIx@tN!JqEY)VRvd^7C@KA$JFe5_)op4LNpVawb~oRE5rhIvQ104 zl2}Frwf<{3Q2DgWRW>zNB-7^e`6{u3Xd<2=*zlQ_&!>KVpC!VCO04B5CU{xQ>x7m= zn^H|{|DdhnU>#Aa^)Dl46Izr1x8-!^5fXO|%huy|m7A z2raL6`-7%8<1J!J_C$_J&mOrpZ*jYm=<#Y}cAp)ynwBrJ-m&|wsOu)|4r_hC3;+ z&HRUM)3N&L`%q$$!#CV`\n" +"POT-Creation-Date: 2026-08-17T14:52:09+00:00\n" +"X-Translator: ROBOTSTXT \n" "Language-Team: Español (España) \n" "Language: es_ES\n" "MIME-Version: 1.0\n" @@ -13,289 +16,569 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Domain: robotstxt-manager\n" -msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." -msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." -msgstr[0] "%d plugin del catálogo no es compatible con la versión de WordPress o de PHP de este sitio." -msgstr[1] "%d plugins del catálogo no son compatibles con la versión de WordPress o de PHP de este sitio." +#. Plugin Name of the plugin +#: robotstxt-manager.php +msgid "Manager (by ROBOTSTXT)" +msgstr "Manager (de ROBOTSTXT)" -msgid "%s activated." -msgstr "%s activado." +#. Plugin URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/plugins/robotstxt-manager/" +msgstr "https://www.robotstxt.software/plugins/robotstxt-manager/" -msgid "%s installed. Activate it from the list below." -msgstr "%s instalado. Actívalo desde la lista de abajo." - -msgid "%s updated." -msgstr "%s actualizado." - -msgid "(about %s)" -msgstr "(sobre %s)" - -msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." -msgstr "Hay una clave guardada (últimos 4 caracteres: %s). Déjalo en blanco para mantener la clave actual; escribe un valor nuevo para sustituirla." - -msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." -msgstr "Hay una clave guardada pero no se pudo descodificar. Puedes sustituirla escribiendo un valor nuevo arriba, o borrarla con el botón de abajo." - -msgid "API Key" -msgstr "Clave API" - -msgid "API key deleted. Save changes to persist." -msgstr "Clave API borrada. Guarda los cambios para que surta efecto." - -msgid "API key is not configured." -msgstr "La clave API no está configurada." - -msgid "Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage." -msgstr "Clave API a nivel de cuenta emitida por la tienda ROBOTSTXT. Es necesaria para autenticar las solicitudes del catálogo y de las suscripciones. El valor se cifra antes de guardarse." - -msgid "Action" -msgstr "Acción" - -msgid "Activate" -msgstr "Activar" - -msgid "An unexpected error occurred." -msgstr "Ha ocurrido un error inesperado." - -msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." -msgstr "URL base de la instalación remota de Plugins Core de la que este sitio obtendrá el catálogo de plugins." - -msgid "Buy" -msgstr "Comprar" - -msgid "Cache" -msgstr "Caché" - -msgid "Catalog Cache (minutes)" -msgstr "Caché del catálogo (minutos)" - -msgid "Catalog refreshed." -msgstr "Catálogo actualizado." - -msgid "Connected." -msgstr "Conectado." - -msgid "Connection" -msgstr "Conexión" - -msgid "Could not create a temporary file for download." -msgstr "No se pudo crear un archivo temporal para la descarga." - -msgid "Could not reach Plugins Core: %s" -msgstr "No se pudo conectar con Plugins Core: %s" - -msgid "Data on Uninstall" -msgstr "Datos al desinstalar" - -msgid "Delete API key" -msgstr "Borrar la clave API" - -msgid "Delete all plugin data when the plugin is uninstalled." -msgstr "Borrar todos los datos del plugin cuando se desinstale." - -msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." -msgstr "¿Seguro que quieres borrar la clave API guardada? El catálogo y los datos de suscripción dejarán de funcionar hasta que se introduzca una clave nueva." - -msgid "Deleting…" -msgstr "Borrando…" - -msgid "Download failed (HTTP %d)." -msgstr "Fallo en la descarga (HTTP %d)." - -msgid "Download failed: %s" -msgstr "Fallo en la descarga: %s" - -msgid "Free" -msgstr "Gratis" - -msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." -msgstr "Cuánto tiempo se guarda en caché la respuesta del catálogo de Core en un transient. Por defecto: 60 minutos. Los valores más bajos actualizan con más frecuencia a costa de más solicitudes a Core. Máximo: 1440 (24 horas)." - -msgid "Incompatible" -msgstr "Incompatible" - -msgid "Install" -msgstr "Instalar" - -msgid "Installation failed." -msgstr "Fallo en la instalación." - -msgid "Installation failed: %s" -msgstr "Fallo en la instalación: %s" - -msgid "Installed (inactive)" -msgstr "Instalado (inactivo)" - -msgid "Insufficient permissions." -msgstr "Permisos insuficientes." - - -msgid "Manager (by ROBOTSTXT) — Plugins" -msgstr "Manager (by ROBOTSTXT) — Plugins" - -msgid "Manager (by ROBOTSTXT) — Settings" -msgstr "Manager (by ROBOTSTXT) — Ajustes" - -msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." -msgstr "La tienda ROBOTSTXT no devolvió ningún plugin. Revisa la URL de la tienda y la clave API en la página de ajustes, o pulsa Actualizar para volver a intentarlo." - -msgid "Not installed" -msgstr "No instalado" - -msgid "Plugin" -msgstr "Plugin" - -msgid "Plugin is not installed." -msgstr "El plugin no está instalado." - -msgid "Plugin not found in catalog." -msgstr "Plugin no encontrado en el catálogo." - - -msgid "Price" -msgstr "Precio" +#. Description of the plugin +#: robotstxt-manager.php +msgid "" +"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." +msgstr "" +"Escritorio del lado del cliente para el ecosistema de plugins de ROBOTSTXT. " +"Muestra el catálogo de una instalación remota de Plugins Core, resuelve el " +"estado local de instalación y actualización, e instala, activa y actualiza " +"plugins directamente desde la tienda." +#. Author of the plugin +#: robotstxt-manager.php admin/class-robotstxt-manager-admin.php:48 msgid "ROBOTSTXT" msgstr "ROBOTSTXT" -msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." -msgstr "ROBOTSTXT Manager aún no está configurado. Configura la URL de la tienda y la clave API para ver tu catálogo de plugins." +#. Author URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/" +msgstr "https://www.robotstxt.software/" -msgid "Refresh catalog" -msgstr "Actualizar el catálogo" - -msgid "Requires PHP" -msgstr "Requiere PHP" - -msgid "Requires WP" -msgstr "Requiere WP" - -msgid "Settings" -msgstr "Ajustes" - - -msgid "Store URL" -msgstr "URL de la tienda" - -msgid "Store URL is not configured." -msgstr "La URL de la tienda no está configurada." - -msgid "Store is not configured." -msgstr "La tienda no está configurada." - -msgid "Test connection" -msgstr "Probar la conexión" - -msgid "Testing…" -msgstr "Probando…" - -msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." -msgstr "El formato de la clave API no es válido. Copia la clave completa desde tu página de cuenta de ROBOTSTXT." - -msgid "The store responded with HTTP %d. Check the Store URL and API key." -msgstr "La tienda respondió con HTTP %d. Revisa la URL de la tienda y la clave API." - -msgid "The store returned an invalid file." -msgstr "La tienda devolvió un archivo no válido." - -msgid "This site runs WordPress %1$s on PHP %2$s." -msgstr "Este sitio funciona con WordPress %1$s y PHP %2$s." - -msgid "Too many refreshes. Please wait a minute before refreshing again." -msgstr "Demasiadas actualizaciones. Espera un minuto antes de volver a actualizar." - - -msgid "Up to date" -msgstr "Actualizado" - -msgid "Update" -msgstr "Actualizar" - -msgid "Update available (v%1$s → v%2$s)" -msgstr "Actualización disponible (v%1$s → v%2$s)" - -msgid "Visit website" -msgstr "Visitar el sitio web" +#: admin/class-robotstxt-manager-admin.php:47 admin/views/page-catalog.php:66 +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "Manager (by ROBOTSTXT) — Plugins" +#: admin/class-robotstxt-manager-admin.php:64 +#: admin/class-robotstxt-manager-settings.php:207 msgid "You do not have sufficient permissions to access this page." msgstr "No tienes permisos suficientes para acceder a esta página." -msgid "Your server runs PHP" -msgstr "Tu servidor funciona con PHP" +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:108 +#, php-format +msgid "" +"The payment for %s failed. Update your payment method from your ROBOTSTXT " +"account page to keep access." +msgstr "" +"El pago de %s falló. Actualiza tu método de pago desde tu página de cuenta " +"de ROBOTSTXT para mantener el acceso." -msgid "Your site runs WordPress" -msgstr "Tu sitio funciona con WordPress" - -msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." -msgstr "Esta es la tienda de plugins de ROBOTSTXT: el catálogo de plugins publicados por ROBOTSTXT, instalables y actualizables directamente desde tu propio wp-admin. Los plugins gratis se instalan con un clic; los plugins premium requieren una suscripción anual que se compra en nuestro sitio web." - -msgid "Version" -msgstr "Versión" - -msgid "Status" -msgstr "Estado" - -msgid "€%s / year" -msgstr "%s €/año" - -msgid "Support" -msgstr "Soporte" - -msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." -msgstr "¿Necesitas ayuda con un plugin de ROBOTSTXT? Visita el sitio web del plugin (el enlace en su fila de arriba) para ver la documentación y las guías, o contáctanos a través de nuestro sitio web: estaremos encantados de ayudarte." - -msgid "Payments, and how it works" -msgstr "Pagos, y cómo funciona" - -msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." -msgstr "Los plugins gratis se instalan al instante y sin coste. Los plugins premium son suscripciones anuales: pagas una vez en nuestro sitio web y la suscripción se renueva automáticamente cada año hasta que la canceles. Puedes cancelarla en cualquier momento desde tu página de cuenta de ROBOTSTXT; el acceso sigue funcionando hasta el final del periodo pagado. Todos los pagos los procesa de forma segura Mollie; nosotros nunca vemos ni guardamos los datos de tu tarjeta." - -msgid "Installed required plugins: %s." -msgstr "Plugins requeridos instalados: %s." - -msgid "Could not install the required plugin %s." -msgstr "No se pudo instalar el plugin requerido %s." - -msgid "WordPress.org lookup failed: %s" -msgstr "La búsqueda en WordPress.org falló: %s" - -msgid "No download found on WordPress.org for %s." -msgstr "No se encontró ninguna descarga en WordPress.org para %s." - -msgid "The downloaded plugin for %s does not have the expected folder structure." -msgstr "El plugin descargado para %s no tiene la estructura de carpetas esperada." - -msgid "Requires: %s" -msgstr "Requiere: %s" - -msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." -msgstr "El pago de %s falló. Actualiza tu método de pago desde tu página de cuenta de ROBOTSTXT para mantener el acceso." - -msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." -msgstr "La suscripción de %s ha caducado, pero el plugin sigue activo en este sitio. Renuévela desde tu página de cuenta de ROBOTSTXT para seguir recibiendo actualizaciones." - -msgid "Subscribed" -msgstr "Suscrito" - -msgid "Subscribed — %d days left" -msgstr "Suscrito — quedan %d días" - -msgid "Payment failed" -msgstr "Pago fallido" - -msgid "Cancelled" -msgstr "Cancelada" - -msgid "Expired" -msgstr "Caducada" +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:119 +#, php-format +msgid "" +"The subscription for %s has expired, but the plugin is still active on this " +"site. Renew from your ROBOTSTXT account page to keep receiving updates." +msgstr "" +"La suscripción de %s ha caducado, pero el plugin sigue activo en este sitio. " +"Renuévela desde tu página de cuenta de ROBOTSTXT para seguir recibiendo " +"actualizaciones." +#. translators: 1: plugin slug, 2: days remaining. +#: admin/class-robotstxt-manager-admin.php:132 +#, php-format msgid "The subscription for %1$s expires in %2$d day." 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[1] "La suscripción de %1$s caduca en %2$d días." -msgid "Create your free account at the ROBOTSTXT store 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 la tienda de ROBOTSTXT para obtener tu clave API personal. La clave vincula tus suscripciones a este sitio y desbloquea los plugins premium y sus actualizaciones." +#: admin/class-robotstxt-manager-admin.php:153 +#: admin/class-robotstxt-manager-installer.php:421 +#: admin/class-robotstxt-manager-settings.php:379 +#: admin/class-robotstxt-manager-settings.php:405 +msgid "Insufficient permissions." +msgstr "Permisos insuficientes." -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." +#: admin/class-robotstxt-manager-admin.php:197 +msgid "Too many refreshes. Please wait a minute before refreshing again." +msgstr "Demasiadas actualizaciones seguidas. Espera un minuto antes de volver a actualizar." + +#: admin/class-robotstxt-manager-installer.php:51 +#: admin/class-robotstxt-manager-installer.php:518 +msgid "Plugin not found in catalog." +msgstr "Plugin no encontrado en el catálogo." + +#. translators: %s: plugin name. +#: admin/class-robotstxt-manager-installer.php:73 +#, php-format +msgid "%s installed. Activate it from the list below." +msgstr "%s instalado. Actívalo desde la lista de abajo." + +#. translators: %s: dependency slug. +#: admin/class-robotstxt-manager-installer.php:114 +#, php-format +msgid "Could not install the required plugin %s." +msgstr "No se pudo instalar el plugin requerido %s." + +#. translators: %s: list of installed dependency names. +#: admin/class-robotstxt-manager-installer.php:130 +#, php-format +msgid "Installed required plugins: %s." +msgstr "Plugins requeridos instalados: %s." + +#. translators: %s: error message from WordPress.org. +#: admin/class-robotstxt-manager-installer.php:272 +#, php-format +msgid "WordPress.org lookup failed: %s" +msgstr "La búsqueda en WordPress.org falló: %s" + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:281 +#, php-format +msgid "No download found on WordPress.org for %s." +msgstr "No se encontró ninguna descarga en WordPress.org para %s." + +#: admin/class-robotstxt-manager-installer.php:287 +#: admin/class-robotstxt-manager-installer.php:540 +msgid "Could not create a temporary file for download." +msgstr "No se pudo crear un archivo temporal para la descarga." + +#. translators: %s: HTTP transport error message. +#: admin/class-robotstxt-manager-installer.php:303 +#: admin/class-robotstxt-manager-installer.php:573 +#, php-format +msgid "Download failed: %s" +msgstr "Fallo en la descarga: %s" + +#. translators: %d: HTTP status code. +#: admin/class-robotstxt-manager-installer.php:310 +#: admin/class-robotstxt-manager-installer.php:588 +#, php-format +msgid "Download failed (HTTP %d)." +msgstr "Fallo en la descarga (HTTP %d)." + +#: admin/class-robotstxt-manager-installer.php:316 +#: admin/class-robotstxt-manager-installer.php:597 +msgid "The store returned an invalid file." +msgstr "La tienda devolvió un archivo no válido." + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:622 +#, php-format +msgid "Installation failed: %s" +msgstr "Fallo en la instalación: %s" + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:625 +msgid "Installation failed." +msgstr "Fallo en la instalación." + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:341 +#, php-format +msgid "" +"The downloaded plugin for %s does not have the expected folder structure." +msgstr "" +"El plugin descargado para %s no tiene la estructura de carpetas esperada." + +#: admin/class-robotstxt-manager-installer.php:363 +#: admin/class-robotstxt-manager-installer.php:394 +msgid "Plugin is not installed." +msgstr "El plugin no está instalado." + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:375 +#, php-format +msgid "%s activated." +msgstr "%s activado." + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:406 +#, php-format +msgid "%s updated." +msgstr "%s actualizado." + +#: admin/class-robotstxt-manager-installer.php:512 +msgid "Store is not configured." +msgstr "La tienda no está configurada." + +#: admin/class-robotstxt-manager-settings.php:59 +#: admin/views/page-settings.php:13 +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "Manager (by ROBOTSTXT) — Ajustes" + +#: admin/class-robotstxt-manager-settings.php:60 +msgid "Settings" +msgstr "Ajustes" + +#: admin/class-robotstxt-manager-settings.php:75 +msgid "Connection" +msgstr "Conexión" + +#: admin/class-robotstxt-manager-settings.php:92 +msgid "Store URL" +msgstr "URL de la tienda" + +#: admin/class-robotstxt-manager-settings.php:110 +msgid "API Key" +msgstr "Clave API" + +#: admin/class-robotstxt-manager-settings.php:118 +msgid "Cache" +msgstr "Caché" + +#: admin/class-robotstxt-manager-settings.php:135 +msgid "Catalog Cache (minutes)" +msgstr "Caché del catálogo (minutos)" + +#: admin/class-robotstxt-manager-settings.php:153 +msgid "Data on Uninstall" +msgstr "Datos al desinstalar" + +#: admin/class-robotstxt-manager-settings.php:188 +msgid "Testing…" +msgstr "Probando…" + +#: admin/class-robotstxt-manager-settings.php:189 +#: admin/class-robotstxt-manager-settings.php:274 +msgid "Test connection" +msgstr "Probar la conexión" + +#: admin/class-robotstxt-manager-settings.php:190 +msgid "Deleting…" +msgstr "Borrando…" + +#: admin/class-robotstxt-manager-settings.php:191 +#: admin/class-robotstxt-manager-settings.php:278 +msgid "Delete API key" +msgstr "Borrar la clave API" + +#: admin/class-robotstxt-manager-settings.php:192 +msgid "" +"Delete the stored API key? The catalog and subscription data will stop " +"working until a new key is entered." +msgstr "" +"¿Seguro que quieres borrar la clave API guardada? El catálogo y los datos de " +"suscripción dejarán de funcionar hasta que se introduzca una clave nueva." + +#: admin/class-robotstxt-manager-settings.php:193 +#: admin/class-robotstxt-manager-settings.php:414 +msgid "API key deleted. Save changes to persist." +msgstr "Clave API borrada. Guarda los cambios para que surta efecto." + +#: admin/class-robotstxt-manager-settings.php:194 +msgid "An unexpected error occurred." +msgstr "Ha ocurrido un error inesperado." + +#: admin/class-robotstxt-manager-settings.php:226 +msgid "" +"Base URL of the remote Plugins Core installation that this site will pull " +"the plugin catalog from." +msgstr "" +"URL base de la instalación remota de Plugins Core de la que este sitio " +"obtendrá el catálogo de plugins." + +#. translators: %s: last 4 characters of the stored API key. +#: admin/class-robotstxt-manager-settings.php:257 +#, php-format +msgid "" +"A key is stored (last 4 characters: %s). Leave blank to keep " +"the existing key; enter a new value to replace it." +msgstr "" +"Hay una clave guardada (últimos 4 caracteres: %s). Déjalo en " +"blanco para mantener la clave actual; escribe un valor nuevo para " +"sustituirla." + +#: admin/class-robotstxt-manager-settings.php:262 +msgid "" +"A key is stored but could not be decoded. You can replace it by entering a " +"new value above, or delete it with the button below." +msgstr "" +"Hay una clave guardada pero no se pudo descodificar. Puedes sustituirla " +"escribiendo un valor nuevo arriba, o borrarla con el botón de abajo." + +#: admin/class-robotstxt-manager-settings.php:266 +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." + +#: admin/class-robotstxt-manager-settings.php:298 +msgid "" +"How long the catalog response from Core is cached in a transient. Default: " +"60 minutes. Lower values refresh more often at the cost of more requests to " +"Core. Maximum: 1440 (24 hours)." +msgstr "" +"Cuánto tiempo se guarda en caché la respuesta del catálogo de Core en un " +"transient. Por defecto: 60 minutos. Los valores más bajos actualizan con más " +"frecuencia a costa de más solicitudes a Core. Máximo: 1440 (24 horas)." + +#: admin/class-robotstxt-manager-settings.php:313 +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "Borrar todos los datos del plugin cuando se desinstale." + +#: admin/class-robotstxt-manager-settings.php:343 +msgid "" +"The API key format is invalid. Copy the full key from your ROBOTSTXT account " +"page." +msgstr "" +"El formato de la clave API no es válido. Copia la clave completa desde tu " +"página de cuenta de ROBOTSTXT." + +#: admin/views/page-catalog.php:69 +msgid "" +"This is the ROBOTSTXT plugin store: the catalog of plugins published by " +"ROBOTSTXT, installable and updatable straight from your own wp-admin. Free " +"plugins install with one click; premium plugins require an annual " +"subscription that you purchase on our website." +msgstr "" +"Esta es la tienda de plugins de ROBOTSTXT: el catálogo de plugins publicados " +"por ROBOTSTXT, instalables y actualizables directamente desde tu propio wp-" +"admin. Los plugins gratis se instalan con un clic; los plugins premium " +"requieren una suscripción anual que se compra en nuestro sitio web." + +#: admin/views/page-catalog.php:73 +msgid "Catalog refreshed." +msgstr "Catálogo actualizado." + +#. translators: %s: store registration URL. +#: admin/views/page-catalog.php:89 +#, php-format +msgid "" +"Create your free account at the ROBOTSTXT store 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 la tienda de ROBOTSTXT para obtener tu clave API personal. " +"La clave vincula tus suscripciones a este sitio y desbloquea los plugins " +"premium y sus actualizaciones." + +#. translators: %s: settings URL. +#: admin/views/page-catalog.php:109 +#, php-format +msgid "" +"ROBOTSTXT Manager is not configured yet. Set the Store URL " +"and API key to see your plugin catalog." +msgstr "" +"ROBOTSTXT Manager aún no está configurado. Configura la URL " +"de la tienda y la clave API para ver tu catálogo de plugins." + +#: admin/views/page-catalog.php:119 +msgid "" +"No plugins were returned by the ROBOTSTXT store. Check the Store URL and API " +"key on the Settings page, or click Refresh to try again." +msgstr "" +"La tienda ROBOTSTXT no devolvió ningún plugin. Revisa la URL de la tienda y " +"la clave API en la página de ajustes, o pulsa Actualizar para volver a " +"intentarlo." + +#: admin/views/page-catalog.php:122 admin/views/page-catalog.php:125 +msgid "Refresh catalog" +msgstr "Actualizar el catálogo" + +#: admin/views/page-catalog.php:131 +msgid "Plugin" +msgstr "Plugin" + +#: admin/views/page-catalog.php:132 +msgid "Version" +msgstr "Versión" + +#: admin/views/page-catalog.php:133 +msgid "Requires WP" +msgstr "Requiere WP" + +#: admin/views/page-catalog.php:134 +msgid "Requires PHP" +msgstr "Requiere PHP" + +#: admin/views/page-catalog.php:135 +msgid "Price" +msgstr "Precio" + +#: admin/views/page-catalog.php:136 +msgid "Action" +msgstr "Acción" + +#: admin/views/page-catalog.php:137 +msgid "Status" +msgstr "Estado" + +#: admin/views/page-catalog.php:239 +msgid "Your site runs WordPress" +msgstr "Tu sitio funciona con WordPress" + +#: admin/views/page-catalog.php:251 +msgid "Your server runs PHP" +msgstr "Tu servidor funciona con PHP" + +#. translators: %s: formatted price number. +#: admin/views/page-catalog.php:264 +#, php-format +msgid "€%s / year" +msgstr "%s €/año" + +#. translators: %d: days remaining. +#: admin/views/page-catalog.php:282 +#, php-format +msgid "Subscribed — %d days left" +msgstr "Suscrito — quedan %d días" + +#: admin/views/page-catalog.php:286 +msgid "Subscribed" +msgstr "Suscrito" + +#: admin/views/page-catalog.php:289 +msgid "Payment failed" +msgstr "Pago fallido" + +#: admin/views/page-catalog.php:291 +msgid "Cancelled" +msgstr "Cancelada" + +#: admin/views/page-catalog.php:293 +msgid "Expired" +msgstr "Caducada" + +#: admin/views/page-catalog.php:303 +msgid "Free" +msgstr "Gratis" + +#: admin/views/page-catalog.php:309 +msgid "Incompatible" +msgstr "Incompatible" + +#: admin/views/page-catalog.php:312 +msgid "Buy" +msgstr "Comprar" + +#: admin/views/page-catalog.php:317 +msgid "Install" +msgstr "Instalar" + +#: admin/views/page-catalog.php:319 +msgid "Activate" +msgstr "Activar" + +#: admin/views/page-catalog.php:321 +msgid "Update" +msgstr "Actualizar" + +#: admin/views/page-catalog.php:327 +msgid "Not installed" +msgstr "No instalado" + +#: admin/views/page-catalog.php:329 +msgid "Installed (inactive)" +msgstr "Instalado (inactivo)" + +#. translators: 1: installed version, 2: available version. +#: admin/views/page-catalog.php:334 +#, php-format +msgid "Update available (v%1$s → v%2$s)" +msgstr "Actualización disponible (v%1$s → v%2$s)" + +#: admin/views/page-catalog.php:340 +msgid "Up to date" +msgstr "Actualizado" + +#: admin/views/page-catalog.php:349 +msgid "Visit website" +msgstr "Visitar el sitio web" + +#. translators: %s: plugin name. +#: admin/views/page-catalog.php:349 +#, php-format +msgid "(about %s)" +msgstr "(sobre %s)" + +#. translators: %s: list of plugin slugs. +#: admin/views/page-catalog.php:360 +#, php-format +msgid "Requires: %s" +msgstr "Requiere: %s" + +#: admin/views/page-catalog.php:374 +msgid "Support" +msgstr "Soporte" + +#: admin/views/page-catalog.php:376 +msgid "" +"Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in " +"its row above) for documentation and guides, or contact us through our " +"website — we are happy to help." +msgstr "" +"¿Necesitas ayuda con un plugin de ROBOTSTXT? Visita el sitio web del plugin " +"(el enlace en su fila de arriba) para ver la documentación y las guías, o " +"contáctanos a través de nuestro sitio web: estaremos encantados de ayudarte." + +#: admin/views/page-catalog.php:379 +msgid "Payments, and how it works" +msgstr "Pagos, y cómo funciona" + +#: admin/views/page-catalog.php:381 +msgid "" +"Free plugins install instantly at no cost. Premium plugins are annual " +"subscriptions: you pay once on our website and the subscription renews " +"automatically every year until you cancel. You can cancel at any time from " +"your ROBOTSTXT account page — access keeps working until the end of the paid " +"period. All payments are processed securely by Mollie; we never see or store " +"your card details." +msgstr "" +"Los plugins gratis se instalan al instante y sin coste. Los plugins premium " +"son suscripciones anuales: pagas una vez en nuestro sitio web y la " +"suscripción se renueva automáticamente cada año hasta que la canceles. " +"Puedes cancelarla en cualquier momento desde tu página de cuenta de " +"ROBOTSTXT; el acceso sigue funcionando hasta el final del periodo pagado. " +"Todos los pagos los procesa de forma segura Mollie; nosotros nunca vemos ni " +"guardamos los datos de tu tarjeta." + +#. translators: %d: number of incompatible plugins. +#: admin/views/page-catalog.php:391 +#, php-format +msgid "" +"%d plugin in the catalog is not compatible with this site's WordPress or PHP " +"version." +msgid_plural "" +"%d plugins in the catalog are not compatible with this site's WordPress or " +"PHP version." +msgstr[0] "" +"%d plugin del catálogo no es compatible con la versión de WordPress o de PHP " +"de este sitio." +msgstr[1] "" +"%d plugins del catálogo no son compatibles con la versión de WordPress o de " +"PHP de este sitio." + +#. translators: 1: local WP version, 2: local PHP version. +#: admin/views/page-catalog.php:400 +#, php-format +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "Este sitio funciona con WordPress %1$s y PHP %2$s." + +#: includes/class-robotstxt-manager-core-client.php:141 +msgid "Store URL is not configured." +msgstr "La URL de la tienda no está configurada." + +#: includes/class-robotstxt-manager-core-client.php:148 +msgid "API key is not configured." +msgstr "La clave API no está configurada." + +#. translators: %s: HTTP transport error message. +#: includes/class-robotstxt-manager-core-client.php:158 +#, php-format +msgid "Could not reach Plugins Core: %s" +msgstr "No se pudo conectar con Plugins Core: %s" + +#. translators: %d: HTTP status code. +#: includes/class-robotstxt-manager-core-client.php:168 +#, php-format +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "La tienda respondió con HTTP %d. Comprueba la URL de la tienda y la clave API." + +#: includes/class-robotstxt-manager-core-client.php:177 +msgid "Connected." +msgstr "Conectado." diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index 4d2d0c3..e0350b5 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -1,33 +1,480 @@ +# Copyright (C) 2026 ROBOTSTXT +# This file is distributed under the GPL-3.0-or-later. +msgid "" +msgstr "" +"Project-Id-Version: Manager (by ROBOTSTXT) 1.4.1\n" +"Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2026-08-17T14:52:09+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.12.0\n" +"X-Domain: robotstxt-manager\n" +#. Plugin Name of the plugin +#: robotstxt-manager.php +msgid "Manager (by ROBOTSTXT)" +msgstr "" +#. Plugin URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/plugins/robotstxt-manager/" +msgstr "" + +#. Description of the plugin +#: robotstxt-manager.php +msgid "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." +msgstr "" + +#. Author of the plugin +#: robotstxt-manager.php +#: admin/class-robotstxt-manager-admin.php:48 +msgid "ROBOTSTXT" +msgstr "" + +#. Author URI of the plugin +#: robotstxt-manager.php +msgid "https://www.robotstxt.software/" +msgstr "" + +#: admin/class-robotstxt-manager-admin.php:47 +#: admin/views/page-catalog.php:66 +msgid "Manager (by ROBOTSTXT) — Plugins" +msgstr "" + +#: admin/class-robotstxt-manager-admin.php:64 +#: admin/class-robotstxt-manager-settings.php:207 +msgid "You do not have sufficient permissions to access this page." +msgstr "" + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:108 +#, php-format msgid "The payment for %s failed. Update your payment method from your ROBOTSTXT account page to keep access." msgstr "" +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-admin.php:119 +#, php-format msgid "The subscription for %s has expired, but the plugin is still active on this site. Renew from your ROBOTSTXT account page to keep receiving updates." msgstr "" +#. translators: 1: plugin slug, 2: days remaining. +#: admin/class-robotstxt-manager-admin.php:132 +#, php-format msgid "The subscription for %1$s expires in %2$d day." msgid_plural "The subscription for %1$s expires in %2$d days." msgstr[0] "" msgstr[1] "" -msgid "Subscribed" +#: admin/class-robotstxt-manager-admin.php:153 +#: admin/class-robotstxt-manager-installer.php:421 +#: admin/class-robotstxt-manager-settings.php:379 +#: admin/class-robotstxt-manager-settings.php:405 +msgid "Insufficient permissions." msgstr "" -msgid "Subscribed — %d days left" +#: admin/class-robotstxt-manager-admin.php:197 +msgid "Too many refreshes. Please wait a minute before refreshing again." msgstr "" -msgid "Payment failed" +#: admin/class-robotstxt-manager-installer.php:51 +#: admin/class-robotstxt-manager-installer.php:518 +msgid "Plugin not found in catalog." msgstr "" -msgid "Cancelled" +#. translators: %s: plugin name. +#: admin/class-robotstxt-manager-installer.php:73 +#, php-format +msgid "%s installed. Activate it from the list below." msgstr "" -msgid "Expired" +#. translators: %s: dependency slug. +#: admin/class-robotstxt-manager-installer.php:114 +#, php-format +msgid "Could not install the required plugin %s." msgstr "" +#. translators: %s: list of installed dependency names. +#: admin/class-robotstxt-manager-installer.php:130 +#, php-format +msgid "Installed required plugins: %s." +msgstr "" + +#. translators: %s: error message from WordPress.org. +#: admin/class-robotstxt-manager-installer.php:272 +#, php-format +msgid "WordPress.org lookup failed: %s" +msgstr "" + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:281 +#, php-format +msgid "No download found on WordPress.org for %s." +msgstr "" + +#: admin/class-robotstxt-manager-installer.php:287 +#: admin/class-robotstxt-manager-installer.php:540 +msgid "Could not create a temporary file for download." +msgstr "" + +#. translators: %s: HTTP transport error message. +#: admin/class-robotstxt-manager-installer.php:303 +#: admin/class-robotstxt-manager-installer.php:573 +#, php-format +msgid "Download failed: %s" +msgstr "" + +#. translators: %d: HTTP status code. +#: admin/class-robotstxt-manager-installer.php:310 +#: admin/class-robotstxt-manager-installer.php:588 +#, php-format +msgid "Download failed (HTTP %d)." +msgstr "" + +#: admin/class-robotstxt-manager-installer.php:316 +#: admin/class-robotstxt-manager-installer.php:597 +msgid "The store returned an invalid file." +msgstr "" + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:622 +#, php-format +msgid "Installation failed: %s" +msgstr "" + +#. translators: %s: upgrader error message. +#: admin/class-robotstxt-manager-installer.php:334 +#: admin/class-robotstxt-manager-installer.php:625 +msgid "Installation failed." +msgstr "" + +#. translators: %s: plugin slug. +#: admin/class-robotstxt-manager-installer.php:341 +#, php-format +msgid "The downloaded plugin for %s does not have the expected folder structure." +msgstr "" + +#: admin/class-robotstxt-manager-installer.php:363 +#: admin/class-robotstxt-manager-installer.php:394 +msgid "Plugin is not installed." +msgstr "" + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:375 +#, php-format +msgid "%s activated." +msgstr "" + +#. translators: %s: plugin name (slug). +#: admin/class-robotstxt-manager-installer.php:406 +#, php-format +msgid "%s updated." +msgstr "" + +#: admin/class-robotstxt-manager-installer.php:512 +msgid "Store is not configured." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:59 +#: admin/views/page-settings.php:13 +msgid "Manager (by ROBOTSTXT) — Settings" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:60 +msgid "Settings" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:75 +msgid "Connection" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:92 +msgid "Store URL" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:110 +msgid "API Key" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:118 +msgid "Cache" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:135 +msgid "Catalog Cache (minutes)" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:153 +msgid "Data on Uninstall" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:188 +msgid "Testing…" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:189 +#: admin/class-robotstxt-manager-settings.php:274 +msgid "Test connection" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:190 +msgid "Deleting…" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:191 +#: admin/class-robotstxt-manager-settings.php:278 +msgid "Delete API key" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:192 +msgid "Delete the stored API key? The catalog and subscription data will stop working until a new key is entered." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:193 +#: admin/class-robotstxt-manager-settings.php:414 +msgid "API key deleted. Save changes to persist." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:194 +msgid "An unexpected error occurred." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:226 +msgid "Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from." +msgstr "" + +#. translators: %s: last 4 characters of the stored API key. +#: admin/class-robotstxt-manager-settings.php:257 +#, php-format +msgid "A key is stored (last 4 characters: %s). Leave blank to keep the existing key; enter a new value to replace it." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:262 +msgid "A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:266 +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 "" + +#: admin/class-robotstxt-manager-settings.php:298 +msgid "How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours)." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:313 +msgid "Delete all plugin data when the plugin is uninstalled." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:343 +msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." +msgstr "" + +#: admin/views/page-catalog.php:69 +msgid "This is the ROBOTSTXT plugin store: the catalog of plugins published by ROBOTSTXT, installable and updatable straight from your own wp-admin. Free plugins install with one click; premium plugins require an annual subscription that you purchase on our website." +msgstr "" + +#: admin/views/page-catalog.php:73 +msgid "Catalog refreshed." +msgstr "" + +#. translators: %s: store registration URL. +#: admin/views/page-catalog.php:89 +#, php-format msgid "Create your free account at the ROBOTSTXT store 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." +#. translators: %s: settings URL. +#: admin/views/page-catalog.php:109 +#, php-format +msgid "ROBOTSTXT Manager is not configured yet. Set the Store URL and API key to see your plugin catalog." +msgstr "" + +#: admin/views/page-catalog.php:119 +msgid "No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again." +msgstr "" + +#: admin/views/page-catalog.php:122 +#: admin/views/page-catalog.php:125 +msgid "Refresh catalog" +msgstr "" + +#: admin/views/page-catalog.php:131 +msgid "Plugin" +msgstr "" + +#: admin/views/page-catalog.php:132 +msgid "Version" +msgstr "" + +#: admin/views/page-catalog.php:133 +msgid "Requires WP" +msgstr "" + +#: admin/views/page-catalog.php:134 +msgid "Requires PHP" +msgstr "" + +#: admin/views/page-catalog.php:135 +msgid "Price" +msgstr "" + +#: admin/views/page-catalog.php:136 +msgid "Action" +msgstr "" + +#: admin/views/page-catalog.php:137 +msgid "Status" +msgstr "" + +#: admin/views/page-catalog.php:239 +msgid "Your site runs WordPress" +msgstr "" + +#: admin/views/page-catalog.php:251 +msgid "Your server runs PHP" +msgstr "" + +#. translators: %s: formatted price number. +#: admin/views/page-catalog.php:264 +#, php-format +msgid "€%s / year" +msgstr "" + +#. translators: %d: days remaining. +#: admin/views/page-catalog.php:282 +#, php-format +msgid "Subscribed — %d days left" +msgstr "" + +#: admin/views/page-catalog.php:286 +msgid "Subscribed" +msgstr "" + +#: admin/views/page-catalog.php:289 +msgid "Payment failed" +msgstr "" + +#: admin/views/page-catalog.php:291 +msgid "Cancelled" +msgstr "" + +#: admin/views/page-catalog.php:293 +msgid "Expired" +msgstr "" + +#: admin/views/page-catalog.php:303 +msgid "Free" +msgstr "" + +#: admin/views/page-catalog.php:309 +msgid "Incompatible" +msgstr "" + +#: admin/views/page-catalog.php:312 +msgid "Buy" +msgstr "" + +#: admin/views/page-catalog.php:317 +msgid "Install" +msgstr "" + +#: admin/views/page-catalog.php:319 +msgid "Activate" +msgstr "" + +#: admin/views/page-catalog.php:321 +msgid "Update" +msgstr "" + +#: admin/views/page-catalog.php:327 +msgid "Not installed" +msgstr "" + +#: admin/views/page-catalog.php:329 +msgid "Installed (inactive)" +msgstr "" + +#. translators: 1: installed version, 2: available version. +#: admin/views/page-catalog.php:334 +#, php-format +msgid "Update available (v%1$s → v%2$s)" +msgstr "" + +#: admin/views/page-catalog.php:340 +msgid "Up to date" +msgstr "" + +#: admin/views/page-catalog.php:349 +msgid "Visit website" +msgstr "" + +#. translators: %s: plugin name. +#: admin/views/page-catalog.php:349 +#, php-format +msgid "(about %s)" +msgstr "" + +#. translators: %s: list of plugin slugs. +#: admin/views/page-catalog.php:360 +#, php-format +msgid "Requires: %s" +msgstr "" + +#: admin/views/page-catalog.php:374 +msgid "Support" +msgstr "" + +#: admin/views/page-catalog.php:376 +msgid "Need help with a ROBOTSTXT plugin? Visit the plugin's website (the link in its row above) for documentation and guides, or contact us through our website — we are happy to help." +msgstr "" + +#: admin/views/page-catalog.php:379 +msgid "Payments, and how it works" +msgstr "" + +#: admin/views/page-catalog.php:381 +msgid "Free plugins install instantly at no cost. Premium plugins are annual subscriptions: you pay once on our website and the subscription renews automatically every year until you cancel. You can cancel at any time from your ROBOTSTXT account page — access keeps working until the end of the paid period. All payments are processed securely by Mollie; we never see or store your card details." +msgstr "" + +#. translators: %d: number of incompatible plugins. +#: admin/views/page-catalog.php:391 +#, php-format +msgid "%d plugin in the catalog is not compatible with this site's WordPress or PHP version." +msgid_plural "%d plugins in the catalog are not compatible with this site's WordPress or PHP version." +msgstr[0] "" +msgstr[1] "" + +#. translators: 1: local WP version, 2: local PHP version. +#: admin/views/page-catalog.php:400 +#, php-format +msgid "This site runs WordPress %1$s on PHP %2$s." +msgstr "" + +#: includes/class-robotstxt-manager-core-client.php:141 +msgid "Store URL is not configured." +msgstr "" + +#: includes/class-robotstxt-manager-core-client.php:148 +msgid "API key is not configured." +msgstr "" + +#. translators: %s: HTTP transport error message. +#: includes/class-robotstxt-manager-core-client.php:158 +#, php-format +msgid "Could not reach Plugins Core: %s" +msgstr "" + +#. translators: %d: HTTP status code. +#: includes/class-robotstxt-manager-core-client.php:168 +#, php-format +msgid "The store responded with HTTP %d. Check the Store URL and API key." +msgstr "" + +#: includes/class-robotstxt-manager-core-client.php:177 +msgid "Connected." msgstr "" diff --git a/readme.txt b/readme.txt index f83cd64..b2f0888 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,11 @@ === Manager (by ROBOTSTXT) === -Contributors: javiercasares, robotstxt +Contributors: robotstxt, javiercasares Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.4.0 +Stable tag: 1.4.1 Requires PHP: 8.0 -Version: 1.4.0 +Version: 1.4.1 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -92,6 +92,15 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == +Only the 3 last versions. The full changelog will be at changelog.txt + += 1.4.1 = + +_Release date: 2026-08-17_ + +* Housekeeping release: plugin identity now points at the ROBOTSTXT software site — Plugin URI and Update URI are `https://www.robotstxt.software/plugins/robotstxt-manager/`, Author URI is `https://www.robotstxt.software/`, and this readme's full-changelog link points to the same page. +* Tooling aligned with the declared real floors: PHPCS `testVersion` and the preflight scan range now cover PHP 8.0-8.5 (was 7.4/8.4). + = 1.4.0 = _Release date: 2026-08-17_ @@ -104,124 +113,9 @@ _Release date: 2026-08-17_ * Descriptions render in the admin's language (Core 1.8.0+ `description_translations`), English fallback. -= 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 = - -_Release date: 2026-08-17_ - -* Plugin icons in the catalog rows (64×64, from the store's `icon_url`; white placeholder when missing). - -= 1.2.0 = - -_Release date: 2026-08-15_ - -* Subscription status (Phase 4): pills next to premium prices (Subscribed / N days left / Payment failed / Cancelled / Expired) and admin notices for failed payments, upcoming expiries, and expired-but-active plugins. Cached hourly from Core's /me/subscriptions. - -= 1.1.0 = - -_Release date: 2026-08-15_ - -* Cascade dependency install: required plugins (ecosystem or WordPress.org, e.g. Action Scheduler) are installed automatically before the plugin; "Requires:" hints in the catalog. - -= 1.0.0 = - -_Release date: 2026-08-15_ - -* Catalog redesign: columns Plugin · Version · Requires WP · Requires PHP · Price · Action · Status; Type merged into Price ("Free" or the annual price); per-plugin website link + description always open under each row; intro text after the title; Support and Payments sections below the table. First stable release. - -= 0.6.0 = - -_Release date: 2026-08-15_ - -* Spanish (es_ES) and Catalan (ca) translations for all admin strings (73 msgids, POT regenerated). -* "Refresh catalog" rate-limited to 6 per minute per user. - -= 0.5.3 = - -_Release date: 2026-08-15_ - -* Authenticated encryption (encrypt-then-MAC, matching Core 1.6.0) for the stored API key: tampered payloads fail closed; legacy-stored keys keep working and upgrade on next save. - -= 0.5.2 = - -_Release date: 2026-08-15_ - -* Fixed: updating (and installing) a plugin from the Manager catalog page failed with a fatal or a bare "Installation failed." Two defects in the panel's upgrader path: `Plugin_Upgrader` was never loaded in the `admin-post` context (it lives in its own file since the WordPress 5.3 class split, and only `class-wp-upgrader.php` was required), and the overwrite option was passed under the key `overwrite`, which current WordPress reads as `overwrite_package` — so the existing folder was never cleared. Verified end-to-end against the live store. - -= 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_ - -* Native update integration: catalog plugins now show WordPress's standard "Update available" badge and update through the regular wp-admin flow. Updates download from the ROBOTSTXT store (premium plugins authenticate via the account API key; requires Core 1.5.0+). "View details" modal data comes from the catalog. Refreshing the catalog also forces a fresh WordPress update check. - -= 0.4.0 = - -_Release date: 2026-08-14_ - -* Click-to-expand rows in the catalog: each plugin's description and a link to its website are shown in a detail row under the plugin name (CSS-only toggle, no JavaScript). Requires Core 1.4.2+ on the store. - -= 0.3.1 = - -_Release date: 2026-08-14_ - -* Default store URL is now `https://www.robotstxt.software`. Existing saved URLs are preserved. - -= 0.3.0 = - -_Release date: 2026-08-13_ - -* Classic install/activate/update flow: actions run on a full page load (`admin-post.php` with per-action-and-slug nonces) and report the outcome through standard admin notices (green success / red error), replacing the previous AJAX flow. - -= 0.2.0 = - -_Release date: 2026-08-13_ - -* Install action: free and premium plugins install directly from the catalog, authenticated with the account-level API key against Core's `/download` endpoint (requires Core 1.4.0+). - -= 0.1.3 = - -_Release date: 2026-08-12_ - -* Composer PHP requirement aligned to scan floor (>=7.4). - -= 0.1.2 = - -_Release date: 2026-08-12_ - -* Documentation refresh: readme.txt and changelog.txt aligned to the standard templates. Compatibility scan results documented. - -= 0.1.1 = - -_Release date: 2026-08-12_ - -* PHPCompatibility scan (5.6–8.5): real PHP floor is 7.2; project deliberately declares 8.4 to match the ecosystem. -* wp-compat scan against WordPress 4.7: zero errors. Real WP floor is ≤ 4.7; project declares 7.0 to match the ecosystem support window. - -= 0.1.0 = - -_Release date: 2026-08-12_ - -* Phase 1 scaffold. - = Previous versions = -If you want to see the full changelog, visit the [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager/raw/branch/main/changelog.txt) file. +If you want to see the full changelog, visit the [plugin page](https://www.robotstxt.software/plugins/robotstxt-manager/). == Compliance == diff --git a/robotstxt-manager.php b/robotstxt-manager.php index 36e9340..dfa157b 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -1,13 +1,14 @@ Date: Tue, 18 Aug 2026 10:47:06 +0000 Subject: [PATCH 17/23] v1.5.0 --- admin/class-robotstxt-manager-settings.php | 39 ++++++++++++++++++- admin/js/robotstxt-manager-settings.js | 3 ++ admin/views/page-settings.php | 1 + changelog.txt | 21 ++++++++++ .../class-robotstxt-manager-core-client.php | 11 +++++- languages/robotstxt-manager.pot | 8 +++- readme.txt | 20 ++++++---- robotstxt-manager.php | 4 +- 8 files changed, 91 insertions(+), 16 deletions(-) diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index cddc28f..2cbd759 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -263,7 +263,16 @@ class Robotstxt_Manager_Settings { } echo '

'; } else { - echo '

' . 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' ) . '

'; + printf( + '

%s

', + wp_kses_post( + sprintf( + /* translators: %s: Registration URL. */ + __( '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' ), + esc_url( 'https://www.robotstxt.software/wp-login.php?action=register' ) + ) + ) + ); } // Action buttons. @@ -333,6 +342,14 @@ class Robotstxt_Manager_Settings { return is_string( $raw ) ? $raw : ''; } + // If the input is already encrypted (v2: prefix), it means the browser + // auto-filled the password field with the stored encrypted value. + // Return it as-is (already encrypted) rather than re-encrypting or + // trying to read the option (which may not be saved yet in the WP flow). + if ( str_starts_with( $plain, 'v2:' ) ) { + return $plain; + } + // 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. @@ -379,7 +396,25 @@ class Robotstxt_Manager_Settings { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'robotstxt-manager' ) ) ); } - $client = Robotstxt_Manager_Core_Client::from_options(); + // Allow testing a key from the form field (not yet saved) by passing it in the request. + $input_key = ''; + if ( isset( $_POST['robotstxt_manager_api_key'] ) ) { + $unslashed = wp_unslash( $_POST['robotstxt_manager_api_key'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below. + if ( is_string( $unslashed ) ) { + $input_key = sanitize_text_field( $unslashed ); + } + } + + $store_url = get_option( 'robotstxt_manager_store_url', 'https://www.robotstxt.software' ); + $store_url = is_string( $store_url ) ? $store_url : 'https://www.robotstxt.software'; + + // Use input key if provided, otherwise fall back to saved (decrypted) key. + if ( '' !== $input_key ) { + $client = new Robotstxt_Manager_Core_Client( $store_url, $input_key ); + } else { + $client = Robotstxt_Manager_Core_Client::from_options(); + } + $result = $client->test_connection(); if ( $result['ok'] ) { diff --git a/admin/js/robotstxt-manager-settings.js b/admin/js/robotstxt-manager-settings.js index c0f465a..f11d499 100644 --- a/admin/js/robotstxt-manager-settings.js +++ b/admin/js/robotstxt-manager-settings.js @@ -20,11 +20,14 @@ $btn.prop( 'disabled', true ).text( RobotstxtManagerSettings.i18n.testing ); $result.text( '' ).css( 'color', '' ); + var apiKey = $( '#robotstxt_manager_api_key' ).val(); + $.post( RobotstxtManagerSettings.ajaxUrl, { action: 'robotstxt_manager_test_connection', nonce: RobotstxtManagerSettings.nonce, + robotstxt_manager_api_key: apiKey, }, function ( response ) { if ( response.success ) { diff --git a/admin/views/page-settings.php b/admin/views/page-settings.php index 89b5d4a..83cd17d 100644 --- a/admin/views/page-settings.php +++ b/admin/views/page-settings.php @@ -11,6 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) { ?>

+
get( '/plugins' ); + $response = $this->get( '/me/subscriptions' ); if ( is_wp_error( $response ) ) { return array( @@ -161,6 +161,13 @@ class Robotstxt_Manager_Core_Client { $code = (int) wp_remote_retrieve_response_code( $response ); + if ( 401 === $code ) { + return array( + 'ok' => false, + 'message' => __( 'Invalid API key. Please check your key and try again.', 'robotstxt-manager' ), + ); + } + if ( 200 !== $code ) { return array( 'ok' => false, @@ -175,7 +182,7 @@ class Robotstxt_Manager_Core_Client { return array( 'ok' => true, 'message' => __( 'Connected.', 'robotstxt-manager' ), - 'catalog_count' => $count, + 'subscriptions' => $count, ); } diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index e0350b5..cae3906 100644 --- a/languages/robotstxt-manager.pot +++ b/languages/robotstxt-manager.pot @@ -2,7 +2,7 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Manager (by ROBOTSTXT) 1.4.1\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 1.5.0\n" "Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -259,7 +259,7 @@ msgid "A key is stored but could not be decoded. You can replace it by entering msgstr "" #: admin/class-robotstxt-manager-settings.php:266 -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." +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 "" #: admin/class-robotstxt-manager-settings.php:298 @@ -475,6 +475,10 @@ msgstr "" msgid "The store responded with HTTP %d. Check the Store URL and API key." msgstr "" +#: includes/class-robotstxt-manager-core-client.php:164 +msgid "Invalid API key. Please check your key and try again." +msgstr "" + #: includes/class-robotstxt-manager-core-client.php:177 msgid "Connected." msgstr "" diff --git a/readme.txt b/readme.txt index b2f0888..1c495ba 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: robotstxt, javiercasares Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.4.1 +Stable tag: 1.5.0 Requires PHP: 8.0 -Version: 1.4.1 +Version: 1.5.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -94,6 +94,16 @@ 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 += 1.5.0 = + +_Release date: 2026-08-18_ + +* 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.4.1 = _Release date: 2026-08-17_ @@ -107,12 +117,6 @@ _Release date: 2026-08-17_ * Premium update URLs carry a short-lived download token (Core 1.9.0+) instead of the API key; automatic fallback on older Core. -= 1.3.1 = - -_Release date: 2026-08-17_ - -* Descriptions render in the admin's language (Core 1.8.0+ `description_translations`), English fallback. - = Previous versions = If you want to see the full changelog, visit the [plugin page](https://www.robotstxt.software/plugins/robotstxt-manager/). diff --git a/robotstxt-manager.php b/robotstxt-manager.php index dfa157b..846e580 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * Plugin Name: Manager (by ROBOTSTXT) * 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. - * Version: 1.4.1 + * Version: 1.5.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Update URI: https://www.robotstxt.software/plugins/robotstxt-manager/ @@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.4.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.5.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) ); From 5589c54b9bfaa2fd8ceaed857f92b47c40d2965e Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Tue, 18 Aug 2026 10:48:13 +0000 Subject: [PATCH 18/23] v1.6.0 --- admin/class-robotstxt-manager-admin.php | 18 ++-- admin/class-robotstxt-manager-installer.php | 6 +- admin/class-robotstxt-manager-settings.php | 100 +++++++++++++++--- admin/views/page-catalog.php | 2 +- admin/views/page-settings.php | 4 +- changelog.txt | 8 +- .../class-robotstxt-manager-activator.php | 10 +- .../class-robotstxt-manager-core-client.php | 18 ++-- includes/class-robotstxt-manager-updater.php | 4 +- languages/robotstxt-manager.pot | 2 +- readme.txt | 13 ++- robotstxt-manager.php | 5 +- uninstall.php | 6 +- 13 files changed, 138 insertions(+), 58 deletions(-) diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index 1921cc6..20b8b41 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -33,7 +33,8 @@ class Robotstxt_Manager_Admin { * @return void */ public function register( Robotstxt_Manager_Loader $loader ): void { - $loader->add_action( 'admin_menu', $this, 'add_menu' ); + $menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu'; + $loader->add_action( $menu_hook, $this, 'add_menu' ); $loader->add_action( 'admin_post_robotstxt_manager_refresh_catalog', $this, 'handle_refresh' ); } @@ -43,10 +44,11 @@ class Robotstxt_Manager_Admin { * @return void */ public function add_menu(): void { + $cap = is_multisite() ? 'manage_network_options' : 'manage_options'; add_menu_page( esc_html__( 'Manager (by ROBOTSTXT) — Plugins', 'robotstxt-manager' ), esc_html__( 'ROBOTSTXT', 'robotstxt-manager' ), - 'manage_options', + $cap, self::PAGE_SLUG, array( $this, 'render_page' ), 'dashicons-screenoptions', @@ -60,7 +62,7 @@ class Robotstxt_Manager_Admin { * @return void */ public function render_page(): void { - if ( ! current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-manager' ) ); } @@ -149,21 +151,21 @@ class Robotstxt_Manager_Admin { * @return void */ public function handle_refresh(): void { - if ( ! current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-manager' ) ); } check_admin_referer( 'robotstxt_manager_refresh_catalog' ); $bucket = 'robotstxt_manager_refresh_' . get_current_user_id(); - $hits_raw = get_transient( $bucket ); + $hits_raw = get_site_transient( $bucket ); $hits = is_numeric( $hits_raw ) ? (int) $hits_raw : 0; if ( $hits >= 6 ) { $this->redirect_refresh_error(); } - set_transient( $bucket, $hits + 1, MINUTE_IN_SECONDS ); + set_site_transient( $bucket, $hits + 1, MINUTE_IN_SECONDS ); $client = Robotstxt_Manager_Core_Client::from_options(); $client->clear_catalog_cache(); @@ -175,7 +177,7 @@ class Robotstxt_Manager_Admin { 'page' => self::PAGE_SLUG, 'refreshed' => '1', ), - admin_url( 'admin.php' ) + ( is_multisite() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ) ) ); wp_safe_redirect( $redirect ); @@ -197,7 +199,7 @@ class Robotstxt_Manager_Admin { __( 'Too many refreshes. Please wait a minute before refreshing again.', 'robotstxt-manager' ) ), ), - admin_url( 'admin.php' ) + ( is_multisite() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ) ) ) ); exit; diff --git a/admin/class-robotstxt-manager-installer.php b/admin/class-robotstxt-manager-installer.php index 2714e78..28046a0 100644 --- a/admin/class-robotstxt-manager-installer.php +++ b/admin/class-robotstxt-manager-installer.php @@ -417,7 +417,7 @@ class Robotstxt_Manager_Installer { * @return string The sanitized plugin slug. */ private function authorize( string $action ): string { - if ( ! current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-manager' ) ); } @@ -543,7 +543,7 @@ class Robotstxt_Manager_Installer { $headers = array(); if ( $use_download_endpoint ) { - $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); + $api_key_raw = get_site_option( 'robotstxt_manager_api_key', '' ); $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; if ( '' !== $api_key ) { @@ -686,7 +686,7 @@ class Robotstxt_Manager_Installer { 'robotstxt_manager_result' => $result, 'robotstxt_manager_message' => rawurlencode( $message ), ), - admin_url( 'admin.php' ) + ( is_multisite() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ) ) ) ); exit; diff --git a/admin/class-robotstxt-manager-settings.php b/admin/class-robotstxt-manager-settings.php index 2cbd759..d1fcf12 100644 --- a/admin/class-robotstxt-manager-settings.php +++ b/admin/class-robotstxt-manager-settings.php @@ -41,7 +41,8 @@ class Robotstxt_Manager_Settings { * @return void */ public function register( Robotstxt_Manager_Loader $loader ): void { - $loader->add_action( 'admin_menu', $this, 'add_settings_page' ); + $menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu'; + $loader->add_action( $menu_hook, $this, 'add_settings_page' ); $loader->add_action( 'admin_init', $this, 'register_settings' ); $loader->add_action( 'admin_enqueue_scripts', $this, 'enqueue_scripts' ); $loader->add_action( 'wp_ajax_robotstxt_manager_test_connection', $this, 'handle_test_connection' ); @@ -54,11 +55,12 @@ class Robotstxt_Manager_Settings { * @return void */ public function add_settings_page(): void { + $cap = is_multisite() ? 'manage_network_options' : 'manage_options'; add_submenu_page( Robotstxt_Manager_Admin::PAGE_SLUG, esc_html__( 'Manager (by ROBOTSTXT) — Settings', 'robotstxt-manager' ), esc_html__( 'Settings', 'robotstxt-manager' ), - 'manage_options', + $cap, self::PAGE_SLUG, array( $this, 'render_page' ) ); @@ -203,20 +205,84 @@ class Robotstxt_Manager_Settings { * @return void */ public function render_page(): void { - if ( ! current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-manager' ) ); } + $this->handle_form_submission(); + require_once ROBOTSTXT_MANAGER_DIR . 'admin/views/page-settings.php'; } + /** + * Handles manual form submission for network settings. + * + * The WordPress Settings API (options.php) does not handle network + * options, so the network settings page must process its own form. + * + * @return void + */ + private function handle_form_submission(): void { + if ( ! isset( $_POST['robotstxt_manager_settings_group_nonce'] ) ) { + return; + } + + check_admin_referer( 'robotstxt_manager_settings_group', 'robotstxt_manager_settings_group_nonce' ); + + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have sufficient permissions to manage settings.', 'robotstxt-manager' ) ); + } + + // Store URL. + $store_url = ''; + if ( isset( $_POST['robotstxt_manager_store_url'] ) ) { + $raw = wp_unslash( $_POST['robotstxt_manager_store_url'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below. + $store_url = is_string( $raw ) ? esc_url_raw( $raw ) : ''; + } + update_site_option( 'robotstxt_manager_store_url', $store_url ); + + // API key. + $api_key = $this->sanitize_api_key( '' ); + if ( isset( $_POST['robotstxt_manager_api_key'] ) ) { + $raw = wp_unslash( $_POST['robotstxt_manager_api_key'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized by sanitize_api_key(). + if ( is_string( $raw ) ) { + $api_key = $this->sanitize_api_key( $raw ); + } + } + update_site_option( 'robotstxt_manager_api_key', $api_key ); + + // Cache TTL. + $cache_ttl = 60; + if ( isset( $_POST['robotstxt_manager_cache_ttl_minutes'] ) ) { + $raw = wp_unslash( $_POST['robotstxt_manager_cache_ttl_minutes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized by sanitize_cache_ttl(). + $cache_ttl = $this->sanitize_cache_ttl( $raw ); + } + update_site_option( 'robotstxt_manager_cache_ttl_minutes', $cache_ttl ); + + // Delete on uninstall. + $delete_on_uninstall = isset( $_POST['robotstxt_manager_delete_data_on_uninstall'] ) ? true : false; + update_site_option( 'robotstxt_manager_delete_data_on_uninstall', $delete_on_uninstall ); + + // Redirect with success flag. + $goback = add_query_arg( + array( + 'page' => self::PAGE_SLUG, + 'settings-updated' => 'true', + ), + ( is_multisite() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ) ) + ); + + wp_safe_redirect( $goback ); + exit; + } + /** * Renders the Store URL field. * * @return void */ public function render_field_store_url(): void { - $raw = get_option( 'robotstxt_manager_store_url', 'https://www.robotstxt.software' ); + $raw = get_site_option( 'robotstxt_manager_store_url', 'https://www.robotstxt.software' ); $value = is_string( $raw ) ? $raw : 'https://www.robotstxt.software'; printf( @@ -234,7 +300,7 @@ class Robotstxt_Manager_Settings { * @return void */ public function render_field_api_key(): void { - $stored = get_option( 'robotstxt_manager_api_key', '' ); + $stored = get_site_option( 'robotstxt_manager_api_key', '' ); $has_key = is_string( $stored ) && '' !== $stored; $last4 = ''; @@ -297,7 +363,7 @@ class Robotstxt_Manager_Settings { * @return void */ public function render_field_cache_ttl(): void { - $raw = get_option( 'robotstxt_manager_cache_ttl_minutes', 60 ); + $raw = get_site_option( 'robotstxt_manager_cache_ttl_minutes', 60 ); $value = is_numeric( $raw ) ? (int) $raw : 60; printf( @@ -313,7 +379,7 @@ class Robotstxt_Manager_Settings { * @return void */ public function render_field_delete_on_uninstall(): void { - $value = (bool) get_option( 'robotstxt_manager_delete_data_on_uninstall', false ); + $value = (bool) get_site_option( 'robotstxt_manager_delete_data_on_uninstall', false ); echo '