PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.0
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.0
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | includes/class-deactivator.php +95 -155 2.10.23.0.0 View file →
@@ -20,22 +20,67 @@
20 20 class Vigilante_Deactivator {
21 21
22 22 /**
23 23 * Run deactivation tasks
24 + *
25 + * @param bool $network_wide Whether core is deactivating the plugin for the
26 + * whole network, as it passes it to the hook.
24 27 */
25 - public static function deactivate() {
26 - // ALWAYS remove htaccess rules using the centralized manager
27 - self::remove_htaccess_rules();
28 + public static function deactivate( $network_wide = false ) {
29 + /*
30 + * wp-config.php and the root .htaccess belong to the main site of a
31 + * network, and the gate that protects them asks whether the request is
32 + * on the main site. A network-wide deactivation can arrive from any site,
33 + * though: the REST plugins endpoint answers on every site's URL, and
34 + * WP-CLI takes --network together with the --url of a subsite. Asked
35 + * there, the gate said no and the blocks stayed behind, with no plugin
36 + * left to remove them. So the question is asked on the main site. Only
37 + * the site changes: the capability is still checked against whoever is
38 + * deactivating, so a subsite administrator gains nothing from it.
39 + */
40 + $switched = false;
41 + if ( $network_wide && is_multisite() && ! is_main_site() ) {
42 + switch_to_blog( get_main_site_id() );
43 + $switched = true;
44 + }
28 45
29 - // ALWAYS remove wp-config security constants and restore originals
30 - self::remove_wpconfig_security();
46 + try {
47 + /*
48 + * Activating network-wide does not clear a site's own activation, so
49 + * a Vigilant that was active on the main site before it was activated
50 + * for the network is still running there after a network
51 + * deactivation. Its blocks are still in use, and removing them would
52 + * leave that copy running without the protections it wrote.
53 + */
54 + if ( ! self::still_active_here( $network_wide ) ) {
55 + // ALWAYS remove htaccess rules using the centralized manager
56 + self::remove_htaccess_rules();
31 57
32 - // Clear scheduled events
33 - self::clear_scheduled_events();
58 + // ALWAYS remove wp-config security constants and restore originals
59 + self::remove_wpconfig_security();
60 + }
61 + } finally {
62 + if ( $switched ) {
63 + restore_current_blog();
64 + }
65 + }
34 66
35 - // Send deactivation email
36 - self::send_deactivation_email();
67 + /*
68 + * The same question for the site the request is on: a copy that keeps
69 + * running here keeps its schedule, and since it has not been deactivated
70 + * it is not the one to announce that protection is off. The transients
71 + * below are cleared either way; for a copy that keeps running that only
72 + * resets the alert engine's counters and cooldowns, so an alert can come
73 + * back sooner than it would have.
74 + */
75 + if ( ! self::still_active_here( $network_wide ) ) {
76 + // Clear scheduled events
77 + self::clear_scheduled_events();
37 78
79 + // Send deactivation email
80 + self::send_deactivation_email();
81 + }
82 +
38 83 // Clear transients
39 84 delete_transient( 'vigilante_activated' );
40 85 delete_transient( 'vigilante_restore_on_deactivate' );
41 86 delete_transient( 'vigilante_backup_error' );
@@ -68,80 +113,56 @@
68 113 /**
69 114 * Remove wp-config security constants and restore original values
70 115 */
71 116 private static function remove_wpconfig_security() {
117 + /*
118 + * One removal for the whole plugin. This used to be a copy of
119 + * Vigilante_Wpconfig_Security::remove_constants(), and the copy had
120 + * drifted from it in three ways. Until 2.11.5 it did not ask the network
121 + * gate, so the administrator of a subsite who deactivated a per-site
122 + * Vigilant stripped the constants for every site of the network (reported
123 + * by the wordpress.org automated security review). It cut the block line
124 + * by line, so a block that had lost its END marker took everything after
125 + * it, the require of wp-settings.php included. And it wrote through
126 + * WP_Filesystem with FS_CHMOD_FILE, which is "permissions of index.php |
127 + * 0644", so a wp-config.php kept at 0600 or 0640 was left at 0644; a
128 + * direct write keeps the permissions the file already has.
129 + */
72 130 $wpconfig_path = ABSPATH . 'wp-config.php';
73 131
74 - if ( ! file_exists( $wpconfig_path ) ) {
132 + // remove_constants() writes the file directly, so there is nothing to do
133 + // when PHP cannot.
134 + if ( ! file_exists( $wpconfig_path ) || ! wp_is_writable( $wpconfig_path ) ) {
75 135 return;
76 136 }
77 137
78 - // Initialize WP_Filesystem
79 - global $wp_filesystem;
80 - if ( ! function_exists( 'WP_Filesystem' ) ) {
81 - require_once ABSPATH . 'wp-admin/includes/file.php';
82 - }
83 - WP_Filesystem();
138 + require_once VIGILANTE_INCLUDES_DIR . 'class-wpconfig-security.php';
84 139
85 - if ( ! $wp_filesystem || ! $wp_filesystem->is_writable( $wpconfig_path ) ) {
86 - return;
87 - }
140 + // remove_constants() asks the network gate and leaves alone a block that
141 + // has lost one of its markers.
142 + $wpconfig = new Vigilante_Wpconfig_Security( new Vigilante_Settings() );
143 + $wpconfig->remove_constants();
144 + }
88 145
89 - $content = $wp_filesystem->get_contents( $wpconfig_path );
90 -
91 - if ( false === $content || empty( $content ) ) {
92 - return;
93 - }
94 -
95 - $modified = false;
96 -
97 - // Step 1: Remove our Vigilante block
98 - if ( strpos( $content, '/* BEGIN Vigilante Security Constants */' ) !== false ) {
99 - $lines = explode( "\n", $content );
100 - $new_lines = array();
101 - $inside_block = false;
102 -
103 - foreach ( $lines as $line ) {
104 - if ( strpos( $line, '/* BEGIN Vigilante Security Constants */' ) !== false ) {
105 - $inside_block = true;
106 - continue;
107 - }
108 -
109 - if ( strpos( $line, '/* END Vigilante Security Constants */' ) !== false ) {
110 - $inside_block = false;
111 - continue;
112 - }
113 -
114 - if ( ! $inside_block ) {
115 - $new_lines[] = $line;
116 - }
117 - }
118 -
119 - $content = implode( "\n", $new_lines );
120 - $modified = true;
121 - }
122 -
123 - // Step 2: Uncomment original constants (restore [VIGILANTE_ORIGINAL] lines)
124 - $original_marker = '// [VIGILANTE_ORIGINAL] ';
125 - if ( strpos( $content, $original_marker ) !== false ) {
126 - $pattern = '/^(\s*)' . preg_quote( $original_marker, '/' ) . '(.+)$/m';
127 - $content = preg_replace( $pattern, '$1$2', $content );
128 - $modified = true;
129 - }
130 -
131 - if ( ! $modified ) {
132 - return;
133 - }
134 -
135 - // Clean up multiple empty lines
136 - $content = preg_replace( '/\n{3,}/', "\n\n", $content );
137 -
138 - // Safety check: must still have basic wp-config content
139 - if ( strpos( $content, 'DB_NAME' ) === false ) {
140 - return; // Don't write if it would corrupt wp-config
141 - }
142 -
143 - $wp_filesystem->put_contents( $wpconfig_path, $content, FS_CHMOD_FILE );
146 + /**
147 + * Whether a per-site Vigilant keeps running on the current site
148 + *
149 + * Only a network-wide deactivation can leave one behind, and only then is
150 + * the site's own list final when the hook runs: core saves the lists after
151 + * the hook, so for a per-site deactivation the list still names the plugin.
152 + * Code that calls deactivate_plugins() without saying whether it is
153 + * network-wide on a plugin active both ways drops the site's entry too,
154 + * after the hook, and then this answers yes for a copy that is going away;
155 + * core itself never makes that call.
156 + *
157 + * @since 2.11.6
158 + *
159 + * @param bool $network_wide Whether the deactivation is network-wide.
160 + * @return bool
161 + */
162 + private static function still_active_here( $network_wide ) {
163 + return $network_wide && is_multisite()
164 + && in_array( VIGILANTE_PLUGIN_BASENAME, (array) get_option( 'active_plugins', array() ), true );
144 165 }
145 166
146 167 /**
147 168 * Clear all scheduled cron events
@@ -214,87 +235,6 @@
214 235 __( 'Deactivated by', 'vigilante' ) => $user_info,
215 236 ) );
216 237
217 238 Vigilante_Email_Template::send( $to, $subject, __( 'Plugin deactivated', 'vigilante' ), $body );
218 - }
219 -
220 - /**
221 - * Full uninstall - removes all data
222 - * Called from uninstall.php
223 - */
224 - public static function uninstall() {
225 - // Drop database tables
226 - $database = new Vigilante_Database();
227 - $database->drop_tables();
228 -
229 - // Remove all options
230 - delete_option( 'vigilante_options' );
231 - delete_option( 'vigilante_db_version' );
232 - delete_option( 'vigilante_activated_time' );
233 - delete_option( 'vigilante_dismissed_notices' );
234 - delete_option( 'vigilante_backup_timestamp' );
235 - delete_option( 'vigilante_last_integrity_results' );
236 - delete_option( 'vigilante_last_integrity_scan' );
237 - delete_option( 'vigilante_critical_files_baseline' );
238 - delete_option( 'vigilante_analyzer_last_scan' );
239 - delete_option( 'vigilante_analyzer_history' );
240 - delete_option( 'vigilante_analyzer_fix_log' );
241 -
242 - // Remove transients
243 - delete_transient( 'vigilante_activated' );
244 - delete_transient( 'vigilante_restore_on_deactivate' );
245 - delete_transient( 'vigilante_backup_error' );
246 - delete_transient( 'vigilante_file_integrity_last_scan' );
247 -
248 - // Audit Alerts: remove every engine transient (counters, cooldowns and
249 - // the immediate anti-duplicate keys), including their timeout twins.
250 - // Names are dynamic (md5 per event), so a prefix sweep is the only way.
251 - global $wpdb;
252 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-off uninstall cleanup; dynamic transient names cannot be enumerated individually.
253 - $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\\_transient\\_vigilante\\_aa\\_%' OR option_name LIKE '\\_transient\\_timeout\\_vigilante\\_aa\\_%'" );
254 -
255 - // Remove backup directory
256 - self::remove_backup_directory();
257 -
258 - // Clear scheduled events
259 - self::clear_scheduled_events();
260 -
261 - // Remove htaccess rules
262 - self::remove_htaccess_rules();
263 -
264 - // Remove wp-config security constants and restore originals
265 - self::remove_wpconfig_security();
266 - }
267 -
268 - /**
269 - * Remove backup directory and its contents
270 - */
271 - private static function remove_backup_directory() {
272 - $backup_dirs = array(
273 - WP_CONTENT_DIR . '/vigilante-backups',
274 - );
275 -
276 - if ( defined( 'VIGILANTE_BACKUP_DIR' ) ) {
277 - $backup_dirs[] = VIGILANTE_BACKUP_DIR;
278 - }
279 -
280 - // Initialize WP_Filesystem
281 - global $wp_filesystem;
282 - if ( ! function_exists( 'WP_Filesystem' ) ) {
283 - require_once ABSPATH . 'wp-admin/includes/file.php';
284 - }
285 - WP_Filesystem();
286 -
287 - if ( ! $wp_filesystem ) {
288 - return;
289 - }
290 -
291 - foreach ( $backup_dirs as $backup_dir ) {
292 - if ( ! $wp_filesystem->is_dir( $backup_dir ) ) {
293 - continue;
294 - }
295 -
296 - // Remove directory and contents recursively
297 - $wp_filesystem->delete( $backup_dir, true );
298 - }
299 239 }
300 240 }