ARGUS WordPress Defence 7.23.2

- New: License section in Settings -- a 30-day trial with every
  feature active starts automatically, no registration required to
  start. Protection continues regardless of license status; the trial
  only affects what's shown, never what's enforced. Enter a license
  key to keep Premium features once the trial ends. This is the first
  release where the license system is actually visible anywhere --
  it existed in the codebase before but had no entry form and was
  excluded from every published build.
- Intelligence page: rewrote descriptions in plain language about what
  each thing does for your site's protection, not internal
  implementation details (was showing raw technical descriptions like
  a "signature corpus" and "RIR delegation database" range counts).

Verified end-to-end before publishing (not just code review): a real
signup and license created on the actual companion license service,
then submitted through the real Settings page form exactly as a
customer would -- status went from "TRIAL -- 30 DAYS LEFT" to
"LICENSED" on both the Settings and Overview pages. This exact ZIP was
also installed fresh and every admin page loaded with zero errors
before this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ARGUS
2026-08-09 19:37:16 +00:00
co-authored by Claude Sonnet 5
parent 8a6c9ca4de
commit c4db87af84
8 changed files with 212 additions and 46 deletions
+6 -1
View File
@@ -4,7 +4,7 @@ Automatic WordPress security. A local firewall, malware and file-integrity scann
vulnerability protection, and scheduled backups — protecting your site with zero manual vulnerability protection, and scheduled backups — protecting your site with zero manual
configuration required. configuration required.
**Version:** 7.23.1 **Version:** 7.23.2
**Requires:** WordPress 6.0+, PHP 7.4+ **Requires:** WordPress 6.0+, PHP 7.4+
**License:** GPLv2 or later **License:** GPLv2 or later
@@ -30,6 +30,11 @@ configuration required.
Everything except Global Threat Intelligence works out of the box. There is nothing to Everything except Global Threat Intelligence works out of the box. There is nothing to
configure to get protected. configure to get protected.
**License**: a 30-day trial with every feature active starts automatically on activation --
no registration required to start. Protection never stops when the trial ends; ARGUS Defence →
Settings → License shows the days remaining and where to enter a license key to keep Premium
features. This section is specific to this self-distributed build.
## Status ## Status
This is a self-distributed release: it is not listed on WordPress.org and has not gone This is a self-distributed release: it is not listed on WordPress.org and has not gone
+6
View File
@@ -655,6 +655,12 @@ class Argus_Admin {
public static function render_settings() { public static function render_settings() {
$saved = false; $saved = false;
$template_applied = null; $template_applied = null;
$license_result = null;
if ( class_exists( 'Argus_License' ) && isset( $_POST['argus_wpd_activate_license_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_activate_license_nonce'] ) ), 'argus_wpd_activate_license' ) ) { // phpcs:ignore
Argus_License::set_license_key( sanitize_text_field( wp_unslash( $_POST['license_key'] ?? '' ) ) ); // phpcs:ignore
$license_result = Argus_License::validate_now();
}
if ( isset( $_POST['argus_wpd_apply_template_nonce'], $_POST['template'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_apply_template_nonce'] ) ), 'argus_wpd_apply_template' ) ) { // phpcs:ignore if ( isset( $_POST['argus_wpd_apply_template_nonce'], $_POST['template'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_apply_template_nonce'] ) ), 'argus_wpd_apply_template' ) ) { // phpcs:ignore
$key = sanitize_key( wp_unslash( $_POST['template'] ) ); // phpcs:ignore $key = sanitize_key( wp_unslash( $_POST['template'] ) ); // phpcs:ignore
+19 -31
View File
@@ -40,75 +40,63 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
<div class="argus-wpd-tile-row"> <div class="argus-wpd-tile-row">
<div class="argus-wpd-tile"> <div class="argus-wpd-tile">
<div class="argus-wpd-tile-label"><?php esc_html_e( 'WAF Rule Corpus', 'argus-wordpress-defence' ); ?></div> <div class="argus-wpd-tile-label"><?php esc_html_e( 'Firewall', 'argus-wordpress-defence' ); ?></div>
<div class="argus-wpd-tile-value"><?php echo esc_html( number_format_i18n( $waf_rule_count ) ); ?></div> <div class="argus-wpd-tile-value" style="font-size:16px;"><span style="color:var(--emerald)"><?php esc_html_e( 'Protecting', 'argus-wordpress-defence' ); ?></span></div>
</div> </div>
<div class="argus-wpd-tile"> <div class="argus-wpd-tile">
<div class="argus-wpd-tile-label"><?php esc_html_e( 'Vulnerability Intelligence', 'argus-wordpress-defence' ); ?></div> <div class="argus-wpd-tile-label"><?php esc_html_e( 'Vulnerability Protection', 'argus-wordpress-defence' ); ?></div>
<div class="argus-wpd-tile-value" style="font-size:16px;"> <div class="argus-wpd-tile-value" style="font-size:16px;"><span style="color:var(--emerald)"><?php esc_html_e( 'Protecting', 'argus-wordpress-defence' ); ?></span></div>
<?php if ( $vuln_row_count > 0 ) : ?>
<span style="color:var(--emerald)"><?php esc_html_e( 'Active', 'argus-wordpress-defence' ); ?></span>
<?php else : ?>
<span style="color:var(--faint)"><?php esc_html_e( 'Standby', 'argus-wordpress-defence' ); ?></span>
<?php endif; ?>
</div>
</div> </div>
<div class="argus-wpd-tile"> <div class="argus-wpd-tile">
<div class="argus-wpd-tile-label"><?php esc_html_e( 'GeoIP Database', 'argus-wordpress-defence' ); ?></div> <div class="argus-wpd-tile-label"><?php esc_html_e( 'Location Awareness', 'argus-wordpress-defence' ); ?></div>
<div class="argus-wpd-tile-value" style="font-size:16px;"> <div class="argus-wpd-tile-value" style="font-size:16px;">
<?php if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) : ?> <?php if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) : ?>
<span style="color:var(--emerald)"><?php esc_html_e( 'Loaded', 'argus-wordpress-defence' ); ?></span> <span style="color:var(--emerald)"><?php esc_html_e( 'Active', 'argus-wordpress-defence' ); ?></span>
<?php else : ?> <?php else : ?>
<span style="color:var(--faint)"><?php esc_html_e( 'Building', 'argus-wordpress-defence' ); ?></span> <span style="color:var(--faint)"><?php esc_html_e( 'Activating', 'argus-wordpress-defence' ); ?></span>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
</div> </div>
<div class="argus-wpd-panel"> <div class="argus-wpd-panel">
<h2><?php esc_html_e( 'Local Intelligence Sources', 'argus-wordpress-defence' ); ?></h2> <h2><?php esc_html_e( 'What\'s Protecting Your Site', 'argus-wordpress-defence' ); ?></h2>
<div class="argus-wpd-ioc-row"> <div class="argus-wpd-ioc-row">
<span class="argus-wpd-ioc-type">WAF</span> <span class="argus-wpd-ioc-type"><?php esc_html_e( 'Firewall', 'argus-wordpress-defence' ); ?></span>
<span class="argus-wpd-ioc-desc"><?php echo esc_html( sprintf( __( 'Application-layer signature corpus (SQLi/XSS/RCE/path traversal/protocol anomaly) -- %d rules, updated with this plugin.', 'argus-wordpress-defence' ), $waf_rule_count ) ); ?></span> <span class="argus-wpd-ioc-desc"><?php esc_html_e( 'Blocks malicious requests -- SQL injection, cross-site scripting, remote code execution, path traversal, and more -- before they reach your site. Kept up to date automatically.', 'argus-wordpress-defence' ); ?></span>
</div> </div>
<div class="argus-wpd-ioc-row"> <div class="argus-wpd-ioc-row">
<span class="argus-wpd-ioc-type">VULN</span> <span class="argus-wpd-ioc-type"><?php esc_html_e( 'Vulnerability Protection', 'argus-wordpress-defence' ); ?></span>
<span class="argus-wpd-ioc-desc"> <span class="argus-wpd-ioc-desc">
<?php <?php
echo esc_html( echo esc_html(
$vuln_row_count > 0 $vuln_row_count > 0
? __( 'Vulnerability intelligence -- actively matching installed plugins and themes against known security issues.', 'argus-wordpress-defence' ) ? __( 'Actively protecting your installed plugins and themes by checking them against known security issues.', 'argus-wordpress-defence' )
: __( 'Vulnerability intelligence -- WordPress core is checked live against WordPress.org. Plugin and theme results activate automatically as intelligence becomes available.', 'argus-wordpress-defence' ) : __( 'Actively protecting WordPress core against known security issues. Plugin and theme protection activates automatically as new information becomes available.', 'argus-wordpress-defence' )
); );
?> ?>
</span> </span>
</div> </div>
<div class="argus-wpd-ioc-row"> <div class="argus-wpd-ioc-row">
<span class="argus-wpd-ioc-type">GEOIP</span> <span class="argus-wpd-ioc-type"><?php esc_html_e( 'Location Awareness', 'argus-wordpress-defence' ); ?></span>
<span class="argus-wpd-ioc-desc"> <span class="argus-wpd-ioc-desc">
<?php <?php
if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) { if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) {
echo esc_html( esc_html_e( 'Identifies where threats are coming from automatically, with no external service or configuration required.', 'argus-wordpress-defence' );
sprintf(
__( 'Self-hosted RIR delegation database -- %s IPv4 ranges, last refreshed %s.', 'argus-wordpress-defence' ),
number_format_i18n( $geoip_status['range_count'] ),
$geoip_status['last_refresh'] ? human_time_diff( strtotime( $geoip_status['last_refresh'] . ' UTC' ) ) . ' ' . __( 'ago', 'argus-wordpress-defence' ) : __( 'never', 'argus-wordpress-defence' )
)
);
} else { } else {
esc_html_e( 'Self-hosted RIR delegation database has not built its first snapshot yet -- refreshes automatically once daily, no action needed.', 'argus-wordpress-defence' ); esc_html_e( 'Activating automatically -- no action needed.', 'argus-wordpress-defence' );
} }
?> ?>
</span> </span>
</div> </div>
<div class="argus-wpd-ioc-row"> <div class="argus-wpd-ioc-row">
<span class="argus-wpd-ioc-type">ANIS</span> <span class="argus-wpd-ioc-type"><?php esc_html_e( 'Global Threat Network', 'argus-wordpress-defence' ); ?></span>
<span class="argus-wpd-ioc-desc"> <span class="argus-wpd-ioc-desc">
<?php <?php
if ( 'not_connected' === $anis_status['protection'] ) { if ( 'not_connected' === $anis_status['protection'] ) {
esc_html_e( 'Global threat intelligence -- connecting automatically.', 'argus-wordpress-defence' ); esc_html_e( 'Connecting automatically -- no action needed.', 'argus-wordpress-defence' );
} else { } else {
esc_html_e( 'Global threat intelligence -- automatically updated to help identify malicious sources.', 'argus-wordpress-defence' ); esc_html_e( 'Helps identify and block malicious sources seen across other protected sites, automatically.', 'argus-wordpress-defence' );
} }
?> ?>
</span> </span>
+38
View File
@@ -182,6 +182,44 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php if ( class_exists( 'Argus_License' ) ) : ?>
<div class="argus-wpd-panel">
<h2><?php esc_html_e( 'License', 'argus-wordpress-defence' ); ?></h2>
<?php $license_summary = Argus_License::summary(); ?>
<p style="font-size:13px;color:var(--emerald);margin:0 0 6px;display:flex;align-items:center;gap:8px;">
<span class="argus-wpd-status-chip chip-emerald" style="padding:2px 10px;">
<span class="dot"></span>
<span class="state">
<?php
if ( 'licensed' === $license_summary['status'] ) {
esc_html_e( 'LICENSED', 'argus-wordpress-defence' );
} elseif ( 'trial' === $license_summary['status'] ) {
echo esc_html( sprintf(
/* translators: %d: number of days remaining in the free trial */
_n( 'TRIAL -- %d DAY LEFT', 'TRIAL -- %d DAYS LEFT', $license_summary['days_remaining'], 'argus-wordpress-defence' ),
$license_summary['days_remaining']
) );
} else {
esc_html_e( 'TRIAL ENDED', 'argus-wordpress-defence' );
}
?>
</span>
</span>
</p>
<?php if ( ! empty( $license_result ) ) : ?>
<p style="font-size:12.5px;margin:8px 0 0;color:<?php echo ! empty( $license_result['success'] ) ? 'var(--emerald)' : 'var(--rose)'; ?>;"><?php echo esc_html( $license_result['message'] ); ?></p>
<?php endif; ?>
<form method="post" style="margin-top:14px;display:flex;gap:8px;align-items:center;">
<?php wp_nonce_field( 'argus_wpd_activate_license', 'argus_wpd_activate_license_nonce' ); ?>
<input type="text" name="license_key" placeholder="<?php esc_attr_e( 'ARGUS-XXXX-XXXX-...', 'argus-wordpress-defence' ); ?>" value="<?php echo esc_attr( $license_summary['has_key'] ? Argus_License::license_key() : '' ); ?>" style="flex:1;max-width:380px;padding:8px 10px;border-radius:6px;border:1px solid var(--border-strong);background:var(--panel-2);color:var(--text);font-family:var(--mono);font-size:13px;" />
<button type="submit" class="button button-primary"><?php esc_html_e( 'Activate', 'argus-wordpress-defence' ); ?></button>
</form>
<?php if ( $license_summary['purchase_url'] ) : ?>
<p style="font-size:12.5px;margin:12px 0 0;"><a href="<?php echo esc_url( $license_summary['purchase_url'] ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Get a license', 'argus-wordpress-defence' ); ?></a></p>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="argus-wpd-panel"> <div class="argus-wpd-panel">
<h2><?php esc_html_e( 'Website Acceleration', 'argus-wordpress-defence' ); ?></h2> <h2><?php esc_html_e( 'Website Acceleration', 'argus-wordpress-defence' ); ?></h2>
<p style="color:var(--faint);font-size:12.5px;margin:-8px 0 12px;"><?php esc_html_e( 'Cache & Performance now has its own dedicated page -- speed up your website, reduce load, and see what\'s being accelerated.', 'argus-wordpress-defence' ); ?></p> <p style="color:var(--faint);font-size:12.5px;margin:-8px 0 12px;"><?php esc_html_e( 'Cache & Performance now has its own dedicated page -- speed up your website, reduce load, and see what\'s being accelerated.', 'argus-wordpress-defence' ); ?></p>
+2 -2
View File
@@ -3,7 +3,7 @@
* Plugin Name: ARGUS WordPress Defence * Plugin Name: ARGUS WordPress Defence
* Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence * Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence
* Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation. * Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation.
* Version: 7.23.1 * Version: 7.23.2
* Requires at least: 6.0 * Requires at least: 6.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Author: ARGUS * Author: ARGUS
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
define( 'ARGUS_WPD_VERSION', '7.23.1' ); define( 'ARGUS_WPD_VERSION', '7.23.2' );
define( 'ARGUS_WPD_FILE', __FILE__ ); define( 'ARGUS_WPD_FILE', __FILE__ );
define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) ); define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) );
define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) ); define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) );
+7 -11
View File
@@ -46,24 +46,20 @@ rm -rf \
if [ "${CHANNEL}" = "wporg" ]; then if [ "${CHANNEL}" = "wporg" ]; then
# A plugin hosted on WordPress.org must rely solely on WordPress.org's own # A plugin hosted on WordPress.org must rely solely on WordPress.org's own
# update channel -- never a self-update-from-external-manifest mechanism, # update channel -- never a self-update-from-external-manifest mechanism,
# even one that ships inert-by-default. class_exists( 'Argus_Update_Client' ) # even one that ships inert-by-default -- and must stay genuinely,
# guards every reference to this class elsewhere in the codebase, so # permanently functional with no license/trial requirement (WordPress.org
# removing the file here is sufficient; nothing else needs to change. # explicitly prohibits trialware). class_exists() guards every reference
# to both classes elsewhere in the codebase, so removing the files here is
# sufficient; nothing else needs to change per build.
echo "==> [wporg channel] Removing the self-update client (WordPress.org must be the only update channel)" echo "==> [wporg channel] Removing the self-update client (WordPress.org must be the only update channel)"
rm -f "${STAGE_DIR}/includes/class-argus-update-client.php" rm -f "${STAGE_DIR}/includes/class-argus-update-client.php"
echo "==> [wporg channel] Removing the license/trial system (WordPress.org prohibits trialware)"
rm -f "${STAGE_DIR}/includes/class-argus-license.php"
elif [ "${CHANNEL}" != "self" ]; then elif [ "${CHANNEL}" != "self" ]; then
echo "==> BUILD FAILED: unknown channel '${CHANNEL}' (expected 'self' or 'wporg')" echo "==> BUILD FAILED: unknown channel '${CHANNEL}' (expected 'self' or 'wporg')"
exit 1 exit 1
fi fi
# WordPress.org explicitly prohibits trialware -- a plugin submitted to the
# directory must stay genuinely, permanently functional with no license
# requirement. class_exists( 'Argus_License' ) guards every reference to
# this class elsewhere, so removing the file here is sufficient; the free
# build never has a trial timer at all.
echo "==> Removing the license/trial system (WordPress.org prohibits trialware)"
rm -f "${STAGE_DIR}/includes/class-argus-license.php"
echo "==> Allowlist audit -- fail closed on anything unexpected" echo "==> Allowlist audit -- fail closed on anything unexpected"
UNEXPECTED=0 UNEXPECTED=0
while IFS= read -r -d '' item; do while IFS= read -r -d '' item; do
+127
View File
@@ -0,0 +1,127 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Premium-build-only, same as Argus_Update_Client: excluded from the
// WordPress.org release ZIP (bin/build-release.sh), guarded everywhere with
// class_exists() so the free build never references it. WP.org's guidelines
// prohibit trialware in a plugin submitted to the directory -- the free
// build stays genuinely, permanently functional, with no trial timer and no
// license requirement anywhere in it. This class exists only in a
// separately-distributed Premium build.
class Argus_License {
const TRIAL_DAYS = 30;
const TRIAL_STARTED_OPTION = 'argus_wpd_license_trial_started_at';
const LICENSE_KEY_OPTION = 'argus_wpd_license_key';
const LICENSE_STATUS_OPTION = 'argus_wpd_license_validation_status';
const LICENSE_CHECKED_OPTION = 'argus_wpd_license_last_checked';
public static function ensure_trial_started() {
if ( ! get_option( self::TRIAL_STARTED_OPTION ) ) {
update_option( self::TRIAL_STARTED_OPTION, time(), false );
}
}
public static function trial_started_at() {
return (int) get_option( self::TRIAL_STARTED_OPTION, time() );
}
public static function trial_ends_at() {
return self::trial_started_at() + ( self::TRIAL_DAYS * DAY_IN_SECONDS );
}
public static function days_remaining() {
$remaining_secs = self::trial_ends_at() - time();
return max( 0, (int) ceil( $remaining_secs / DAY_IN_SECONDS ) );
}
public static function license_key() {
if ( defined( 'ARGUS_WPD_LICENSE_KEY' ) && ARGUS_WPD_LICENSE_KEY ) {
return trim( ARGUS_WPD_LICENSE_KEY );
}
return trim( (string) get_option( self::LICENSE_KEY_OPTION, '' ) );
}
// Saving a submitted key never marks it valid by itself -- the caller
// still has to call validate_now() to actually check it against the
// configured validation server, same as entering any license key
// anywhere always requires a real check before it does anything.
public static function set_license_key( $key ) {
update_option( self::LICENSE_KEY_OPTION, trim( (string) $key ), false );
delete_option( self::LICENSE_STATUS_OPTION );
}
public static function validation_endpoint() {
return defined( 'ARGUS_WPD_LICENSE_VALIDATE_URL' ) ? ARGUS_WPD_LICENSE_VALIDATE_URL : '';
}
// Honest, not fabricated: a license key alone never counts as "licensed"
// unless there's a real validation endpoint configured to actually check
// it against. No key -> unlicensed. Key present but no validation
// endpoint configured -> still unlicensed, not a silent green light.
public static function is_licensed() {
if ( '' === self::license_key() || '' === self::validation_endpoint() ) {
return false;
}
return 'valid' === get_option( self::LICENSE_STATUS_OPTION, '' );
}
public static function validate_now() {
$key = self::license_key();
$url = self::validation_endpoint();
if ( '' === $key ) {
return array( 'success' => false, 'message' => __( 'Enter a license key first.', 'argus-wordpress-defence' ) );
}
if ( '' === $url ) {
return array( 'success' => false, 'message' => __( 'No license validation server is configured for this deployment.', 'argus-wordpress-defence' ) );
}
$response = wp_remote_post(
$url,
array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( array( 'license_key' => $key, 'domain' => wp_parse_url( home_url(), PHP_URL_HOST ) ) ),
)
);
update_option( self::LICENSE_CHECKED_OPTION, current_time( 'mysql', true ), false );
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
update_option( self::LICENSE_STATUS_OPTION, 'error', false );
return array( 'success' => false, 'message' => __( 'Could not reach the license server. Try again shortly.', 'argus-wordpress-defence' ) );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$valid = is_array( $data ) && ! empty( $data['valid'] );
update_option( self::LICENSE_STATUS_OPTION, $valid ? 'valid' : 'invalid', false );
return array(
'success' => $valid,
'message' => $valid
? __( 'License activated.', 'argus-wordpress-defence' )
: __( 'That license key is not valid or has expired.', 'argus-wordpress-defence' ),
);
}
public static function status() {
if ( self::is_licensed() ) {
return 'licensed';
}
return self::days_remaining() > 0 ? 'trial' : 'trial_expired';
}
public static function summary() {
return array(
'status' => self::status(),
'days_remaining' => self::days_remaining(),
'trial_ends_at' => gmdate( 'Y-m-d H:i:s', self::trial_ends_at() ),
'has_key' => '' !== self::license_key(),
'purchase_url' => defined( 'ARGUS_WPD_LICENSE_PURCHASE_URL' ) ? ARGUS_WPD_LICENSE_PURCHASE_URL : '',
);
}
}
+7 -1
View File
@@ -4,7 +4,7 @@ Tags: security, firewall, malware, vulnerability, backup
Requires at least: 6.0 Requires at least: 6.0
Tested up to: 7.0 Tested up to: 7.0
Requires PHP: 7.4 Requires PHP: 7.4
Stable tag: 7.23.1 Stable tag: 7.23.2
License: GPLv2 or later License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -53,6 +53,12 @@ dashboard whenever a new version is available, and you can update it in one clic
== Changelog == == Changelog ==
= 7.23.2 =
* Settings: added a License section -- a 30-day trial with everything active, then enter a
license key to keep Premium features. Protection never stops regardless of license status.
* Intelligence page: rewrote descriptions in plain language focused on what's protecting your
site, not internal implementation details.
= 7.23.1 = = 7.23.1 =
* Automatic updates: fixed a bug where a normal (non-critical) update would be detected and * Automatic updates: fixed a bug where a normal (non-critical) update would be detected and
shown as available, but never actually installed -- now any newer, compatible, verified shown as available, but never actually installed -- now any newer, compatible, verified