From 231ef59bb91f3246922115b43d30415c41c998b0 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Sat, 28 Mar 2026 10:06:01 +0000 Subject: [PATCH] v1.1.0 --- changelog.txt | 86 ++++++++++++++++++++++---- includes/class-audit-log.php | 16 ++--- includes/class-bulk-actions.php | 4 +- includes/class-cli-commands.php | 42 ++++++------- includes/class-compliance-report.php | 50 +++++++-------- includes/class-enforcement.php | 2 +- includes/class-network-settings.php | 8 +-- includes/class-rest-api.php | 4 +- includes/class-role-manager.php | 4 +- includes/class-settings.php | 66 ++++++++++---------- includes/class-two-factor-extended.php | 2 +- readme.txt | 71 ++++++++++++++++++--- robotstxt-updater.php | 20 +++--- two-factor-extended.php | 4 +- update.json | 10 +-- 15 files changed, 254 insertions(+), 135 deletions(-) diff --git a/changelog.txt b/changelog.txt index c6dd20b..ac82bca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,73 @@ == Changelog == += 1.1.0 = + +_Release date: 2026-03-28_ + +**Changed** + +* Code quality: phpcbf auto-fixed 112 formatting issues across 12 files (operator alignment, array double arrows, pre-increment style). + +**Compatibility** + +* WordPress: 6.7, 6.8, 6.9 +* PHP: 8.2, 8.3, 8.4, 8.5 +* MariaDB: 10.6 or newer +* Multisite: Supported +* Two-Factor plugin: 0.16 or newer +* Tested on WordPress 6.7.5, PHP 8.5.3; PHPUnit 9.6.34 — 28 unit tests, 64 assertions, all passing + += 1.0.2 = + +_Release date: 2026-03-28_ + +**Changed** + +* Plugin renamed from "Two Factor Extended" to "Two-Factor Extended" (hyphen added for consistency with the Two-Factor plugin naming convention). +* `get_required_providers_for_user()` now intersects enforcement requirements with Two-Factor's globally-enabled providers list (`two_factor_enabled_providers` option, introduced in Two-Factor 0.16). Providers disabled site-wide by Two-Factor will no longer be enforced. +* Added Catalan (ca) translation — 145 strings, 100% coverage. +* Updated Spanish (es_ES) translation to reflect the new plugin name. + +**Multisite** + +* Two-Factor (base plugin) has no network-level settings — it is entirely per-site. Two-Factor Extended already provides the network layer on top of this with network-wide enforcement, super admin requirements, and site override controls. No changes required. + +**Compatibility** + +* WordPress: 6.7, 6.8, 6.9 +* PHP: 8.2, 8.3, 8.4, 8.5 +* MariaDB: 10.6 or newer +* Multisite: Supported +* Two-Factor plugin: 0.16 or newer +* Tested on WordPress 6.7.5, PHP 8.5.3; PHPUnit 9.6.34 — 28 unit tests, 64 assertions, all passing + += 1.0.1 = + +_Release date: 2026-03-28_ + +**Changed** + +* Settings menu now registered at `admin_menu` priority 20 to always appear after Two Factor plugin's Settings page (Two Factor 0.16+). +* PHPUnit downgraded from 10.x to 9.6 for compatibility with the WordPress test library. +* `phpunit.xml.dist` updated: uses PHPUnit 9 schema, `` block replaces removed attributes, `suffix=".php"` for test directory discovery. +* Test files renamed from `test-*.php` to `Test_*.php` to match PHPUnit 10/9 class name mapping (class names unchanged). +* `yoast/phpunit-polyfills` downgraded to `^1.1` (matches WordPress core requirement). + +**Fixed** + +* PHPCS: spacing after parameter type in `robotstxt-updater.php`, `class-enforcement.php`, `class-bulk-actions.php`. +* PHPCS: `list` docblock type replaced with `int[]` to match `array` PHP type hint in `class-bulk-actions.php`. +* PHPStan level 9: all errors resolved across all plugin files. + +**Compatibility** + +* WordPress: 6.7, 6.8, 6.9 +* PHP: 8.2, 8.3, 8.4, 8.5 +* MariaDB: 10.6 or newer +* Multisite: Supported +* Two-Factor plugin: 0.16 or newer +* Tested on WordPress 6.7.5, PHP 8.5.3; PHPUnit 9.6.34 — 28 unit tests, 64 assertions, all passing + = 1.0.0 = _Release date: 2026-02-17_ @@ -79,18 +147,12 @@ This is the first comprehensive release of Two-Factor Extended, implementing ent **Compatibility** -* WordPress: 6.7 - 6.9 -* PHP: 8.2 - 8.5 -* MariaDB: 10.6+ -* Two Factor Plugin: 0.15.0 (tested) -* Multisite: Fully supported (Network: true) - -**Testing Environment** - -* WordPress: 6.9 -* PHP: 8.2 -* MariaDB: 10.6 -* Testing: Plugin detection verified, dependency checks working, PHPCS passing, unit tests passing +* WordPress: 6.7, 6.8, 6.9 +* PHP: 8.2, 8.3, 8.4, 8.5 +* MariaDB: 10.6 or newer +* Multisite: Supported +* Two-Factor plugin: 0.15 or newer +* Tested on WordPress 6.9, PHP 8.2, MariaDB 10.6; plugin detection verified, dependency checks working, PHPCS passing, unit tests passing **Development Tools** diff --git a/includes/class-audit-log.php b/includes/class-audit-log.php index f52f456..18d656e 100644 --- a/includes/class-audit-log.php +++ b/includes/class-audit-log.php @@ -120,11 +120,11 @@ class Two_Factor_Extended_Audit_Log { return; } - $providers = Two_Factor_Extended_Provider_Detector::get_user_enabled_providers( $user_id ); + $providers = Two_Factor_Extended_Provider_Detector::get_user_enabled_providers( $user_id ); $provider_names = array(); foreach ( array_keys( $providers ) as $class ) { - $names = Two_Factor_Extended_Provider_Detector::get_provider_names(); + $names = Two_Factor_Extended_Provider_Detector::get_provider_names(); $provider_names[] = $names[ $class ] ?? $class; } @@ -301,7 +301,7 @@ class Two_Factor_Extended_Audit_Log { * @since 0.1.0 */ public function cleanup_old_logs(): void { - $logs = $this->get_logs(); + $logs = $this->get_logs(); $cutoff_time = current_time( 'timestamp' ) - ( self::RETENTION_DAYS * DAY_IN_SECONDS ); $filtered_logs = array_filter( @@ -328,7 +328,7 @@ class Two_Factor_Extended_Audit_Log { public function export_to_csv( array $filters = array() ): string { $logs = $this->get_logs( $filters ); - $csv = array(); + $csv = array(); $csv[] = array( 'Timestamp', 'Action', 'Description', 'User', 'Actor', 'IP Address' ); foreach ( $logs as $log ) { @@ -424,8 +424,8 @@ class Two_Factor_Extended_Audit_Log { $logs = $this->get_logs(); $stats = array( - 'total' => count( $logs ), - 'by_action' => array(), + 'total' => count( $logs ), + 'by_action' => array(), 'recent_count' => 0, ); @@ -439,11 +439,11 @@ class Two_Factor_Extended_Audit_Log { if ( ! isset( $stats['by_action'][ $action ] ) ) { $stats['by_action'][ $action ] = 0; } - $stats['by_action'][ $action ]++; + ++$stats['by_action'][ $action ]; // Count recent logs (last 7 days). if ( $ts >= $recent_cutoff ) { - $stats['recent_count']++; + ++$stats['recent_count']; } } diff --git a/includes/class-bulk-actions.php b/includes/class-bulk-actions.php index 90f71df..b336833 100644 --- a/includes/class-bulk-actions.php +++ b/includes/class-bulk-actions.php @@ -158,7 +158,7 @@ class Two_Factor_Extended_Bulk_Actions { ); } - $processed++; + ++$processed; } return $processed; @@ -203,7 +203,7 @@ class Two_Factor_Extended_Bulk_Actions { ); } - $processed++; + ++$processed; } return $processed; diff --git a/includes/class-cli-commands.php b/includes/class-cli-commands.php index 514cc1a..55c9ef3 100644 --- a/includes/class-cli-commands.php +++ b/includes/class-cli-commands.php @@ -66,29 +66,29 @@ class Two_Factor_Extended_CLI_Commands { $overview = array( array( - 'Metric' => 'Total Users', - 'Count' => $stats['total_users'], - 'Percentage' => '100%', + 'Metric' => 'Total Users', + 'Count' => $stats['total_users'], + 'Percentage' => '100%', ), array( - 'Metric' => 'Compliant', - 'Count' => $stats['compliant_users'], - 'Percentage' => $this->calculate_percentage( $stats['compliant_users'], $stats['total_users'] ), + 'Metric' => 'Compliant', + 'Count' => $stats['compliant_users'], + 'Percentage' => $this->calculate_percentage( $stats['compliant_users'], $stats['total_users'] ), ), array( - 'Metric' => 'Non-Compliant', - 'Count' => $stats['non_compliant'], - 'Percentage' => $this->calculate_percentage( $stats['non_compliant'], $stats['total_users'] ), + 'Metric' => 'Non-Compliant', + 'Count' => $stats['non_compliant'], + 'Percentage' => $this->calculate_percentage( $stats['non_compliant'], $stats['total_users'] ), ), array( - 'Metric' => 'In Grace Period', - 'Count' => $stats['grace_period'], - 'Percentage' => $this->calculate_percentage( $stats['grace_period'], $stats['total_users'] ), + 'Metric' => 'In Grace Period', + 'Count' => $stats['grace_period'], + 'Percentage' => $this->calculate_percentage( $stats['grace_period'], $stats['total_users'] ), ), array( - 'Metric' => 'No Requirements', - 'Count' => $stats['no_requirements'], - 'Percentage' => $this->calculate_percentage( $stats['no_requirements'], $stats['total_users'] ), + 'Metric' => 'No Requirements', + 'Count' => $stats['no_requirements'], + 'Percentage' => $this->calculate_percentage( $stats['no_requirements'], $stats['total_users'] ), ), ); @@ -105,11 +105,11 @@ class Two_Factor_Extended_CLI_Commands { $role_name = Two_Factor_Extended_Role_Manager::get_role_display_name( $role_key ); $by_role[] = array( - 'Role' => $role_name, - 'Total' => $role_stats['total'], - 'Compliant' => $role_stats['compliant'], - 'Non-Compliant' => $role_stats['non_compliant'], - 'Compliance %' => $this->calculate_percentage( $role_stats['compliant'], $role_stats['total'] ), + 'Role' => $role_name, + 'Total' => $role_stats['total'], + 'Compliant' => $role_stats['compliant'], + 'Non-Compliant' => $role_stats['non_compliant'], + 'Compliance %' => $this->calculate_percentage( $role_stats['compliant'], $role_stats['total'] ), ); } @@ -196,7 +196,7 @@ class Two_Factor_Extended_CLI_Commands { ); } - $processed++; + ++$processed; $progress->tick(); } diff --git a/includes/class-compliance-report.php b/includes/class-compliance-report.php index 74074f3..7be2109 100644 --- a/includes/class-compliance-report.php +++ b/includes/class-compliance-report.php @@ -53,12 +53,12 @@ class Two_Factor_Extended_Compliance_Report { $users = get_users( $user_args ); $stats = array( - 'total_users' => count( $users ), - 'compliant_users' => 0, - 'non_compliant' => 0, - 'grace_period' => 0, - 'no_requirements' => 0, - 'by_role' => array(), + 'total_users' => count( $users ), + 'compliant_users' => 0, + 'non_compliant' => 0, + 'grace_period' => 0, + 'no_requirements' => 0, + 'by_role' => array(), ); $enforcement = two_factor_extended()->get_enforcement(); @@ -71,7 +71,7 @@ class Two_Factor_Extended_Compliance_Report { $required = $enforcement->get_required_providers_for_user( $user->ID ); if ( empty( $required ) ) { - $stats['no_requirements']++; + ++$stats['no_requirements']; continue; } @@ -79,11 +79,11 @@ class Two_Factor_Extended_Compliance_Report { $in_grace = $enforcement->is_in_grace_period( $user->ID ); if ( $compliant ) { - $stats['compliant_users']++; + ++$stats['compliant_users']; } elseif ( $in_grace ) { - $stats['grace_period']++; + ++$stats['grace_period']; } else { - $stats['non_compliant']++; + ++$stats['non_compliant']; } // Count by role. @@ -92,18 +92,18 @@ class Two_Factor_Extended_Compliance_Report { foreach ( $roles as $role ) { if ( ! isset( $stats['by_role'][ $role ] ) ) { $stats['by_role'][ $role ] = array( - 'total' => 0, - 'compliant' => 0, + 'total' => 0, + 'compliant' => 0, 'non_compliant' => 0, ); } - $stats['by_role'][ $role ]['total']++; + ++$stats['by_role'][ $role ]['total']; if ( $compliant ) { - $stats['by_role'][ $role ]['compliant']++; + ++$stats['by_role'][ $role ]['compliant']; } else { - $stats['by_role'][ $role ]['non_compliant']++; + ++$stats['by_role'][ $role ]['non_compliant']; } } } @@ -194,13 +194,13 @@ class Two_Factor_Extended_Compliance_Report { return array(); } - $sites = get_sites( array( 'number' => 999 ) ); + $sites = get_sites( array( 'number' => 999 ) ); $report = array( - 'total_sites' => count( $sites ), - 'total_users' => 0, - 'compliant' => 0, - 'non_compliant' => 0, - 'by_site' => array(), + 'total_sites' => count( $sites ), + 'total_users' => 0, + 'compliant' => 0, + 'non_compliant' => 0, + 'by_site' => array(), ); foreach ( $sites as $site ) { @@ -216,8 +216,8 @@ class Two_Factor_Extended_Compliance_Report { 'non_compliant' => $site_stats['non_compliant'], ); - $report['total_users'] += $site_stats['total_users']; - $report['compliant'] += $site_stats['compliant_users']; + $report['total_users'] += $site_stats['total_users']; + $report['compliant'] += $site_stats['compliant_users']; $report['non_compliant'] += $site_stats['non_compliant']; restore_current_blog(); @@ -238,7 +238,7 @@ class Two_Factor_Extended_Compliance_Report { public function export_to_csv( array $args = array() ): string { $non_compliant = $this->get_non_compliant_users( $args ); - $csv = array(); + $csv = array(); $csv[] = array( 'User ID', 'Username', 'Email', 'Roles', 'Missing Providers', 'Grace Period', 'Days Remaining' ); foreach ( $non_compliant as $user_data ) { @@ -317,7 +317,7 @@ class Two_Factor_Extended_Compliance_Report { foreach ( $stats['by_role'] as $role => $role_stats ) { $role_name = Two_Factor_Extended_Role_Manager::get_role_display_name( $role ); - $message .= sprintf( + $message .= sprintf( ' %s: %d/%d compliant', $role_name, $role_stats['compliant'], diff --git a/includes/class-enforcement.php b/includes/class-enforcement.php index 7025f8d..989184a 100644 --- a/includes/class-enforcement.php +++ b/includes/class-enforcement.php @@ -393,7 +393,7 @@ class Two_Factor_Extended_Enforcement { * @return array Array of provider labels. */ private function get_provider_labels( array $provider_classes ): array { - $names = Two_Factor_Extended_Provider_Detector::get_provider_names(); + $names = Two_Factor_Extended_Provider_Detector::get_provider_names(); $labels = array(); foreach ( $provider_classes as $class ) { diff --git a/includes/class-network-settings.php b/includes/class-network-settings.php index 8e15a45..18623c7 100644 --- a/includes/class-network-settings.php +++ b/includes/class-network-settings.php @@ -247,9 +247,9 @@ class Two_Factor_Extended_Network_Settings { * @param array $settings Current network settings. */ private function render_network_role_requirements( array $settings ): void { - $roles = Two_Factor_Extended_Role_Manager::get_all_roles(); - $providers = Two_Factor_Extended_Provider_Detector::get_provider_names(); - $network_reqs = isset( $settings['role_requirements'] ) && is_array( $settings['role_requirements'] ) ? $settings['role_requirements'] : array(); + $roles = Two_Factor_Extended_Role_Manager::get_all_roles(); + $providers = Two_Factor_Extended_Provider_Detector::get_provider_names(); + $network_reqs = isset( $settings['role_requirements'] ) && is_array( $settings['role_requirements'] ) ? $settings['role_requirements'] : array(); if ( empty( $providers ) ) { ?> @@ -384,7 +384,7 @@ class Two_Factor_Extended_Network_Settings { // Grace period days. if ( isset( $input['grace_period_days'] ) && is_numeric( $input['grace_period_days'] ) ) { - $days = (int) $input['grace_period_days']; + $days = (int) $input['grace_period_days']; $sanitized['grace_period_days'] = max( 0, min( 365, $days ) ); } diff --git a/includes/class-rest-api.php b/includes/class-rest-api.php index 9f1a4e8..5f963ad 100644 --- a/includes/class-rest-api.php +++ b/includes/class-rest-api.php @@ -368,7 +368,7 @@ class Two_Factor_Extended_REST_API { ); } - $processed++; + ++$processed; } return new WP_REST_Response( @@ -435,7 +435,7 @@ class Two_Factor_Extended_REST_API { ); } - $processed++; + ++$processed; } return new WP_REST_Response( diff --git a/includes/class-role-manager.php b/includes/class-role-manager.php index 5784e81..9016782 100644 --- a/includes/class-role-manager.php +++ b/includes/class-role-manager.php @@ -32,8 +32,8 @@ class Two_Factor_Extended_Role_Manager { require_once ABSPATH . 'wp-admin/includes/user.php'; } - $roles = array(); - $wp_roles = get_editable_roles(); + $roles = array(); + $wp_roles = get_editable_roles(); foreach ( $wp_roles as $role_slug => $role_data ) { $roles[ $role_slug ] = translate_user_role( $role_data['name'] ); diff --git a/includes/class-settings.php b/includes/class-settings.php index 29332e2..609cc9b 100644 --- a/includes/class-settings.php +++ b/includes/class-settings.php @@ -453,7 +453,7 @@ class Two_Factor_Extended_Settings { $provider_name ) : @@ -537,16 +537,16 @@ class Two_Factor_Extended_Settings { $provider_name ) : $checked = in_array( $provider_class, $role_visibility, true ); $is_required = in_array( $provider_class, $role_required, true ); - $disabled = $is_required ? 'disabled' : ''; - $checked = $checked || $is_required; + $disabled = $is_required ? 'disabled' : ''; + $checked = $checked || $is_required; ?>