From bb3beaa353734dcf2a1f141ed1b4ff68edea4d6e Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Wed, 23 Sep 2026 06:11:14 +0000 Subject: [PATCH] v1.9.1 --- admin/class-robotstxt-manager-admin.php | 96 ++++++++ admin/class-robotstxt-manager-installer.php | 45 +++- admin/views/page-catalog.php | 40 +++- changelog.txt | 43 ++++ includes/class-robotstxt-manager-updater.php | 118 ++++++++-- languages/robotstxt-manager-ca.mo | Bin 13148 -> 13711 bytes languages/robotstxt-manager-ca.po | 7 + languages/robotstxt-manager-es_ES.mo | Bin 13060 -> 13618 bytes languages/robotstxt-manager-es_ES.po | 7 + languages/robotstxt-manager.pot | 222 +++++++++++-------- readme.txt | 30 +-- robotstxt-manager.php | 4 +- 12 files changed, 465 insertions(+), 147 deletions(-) diff --git a/admin/class-robotstxt-manager-admin.php b/admin/class-robotstxt-manager-admin.php index 20b8b41..ef4ef1a 100644 --- a/admin/class-robotstxt-manager-admin.php +++ b/admin/class-robotstxt-manager-admin.php @@ -36,6 +36,8 @@ class Robotstxt_Manager_Admin { $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' ); + $loader->add_action( 'admin_notices', $this, 'security_update_notices' ); + $loader->add_action( 'network_admin_notices', $this, 'security_update_notices' ); } /** @@ -77,9 +79,103 @@ class Robotstxt_Manager_Admin { $manager_has_api_key = $client->has_api_key(); $manager_store_url = $client->get_store_url(); + // Security patches declared for exactly the versions this site runs. + $manager_security_updates = $this->get_security_updates( $catalog, $local ); + require ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php'; } + /** + * Returns the security patches that apply to the exact versions this + * site runs (Core 1.16.0+ `security_patches` catalog data). + * + * @param list> $catalog Catalog entries. + * @param array $local Local state by slug. + * + * @return list + */ + public function get_security_updates( array $catalog, array $local ): array { + $updates = array(); + + foreach ( $catalog as $entry ) { + $raw_slug = $entry['slug'] ?? ''; + $slug = is_string( $raw_slug ) ? $raw_slug : ''; + + if ( '' === $slug ) { + continue; + } + + $state = $local[ $slug ] ?? null; + + if ( ! is_array( $state ) || empty( $state['installed'] ) ) { + continue; + } + + $raw_version = $state['version'] ?? ''; + $version = is_string( $raw_version ) ? $raw_version : ''; + $patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $version ); + + if ( '' === $patch ) { + continue; + } + + $raw_name = $entry['name'] ?? ''; + $clean_name = is_string( $raw_name ) && '' !== $raw_name ? $raw_name : $slug; + + $updates[] = array( + 'slug' => $slug, + 'name' => $clean_name, + 'installed' => $version, + 'patch' => $patch, + ); + } + + return $updates; + } + + /** + * Renders the security-update notices on the Plugins screen (not on the + * Manager catalog page, which shows its own block). + * + * @return void + */ + public function security_update_notices(): void { + if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { + return; + } + + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + $base = ( $screen instanceof WP_Screen ) ? (string) $screen->base : ''; + + if ( ! in_array( $base, array( 'plugins', 'plugins-network' ), true ) ) { + return; + } + + $client = Robotstxt_Manager_Core_Client::from_options(); + + if ( ! $client->is_configured() ) { + return; + } + + $catalog = $client->get_catalog(); + $security = $this->get_security_updates( $catalog, $this->resolve_local_state( $catalog ) ); + + foreach ( $security as $update ) { + echo '

'; + echo wp_kses_post( + sprintf( + /* translators: 1: plugin name, 2: installed version, 3: patch version, 4: update URL. */ + __( 'Security update available: %1$s (v%2$s → v%3$s). Update now — this is a security patch for the version this site runs, not a feature update.', 'robotstxt-manager' ), + esc_html( $update['name'] ), + esc_html( $update['installed'] ), + esc_html( $update['patch'] ), + esc_url( self::action_url( 'update', $update['slug'] ) ) + ) + ); + echo '

'; + } + } + /** * Builds admin notices for subscriptions that need attention. diff --git a/admin/class-robotstxt-manager-installer.php b/admin/class-robotstxt-manager-installer.php index fe3bfb2..b0fd1ba 100644 --- a/admin/class-robotstxt-manager-installer.php +++ b/admin/class-robotstxt-manager-installer.php @@ -379,7 +379,9 @@ class Robotstxt_Manager_Installer { } /** - * Updates an installed plugin to the latest catalog version. + * Updates an installed plugin to the latest catalog version — or, when a + * security patch is declared for the exact installed version (Core + * 1.16.0+), to that patch instead of the feature mainline. * * @return void */ @@ -394,7 +396,17 @@ class Robotstxt_Manager_Installer { $this->redirect_error( __( 'Plugin is not installed.', 'robotstxt-manager' ) ); } - $result = $this->download_and_install( $slug, true ); + $patch = ''; + + $entry = $this->find_catalog_entry( $slug ); + + if ( is_array( $entry ) ) { + $all = get_plugins(); + $version = isset( $all[ $file ]['Version'] ) && is_string( $all[ $file ]['Version'] ) ? $all[ $file ]['Version'] : ''; + $patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $version ); + } + + $result = $this->download_and_install( $slug, true, $patch ); if ( is_wp_error( $result ) ) { $this->redirect_error( $result->get_error_message() ); @@ -502,10 +514,12 @@ class Robotstxt_Manager_Installer { * * @param string $slug Plugin slug. * @param bool $overwrite Whether to overwrite an existing install (update). + * @param string $security_version Patch version to download instead of the + * stable mainline (Core 1.16.0+), '' for stable. * * @return true|WP_Error True on success. */ - private function download_and_install( string $slug, bool $overwrite = false ) { + private function download_and_install( string $slug, bool $overwrite = false, string $security_version = '' ) { $client = Robotstxt_Manager_Core_Client::from_options(); if ( ! $client->is_configured() ) { @@ -529,15 +543,26 @@ class Robotstxt_Manager_Installer { // fetched directly (no auth). Everything else goes through Core's // authenticated download endpoint (account API key as Bearer), with // this site's domain for per-domain license binding (Core 1.11.0+). - $use_download_endpoint = ! ( $is_free && '' !== $dl_url ); + // Security patches always stream through the endpoint with a version + // parameter — the public URL only carries the mainline stable ZIP. + $use_download_endpoint = '' !== $security_version || ! ( $is_free && '' !== $dl_url ); - $zip_url = $use_download_endpoint - ? add_query_arg( - 'domain', - rawurlencode( $this->site_domain() ), + if ( $use_download_endpoint ) { + $dl_args = array( + 'domain' => rawurlencode( $this->site_domain() ), + ); + + if ( '' !== $security_version ) { + $dl_args['version'] = rawurlencode( $security_version ); + } + + $zip_url = add_query_arg( + $dl_args, $client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download' - ) - : $dl_url; + ); + } else { + $zip_url = $dl_url; + } $tmp_file = wp_tempnam( $slug . '.zip' ); diff --git a/admin/views/page-catalog.php b/admin/views/page-catalog.php index 38dd0f4..68ae63a 100644 --- a/admin/views/page-catalog.php +++ b/admin/views/page-catalog.php @@ -25,6 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) { * @var array $local * @var array> $manager_subscriptions * @var list $manager_notices + * @var list $manager_security_updates * @var bool $manager_has_api_key * @var string $manager_store_url */ @@ -79,6 +80,25 @@ $compat_warnings = 0; + +
+

+ Security update available: %1$s (v%2$s → v%3$s). Update now — this is a security patch for the version this site runs, not a feature update.', 'robotstxt-manager' ), + esc_html( $manager_security['name'] ), + esc_html( $manager_security['installed'] ), + esc_html( $manager_security['patch'] ), + esc_url( Robotstxt_Manager_Admin::action_url( 'update', $manager_security['slug'] ) ) + ) + ); + ?> +

+
+ +

@@ -217,7 +237,15 @@ $compat_warnings = 0; $l_version = is_string( $raw_lv ) ? $raw_lv : ''; $update_available = $l_installed && '' !== $remote_v && '' !== $l_version - && version_compare( $l_version, $remote_v, '<' ); + && version_compare( $l_version, $remote_v, '<' ); + + // Security patch declared for exactly the installed version: + // the Update action installs the patch, not the mainline. + $security_patch = Robotstxt_Manager_Updater::security_patch_for( $entry, $l_version ); + + if ( '' !== $security_patch ) { + $update_available = true; + } $row_class = ( ! $wp_ok || ! $php_ok ) ? ' robotstxt-manager-row--incompatible' : ''; ?> @@ -346,6 +374,15 @@ $compat_warnings = 0; echo '' . esc_html__( 'Not installed', 'robotstxt-manager' ) . ''; } elseif ( ! $l_active ) { echo '' . esc_html__( 'Installed (inactive)', 'robotstxt-manager' ) . ''; + } elseif ( '' !== $security_patch ) { + echo '' . esc_html( + sprintf( + /* translators: 1: installed version, 2: security patch version. */ + __( 'Security update available (v%1$s → v%2$s)', 'robotstxt-manager' ), + $l_version, + $security_patch + ) + ) . ''; } elseif ( $update_available ) { echo '' . esc_html( sprintf( @@ -433,6 +470,7 @@ $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; } +.robotstxt-manager-state--security { color: #d63638; font-weight: 600; } /* Detail rows (always open): muted, attached to the row above. */ .robotstxt-manager-detail-row td { border-top: none !important; padding-top: 0; } diff --git a/changelog.txt b/changelog.txt index d546207..63a109e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,48 @@ == Changelog == += 1.9.1 = + +_Release date: 2026-09-23_ + +**Changed** + +* Maintenance pass over the 1.9.0 security-patch feature: `composer update` (no changes — dependencies already current, no known CVEs), full code + security review of the 1.9.0 diff (clean-context pre-deploy audit: escaping, capability gating, CSRF, and the 1.8.1 updater invariants all verified correct; its two suggestions were applied — see below), and floors re-verified. +* Pre-deploy audit hardening: `security_patch_for()` only accepts declarations that strictly increase the version — a store typo (self-mapping or downgrade) can no longer produce a downgrade/re-install "update" notice; the `version` argument in premium/free package URLs is now `rawurlencode`d, matching the installer. POT regenerated at 1.9.1. + +**Compatibility** + +* WordPress: 4.4 - 7.1 (scan-verified 2026-09-23: wp-compat ladder clean at 4.4 through 4.7; no WordPress API newer than 4.4 in use; the dev environment here runs WordPress 7.2-alpha trunk with the suite green, but 7.2 is not GA and `Tested up to` stays at the AGENTS window top) +* PHP: 8.0 - 8.5 (scan-verified 2026-09-23: PHPCompatibility ladder 5.6-8.5 — scan-clean from 7.4; manual audit keeps the real floor at 8.0: `mixed` hints, `str_contains()`, `str_starts_with()`) + +**Tests** + +* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): pass +* PHPStan max (level 10) + wp-compat: pass +* PHPUnit: 158 tests, 423 assertions (also green against WordPress 7.2-alpha trunk / test library rebuilt from wordpress-develop master) + += 1.9.0 = + +_Release date: 2026-09-22_ + +**Added** + +* Security-update notices (with Core 1.16.0+): when the store declares a security patch for the exact version this site runs (e.g. 1.2.3.1 applies to 1.2.3), Manager shows a red "Security update available: %name% (vX → vY) — Update now" notice on its catalog page **and** on the Plugins screen, and the catalog row status reads "Security update available (vX → vY)". The Update action installs the declared patch — never the feature mainline — so sites receive the security fix without being pushed across feature versions. +* The native update integration (update badge, `wp plugin update`, auto-updates) also targets the declared patch: the injected update entry carries the patch version and a versioned, authenticated package URL. Patches declared for other versions never apply (exact match on the installed version). Chained patches work (1.2.3.1 → 1.2.3.2 once declared). +* Spanish (es_ES) and Catalan (ca) translations for the new strings; POT regenerated. + +**Requires Core 1.16.0+ on the store** for patch data and versioned downloads (older Core: the catalog simply carries no patches and Manager behaves as before). + +**Tests** + +* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): pass +* PHPStan max (level 10) + wp-compat: pass +* PHPUnit: 157 tests, 420 assertions + +**Compatibility** + +* WordPress: 4.4 - 7.1 (unchanged) +* PHP: 8.0 - 8.5 (unchanged) + = 1.8.1 = _Release date: 2026-09-22_ diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php index 7392193..1a24e7e 100644 --- a/includes/class-robotstxt-manager-updater.php +++ b/includes/class-robotstxt-manager-updater.php @@ -109,6 +109,21 @@ class Robotstxt_Manager_Updater { continue; } + // Security patch declared for exactly this installed version + // (Core 1.16.0+): offer the patch, never the feature mainline. + $patch = self::security_patch_for( $entry, $version ); + + if ( '' !== $patch ) { + if ( 'premium' === $entry['type'] && ! $this->has_api_key() ) { + unset( $updates->response[ $plugin_file ] ); + continue; + } + + $updates->response[ $plugin_file ] = $this->build_update_object( $slug, $plugin_file, $entry, null, $patch ); + unset( $updates->no_update[ $plugin_file ] ); + continue; + } + if ( version_compare( $version, $entry['new_version'], '<' ) ) { // Premium updates need the account key in the package URL; // without a usable key the native updater would only hit a @@ -155,6 +170,56 @@ class Robotstxt_Manager_Updater { return '' !== $store_host && $store_host === $entry_host; } + /** + * Returns the security-patch version declared for exactly the given + * installed version, or '' when none applies. + * + * Exact-match by design: a patch declared for 1.2.3 applies only to + * sites running 1.2.3 — other versions follow the normal mainline flow. + * A declaration that does not strictly increase the version (typo, + * downgrade, re-install loop) never counts as a patch. + * + * @param array $entry Catalog row (raw or normalised). + * @param string $installed_version Version installed on this site. + * + * @return string Patch version, or ''. + */ + public static function security_patch_for( array $entry, string $installed_version ): string { + if ( '' === $installed_version ) { + return ''; + } + + $patches = self::normalize_patches( $entry ); + $patch = $patches[ $installed_version ] ?? ''; + + if ( '' === $patch || ! version_compare( $installed_version, $patch, '<' ) ) { + return ''; + } + + return $patch; + } + + /** + * Normalises a catalog row's security_patches field into a string map. + * + * @param array $row Catalog row. + * + * @return array Installed version => patch version. + */ + private static function normalize_patches( array $row ): array { + $raw = $row['security_patches'] ?? array(); + $raw = is_array( $raw ) ? $raw : array(); + $clean = array(); + + foreach ( $raw as $applies_to => $patch ) { + if ( is_string( $applies_to ) && is_string( $patch ) ) { + $clean[ $applies_to ] = $patch; + } + } + + return $clean; + } + /** * Returns the installed plugin versions, from the object-cached plugin list. * @@ -258,7 +323,7 @@ class Robotstxt_Manager_Updater { * * @param list> $catalog Catalog entries from Core. * - * @return array> Normalised entries keyed by slug. + * @return array, requires_wp:string, requires_php:string, tested_up_to:string, page_url:string, description:string, icon_url:string, banner_url:string}> Normalised entries keyed by slug. */ private function catalog_by_slug( array $catalog ): array { $entries = array(); @@ -272,16 +337,17 @@ class Robotstxt_Manager_Updater { } $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->localized_description( $row ), - 'icon_url' => $this->str( $row, 'icon_url', '' ), - 'banner_url' => $this->str( $row, 'banner_url', '' ), + 'name' => $this->str( $row, 'name', $slug ), + 'type' => $this->str( $row, 'type', 'free' ), + 'new_version' => $this->str( $row, 'current_version', '' ), + 'security_patches' => self::normalize_patches( $row ), + '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->localized_description( $row ), + 'icon_url' => $this->str( $row, 'icon_url', '' ), + 'banner_url' => $this->str( $row, 'banner_url', '' ), ); } @@ -291,25 +357,29 @@ class Robotstxt_Manager_Updater { /** * 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. + * @param string $slug Plugin slug. + * @param string $plugin_file Plugin basename. + * @param array{name:string, type:string, new_version:string, security_patches:array, requires_wp:string, requires_php:string, tested_up_to:string, page_url:string, description:string, icon_url:string, banner_url:string} $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. + * @param string $security_version Patch version when the update is + * a security patch ('' otherwise). * * @return object stdClass for the transient bucket. */ - private function build_update_object( string $slug, string $plugin_file, array $entry, ?string $current_version = null ): object { + private function build_update_object( string $slug, string $plugin_file, array $entry, ?string $current_version = null, string $security_version = '' ): 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'], + 'new_version' => $is_no_update + ? $current_version + : ( '' !== $security_version ? $security_version : $entry['new_version'] ), 'url' => $entry['page_url'], - 'package' => $is_no_update ? '' : $this->package_url( $slug, $entry['type'] ), + 'package' => $is_no_update ? '' : $this->package_url( $slug, $entry['type'], $security_version ), 'requires' => $entry['requires_wp'], 'requires_php' => $entry['requires_php'], 'tested' => $entry['tested_up_to'], @@ -354,13 +424,15 @@ class Robotstxt_Manager_Updater { * 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. + * Security patches add a validated `version` parameter (Core 1.16.0+). * * @param string $slug Plugin slug. * @param string $type Plugin type ('free' or 'premium'). + * @param string $security_version Patch version when serving a security patch. * * @return string Package URL. */ - private function package_url( string $slug, string $type ): string { + private function package_url( string $slug, string $type, string $security_version = '' ): string { $client = Robotstxt_Manager_Core_Client::from_options(); $url = $client->get_store_url() . '/wp-json/robotstxt-core/v1/plugins/' . rawurlencode( $slug ) . '/download'; @@ -368,6 +440,10 @@ class Robotstxt_Manager_Updater { 'domain' => $this->site_domain(), ); + if ( '' !== $security_version ) { + $args['version'] = rawurlencode( $security_version ); + } + if ( 'premium' === $type ) { // Preferred: a short-lived download token (Core 1.9.0+) — keeps the // long-lived API key out of the update transient and access logs. diff --git a/languages/robotstxt-manager-ca.mo b/languages/robotstxt-manager-ca.mo index cdc25e04ce56363118a820783e47daaf7f277035..577499a1123f679e33a07f4cd1aa7779a2542d1f 100644 GIT binary patch delta 2686 zcmbW&TWl0n9LMofTe>Y!id+Ol+M%>iN@*z+fl>;s7cL?y7ZFA1u-$DJmfdA%wu&Jv zC?FV#NQ+T|_#m_*@?aKuAo?IxNr?KOQKO;~VnQS&iqZJsi{IbQa8VND{5u?F%J*ot{h`t!&8jMlNGs}?@-rW#$os?D)9g}8Kx4I(kaDy9EHpAHjH5z z?!zRS3%#H-XfoXYr1)CvpvvJx4E z7qA?=v7cqkUObqlqf%CmHD)H(A%A8)FAX`A7`1}4q%S{3ef|Zu;~%IMt))Cltlif| zJ~G>pT{TBA!2M365;{}vZCUygI-2+^oPvWZjH$+EEanR_WEafV@!qL_2~~+VaUcGO zs#t1*F)Z3Vhr0e4YGLpDp2MHG-{*LN_uu1LB;m9<%*H9B<4ASQDb$TVLuGyyN!kQB zd7K|J$+rcy!c|BPW(!`y*RdY=v5Ld^A?mSio9u0ggL=xI!F)acuhL;#%n{TN&j*-? z7g4AB5^AMaQ3*U<;~kz?kQJC-e|!>E$ul?r|3p1zfm*7HMaZH}32MFtSitj zf)-^;_oq`$hpL$w7{DMZfeonAZp8*XirR_`$gwba)4i2cq0Yo2)ctlK^Jag(Iab+6 zZ4p|8ww6L>&l;Ddv;RyU<_(ox2j=E>w?ELTv^Cm}RzhW0TSD-No8=nNl!R8AtHD2- zwqTC`-dHRq>b3u~=_qTp`5xC_@1Zw=(7w$hRE5Qas-iZOSW2uUh7&7@#|V{qB%!8? zt|s(9M(t68GifS`1=|0)#4192a;<4!)gC8wHcB<{n@+tRv76f(2HL_ZVi>`eXa9*o zcB>@E$2>$d6AuzPoSHw|T6him{TVpO?=SRSi^~W-I`3Z{|~ce9mBwApURvODca)Ls`2H8j@x zmsk~b<&IU^SuqtZ?%Hp4R!l2*Ce~Puw$+ggh3Ax2)R#MDO)I=R#Ntmh*4j^9tVC<5&F<^{`*ev=lKi8#w?LBFlnPm4)gjp#;Uk8QYel2+pw`N5 tY|H*v_Tf}4NO7F5!zCj#V@rAm&AgGxf2~lPr9JaDi*1{jIa_@o|1X2DaNYm_ delta 2143 zcmX}teQeEF9LMo*xuyD`T2vKtP3K{=x)^#}HrK;i^Dv5;+R!DdtRfRch9a6}g_w9*W-B*rlZ?_mi(#VjoDW|oODtjBT;@H^~^rTlU@ zPQoIr!}K(>&B!AH+s-PiWxH_z9>fo@4T;72XY&UbMb@+-*au^nkCk`>7b9!hIHHsZ z9Emeg0n}p@kKtWBk9i99>+WX!SR0pd*p7Ah9}84x(Kt}0s$kF;mtz5biZ9}R9E?9;3h&~}*i00g@eGoL zO=UGDP=zXS4Qlu5u@DLr-1rTXRwSLwb+7_F^@Va^ZB?0Kfz|a zj-4s>3ZAA)d`#IAcmVa_Tc`l542*O z`Bl_jW|7WK9$bj0nEy@Ms!%H@b~K(wW%vXYh`nI82ePmUzd%*6gr}F{cpQYQ0tRI) zyc-qyRJnSO*8um6-Yb3)K>k1dhl(GVFBeG zfk6d>T>fA!QeE3r9G>z+s0y6Go%jG%sf{nW!7R|bl_1GVQl!^5{=l(!O<;ZhuepW#FN24BN{9E8Jo3H27#y%KK4 zdSv@;8>+HjB1N_q)c4FqjOhKp&!8JO9-%VrK)r@rsgDlL5zNMW7o8QDru*1%J(y6 zqpNI#{5|cd_D_d#g1?uK3+Qjrm5{2X_8$EWHwr7H>MWwq_V+5a|H@ohsV%1KRF9?W zb(u_8DQD8zR(IgQ{hxRTb#QCwoMhLQa#g9-IM43Oi?0gTB3yIm+TvSecurity update available: %1$s (v%2$s → v%3$s). Update now — this is a security patch for the version this site runs, not a feature update." +msgstr "Actualització de seguretat disponible: %1$s (v%2$s → v%3$s). Actualitza ara — és un pedaç de seguretat per a la versió que fa servir aquest lloc, no una actualització de funcions." + +msgid "Security update available (v%1$s → v%2$s)" +msgstr "Actualització de seguretat disponible (v%1$s → v%2$s)" diff --git a/languages/robotstxt-manager-es_ES.mo b/languages/robotstxt-manager-es_ES.mo index f82d8d7fe1b5a598d265d774bb3fdb50249293bd..ecaef84b6fbcd325decf227b2e9be0db382a864b 100644 GIT binary patch delta 2652 zcmbW&TWnNC9LMp&mR=|qEg(Xn45ck}DJ_L^YfHIVE(&-lh?jyrY<(L5RUc5==B2qKSrRd{7e+A^1Q-h_}SVN58*4rwz)R6aM|ooIQJH z{{NY?^hH1DO8;D(d)m;##B`!Q$C!THl*b=hPk}KPa4(L-nT5vG;Cw8?HXMVUcnLjx z4d-$(jb||*E4aA|=i@Zoj-y9$OM;H(dUdQZ%wf_v1CQb^975J)3YmR0jzi`!6LAX8 zz+zmCKjH?|f@Y8Rt-)Hx%TWt((c%E!)Dkbzd4vn6CKyB44B-;|5WDamvPQH0VSnXe zoXGffRAwhJ7ti34QO3N5ml;>GD%NgppzgbilkiW}!t;5^B$a3;otapVQ*kRkiZLw1 z!#Eq?#SC7<5FX@dhw(imX;U}ZPhcgg#9L66b@4ImL2YqAs**#PRw>WZ@$fQ|tO+r@ zR^EWhe0?x(#d(a|QCoEkHNk1ra|Tfp4522xirR{wumta*Dv?9_nx|+A_16t0TzD2M zaU<@>C|<=i7~*NFz$>^D-@pO<9djsoA7xWTj*|9j%%Gn87ixi;U6ss7Eg*!d*uoO( zuhOsLLL;`L_Gl1!wz-J<{3~3JcTtroC(V0IT!)`9E-elA8W-q3RE8ydSqqtm*KrA+ z!D4I7alDYGqtZ3bGNuVzP%AxzRZMUewc>BMD1ShUH?a#<7G2+i%G5(W?*Os}(}z^u zyn{tN?<3TLz9{$aOMgpe2^X%T_O`f!cNd%SZ`_TlNPDG!>QlIsaX%`P&roNiZH`~5 zPE_TNqRzr9 zz{M_1;YPfLdR;@Dw5RY*q$uWBd=aPCv3N{kIVGR5kdw}M%_3u3F@<{WmzW+y=Q}!7 z&D=um+1Pr&^pjC%A%udRuMW_n+OhlwoT-(ez>+1{CJve#7o3FLT`bZwzQ4V|1oOX zqH02w-JtznNxVR)bi-|P(Aj|{gvveKbn0~w?{7O9XbW`?CK1|iz1C_v)M^|X^9-?? zc$yeZX#Q+l!{2bwUy9>{z7Cw)%S0=oSM*6jC%u@^d6-S;l~mJ-e<<5CW-U5FUo;a< zgbpodQ!TT!Aa_){$x9~UvE9wBk#H*ECimM^PluC?Sf|%~uZLKgW8* z^Yl2$aHm}yjVJgtVtXSA&yC0YJ3W^bBvLW2+Q#BZz7>r)$y6c|EUqTgP}n%OV0gg; zzfCR6c$3c+j3l!%%;0G8&`m|zv1)mDOkuPdcpj>~7z>oD2$h+*8*}j(zKUmY9)68+e2A}MAEW5O zVPq1vhTb%R7F3E`QLER9Gq4M_v=>mB971LCTXfo~+@-?ct>$HKDf-?xR^acr z0P{)jd~_P9Jf(5~m5IHD-Y!3iOKG1*&E#j)p4eUFrLY~9u|Cw^_!2d+n|PlAe1pH~ z^Aayphj^O4*Nv=-#gM(@SU(j#cm$P_QDm8I3N@pgQt$o(-zJ>o{(fABTi6I+;Azy7 zR4_fc8r4rTYG5Cs-Y@%*v02Q$?{KuJOykBK{1it}KU}}i>u3)KXm|MSZnCv>Tnl_4Q_%G^|B);zbt^hf?R)PVZZ`D+^#u045LDaFF!gulGGH*w2`jI)=AZkDpsEkjdLzZodijG4Oy=bJZs2Lpb zy@)yu!>Ep*BG0v5go>6;MYB>-Ha8NrgbIho-AnJ|+r$S%3&ArSH)Yh_qPJKIk?1vf zKbx%}l;*kqJ#8MXqh_F^q!J`H5$_Pi1Y5x^+qtgv>u+Kmv4v>z@73%5uT7*hsca*( z_DhILVilnoHxewPy+x?#C~YQqp}GG#bp%y*5=ym-GP{FNMpcrDOky>mWn^`&is!qf znQI;qa$DZcU+>qorq7FBHqQ&oZtwXG?HhLqk?mous9VrlI08;4hbah%-};i_Gq5#N)Ro)Dj(zAPhtGWSYaJQ!-viVv3^O#L52 CTEd_J diff --git a/languages/robotstxt-manager-es_ES.po b/languages/robotstxt-manager-es_ES.po index e691fd8..02a4ba7 100644 --- a/languages/robotstxt-manager-es_ES.po +++ b/languages/robotstxt-manager-es_ES.po @@ -582,3 +582,10 @@ msgstr "La tienda respondió con HTTP %d. Comprueba la URL de la tienda y la cla #: includes/class-robotstxt-manager-core-client.php:177 msgid "Connected." msgstr "Conectado." + +#: includes/class-robotstxt-manager-updater.php admin/class-robotstxt-manager-admin.php +msgid "Security update available: %1$s (v%2$s → v%3$s). Update now — this is a security patch for the version this site runs, not a feature update." +msgstr "Actualización de seguridad disponible: %1$s (v%2$s → v%3$s). Actualizar ahora — es un parche de seguridad para la versión que usa este sitio, no una actualización de funciones." + +msgid "Security update available (v%1$s → v%2$s)" +msgstr "Actualización de seguridad disponible (v%1$s → v%2$s)" diff --git a/languages/robotstxt-manager.pot b/languages/robotstxt-manager.pot index f7ac393..3eefa16 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) 1.8.0\n" -"Report-Msgid-Bugs-To: https://www.robotstxt.software/plugins/robotstxt-manager/\n" +"Project-Id-Version: Manager (by ROBOTSTXT) 1.9.1\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-17T14:52:09+00:00\n" +"POT-Creation-Date: 2026-09-23T04:51:33+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" @@ -31,7 +31,7 @@ msgstr "" #. Author of the plugin #: robotstxt-manager.php -#: admin/class-robotstxt-manager-admin.php:48 +#: admin/class-robotstxt-manager-admin.php:52 msgid "ROBOTSTXT" msgstr "" @@ -40,49 +40,56 @@ msgstr "" msgid "https://www.robotstxt.software/" msgstr "" -#: admin/class-robotstxt-manager-admin.php:47 -#: admin/views/page-catalog.php:66 +#: admin/class-robotstxt-manager-admin.php:51 +#: admin/views/page-catalog.php:67 msgid "Manager (by ROBOTSTXT) — Plugins" msgstr "" -#: admin/class-robotstxt-manager-admin.php:64 -#: admin/class-robotstxt-manager-settings.php:207 +#: admin/class-robotstxt-manager-admin.php:68 +#: admin/class-robotstxt-manager-settings.php:210 msgid "You do not have sufficient permissions to access this page." msgstr "" +#. translators: 1: plugin name, 2: installed version, 3: patch version, 4: update URL. +#: admin/class-robotstxt-manager-admin.php:168 +#: admin/views/page-catalog.php:90 +#, php-format +msgid "Security update available: %1$s (v%2$s → v%3$s). Update now — this is a security patch for the version this site runs, not a feature update." +msgstr "" + #. translators: %s: plugin slug. -#: admin/class-robotstxt-manager-admin.php:108 +#: admin/class-robotstxt-manager-admin.php:206 #, 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 +#: admin/class-robotstxt-manager-admin.php:217 #, 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 +#: admin/class-robotstxt-manager-admin.php:230 #, 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] "" -#: 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 +#: admin/class-robotstxt-manager-admin.php:251 +#: admin/class-robotstxt-manager-installer.php:433 +#: admin/class-robotstxt-manager-settings.php:462 +#: admin/class-robotstxt-manager-settings.php:506 msgid "Insufficient permissions." msgstr "" -#: admin/class-robotstxt-manager-admin.php:197 +#: admin/class-robotstxt-manager-admin.php:295 msgid "Too many refreshes. Please wait a minute before refreshing again." msgstr "" #: admin/class-robotstxt-manager-installer.php:51 -#: admin/class-robotstxt-manager-installer.php:518 +#: admin/class-robotstxt-manager-installer.php:532 msgid "Plugin not found in catalog." msgstr "" @@ -117,39 +124,39 @@ msgid "No download found on WordPress.org for %s." msgstr "" #: admin/class-robotstxt-manager-installer.php:287 -#: admin/class-robotstxt-manager-installer.php:540 +#: admin/class-robotstxt-manager-installer.php:570 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 +#: admin/class-robotstxt-manager-installer.php:603 #, 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 +#: admin/class-robotstxt-manager-installer.php:630 #, php-format msgid "Download failed (HTTP %d)." msgstr "" #: admin/class-robotstxt-manager-installer.php:316 -#: admin/class-robotstxt-manager-installer.php:597 +#: admin/class-robotstxt-manager-installer.php:639 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 +#: admin/class-robotstxt-manager-installer.php:664 #, 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 +#: admin/class-robotstxt-manager-installer.php:667 msgid "Installation failed." msgstr "" @@ -160,7 +167,7 @@ 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 +#: admin/class-robotstxt-manager-installer.php:396 msgid "Plugin is not installed." msgstr "" @@ -171,278 +178,297 @@ msgid "%s activated." msgstr "" #. translators: %s: plugin name (slug). -#: admin/class-robotstxt-manager-installer.php:406 +#: admin/class-robotstxt-manager-installer.php:418 #, php-format msgid "%s updated." msgstr "" -#: admin/class-robotstxt-manager-installer.php:512 +#: admin/class-robotstxt-manager-installer.php:526 msgid "Store is not configured." msgstr "" -#: admin/class-robotstxt-manager-settings.php:59 +#. translators: 1: HTTP status code, 2: store error message. +#: admin/class-robotstxt-manager-installer.php:624 +#, php-format +msgid "Download failed (HTTP %1$d): %2$s" +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:62 #: admin/views/page-settings.php:13 msgid "Manager (by ROBOTSTXT) — Settings" msgstr "" -#: admin/class-robotstxt-manager-settings.php:60 +#: admin/class-robotstxt-manager-settings.php:63 msgid "Settings" msgstr "" -#: admin/class-robotstxt-manager-settings.php:75 +#: admin/class-robotstxt-manager-settings.php:78 msgid "Connection" msgstr "" -#: admin/class-robotstxt-manager-settings.php:92 +#: admin/class-robotstxt-manager-settings.php:95 msgid "Store URL" msgstr "" -#: admin/class-robotstxt-manager-settings.php:110 +#: admin/class-robotstxt-manager-settings.php:113 msgid "API Key" msgstr "" -#: admin/class-robotstxt-manager-settings.php:118 +#: admin/class-robotstxt-manager-settings.php:121 msgid "Cache" msgstr "" -#: admin/class-robotstxt-manager-settings.php:135 +#: admin/class-robotstxt-manager-settings.php:138 msgid "Catalog Cache (minutes)" msgstr "" -#: admin/class-robotstxt-manager-settings.php:153 +#: admin/class-robotstxt-manager-settings.php:156 msgid "Data on Uninstall" msgstr "" -#: admin/class-robotstxt-manager-settings.php:188 +#: admin/class-robotstxt-manager-settings.php:191 msgid "Testing…" msgstr "" -#: admin/class-robotstxt-manager-settings.php:189 -#: admin/class-robotstxt-manager-settings.php:274 +#: admin/class-robotstxt-manager-settings.php:192 +#: admin/class-robotstxt-manager-settings.php:349 msgid "Test connection" msgstr "" -#: admin/class-robotstxt-manager-settings.php:190 +#: admin/class-robotstxt-manager-settings.php:193 msgid "Deleting…" msgstr "" -#: admin/class-robotstxt-manager-settings.php:191 -#: admin/class-robotstxt-manager-settings.php:278 +#: admin/class-robotstxt-manager-settings.php:194 +#: admin/class-robotstxt-manager-settings.php:353 msgid "Delete API key" msgstr "" -#: admin/class-robotstxt-manager-settings.php:192 +#: admin/class-robotstxt-manager-settings.php:195 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 +#: admin/class-robotstxt-manager-settings.php:196 +#: admin/class-robotstxt-manager-settings.php:515 msgid "API key deleted. Save changes to persist." msgstr "" -#: admin/class-robotstxt-manager-settings.php:194 +#: admin/class-robotstxt-manager-settings.php:197 msgid "An unexpected error occurred." msgstr "" -#: admin/class-robotstxt-manager-settings.php:226 +#: admin/class-robotstxt-manager-settings.php:233 +msgid "You do not have sufficient permissions to manage settings." +msgstr "" + +#: admin/class-robotstxt-manager-settings.php:292 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 +#: admin/class-robotstxt-manager-settings.php:323 #, 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 +#: admin/class-robotstxt-manager-settings.php:328 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 +#. translators: %s: Registration URL. +#: admin/class-robotstxt-manager-settings.php:337 +#, php-format 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 +#: admin/class-robotstxt-manager-settings.php:373 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 +#: admin/class-robotstxt-manager-settings.php:388 msgid "Delete all plugin data when the plugin is uninstalled." msgstr "" -#: admin/class-robotstxt-manager-settings.php:343 +#: admin/class-robotstxt-manager-settings.php:426 msgid "The API key format is invalid. Copy the full key from your ROBOTSTXT account page." msgstr "" -#: admin/views/page-catalog.php:69 +#: admin/views/page-catalog.php:70 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 +#: admin/views/page-catalog.php:74 msgid "Catalog refreshed." msgstr "" #. translators: %s: store registration URL. -#: admin/views/page-catalog.php:89 +#: admin/views/page-catalog.php:109 #, 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 "" #. translators: %s: settings URL. -#: admin/views/page-catalog.php:109 +#: admin/views/page-catalog.php:129 #, 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 +#: admin/views/page-catalog.php:139 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 +#: admin/views/page-catalog.php:142 +#: admin/views/page-catalog.php:145 msgid "Refresh catalog" msgstr "" -#: admin/views/page-catalog.php:131 +#: admin/views/page-catalog.php:151 msgid "Plugin" msgstr "" -#: admin/views/page-catalog.php:132 +#: admin/views/page-catalog.php:152 msgid "Version" msgstr "" -#: admin/views/page-catalog.php:133 +#: admin/views/page-catalog.php:153 msgid "Requires WP" msgstr "" -#: admin/views/page-catalog.php:134 +#: admin/views/page-catalog.php:154 msgid "Requires PHP" msgstr "" -#: admin/views/page-catalog.php:135 +#: admin/views/page-catalog.php:155 msgid "Price" msgstr "" -#: admin/views/page-catalog.php:136 +#: admin/views/page-catalog.php:156 msgid "Action" msgstr "" -#: admin/views/page-catalog.php:137 +#: admin/views/page-catalog.php:157 msgid "Status" msgstr "" -#: admin/views/page-catalog.php:239 +#: admin/views/page-catalog.php:267 msgid "Your site runs WordPress" msgstr "" -#: admin/views/page-catalog.php:251 +#: admin/views/page-catalog.php:279 msgid "Your server runs PHP" msgstr "" #. translators: %s: formatted price number. -#: admin/views/page-catalog.php:264 +#: admin/views/page-catalog.php:292 #, php-format msgid "€%s / year" msgstr "" #. translators: %d: days remaining. -#: admin/views/page-catalog.php:282 +#: admin/views/page-catalog.php:310 #, php-format msgid "Subscribed — %d days left" msgstr "" -#: admin/views/page-catalog.php:286 +#: admin/views/page-catalog.php:314 +#: admin/views/page-catalog.php:340 msgid "Subscribed" msgstr "" -#: admin/views/page-catalog.php:289 +#: admin/views/page-catalog.php:317 msgid "Payment failed" msgstr "" -#: admin/views/page-catalog.php:291 +#: admin/views/page-catalog.php:319 msgid "Cancelled" msgstr "" -#: admin/views/page-catalog.php:293 +#: admin/views/page-catalog.php:321 msgid "Expired" msgstr "" -#: admin/views/page-catalog.php:303 +#: admin/views/page-catalog.php:350 msgid "Free" msgstr "" -#: admin/views/page-catalog.php:309 +#: admin/views/page-catalog.php:356 msgid "Incompatible" msgstr "" -#: admin/views/page-catalog.php:312 +#: admin/views/page-catalog.php:359 msgid "Buy" msgstr "" -#: admin/views/page-catalog.php:317 +#: admin/views/page-catalog.php:364 msgid "Install" msgstr "" -#: admin/views/page-catalog.php:319 +#: admin/views/page-catalog.php:366 msgid "Activate" msgstr "" -#: admin/views/page-catalog.php:321 +#: admin/views/page-catalog.php:368 msgid "Update" msgstr "" -#: admin/views/page-catalog.php:327 +#: admin/views/page-catalog.php:374 msgid "Not installed" msgstr "" -#: admin/views/page-catalog.php:329 +#: admin/views/page-catalog.php:376 msgid "Installed (inactive)" msgstr "" +#. translators: 1: installed version, 2: security patch version. +#: admin/views/page-catalog.php:381 +#, php-format +msgid "Security update available (v%1$s → v%2$s)" +msgstr "" + #. translators: 1: installed version, 2: available version. -#: admin/views/page-catalog.php:334 +#: admin/views/page-catalog.php:390 #, php-format msgid "Update available (v%1$s → v%2$s)" msgstr "" -#: admin/views/page-catalog.php:340 +#: admin/views/page-catalog.php:396 msgid "Up to date" msgstr "" -#: admin/views/page-catalog.php:349 +#: admin/views/page-catalog.php:405 msgid "Visit website" msgstr "" #. translators: %s: plugin name. -#: admin/views/page-catalog.php:349 +#: admin/views/page-catalog.php:405 #, php-format msgid "(about %s)" msgstr "" #. translators: %s: list of plugin slugs. -#: admin/views/page-catalog.php:360 +#: admin/views/page-catalog.php:416 #, php-format msgid "Requires: %s" msgstr "" -#: admin/views/page-catalog.php:374 +#: admin/views/page-catalog.php:430 msgid "Support" msgstr "" -#: admin/views/page-catalog.php:376 +#: admin/views/page-catalog.php:432 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 +#: admin/views/page-catalog.php:435 msgid "Payments, and how it works" msgstr "" -#: admin/views/page-catalog.php:381 +#: admin/views/page-catalog.php:437 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 +#: admin/views/page-catalog.php:447 #, 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." @@ -450,7 +476,7 @@ msgstr[0] "" msgstr[1] "" #. translators: 1: local WP version, 2: local PHP version. -#: admin/views/page-catalog.php:400 +#: admin/views/page-catalog.php:456 #, php-format msgid "This site runs WordPress %1$s on PHP %2$s." msgstr "" @@ -469,16 +495,16 @@ msgstr "" msgid "Could not reach Plugins Core: %s" msgstr "" +#: includes/class-robotstxt-manager-core-client.php:167 +msgid "Invalid API key. Please check your key and try again." +msgstr "" + #. translators: %d: HTTP status code. -#: includes/class-robotstxt-manager-core-client.php:168 +#: includes/class-robotstxt-manager-core-client.php:175 #, php-format 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 +#: includes/class-robotstxt-manager-core-client.php:184 msgid "Connected." msgstr "" diff --git a/readme.txt b/readme.txt index edf63d6..dbbbd43 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.8.1 +Stable tag: 1.9.1 Requires PHP: 8.0 -Version: 1.8.1 +Version: 1.9.1 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -94,6 +94,19 @@ 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.9.1 = + +_Release date: 2026-09-23_ + +* Maintenance: dependency audit (no updates, no CVEs), full code and security review of the security-patch feature, compatibility floors re-verified (WordPress 4.4, PHP 8.0). No runtime changes. + += 1.9.0 = + +_Release date: 2026-09-22_ + +* New: security-update notices (with Core 1.16.0+). When the store declares a security patch for the exact version your site runs, a red "Update now" notice appears on the Plugins screen and the Manager catalog, and the update installs only that patch — your site is never pushed across feature versions by a security fix. +* The WordPress update badge, `wp plugin update`, and auto-updates also target the declared patch. + = 1.8.1 = _Release date: 2026-09-22_ @@ -103,19 +116,6 @@ _Release date: 2026-09-22_ * Fixed: a hardcoded "Subscribed" label in the multi-license catalog pill is now translatable. * Maintenance: development tooling updated (PHPStan max, WordPress stubs 7.1.0); compatibility floors re-verified (WordPress 4.4, PHP 8.0). -= 1.8.0 = - -_Release date: 2026-08-24_ - -* New: multi-domain license awareness (with Core 1.14.0) — the account's subscriptions are grouped per plugin and catalog pills show how many licenses you hold (e.g. "Subscribed @ example.com ×2") with the domain of the first bound license. - -= 1.7.0 = - -_Release date: 2026-08-18_ - -* New: per-domain license support (Core 1.11.0+) — downloads and download-token exchanges now send this site's domain, so premium licenses bind to this site and the store rejects them from other domains. Catalog subscription pills show the bound domain (e.g. "Subscribed @ example.com"). -* Improvement: download failures now surface the store's own error message (e.g. the domain-mismatch explanation) instead of a bare HTTP status code. - = 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 2cf7de2..9ee06ee 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.8.1 + * Version: 1.9.1 * Requires at least: 4.4 * Requires PHP: 8.0 * Update URI: https://www.robotstxt.software/plugins/robotstxt-manager/ @@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.8.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.9.1' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) );