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
+ )
+ );
+ }
+ }
+}