diff --git a/inc/spbc-admin.php b/inc/spbc-admin.php index 5812c6511..20ebff260 100644 --- a/inc/spbc-admin.php +++ b/inc/spbc-admin.php @@ -199,6 +199,9 @@ function spbc_admin_init() add_filter('manage_users_columns', 'spbc_users_list_pass_check_column'); add_action('wp_ajax_spbc_check_pass_leak', [UsersPassCheckHandler::class, 'checkPassLeak']); } + + // Sync disallow file edit setting with FileEditorDisabler + FileEditorDisabler::syncDisallowFileEditBySettings($spbc->settings); } /** @@ -977,9 +980,6 @@ function spbc_set_malware_scan_warns() $triggers_has_dangerous = DBTriggerService::countTriggersStorage(); - //// Sync disallow file edit setting with FileEditorDisabler - FileEditorDisabler::syncDisallowFileEditBySettings($spbc->settings, $critical_count); - $spbc->data['display_scanner_warnings'] = array( 'critical' => $critical_count, 'signatures' => $signatures_count, diff --git a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorDashboard.php b/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorDashboard.php index 7a911a16a..1688f3235 100644 --- a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorDashboard.php +++ b/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorDashboard.php @@ -29,11 +29,9 @@ protected function needToShow() { global $spbc, $pagenow; - $no_wpconfig_error = !defined('SPBC_WPCONFIG_ERROR') || !constant('SPBC_WPCONFIG_ERROR'); return ( isset($spbc->settings['misc_disable_file_editor']) && $spbc->settings['misc_disable_file_editor'] == 2 && - $no_wpconfig_error && is_admin() && $pagenow === 'index.php' && current_user_can('administrator') && diff --git a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorSettings.php b/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorSettings.php index b6a2cf93b..fe882a54b 100644 --- a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorSettings.php +++ b/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerFileEditorSettings.php @@ -21,11 +21,10 @@ public function __construct(AdminBannersHandler $banners_handler) protected function needToShow() { global $spbc; - $no_wpconfig_error = !defined('SPBC_WPCONFIG_ERROR') || !constant('SPBC_WPCONFIG_ERROR'); + return ( isset($spbc->settings['misc_disable_file_editor']) && $spbc->settings['misc_disable_file_editor'] == 2 && - $no_wpconfig_error && is_admin() && isset($_GET['page']) && $_GET['page'] === 'spbc' && current_user_can('administrator') && diff --git a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerWpConfigError.php b/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerWpConfigError.php deleted file mode 100644 index 0463f935d..000000000 --- a/lib/CleantalkSP/SpbctWP/AdminBannersModule/AdminBanners/AdminBannerWpConfigError.php +++ /dev/null @@ -1,47 +0,0 @@ -banner_id = $this->prefix . self::NAME . '_' . $banners_handler->getUserId(); - } - - protected function needToShow() - { - return ( - !get_option('spbc_hide_wpconfig_error_banner') && - (defined('SPBC_WPCONFIG_ERROR') && constant('SPBC_WPCONFIG_ERROR')) && - current_user_can('administrator') && - !$this->isDismissed() - ); - } - - protected function display() - { - $error = defined('SPBC_WPCONFIG_ERROR') && constant('SPBC_WPCONFIG_ERROR') - ? FileEditorDisabler::getErrorMessage((string)SPBC_WPCONFIG_ERROR) - : FileEditorDisabler::getErrorMessage('config_common_error'); - ?> -
-

-
- 0]; - \CleantalkSP\SpbctWP\FileEditorDisabler\FileEditorDisabler::syncDisallowFileEditBySettings($file_editor_settings); - return self::$deactivation_result; } diff --git a/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisabler.php b/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisabler.php index 92bded0d5..4838ec6e9 100644 --- a/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisabler.php +++ b/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisabler.php @@ -4,76 +4,19 @@ class FileEditorDisabler { - public static $config_path = ''; /** - * Returns the path to wp-config.php. - * Uses get_home_path() if available, otherwise uses ABSPATH. - * @return string - */ - private static function getWpConfigPath() - { - $home_option_path = ''; - if (function_exists('get_home_path')) { - $home_option_path = get_home_path() . 'wp-config.php'; - } - - $config_path = ''; - - $abs_path = ABSPATH . 'wp-config.php'; - $abs_path_prepared = str_replace('\\', '/', ABSPATH) . 'wp-config.php'; - - if (@file_exists($home_option_path)) { - $config_path = $home_option_path; - } elseif (@file_exists($abs_path)) { - $config_path = $abs_path; - } elseif (@file_exists($abs_path_prepared)) { - $config_path = $abs_path_prepared; - } - return $config_path; - } - - /** - * Checks if wp-config.php is readable and writable. - * @return bool - */ - private static function isConfigAccessible() - { - self::$config_path = empty(self::$config_path) ? self::getWpConfigPath() : self::$config_path; - if (empty(self::$config_path) || !file_exists(self::$config_path)) { - if (!defined('SPBC_WPCONFIG_ERROR')) { - define('SPBC_WPCONFIG_ERROR', 'config_not_found'); - } - return false; - } - - if (!is_writable(self::$config_path)) { - if (!defined('SPBC_WPCONFIG_ERROR')) { - define('SPBC_WPCONFIG_ERROR', 'config_write_error'); - } - return false; - } - - return true; - } - - /** - * Syncs the DISALLOW_FILE_EDIT constant in wp-config.php based on the settings. - * If 'misc_disable_file_editor' is set to 1 or 2, it adds DISALLOW_FILE_EDIT. - * If 'misc_disable_file_editor' is not set or set to 0, it removes DISALLOW_FILE_EDIT. + * Syncs the disallow file editor based on the settings. * @param array $settings */ - public static function syncDisallowFileEditBySettings(&$settings, $critical_count = null) + public static function syncDisallowFileEditBySettings(&$settings) { global $spbc; if (is_multisite() && !is_network_admin()) { $settings['misc_disable_file_editor'] = get_site_option('spbc_network_misc_disable_file_editor', 0); } - if (!self::isConfigAccessible()) { - return false; - } - $config = file_get_contents(self::$config_path); - $block_exists = self::hasDisallowFileEditBlock($config); + + $critical_count = self::getCriticalCount(); if ($critical_count !== null && isset($settings['misc_disable_file_editor'])) { $save_settings = false; @@ -102,207 +45,93 @@ public static function syncDisallowFileEditBySettings(&$settings, $critical_coun } } - $enabled = + if ( isset($settings['misc_disable_file_editor']) && - ($settings['misc_disable_file_editor'] == 1 || $settings['misc_disable_file_editor'] == 2); - - if ($enabled && $block_exists) { - return true; - } - if (!$enabled && !$block_exists) { - return true; - } - - if ($enabled) { - return self::addDisallowFileEdit(); - } else { - return self::removeDisallowFileEdit(); - } - } - - /** - * Creates a backup wp-config.php in /cleantalk_backups/wp-config-{random_hash[7]}.php - * @param string $wp_config_path - */ - private static function backupWpConfig($wp_config_path) - { - $backup_dir = defined('SPBC_PLUGIN_DIR') - ? rtrim(SPBC_PLUGIN_DIR, '/\\') . '/cleantalk_backups' - : rtrim(plugin_dir_path(__FILE__), '/\\') . '/cleantalk_backups'; - - if (!is_dir($backup_dir)) { - mkdir($backup_dir, 0755, true); - } - - $hash = substr(md5(microtime() . rand()), 0, 7); - $backup_path = $backup_dir . '/wp-config-' . $hash . '.php'; - if (copy($wp_config_path, $backup_path)) { - self::cleanupBackups($backup_dir); - return $backup_path; - } - return false; - } - - /** - * Cleans up old backups in /cleantalk_backups, keeping only the latest $max_files backups. - * @param string $backup_dir - * @param int $max_files - */ - private static function cleanupBackups($backup_dir, $max_files = 10) - { - if (!is_dir($backup_dir)) { - return; - } - $files = glob($backup_dir . '/wp-config-*.php'); - if (!$files || count($files) <= $max_files) { - return; - } - - usort($files, function ($a, $b) { - return filemtime($b) - filemtime($a); - }); - - $to_delete = array_slice($files, $max_files); - foreach ($to_delete as $file) { - @unlink($file); + ( + $settings['misc_disable_file_editor'] == 1 || + $settings['misc_disable_file_editor'] == 2 + ) + ) { + return self::turnOnDisallowFileEdit(); } } /** - * Adds DISALLOW_FILE_EDIT to wp-config.php between the comments, CleanTalk Security Features. - * @param string $wp_config_path + * Turns on disallow file editor * @return bool */ - private static function addDisallowFileEdit() + private static function turnOnDisallowFileEdit() { - if (!self::isConfigAccessible()) { - return false; - } - - $config = file_get_contents(self::$config_path); - if (self::hasDisallowFileEditConstant($config)) { - return true; - } - - $search_part = "/* That's all, stop editing!"; - $insert = << sprintf( - __( - "config file not found [%s] - check your WordPress installation.", - 'security-malware-firewall' - ), - esc_html(self::$config_path) - ), - 'config_write_error' => sprintf( - __( - "can't write wp-config.php [%s] - set correct permissions or set/remove definition manually: \"define('DISALLOW_FILE_EDIT', true)\"", - 'security-malware-firewall' - ), - esc_html(self::$config_path) - ), - 'config_common_error' => sprintf( - __( - "can't find or rewrite wp-config.php [%s]", - 'security-malware-firewall' - ), - esc_html(self::$config_path) - ), - 'unknown' => __( - "unknown error", - 'security-malware-firewall' - ), - ]; - - $message = isset($error_messages[$error_type]) - ? $error_messages[$error_type] - : $error_messages['unknown']; - - return sprintf( - '%s, "%s" feature: %s', - $spbc->data["wl_brandname"], - 'Disable File Editor', - $message - ); + global $wpdb; + $query = 'SELECT COUNT(*) + FROM ' . SPBC_TBL_SCAN_FILES . ' + WHERE (STATUS = "INFECTED" AND severity = "CRITICAL") + OR STATUS = "DENIED_BY_CLOUD" + OR STATUS = "DENIED_BY_CT"'; + return (int)$wpdb->get_var($query); } } diff --git a/lib/CleantalkSP/Updater/UpdaterScripts.php b/lib/CleantalkSP/Updater/UpdaterScripts.php index edeb15e8e..5520cb9b4 100644 --- a/lib/CleantalkSP/Updater/UpdaterScripts.php +++ b/lib/CleantalkSP/Updater/UpdaterScripts.php @@ -1552,4 +1552,96 @@ public static function updateTo_2_169_0() // phpcs:ignore PSR1.Methods.CamelCaps $spbc->settings['2fa_roles'] = $new_2fa_roles; $spbc->save('settings'); } + + public static function updateTo_2_171_0() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps + { + global $spbc; + + if (!isset($spbc->settings['misc_disable_file_editor'])) { + return; + } + + if ($spbc->settings['misc_disable_file_editor'] === 0) { + return; + } + + // Get the config file + $config_path = ''; + $home_option_path = ''; + $file_name = 'wp-config.php'; + if (function_exists('get_home_path')) { + $home_option_path = get_home_path() . $file_name; + } + $abs_path = ABSPATH . $file_name; + $abs_path_prepared = str_replace('\\', '/', ABSPATH) . $file_name; + if (@file_exists($home_option_path)) { + $config_path = $home_option_path; + } elseif (@file_exists($abs_path)) { + $config_path = $abs_path; + } elseif (@file_exists($abs_path_prepared)) { + $config_path = $abs_path_prepared; + } + if ($config_path === '') { + return; + } + $config = file_get_contents($config_path); + if ($config === false) { + return; + } + + // match the CleanTalk DISALLOW_FILE_EDIT block + $pattern = '/\n?\/\*\s*CleanTalk\s+Security\s+Features\s*\*\/\s*\n\s*define\s*\(\s*[\'"]DISALLOW_FILE_EDIT[\'"]\s*,\s*true\s*\)\s*;\s*\n\s*\/\*\s*CleanTalk\s+Security\s+Features\s*\*\/\s*\n?/i'; + $matches_found = preg_match($pattern, $config); + if ($matches_found) { + preg_match($pattern, $config, $match); + } + $new_config = preg_replace($pattern, "\n", $config, 1); + if ($new_config === $config) { + return; + } else { + file_put_contents($config_path, $new_config); + } + + $is_need_rollback = false; + $is_need_rollback_admin = false; + + // check that site is not broken by get home page + $home_url = home_url(); + $response = wp_remote_get($home_url); + $code = wp_remote_retrieve_response_code($response); + $body = wp_remote_retrieve_body($response); + $pattern = '/Parse.*?error.*?wp-config\.php/i'; + $matches = preg_match($pattern, $body); + if (($code !== 200 && $code !== 403) || is_wp_error($response) || empty($body) || $matches) { + $is_need_rollback = true; + } + + // check that site is not broken by get admin page + $admin_url = admin_url(); + $response = wp_remote_get($admin_url); + $code = wp_remote_retrieve_response_code($response); + $body = wp_remote_retrieve_body($response); + $matches = preg_match($pattern, $body); + if (($code !== 200 && $code !== 403) || is_wp_error($response) || empty($body) || $matches) { + $is_need_rollback_admin = true; + } + + if ($is_need_rollback || $is_need_rollback_admin) { + file_put_contents($config_path, $config); + return; + } + + // remove backups + $backup_dir = defined('SPBC_PLUGIN_DIR') + ? rtrim(SPBC_PLUGIN_DIR, '/\\') . '/cleantalk_backups' + : rtrim(plugin_dir_path(__FILE__), '/\\') . '/cleantalk_backups'; + + if (is_dir($backup_dir)) { + $files = glob($backup_dir . '/*'); + foreach ($files as $file) { + @unlink($file); + } + @rmdir($backup_dir); + } + } } diff --git a/tests/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisablerTest.php b/tests/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisablerTest.php new file mode 100644 index 000000000..e4d02d657 --- /dev/null +++ b/tests/lib/CleantalkSP/SpbctWP/FileEditorDisabler/FileEditorDisablerTest.php @@ -0,0 +1,374 @@ +wpdb = $wpdb; + $this->original_spbc = $spbc; + + // Ensure SPBC_TBL_SCAN_FILES constant is defined + if (!defined('SPBC_TBL_SCAN_FILES')) { + define('SPBC_TBL_SCAN_FILES', $wpdb->base_prefix . 'spbc_scan_results'); + } + + // Clear any existing filters + remove_all_filters('map_meta_cap'); + remove_all_filters('wp_die_handler'); + } + + protected function tearDown(): void + { + parent::tearDown(); + global $spbc; + + // Restore original state + $spbc = $this->original_spbc; + + // Clear filters + remove_all_filters('map_meta_cap'); + remove_all_filters('wp_die_handler'); + } + + /** + * Test that syncDisallowFileEditBySettings enables file editor when setting is 1 + */ + public function testSyncDisallowFileEditBySettings_EnablesWhenSettingIs1() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + // Mock getCriticalCount to return 0 (no critical files) + $this->mockGetCriticalCount(0); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertTrue($result); + $this->assertTrue(has_filter('map_meta_cap')); + $this->assertTrue(has_filter('wp_die_handler')); + } + + /** + * Test that syncDisallowFileEditBySettings enables file editor when setting is 2 (auto mode) and critical files exist + */ + public function testSyncDisallowFileEditBySettings_EnablesWhenSettingIs2() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 2 + ); + + // Mock getCriticalCount to return 3 (critical files exist, so auto mode stays enabled) + $this->mockGetCriticalCount(3); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertTrue($result); + $this->assertTrue(has_filter('map_meta_cap')); + $this->assertTrue(has_filter('wp_die_handler')); + } + + /** + * Test that syncDisallowFileEditBySettings does not enable when setting is 0 + */ + public function testSyncDisallowFileEditBySettings_DoesNotEnableWhenSettingIs0() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 0 + ); + + // Mock getCriticalCount to return 0 + $this->mockGetCriticalCount(0); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertNull($result); + $this->assertFalse(has_filter('map_meta_cap')); + } + + /** + * Test that syncDisallowFileEditBySettings enables auto mode when critical count > 0 + */ + public function testSyncDisallowFileEditBySettings_EnablesAutoModeWhenCriticalCountGreaterThan0() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 0 + ); + + // Mock getCriticalCount to return 5 (critical files found) + $this->mockGetCriticalCount(5); + + // Mock $spbc object with save method + $spbc = $this->createMock(State::class); + $spbc->settings = $settings; + $spbc->expects($this->once()) + ->method('save') + ->with('settings'); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertTrue($result); + $this->assertEquals(2, $settings['misc_disable_file_editor']); + $this->assertTrue(has_filter('map_meta_cap')); + } + + /** + * Test that syncDisallowFileEditBySettings disables auto mode when critical count is 0 + */ + public function testSyncDisallowFileEditBySettings_DisablesAutoModeWhenCriticalCountIs0() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 2 + ); + + // Mock getCriticalCount to return 0 + $this->mockGetCriticalCount(0); + + // Mock $spbc object with save method + $spbc = $this->createMock(State::class); + $spbc->settings = $settings; + $spbc->expects($this->once()) + ->method('save') + ->with('settings'); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertNull($result); + $this->assertEquals(0, $settings['misc_disable_file_editor']); + $this->assertFalse(has_filter('map_meta_cap')); + } + + /** + * Test that syncDisallowFileEditBySettings uses network option in multisite + */ + public function testSyncDisallowFileEditBySettings_UsesNetworkOptionInMultisite() + { + global $spbc; + + // Mock multisite functions + if (!function_exists('is_multisite')) { + $this->markTestSkipped('WordPress multisite functions not available'); + } + + // This test would require mocking is_multisite() and is_network_admin() + // which is complex in WordPress test environment + // For now, we'll test the basic functionality + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + $this->mockGetCriticalCount(0); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertTrue($result); + } + + /** + * Test that map_meta_cap filter blocks edit_plugins capability + */ + public function testMapMetaCapFilter_BlocksEditPlugins() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + $this->mockGetCriticalCount(0); + + FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + // Apply the filter + $caps = array('edit_plugins', 'edit_posts'); + $filtered_caps = apply_filters('map_meta_cap', $caps, 'edit_plugins', 1, array()); + + $this->assertContains('do_not_allow', $filtered_caps); + $this->assertNotContains('edit_plugins', $filtered_caps); + } + + /** + * Test that map_meta_cap filter blocks edit_themes capability + */ + public function testMapMetaCapFilter_BlocksEditThemes() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + $this->mockGetCriticalCount(0); + + FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + // Apply the filter + $caps = array('edit_themes', 'edit_posts'); + $filtered_caps = apply_filters('map_meta_cap', $caps, 'edit_themes', 1, array()); + + $this->assertContains('do_not_allow', $filtered_caps); + $this->assertNotContains('edit_themes', $filtered_caps); + } + + /** + * Test that map_meta_cap filter blocks edit_files capability + */ + public function testMapMetaCapFilter_BlocksEditFiles() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + $this->mockGetCriticalCount(0); + + FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + // Apply the filter + $caps = array('edit_files', 'edit_posts'); + $filtered_caps = apply_filters('map_meta_cap', $caps, 'edit_files', 1, array()); + + $this->assertContains('do_not_allow', $filtered_caps); + $this->assertNotContains('edit_files', $filtered_caps); + } + + /** + * Test that map_meta_cap filter does not affect other capabilities + */ + public function testMapMetaCapFilter_DoesNotAffectOtherCapabilities() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 1 + ); + + $this->mockGetCriticalCount(0); + + FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + // Apply the filter with non-editor capabilities + $caps = array('edit_posts', 'publish_posts', 'delete_posts'); + $filtered_caps = apply_filters('map_meta_cap', $caps, 'edit_posts', 1, array()); + + $this->assertContains('edit_posts', $filtered_caps); + $this->assertContains('publish_posts', $filtered_caps); + $this->assertContains('delete_posts', $filtered_caps); + $this->assertNotContains('do_not_allow', $filtered_caps); + } + + /** + * Test that syncDisallowFileEditBySettings does not save when save method doesn't exist + */ + public function testSyncDisallowFileEditBySettings_DoesNotSaveWhenSaveMethodDoesNotExist() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 0 + ); + + // Mock getCriticalCount to return 5 + $this->mockGetCriticalCount(5); + + // Create a mock object without save method + $spbc = new \stdClass(); + $spbc->settings = $settings; + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + // Should still enable the editor but not save + $this->assertTrue($result); + $this->assertEquals(2, $settings['misc_disable_file_editor']); + } + + /** + * Test that syncDisallowFileEditBySettings handles null critical count + */ + public function testSyncDisallowFileEditBySettings_HandlesNullCriticalCount() + { + global $spbc; + + $settings = array( + 'misc_disable_file_editor' => 0 + ); + + // Mock getCriticalCount to return null + $this->mockGetCriticalCount(null); + + $result = FileEditorDisabler::syncDisallowFileEditBySettings($settings); + + $this->assertNull($result); + $this->assertEquals(0, $settings['misc_disable_file_editor']); + } + + /** + * Helper method to mock getCriticalCount by manipulating the database + */ + protected function mockGetCriticalCount($count) + { + global $wpdb; + + // Create or truncate the test table + $table_name = SPBC_TBL_SCAN_FILES; + $wpdb->query("DROP TABLE IF EXISTS {$table_name}"); + + $charset_collate = $wpdb->get_charset_collate(); + $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( + id bigint(20) NOT NULL AUTO_INCREMENT, + path text NOT NULL, + STATUS varchar(50) NOT NULL, + severity varchar(50) DEFAULT NULL, + PRIMARY KEY (id) + ) {$charset_collate};"; + + require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + dbDelta($sql); + + // Insert test data based on count + if ($count !== null && $count > 0) { + for ($i = 0; $i < $count; $i++) { + // Insert critical infected file + $wpdb->insert( + $table_name, + array( + 'path' => '/test/file' . $i . '.php', + 'STATUS' => 'INFECTED', + 'severity' => 'CRITICAL' + ) + ); + } + } elseif ($count === 0) { + // Insert a non-critical file to ensure table exists but count is 0 + $wpdb->insert( + $table_name, + array( + 'path' => '/test/safe.php', + 'STATUS' => 'OK', + 'severity' => null + ) + ); + } + } +}