Plugin Directory

Changeset 3395451


Ignore:
Timestamp:
11/14/2025 02:44:24 AM (5 months ago)
Author:
TCattd
Message:

1.7.3

Location:
fuerte-wp/trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • fuerte-wp/trunk/CHANGELOG.md

    r3395378 r3395451  
    11# Changelog
    22
    3 # 1.7.1 / 2025-11-13
     3# 1.7.3 / 2025-11-13
    44- Added comprehensive Login Security system with rate limiting and IP lockout functionality.
    55- Implemented failed login attempt tracking with configurable thresholds and lockout durations.
  • fuerte-wp/trunk/README.txt

    r3395401 r3395451  
    22Contributors: tcattd
    33Tags: security, login, protection, admin, brute-force, GDPR, privacy, access-control, multisite
    4 Stable tag: 1.7.2
     4Stable tag: 1.7.3
    55Requires at least: 6.0
    66Tested up to: 6.9
  • fuerte-wp/trunk/SECURITY.md

    r3395378 r3395451  
    55| Version | Supported          |
    66| ------- | ------------------ |
    7 | 1.7.1   | :white_check_mark: |
    8 | <1.7.1  | :x:                |
     7| 1.7.3   | :white_check_mark: |
     8| <1.7.3  | :x:                |
    99
    1010## Reporting a Vulnerability
  • fuerte-wp/trunk/admin/class-fuerte-wp-admin.php

    r3395378 r3395451  
    9494        }
    9595
     96        // Handle debug actions for troubleshooting cache issues
     97        if (isset($_GET['fuertewp_action']) && current_user_can('manage_options')) {
     98            switch ($_GET['fuertewp_action']) {
     99                case 'debug_cache':
     100                    if (defined('WP_DEBUG') && WP_DEBUG) {
     101                        $debug_info = Fuerte_Wp_Config::debug_cache();
     102                        echo '<div class="notice notice-info"><pre>';
     103                        echo 'Fuerte-WP Cache Debug Information:' . "\n";
     104                        echo '=====================================' . "\n";
     105                        echo 'Cache Key: ' . $debug_info['cache_key'] . "\n";
     106                        echo 'Cache Exists: ' . ($debug_info['cache_exists'] ? 'Yes' : 'No') . "\n";
     107                        echo 'Cache Super Users: ' . print_r($debug_info['cache_super_users'], true) . "\n";
     108                        echo 'Fresh Super Users: ' . print_r($debug_info['fresh_super_users'], true) . "\n";
     109                        echo 'Cache Match: ' . ($debug_info['cache_match'] ? 'Yes' : 'No') . "\n";
     110                        echo 'Current URL: ' . $debug_info['current_url'] . "\n";
     111                        echo 'Current Screen: ' . $debug_info['current_screen'] . "\n";
     112                        echo '</pre></div>';
     113                    }
     114                    break;
     115
     116                case 'clear_cache':
     117                    Fuerte_Wp_Config::invalidate_cache();
     118                    echo '<div class="notice notice-success"><p>Fuerte-WP configuration cache cleared.</p></div>';
     119                    break;
     120
     121                case 'force_refresh':
     122                    $fresh_config = Fuerte_Wp_Config::force_refresh();
     123                    echo '<div class="notice notice-success"><p>Fuerte-WP configuration refreshed from database.</p>';
     124                    echo '<p>Super Users in database: ' . (isset($fresh_config['super_users']) ? print_r($fresh_config['super_users'], true) : 'Not set') . '</p></div>';
     125                    break;
     126            }
     127        }
     128
    96129        /*
    97130         * Allow admin options for super users even if config file exists
     
    623656                    function updateLoginUrlPreview() {
    624657                        var enabled = $(\'input[name="fuertewp_login_url_hiding_enabled"]\').prop(\'checked\');
    625                         var slug = $(\'input[name="fuertewp_custom_login_slug"]\').val() || \'secure-login\';
     658                        var slug = $(\'input[name="_fuertewp_custom_login_slug"]\').val() || \'secure-login\';
    626659                        var urlType = $(\'select[name="fuertewp_login_url_type"]\').val() || \'query_param\';
    627660                        var baseUrl = window.location.origin + window.location.pathname.replace(/\/wp-admin.*$/, \'/\');
     
    645678                    $(\'#carbon_fields_container\').on(\'submit\', function() {
    646679                        var loginHidingEnabled = $(\'input[name="fuertewp_login_url_hiding_enabled"]\').prop(\'checked\');
    647                         var customSlug = $(\'input[name="fuertewp_custom_login_slug"]\').val();
     680                        var customSlug = $(\'input[name="_fuertewp_custom_login_slug"]\').val();
    648681
    649682                        if (loginHidingEnabled && (customSlug === \'\' || customSlug.trim() === \'\')) {
    650683                            // Set default value before form submission
    651                             $(\'input[name="fuertewp_custom_login_slug"]\').val(\'secure-login\');
     684                            $(\'input[name="_fuertewp_custom_login_slug"]\').val(\'secure-login\');
    652685
    653686                            // Update preview to show the new default
     
    659692                    $(\'input[name="fuertewp_login_url_hiding_enabled"]\').on(\'change\', function() {
    660693                        var enabled = $(this).prop(\'checked\');
    661                         var customSlug = $(\'input[name="fuertewp_custom_login_slug"]\').val();
     694                        var customSlug = $(\'input[name="_fuertewp_custom_login_slug"]\').val();
    662695
    663696                        if (enabled && (customSlug === \'\' || customSlug.trim() === \'\')) {
    664                             $(\'input[name="fuertewp_custom_login_slug"]\').val(\'secure-login\');
     697                            $(\'input[name="_fuertewp_custom_login_slug"]\').val(\'secure-login\');
    665698                            updateLoginUrlPreview();
    666699                        }
     
    669702                    // Update preview when settings change
    670703                    $(\'input[name="fuertewp_login_url_hiding_enabled"]\').on(\'change\', updateLoginUrlPreview);
    671                     $(\'input[name="fuertewp_custom_login_slug"]\').on(\'input\', updateLoginUrlPreview);
     704                    $(\'input[name="_fuertewp_custom_login_slug"]\').on(\'input\', updateLoginUrlPreview);
    672705                    $(\'select[name="fuertewp_login_url_type"]\').on(\'change\', updateLoginUrlPreview);
    673706
     
    12261259
    12271260        // Validate and ensure custom login slug is never empty
    1228         $custom_login_slug = carbon_get_theme_option('fuertewp_custom_login_slug');
     1261        $custom_login_slug = Fuerte_Wp_Config::get_field('custom_login_slug', 'secure-login');
    12291262        if (empty($custom_login_slug) || trim($custom_login_slug) === '') {
    1230             carbon_set_theme_option('fuertewp_custom_login_slug', 'secure-login');
     1263            Fuerte_Wp_Config::set_field('custom_login_slug', 'secure-login');
    12311264        }
    12321265
     
    12361269        }
    12371270
    1238         $super_users = carbon_get_theme_option('fuertewp_super_users');
     1271        $super_users = Fuerte_Wp_Config::get_field('super_users', [], true);
    12391272
    12401273        // Normalize to array format for consistency
     
    12541287                array_unshift($super_users, $current_user->user_email);
    12551288
    1256                 carbon_set_theme_option('fuertewp_super_users', $super_users);
     1289                Fuerte_Wp_Config::set_field('super_users', $super_users, true);
    12571290            }
    12581291        }
    12591292
    1260         // Set default login security values using direct options
     1293        // Set default login security values using standardized _fuertewp_ pattern
    12611294        $defaults_to_set = [
    1262             'fuertewp_login_enable' => 'enabled',
    1263             'fuertewp_registration_enable' => 'enabled',
    1264             'fuertewp_login_max_attempts' => 5,
    1265             'fuertewp_login_lockout_duration' => 15,
    1266             'fuertewp_custom_login_slug' => 'secure-login', // Ensure default login slug is always set
     1295            '_fuertewp_login_enable' => 'enabled',
     1296            '_fuertewp_registration_enable' => 'enabled',
     1297            '_fuertewp_login_max_attempts' => 5,
     1298            '_fuertewp_login_lockout_duration' => 15,
     1299            '_fuertewp_custom_login_slug' => 'secure-login', // Ensure default login slug is always set
    12671300        ];
    12681301
  • fuerte-wp/trunk/fuerte-wp.php

    r3395401 r3395451  
    66 * Plugin URI:        https://github.com/EstebanForge/Fuerte-WP
    77 * Description:       Stronger WP. Limit access to critical WordPress areas, even other for admins.
    8  * Version:           1.7.2
     8 * Version:           1.7.3
    99 * Author:            Esteban Cuevas
    1010 * Author URI:        https://actitud.xyz
     
    1313 * Text Domain:       fuerte-wp
    1414 * Domain Path:       /languages
    15  * Requires at least: 6.0
     15 * Requires at least: 6.4
    1616 * Tested up to:      6.9
    1717 * Requires PHP:      8.1
     
    1919 * @link              https://actitud.xyz
    2020 * @since             1.3.0
    21  * @package           Fuerte_Wp
    2221 */
    2322
    2423// If this file is called directly, abort.
    25 if (!defined("WPINC")) {
    26     die();
    27 }
    28 
    29 /**
     24if (!defined('WPINC')) {
     25    die();
     26}
     27
     28/*
    3029 * Currently plugin version.
    3130 * Start at version 1.0.0 and use SemVer - https://semver.org
    3231 * Rename this for your plugin and update it as you release new versions.
    3332 */
    34 define("FUERTEWP_PLUGIN_BASE", plugin_basename(__FILE__));
    35 define("FUERTEWP_VERSION", "1.7.2");
    36 define("FUERTEWP_PATH", realpath(plugin_dir_path(__FILE__)) . "/");
    37 define("FUERTEWP_URL", trailingslashit(plugin_dir_url(__FILE__)));
    38 define("FUERTEWP_LATE_PRIORITY", 9999);
    39 
    40 /**
     33define('FUERTEWP_PLUGIN_BASE', plugin_basename(__FILE__));
     34define('FUERTEWP_VERSION', '1.7.3');
     35define('FUERTEWP_PATH', realpath(plugin_dir_path(__FILE__)) . '/');
     36define('FUERTEWP_URL', trailingslashit(plugin_dir_url(__FILE__)));
     37define('FUERTEWP_LATE_PRIORITY', 9999);
     38
     39/*
    4140 * Load Fuerte-WP config file if exist
    4241 */
    43 if (file_exists(ABSPATH . "wp-config-fuerte.php")) {
    44     require_once ABSPATH . "wp-config-fuerte.php";
    45 }
    46 
    47 /**
     42if (file_exists(ABSPATH . 'wp-config-fuerte.php')) {
     43    require_once ABSPATH . 'wp-config-fuerte.php';
     44}
     45
     46/*
    4847 * Exit if FUERTEWP_DISABLE is defined (in wp-config.php or wp-config-fuerte.php)
    4948 */
    50 if (defined("FUERTEWP_DISABLE") && true === FUERTEWP_DISABLE) {
    51     return false;
    52 }
    53 
    54 /**
    55  * Includes & Autoload
     49if (defined('FUERTEWP_DISABLE') && true === FUERTEWP_DISABLE) {
     50    return false;
     51}
     52
     53/**
     54 * Includes & Autoload.
    5655 */
    5756function fuertewp_includes_autoload()
    5857{
    59     if (file_exists(FUERTEWP_PATH . "includes/helpers.php")) {
    60         require_once FUERTEWP_PATH . "includes/helpers.php";
    61     }
    62 
    63     // Elementor has JS issues with Carbon-Fields being loaded while in his editor.
    64     if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "elementor") {
    65         return;
    66     }
    67 
    68     if (file_exists(FUERTEWP_PATH . "vendor/autoload.php")) {
    69         require_once FUERTEWP_PATH . "vendor/autoload.php";
    70     }
    71 }
    72 add_action("after_setup_theme", "fuertewp_includes_autoload", 100);
     58    if (file_exists(FUERTEWP_PATH . 'includes/helpers.php')) {
     59        require_once FUERTEWP_PATH . 'includes/helpers.php';
     60    }
     61
     62    // Elementor has JS issues with Carbon-Fields being loaded while in his editor.
     63    if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'elementor') {
     64        return;
     65    }
     66
     67    if (file_exists(FUERTEWP_PATH . 'vendor/autoload.php')) {
     68        require_once FUERTEWP_PATH . 'vendor/autoload.php';
     69    }
     70}
     71add_action('after_setup_theme', 'fuertewp_includes_autoload', 100);
    7372
    7473/**
     
    8079function fuertewp_load_carbon_fields()
    8180{
    82     if (file_exists(FUERTEWP_PATH . "vendor/autoload.php")) {
     81    if (file_exists(FUERTEWP_PATH . 'vendor/autoload.php')) {
    8382        // Carbon Fields configuration
    8483        // https://github.com/htmlburger/carbon-fields/issues/805#issuecomment-680959592
    8584        // https://docs.carbonfields.net/learn/advanced-topics/compacting-input-vars.html
    8685        define(
    87             "Carbon_Fields\\URL",
    88             FUERTEWP_URL . "vendor/htmlburger/carbon-fields/",
     86            'Carbon_Fields\\URL',
     87            FUERTEWP_URL . 'vendor/htmlburger/carbon-fields/',
    8988        );
    90         define("Carbon_Fields\\COMPACT_INPUT", true);
    91         define("Carbon_Fields\\COMPACT_INPUT_KEY", "fuertewp_carbonfields");
    92 
    93         require_once FUERTEWP_PATH . "vendor/autoload.php";
     89        define('Carbon_Fields\\COMPACT_INPUT', true);
     90        define('Carbon_Fields\\COMPACT_INPUT_KEY', 'fuertewp_carbonfields');
     91
     92        require_once FUERTEWP_PATH . 'vendor/autoload.php';
    9493
    9594        // Initialize Carbon Fields
    96         \Carbon_Fields\Carbon_Fields::boot();
     95        Carbon_Fields\Carbon_Fields::boot();
    9796    }
    9897}
     
    101100/**
    102101 * The code that runs during plugin activation.
    103  * This action is documented in includes/class-fuerte-wp-activator.php
     102 * This action is documented in includes/class-fuerte-wp-activator.php.
    104103 */
    105104function activate_fuerte_wp()
    106105{
    107     require_once plugin_dir_path(__FILE__) .
    108         "includes/class-fuerte-wp-activator.php";
    109     Fuerte_Wp_Activator::activate();
     106    require_once plugin_dir_path(__FILE__)
     107        . 'includes/class-fuerte-wp-activator.php';
     108    Fuerte_Wp_Activator::activate();
    110109}
    111110
    112111/**
    113112 * The code that runs during plugin deactivation.
    114  * This action is documented in includes/class-fuerte-wp-deactivator.php
     113 * This action is documented in includes/class-fuerte-wp-deactivator.php.
    115114 */
    116115function deactivate_fuerte_wp()
    117116{
    118     require_once plugin_dir_path(__FILE__) .
    119         "includes/class-fuerte-wp-deactivator.php";
    120     Fuerte_Wp_Deactivator::deactivate();
    121 }
    122 
    123 register_activation_hook(__FILE__, "activate_fuerte_wp");
    124 register_deactivation_hook(__FILE__, "deactivate_fuerte_wp");
     117    require_once plugin_dir_path(__FILE__)
     118        . 'includes/class-fuerte-wp-deactivator.php';
     119    Fuerte_Wp_Deactivator::deactivate();
     120}
     121
     122register_activation_hook(__FILE__, 'activate_fuerte_wp');
     123register_deactivation_hook(__FILE__, 'deactivate_fuerte_wp');
    125124
    126125/**
     
    132131function fuertewp_check_login_db_version()
    133132{
    134     require_once plugin_dir_path(__FILE__) . "includes/class-fuerte-wp-activator.php";
    135 
    136     $installed_version = get_option('fuertewp_login_db_version', '0.0.0');
    137 
    138     if (version_compare($installed_version, Fuerte_Wp_Activator::DB_VERSION, '<')) {
     133    require_once plugin_dir_path(__FILE__) . 'includes/class-fuerte-wp-activator.php';
     134
     135    $installed_version = get_option('_fuertewp_login_db_version', '0.0.0');
     136
     137    if (version_compare($installed_version, Fuerte_Wp_Activator::get_db_version(), '<')) {
    139138        Fuerte_Wp_Activator::create_login_security_tables();
    140139        Fuerte_Wp_Activator::schedule_cron_jobs();
     
    176175
    177176/**
    178  * Code that runs on plugins uninstallation
     177 * Code that runs on plugins uninstallation.
    179178 */
    180179function uninstall_fuerte_wp()
    181180{
    182     require_once plugin_dir_path(__FILE__) .
    183         "includes/class-fuerte-wp-uninstaller.php";
    184     Fuerte_Wp_Uninstaller::uninstall();
     181    require_once plugin_dir_path(__FILE__)
     182        . 'includes/class-fuerte-wp-uninstaller.php';
     183    Fuerte_Wp_Uninstaller::uninstall();
    185184}
    186185
     
    190189 */
    191190// Load logger first to ensure it's always available for debugging
    192 require plugin_dir_path(__FILE__) . "includes/class-fuerte-wp-logger.php";
     191require plugin_dir_path(__FILE__) . 'includes/class-fuerte-wp-logger.php';
    193192
    194193// Initialize logger immediately - only enable when WP_DEBUG is true
     
    196195Fuerte_Wp_Logger::init_from_constant();
    197196
    198 require plugin_dir_path(__FILE__) . "includes/class-fuerte-wp.php";
     197require plugin_dir_path(__FILE__) . 'includes/class-fuerte-wp.php';
    199198
    200199/**
     
    209208function run_fuerte_wp()
    210209{
    211     $plugin = new Fuerte_Wp();
    212     $plugin->run();
     210    $plugin = new Fuerte_Wp();
     211    $plugin->run();
    213212}
    214213run_fuerte_wp();
    215214
    216 /**
     215/*
    217216 * Hook into plugin updates to clear configuration cache.
    218217 * This ensures that the super users and other configuration are properly
     
    223222add_action('upgrader_process_complete', 'fuertewp_handle_plugin_update', 10, 2);
    224223
    225 function fuertewp_handle_plugin_update($upgrader_object, $options) {
     224function fuertewp_handle_plugin_update($upgrader_object, $options)
     225{
    226226    // Check if this is a plugin update and if it's our plugin
    227227    if ($options['action'] === 'update' && $options['type'] === 'plugin') {
     
    240240
    241241/**
    242  * htaccess security rules
     242 * htaccess security rules.
    243243 */
    244244$fuertewp_htaccess = "
  • fuerte-wp/trunk/includes/class-fuerte-wp-activator.php

    r3395401 r3395451  
    2626    /**
    2727     * Database version for login security feature.
    28      * Increment when database schema changes.
    29      *
    30      * @since 1.7.0
    31      */
    32     const DB_VERSION = '1.0.0';
     28     * Uses plugin version to ensure consistency.
     29     *
     30     * @since 1.7.3
     31     */
     32    public static function get_db_version()
     33    {
     34        return defined('FUERTEWP_VERSION') ? FUERTEWP_VERSION : '1.0.0';
     35    }
    3336
    3437    /**
     
    5255     * Set up initial super user from current admin user.
    5356     * This prevents lockout when the plugin is first activated.
    54      *
    55      * @since 1.7.0
     57     * Uses standardized Config class methods to avoid Carbon Fields timing issues.
     58     *
     59     * @since 1.7.2
    5660     */
    5761    public static function setup_initial_super_user()
    5862    {
    59         // Check if Carbon Fields functions are available
    60         // During activation, Carbon Fields may not be loaded yet
    61         if (!function_exists('carbon_get_theme_option')) {
     63        // Use our standardized Config class methods instead of Carbon Fields
     64        if (!class_exists('Fuerte_Wp_Config')) {
    6265            return;
    6366        }
    6467
    6568        // Check if super_users is already set
    66         $existing_super_users = carbon_get_theme_option('fuertewp_super_users');
    67 
    68         if (empty($existing_super_users) || !is_array($existing_super_users)) {
     69        $existing_super_users = Fuerte_Wp_Config::get_field('super_users', [], true);
     70
     71        if (empty($existing_super_users)) {
    6972            // Get current user if available (works during activation)
    7073            $current_user = wp_get_current_user();
    7174
    7275            if ($current_user && $current_user->ID > 0) {
    73                 // Add current user as super user
    74                 carbon_set_theme_option('fuertewp_super_users', [$current_user->user_email]);
     76                // Add current user as super user using our standardized method
     77                Fuerte_Wp_Config::set_field('super_users', [$current_user->user_email], true);
    7578            }
    7679        }
     
    144147
    145148        // Store database version
    146         add_option('fuertewp_login_db_version', self::DB_VERSION);
     149        add_option('_fuertewp_login_db_version', self::get_db_version());
    147150    }
    148151
  • fuerte-wp/trunk/includes/class-fuerte-wp-config.php

    r3395361 r3395451  
    7373     *
    7474     * @since 1.7.0
     75     * @param bool $bypass_cache Force loading from database, bypassing transient cache
    7576     * @return array Configuration array
    7677     */
    77     public static function get_config()
    78     {
    79         // Try to get from transient first
    80         $cached_config = get_transient(self::$transient_key);
    81         if ($cached_config !== false) {
    82             return $cached_config;
     78    public static function get_config($bypass_cache = false)
     79    {
     80        // Bypass cache if requested or if on settings page for admin users
     81        $force_bypass = $bypass_cache || self::should_bypass_cache();
     82
     83        // Try to get from transient first (unless bypassing)
     84        if (!$force_bypass) {
     85            $cached_config = get_transient(self::$transient_key);
     86            if ($cached_config !== false) {
     87                return $cached_config;
     88            }
    8389        }
    8490
     
    9096        }
    9197
    92         self::save_config($config);
     98        // Only save to cache if not bypassing
     99        if (!$force_bypass) {
     100            self::save_config($config);
     101        }
    93102
    94103        return $config;
     
    150159
    151160    /**
     161     * Force configuration refresh from database.
     162     * Useful for debugging cache issues.
     163     *
     164     * @since 1.7.2
     165     * @return array Fresh configuration from database
     166     */
     167    public static function force_refresh()
     168    {
     169        self::invalidate_cache();
     170        return self::get_config(true);
     171    }
     172
     173    /**
     174     * Debug method to check what's in the cache vs database.
     175     * Only works for admin users and when WP_DEBUG is enabled.
     176     *
     177     * @since 1.7.2
     178     * @return array Debug information
     179     */
     180    public static function debug_cache()
     181    {
     182        if (!current_user_can('manage_options') || !defined('WP_DEBUG') || !WP_DEBUG) {
     183            return [];
     184        }
     185
     186        $cached = get_transient(self::$transient_key);
     187        $fresh = self::get_config(true);
     188
     189        return [
     190            'cache_key' => self::$transient_key,
     191            'cache_exists' => $cached !== false,
     192            'cache_super_users' => isset($cached['super_users']) ? $cached['super_users'] : 'not_set',
     193            'fresh_super_users' => isset($fresh['super_users']) ? $fresh['super_users'] : 'not_set',
     194            'cache_match' => json_encode($cached) === json_encode($fresh),
     195            'current_url' => $_SERVER['REQUEST_URI'] ?? 'unknown',
     196            'current_screen' => (function_exists('get_current_screen') && get_current_screen()) ? get_current_screen()->id : 'function_not_available_or_no_screen',
     197        ];
     198    }
     199
     200    /**
    152201     * Load configuration from database.
    153202     *
     
    159208        $config = [];
    160209
    161         // Load from individual options stored by Carbon Fields
    162         $config['status'] = get_option('fuertewp_status', 'enabled');
    163 
    164         // Load super users from Carbon Fields
    165         $super_users = get_option('_fuertewp_super_users', '');
    166         $config['super_users'] = !empty($super_users) ? array($super_users) : array();
     210        // Load status from _fuertewp_status option
     211        $config['status'] = get_option('_fuertewp_status', 'enabled');
     212
     213        // Load super users with special handling for multiselect field
     214        $config['super_users'] = self::load_multiselect_field('fuertewp_super_users');
    167215
    168216        // Load general settings
     
    236284        return $config;
    237285    }
     286
     287    /**
     288     * Check if cache should be bypassed for the current request.
     289     * Bypasses cache for admin users on settings pages to ensure fresh data.
     290     *
     291     * @since 1.7.2
     292     * @return bool True if cache should be bypassed
     293     */
     294    private static function should_bypass_cache()
     295    {
     296        // Only bypass for admin requests
     297        if (!is_admin()) {
     298            return false;
     299        }
     300
     301        // Check if current user can manage options (admin level)
     302        if (!current_user_can('manage_options')) {
     303            return false;
     304        }
     305
     306        // Bypass for any page with fuerte-wp in the URL
     307        if (isset($_GET['page']) && strpos($_GET['page'], 'fuerte-wp') !== false) {
     308            return true;
     309        }
     310
     311        // Only check screen if we're in admin area and function exists
     312        if (function_exists('get_current_screen')) {
     313            $current_screen = get_current_screen();
     314            if ($current_screen) {
     315                // Bypass for Fuerte-WP settings pages
     316                if ($current_screen->id === 'settings_page_fuerte-wp-options' ||
     317                    $current_screen->id === 'toplevel_page_fuerte-wp-options') {
     318                    return true;
     319                }
     320            }
     321        }
     322
     323        return false;
     324    }
     325
     326    /**
     327     * Standardized method to get any configuration value with proper fallback logic.
     328     * Handles both direct options and Carbon Fields multiselect patterns automatically.
     329     *
     330     * @since 1.7.2
     331     * @param string $key Configuration key (without _fuertewp prefix)
     332     * @param mixed $default Default value if option doesn't exist
     333     * @param bool $is_multiselect Whether this field uses Carbon Fields multiselect pattern
     334     * @return mixed Configuration value
     335     */
     336    public static function get_field($key, $default = null, $is_multiselect = false)
     337    {
     338        $option_name = "_fuertewp_{$key}";
     339
     340        if ($is_multiselect) {
     341            return self::load_multiselect_field("fuertewp_{$key}");
     342        }
     343
     344        return get_option($option_name, $default);
     345    }
     346
     347    /**
     348     * Standardized method to set any configuration value.
     349     *
     350     * @since 1.7.2
     351     * @param string $key Configuration key (without _fuertewp prefix)
     352     * @param mixed $value Value to set
     353     * @param bool $is_multiselect Whether this field uses Carbon Fields multiselect pattern
     354     * @return bool Success status
     355     */
     356    public static function set_field($key, $value, $is_multiselect = false)
     357    {
     358        $option_name = "_fuertewp_{$key}";
     359
     360        if ($is_multiselect && is_array($value)) {
     361            return self::save_multiselect_field("fuertewp_{$key}", $value);
     362        }
     363
     364        return update_option($option_name, $value);
     365    }
     366
     367    /**
     368     * Save multiselect field values using Carbon Fields compatible pattern.
     369     *
     370     * @since 1.7.2
     371     * @param string $field_name Base field name without prefix
     372     * @param array $values Array of values to save
     373     * @return bool Success status
     374     */
     375    public static function save_multiselect_field($field_name, $values)
     376    {
     377        global $wpdb;
     378
     379        // First, delete all existing entries for this field
     380        $option_name_prefix = "_{$field_name}|||";
     381        $wpdb->query($wpdb->prepare(
     382            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
     383            $option_name_prefix . '%'
     384        ));
     385
     386        // Now insert the new values
     387        $success = true;
     388        foreach ($values as $index => $value) {
     389            $option_name = "_{$field_name}|||{$index}|value";
     390            $result = update_option($option_name, $value);
     391            if (!$result) {
     392                $success = false;
     393            }
     394        }
     395
     396        return $success;
     397    }
     398
     399    /**
     400     * Clean up duplicate storage patterns from database.
     401     * Removes old Carbon Fields storage patterns while preserving _fuertewp_* data.
     402     *
     403     * @since 1.7.2
     404     * @return array Cleanup results
     405     */
     406    public static function cleanup_duplicate_patterns()
     407    {
     408        global $wpdb;
     409
     410        $cleanup_results = array(
     411            'deleted_fuertewp_options' => 0,
     412            'deleted_carbon_fields_options' => 0,
     413            'preserved_fuertewp_options' => 0
     414        );
     415
     416        // Only proceed if user has admin capabilities
     417        if (!current_user_can('manage_options')) {
     418            return $cleanup_results;
     419        }
     420
     421        // Get all fuerte-related options
     422        $fuerte_options = $wpdb->get_results(
     423            "SELECT option_name, option_value
     424             FROM {$wpdb->options}
     425             WHERE option_name LIKE '%fuerte%'
     426             ORDER BY option_name ASC"
     427        );
     428
     429        foreach ($fuerte_options as $option) {
     430            $option_name = $option->option_name;
     431
     432            // Delete old fuertewp_* options (without underscore prefix)
     433            if (strpos($option_name, 'fuertewp_') === 0 && strpos($option_name, '_fuertewp_') !== 0) {
     434                // Special handling for login_db_version migration
     435                if ($option_name === 'fuertewp_login_db_version') {
     436                    $underscored_name = '_fuertewp_login_db_version';
     437                    if (!get_option($underscored_name)) {
     438                        $current_version = defined('FUERTEWP_VERSION') ? FUERTEWP_VERSION : '1.7.3';
     439                        update_option($underscored_name, $current_version); // Set to current plugin version
     440                        $cleanup_results['preserved_fuertewp_options']++;
     441                    }
     442                } else {
     443                    // First, migrate data to _fuertewp_ version if it doesn't exist
     444                    $underscored_name = '_' . $option_name;
     445                    if (!get_option($underscored_name)) {
     446                        update_option($underscored_name, $option->option_value);
     447                        $cleanup_results['preserved_fuertewp_options']++;
     448                    }
     449                }
     450
     451                // Delete the old option
     452                delete_option($option_name);
     453                $cleanup_results['deleted_fuertewp_options']++;
     454                continue;
     455            }
     456
     457            // Delete Carbon Fields compacted options
     458            if (strpos($option_name, '_carbon_fields_theme_options_fuerte-wp') === 0) {
     459                delete_option($option_name);
     460                $cleanup_results['deleted_carbon_fields_options']++;
     461                continue;
     462            }
     463
     464            // Delete double underscore options created by Carbon Fields field definitions
     465            if (strpos($option_name, '__fuertewp_') === 0) {
     466                // First, migrate data to single underscore version if it doesn't exist
     467                $single_underscore_name = '_' . substr($option_name, 2); // Remove one underscore
     468                if (!get_option($single_underscore_name)) {
     469                    update_option($single_underscore_name, $option->option_value);
     470                    $cleanup_results['preserved_fuertewp_options']++;
     471                }
     472
     473                // Delete the double underscore option
     474                delete_option($option_name);
     475                $cleanup_results['deleted_fuertewp_options']++;
     476                continue;
     477            }
     478        }
     479
     480        // Clear the cache after cleanup
     481        self::invalidate_cache();
     482
     483        return $cleanup_results;
     484    }
     485
     486    /**
     487     * Get configuration batch for enforcer class using standardized methods.
     488     * Replaces all direct get_option/carbon_get_theme_option calls.
     489     *
     490     * @since 1.7.2
     491     * @return array Configuration options array
     492     */
     493    public static function get_enforcer_config()
     494    {
     495        return array(
     496            // Status and core settings
     497            'fuertewp_status' => self::get_field('status', 'enabled'),
     498            'fuertewp_super_users' => self::get_field('super_users', [], true),
     499
     500            // General settings
     501            'fuertewp_access_denied_message' => self::get_field('access_denied_message', 'Access denied.'),
     502            'fuertewp_recovery_email' => self::get_field('recovery_email', ''),
     503            'fuertewp_sender_email_enable' => self::get_field('sender_email_enable', true),
     504            'fuertewp_sender_email' => self::get_field('sender_email', ''),
     505            'fuertewp_autoupdate_core' => self::get_field('autoupdate_core', false),
     506            'fuertewp_autoupdate_plugins' => self::get_field('autoupdate_plugins', false),
     507            'fuertewp_autoupdate_themes' => self::get_field('autoupdate_themes', false),
     508            'fuertewp_autoupdate_translations' => self::get_field('autoupdate_translations', false),
     509            'fuertewp_autoupdate_frequency' => self::get_field('autoupdate_frequency', 'daily'),
     510
     511            // Login security settings
     512            'fuertewp_login_enable' => self::get_field('login_enable', 'enabled'),
     513            'fuertewp_registration_enable' => self::get_field('registration_enable', 'enabled'),
     514            'fuertewp_login_max_attempts' => self::get_field('login_max_attempts', 5),
     515            'fuertewp_login_lockout_duration' => self::get_field('login_lockout_duration', 60),
     516            'fuertewp_login_increasing_lockout' => self::get_field('login_increasing_lockout', false),
     517            'fuertewp_login_ip_headers' => self::get_field('login_ip_headers', ''),
     518            'fuertewp_login_gdpr_message' => self::get_field('login_gdpr_message', ''),
     519            'fuertewp_login_data_retention' => self::get_field('login_data_retention', 30),
     520            'fuertewp_login_url_hiding_enabled' => self::get_field('login_url_hiding_enabled', false),
     521            'fuertewp_custom_login_slug' => self::get_field('custom_login_slug', 'secure-login'),
     522            'fuertewp_login_url_type' => self::get_field('login_url_type', 'query_param'),
     523            'fuertewp_redirect_invalid_logins' => self::get_field('redirect_invalid_logins', 'home_404'),
     524            'fuertewp_redirect_invalid_logins_url' => self::get_field('redirect_invalid_logins_url', ''),
     525
     526            // Email settings
     527            'fuertewp_emails_fatal_error' => self::get_field('emails_fatal_error', true),
     528            'fuertewp_emails_automatic_updates' => self::get_field('emails_automatic_updates', false),
     529            'fuertewp_emails_comment_awaiting_moderation' => self::get_field('emails_comment_awaiting_moderation', false),
     530            'fuertewp_emails_comment_has_been_published' => self::get_field('emails_comment_has_been_published', false),
     531            'fuertewp_emails_user_reset_their_password' => self::get_field('emails_user_reset_their_password', false),
     532            'fuertewp_emails_user_confirm_personal_data_export_request' => self::get_field('emails_user_confirm_personal_data_export_request', false),
     533            'fuertewp_emails_new_user_created' => self::get_field('emails_new_user_created', true),
     534            'fuertewp_emails_network_new_site_created' => self::get_field('emails_network_new_site_created', false),
     535            'fuertewp_emails_network_new_user_site_registered' => self::get_field('emails_network_new_user_site_registered', false),
     536            'fuertewp_emails_network_new_site_activated' => self::get_field('emails_network_new_site_activated', false),
     537
     538            // Restrictions
     539            'fuertewp_restrictions_restapi_loggedin_only' => self::get_field('restrictions_restapi_loggedin_only', false),
     540            'fuertewp_restrictions_restapi_disable_app_passwords' => self::get_field('restrictions_restapi_disable_app_passwords', true),
     541            'fuertewp_restrictions_disable_xmlrpc' => self::get_field('restrictions_disable_xmlrpc', true),
     542            'fuertewp_restrictions_htaccess_security_rules' => self::get_field('restrictions_htaccess_security_rules', true),
     543            'fuertewp_restrictions_disable_admin_create_edit' => self::get_field('restrictions_disable_admin_create_edit', true),
     544            'fuertewp_restrictions_disable_weak_passwords' => self::get_field('restrictions_disable_weak_passwords', true),
     545            'fuertewp_restrictions_force_strong_passwords' => self::get_field('restrictions_force_strong_passwords', false),
     546            'fuertewp_restrictions_disable_admin_bar_roles' => self::get_field('restrictions_disable_admin_bar_roles', ['subscriber', 'customer'], true),
     547            'fuertewp_restrictions_restrict_permalinks' => self::get_field('restrictions_restrict_permalinks', true),
     548            'fuertewp_restrictions_restrict_acf' => self::get_field('restrictions_restrict_acf', true),
     549            'fuertewp_restrictions_disable_theme_editor' => self::get_field('restrictions_disable_theme_editor', true),
     550            'fuertewp_restrictions_disable_plugin_editor' => self::get_field('restrictions_disable_plugin_editor', true),
     551            'fuertewp_restrictions_disable_theme_install' => self::get_field('restrictions_disable_theme_install', true),
     552            'fuertewp_restrictions_disable_plugin_install' => self::get_field('restrictions_disable_plugin_install', true),
     553            'fuertewp_restrictions_disable_customizer_css' => self::get_field('restrictions_disable_customizer_css', true),
     554
     555            // Advanced restrictions (arrays)
     556            'fuertewp_restricted_scripts' => self::get_field('restricted_scripts', [], true),
     557            'fuertewp_restricted_pages' => self::get_field('restricted_pages', [], true),
     558            'fuertewp_removed_menus' => self::get_field('removed_menus', [], true),
     559            'fuertewp_removed_submenus' => self::get_field('removed_submenus', [], true),
     560            'fuertewp_removed_adminbar_menus' => self::get_field('removed_adminbar_menus', [], true),
     561        );
     562    }
     563
     564    /**
     565     * Load multiselect field values from database.
     566     * Handles the Carbon Fields _field_name|||index|value pattern.
     567     *
     568     * @since 1.7.2
     569     * @param string $field_name Base field name without prefix
     570     * @return array Array of field values
     571     */
     572    private static function load_multiselect_field($field_name)
     573    {
     574        global $wpdb;
     575
     576        $option_name_prefix = "_{$field_name}|||";
     577        $values = array();
     578
     579        // Get all options matching the multiselect pattern
     580        $results = $wpdb->get_results($wpdb->prepare(
     581            "SELECT option_name, option_value
     582             FROM {$wpdb->options}
     583             WHERE option_name LIKE %s
     584             ORDER BY option_name ASC",
     585            $option_name_prefix . '%'
     586        ));
     587
     588        foreach ($results as $result) {
     589            // Parse the option name to get the index
     590            // Pattern: _fuertewp_super_users|||0|value
     591            if (preg_match('/\|\|\|(\d+)\|value$/', $result->option_name, $matches)) {
     592                $values[intval($matches[1])] = $result->option_value;
     593            }
     594        }
     595
     596        // Sort by index and re-index array
     597        ksort($values);
     598        return array_values($values);
     599    }
    238600}
  • fuerte-wp/trunk/includes/class-fuerte-wp-enforcer.php

    r3395401 r3395451  
    544544
    545545    /**
    546      * Get configuration options using Carbon Fields API.
    547      * This ensures proper integration with Carbon Fields custom datastore.
     546     * Get configuration options using standardized Config class methods.
     547     * Replaces all direct get_option/carbon_get_theme_option calls with a single standardized method.
     548     *
     549     * @since 1.7.2
     550     * @return array Configuration options
    548551     */
    549552    private function get_config_options_batch()
    550553    {
    551         // Use Carbon Fields API to get theme options
    552         // This handles the custom datastore and underscore prefix correctly
    553         $options = [];
    554 
    555         // Only attempt to get Carbon Fields options if the function exists
    556         if (function_exists('carbon_get_theme_option')) {
    557             $options['fuertewp_status'] = carbon_get_theme_option('fuertewp_status');
    558             // Get super users from Carbon Fields (where self-healing stores them)
    559             $super_users = carbon_get_theme_option('fuertewp_super_users');
    560 
    561             // Normalize to array format for consistency
    562             if (is_string($super_users) && !empty($super_users)) {
    563                 $options['super_users'] = [$super_users];
    564             } elseif (is_array($super_users)) {
    565                 $options['super_users'] = $super_users;
    566             } else {
    567                 $options['super_users'] = [];
    568             }
    569             $options['fuertewp_access_denied_message'] = carbon_get_theme_option('fuertewp_access_denied_message');
    570             $options['fuertewp_recovery_email'] = carbon_get_theme_option('fuertewp_recovery_email');
    571             $options['fuertewp_sender_email_enable'] = carbon_get_theme_option('fuertewp_sender_email_enable');
    572             $options['fuertewp_sender_email'] = carbon_get_theme_option('fuertewp_sender_email');
    573             $options['fuertewp_autoupdate_core'] = carbon_get_theme_option('fuertewp_autoupdate_core');
    574             $options['fuertewp_autoupdate_plugins'] = carbon_get_theme_option('fuertewp_autoupdate_plugins');
    575             $options['fuertewp_autoupdate_themes'] = carbon_get_theme_option('fuertewp_autoupdate_themes');
    576             $options['fuertewp_autoupdate_translations'] = carbon_get_theme_option('fuertewp_autoupdate_translations');
    577             $options['fuertewp_autoupdate_frequency'] = carbon_get_theme_option('fuertewp_autoupdate_frequency');
    578 
    579             // Login Security settings
    580             $options['fuertewp_login_enable'] = carbon_get_theme_option('fuertewp_login_enable');
    581             $options['fuertewp_registration_enable'] = carbon_get_theme_option('fuertewp_registration_enable');
    582             $options['fuertewp_login_max_attempts'] = carbon_get_theme_option('fuertewp_login_max_attempts');
    583             $options['fuertewp_login_lockout_duration'] = carbon_get_theme_option('fuertewp_login_lockout_duration');
    584             $options['fuertewp_login_lockout_duration_type'] = carbon_get_theme_option('fuertewp_login_lockout_duration_type');
    585             $options['fuertewp_login_cron_cleanup_frequency'] = carbon_get_theme_option('fuertewp_login_cron_cleanup_frequency');
    586 
    587             $options['fuertewp_tweaks_use_site_logo_login'] = carbon_get_theme_option('fuertewp_tweaks_use_site_logo_login');
    588             $options['fuertewp_emails_fatal_error'] = carbon_get_theme_option('fuertewp_emails_fatal_error');
    589             $options['fuertewp_emails_automatic_updates'] = carbon_get_theme_option('fuertewp_emails_automatic_updates');
    590             $options['fuertewp_emails_comment_awaiting_moderation'] = carbon_get_theme_option('fuertewp_emails_comment_awaiting_moderation');
    591             $options['fuertewp_emails_comment_has_been_published'] = carbon_get_theme_option('fuertewp_emails_comment_has_been_published');
    592             $options['fuertewp_emails_user_reset_their_password'] = carbon_get_theme_option('fuertewp_emails_user_reset_their_password');
    593             $options['fuertewp_emails_user_confirm_personal_data_export_request'] = carbon_get_theme_option('fuertewp_emails_user_confirm_personal_data_export_request');
    594             $options['fuertewp_emails_new_user_created'] = carbon_get_theme_option('fuertewp_emails_new_user_created');
    595             $options['fuertewp_emails_network_new_site_created'] = carbon_get_theme_option('fuertewp_emails_network_new_site_created');
    596             $options['fuertewp_emails_network_new_user_site_registered'] = carbon_get_theme_option('fuertewp_emails_network_new_user_site_registered');
    597             $options['fuertewp_emails_network_new_site_activated'] = carbon_get_theme_option('fuertewp_emails_network_new_site_activated');
    598             $options['fuertewp_restrictions_restapi_loggedin_only'] = carbon_get_theme_option('fuertewp_restrictions_restapi_loggedin_only');
    599             $options['fuertewp_restrictions_restapi_disable_app_passwords'] = carbon_get_theme_option('fuertewp_restrictions_restapi_disable_app_passwords');
    600             $options['fuertewp_restrictions_disable_xmlrpc'] = carbon_get_theme_option('fuertewp_restrictions_disable_xmlrpc');
    601             $options['fuertewp_restrictions_htaccess_security_rules'] = carbon_get_theme_option('fuertewp_restrictions_htaccess_security_rules');
    602             $options['fuertewp_restrictions_disable_admin_create_edit'] = carbon_get_theme_option('fuertewp_restrictions_disable_admin_create_edit');
    603             $options['fuertewp_restrictions_disable_weak_passwords'] = carbon_get_theme_option('fuertewp_restrictions_disable_weak_passwords');
    604             $options['fuertewp_restrictions_force_strong_passwords'] = carbon_get_theme_option('fuertewp_restrictions_force_strong_passwords');
    605             $options['fuertewp_restrictions_disable_admin_bar_roles'] = carbon_get_theme_option('fuertewp_restrictions_disable_admin_bar_roles');
    606             $options['fuertewp_restrictions_restrict_permalinks'] = carbon_get_theme_option('fuertewp_restrictions_restrict_permalinks');
    607             $options['fuertewp_restrictions_restrict_acf'] = carbon_get_theme_option('fuertewp_restrictions_restrict_acf');
    608             $options['fuertewp_restrictions_disable_theme_editor'] = carbon_get_theme_option('fuertewp_restrictions_disable_theme_editor');
    609             $options['fuertewp_restrictions_disable_plugin_editor'] = carbon_get_theme_option('fuertewp_restrictions_disable_plugin_editor');
    610             $options['fuertewp_restrictions_disable_theme_install'] = carbon_get_theme_option('fuertewp_restrictions_disable_theme_install');
    611             $options['fuertewp_restrictions_disable_plugin_install'] = carbon_get_theme_option('fuertewp_restrictions_disable_plugin_install');
    612             $options['fuertewp_restrictions_disable_customizer_css'] = carbon_get_theme_option('fuertewp_restrictions_disable_customizer_css');
    613             $options['fuertewp_restricted_scripts'] = carbon_get_theme_option('fuertewp_restricted_scripts');
    614             $options['fuertewp_restricted_pages'] = carbon_get_theme_option('fuertewp_restricted_pages');
    615             $options['fuertewp_removed_menus'] = carbon_get_theme_option('fuertewp_removed_menus');
    616             $options['fuertewp_removed_submenus'] = carbon_get_theme_option('fuertewp_removed_submenus');
    617             $options['fuertewp_removed_adminbar_menus'] = carbon_get_theme_option('fuertewp_removed_adminbar_menus');
    618         }
    619 
    620         return $options;
     554        return Fuerte_Wp_Config::get_enforcer_config();
    621555    }
    622556
  • fuerte-wp/trunk/includes/class-fuerte-wp-login-logger.php

    r3395378 r3395451  
    548548    public function get_remaining_attempts($ip, $username)
    549549    {
    550         $max_attempts = (int)carbon_get_theme_option('fuertewp_login_max_attempts', 5);
     550        $max_attempts = (int)Fuerte_Wp_Config::get_field('login_max_attempts', 5);
    551551
    552552        $failed_count = $this->get_failed_attempts($ip, $username);
  • fuerte-wp/trunk/includes/class-fuerte-wp-login-manager.php

    r3395378 r3395451  
    392392        // Show remaining attempts if any failed
    393393        $remaining = $this->logger->get_remaining_attempts($ip, $username);
    394         $max_attempts = (int)carbon_get_theme_option('fuertewp_login_max_attempts', 5);
     394        $max_attempts = (int)Fuerte_Wp_Config::get_field('login_max_attempts', 5);
    395395
    396396        if ($remaining < $max_attempts) {
     
    585585    {
    586586        // Use the correct container ID ('Fuerte-WP') since we're using compact input
    587         $enabled = carbon_get_theme_option('fuertewp_registration_enable', 'fuerte-wp');
     587        $enabled = Fuerte_Wp_Config::get_field('registration_enable', 'enabled');
    588588
    589589        // Handle different Carbon Fields return formats
Note: See TracChangeset for help on using the changeset viewer.