PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.9.4
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.9.4
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
vigilante / includes / class-activator.php

class-activator.php in Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… 2.9.4, at includes/class-activator.php

451 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Activator Class
4 *
5 * Handles plugin activation tasks
6 *
7 * @package Vigilante
8 */
9
10 // Prevent direct access
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class Vigilante_Activator
17 *
18 * Fired during plugin activation
19 */
20 class Vigilante_Activator {
21
22 /**
23 * Run activation tasks
24 */
25 public static function activate() {
26 // Start output buffering to prevent any accidental output
27 ob_start();
28
29 // Check requirements first
30 if ( ! self::check_requirements() ) {
31 ob_end_clean();
32 return;
33 }
34
35 // Create database tables
36 $database = new Vigilante_Database();
37 $database->create_tables();
38
39 // Initialize default settings
40 $settings = new Vigilante_Settings();
41 $current_options = get_option( Vigilante_Settings::OPTION_NAME );
42
43 if ( false === $current_options ) {
44 // First installation - set defaults
45 update_option( Vigilante_Settings::OPTION_NAME, $settings->get_default_options() );
46 // Refresh settings instance to get new values
47 $settings->clear_cache();
48 $settings = new Vigilante_Settings();
49 } else {
50 // Existing installation - run idempotent migrations
51 if ( self::run_migrations( $current_options ) ) {
52 $settings->clear_cache();
53 $settings = new Vigilante_Settings();
54 }
55 }
56
57 // Create backup of current files FIRST (before any modifications)
58 self::create_activation_backup( $settings );
59
60 // Apply htaccess protection (part of firewall module)
61 if ( $settings->is_module_enabled( 'firewall' ) ) {
62 self::apply_htaccess_protection( $settings );
63 }
64
65 // Apply security headers to htaccess
66 if ( $settings->is_module_enabled( 'security_headers' ) ) {
67 self::apply_security_headers( $settings );
68 }
69
70 // Apply wp-config security (part of wp_hardening module)
71 if ( $settings->is_module_enabled( 'wp_hardening' ) ) {
72 self::apply_wpconfig_security( $settings );
73 }
74
75 // Update WordPress options for HTTPS (part of security_headers module)
76 if ( $settings->is_module_enabled( 'security_headers' ) ) {
77 self::enforce_https( $settings );
78 }
79
80 // Apply comment security settings (part of wp_hardening module)
81 if ( $settings->is_module_enabled( 'wp_hardening' ) ) {
82 self::apply_comment_security( $settings );
83 }
84
85 // Remove sensitive files
86 self::remove_sensitive_files( $settings );
87
88 // Generate critical config files baseline (after all Vigilante writes above)
89 self::generate_critical_baseline( $settings );
90
91 // Schedule cron events
92 self::schedule_events();
93
94 // Set activation transient for admin notice
95 set_transient( 'vigilante_activated', true, 30 );
96
97 // Store activation time
98 update_option( 'vigilante_activated_time', time() );
99
100 // Send activation email if enabled
101 self::send_activation_email( $settings );
102
103 // Flush rewrite rules
104 flush_rewrite_rules();
105
106 // Clean any output that may have been generated
107 ob_end_clean();
108 }
109
110 /**
111 * Idempotent migrations for existing installations.
112 *
113 * @param array $current_options Current vigilante_options array.
114 * @return bool True if any migration changed the stored option.
115 */
116 private static function run_migrations( $current_options ) {
117 $changed = false;
118
119 // Migration: rest_api_security.mode legacy value 'authenticated'
120 // (UI bug shipped a <select> value that did not match the backend
121 // string 'authenticated_only', so manual saves wrote a value the
122 // module ignored). Normalise so the option is honoured again.
123 if ( isset( $current_options['rest_api_security']['mode'] )
124 && 'authenticated' === $current_options['rest_api_security']['mode'] ) {
125 $current_options['rest_api_security']['mode'] = 'authenticated_only';
126 $changed = true;
127 }
128
129 // Migration: rest_api_security.protected_endpoints used to default to
130 // ['/wp/v2/users'], which duplicated the "Block user enumeration"
131 // toggle and confused users (turning that toggle off didn't unblock
132 // /users because protected_endpoints kept it locked in selective
133 // mode). If the saved list is still the legacy single-element default,
134 // empty it out so there is one knob per behaviour. Custom lists
135 // (anything other than exactly ['/wp/v2/users']) are left untouched.
136 if ( isset( $current_options['rest_api_security']['protected_endpoints'] )
137 && is_array( $current_options['rest_api_security']['protected_endpoints'] )
138 && array( '/wp/v2/users' ) === array_values( $current_options['rest_api_security']['protected_endpoints'] ) ) {
139 $current_options['rest_api_security']['protected_endpoints'] = array();
140 $changed = true;
141 }
142
143 // Migration: section-level 'enabled' flag wrongly stored as false.
144 // Earlier 2.4.x betas had a UI save handler that treated the absence
145 // of a field in the form as "checkbox unchecked" — including the
146 // top-level 'enabled' master flag, which has no checkbox in any
147 // section form. This left modules silently disabled even though the
148 // Dashboard master toggle was on. Restore the flag where it makes
149 // sense (master toggle on + flag false).
150 $sections = array(
151 'firewall',
152 'security_headers',
153 'login_security',
154 'rest_api_security',
155 'user_security',
156 'wp_hardening',
157 'file_integrity',
158 'activity_log',
159 );
160 foreach ( $sections as $section_name ) {
161 if ( ! empty( $current_options['modules'][ $section_name ] )
162 && isset( $current_options[ $section_name ] )
163 && is_array( $current_options[ $section_name ] )
164 && array_key_exists( 'enabled', $current_options[ $section_name ] )
165 && empty( $current_options[ $section_name ]['enabled'] ) ) {
166 $current_options[ $section_name ]['enabled'] = true;
167 $changed = true;
168 }
169 }
170
171 if ( $changed ) {
172 update_option( Vigilante_Settings::OPTION_NAME, $current_options );
173 }
174
175 return $changed;
176 }
177
178 /**
179 * Check minimum requirements
180 *
181 * @return bool
182 */
183 private static function check_requirements() {
184 // PHP version check
185 if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
186 add_action( 'admin_notices', function() {
187 printf(
188 '<div class="notice notice-error"><p>%s</p></div>',
189 esc_html__( 'Vigilant requires PHP 7.4 or higher.', 'vigilante' )
190 );
191 });
192 return false;
193 }
194
195 // WordPress version check
196 global $wp_version;
197 if ( version_compare( $wp_version, '5.0', '<' ) ) {
198 add_action( 'admin_notices', function() {
199 printf(
200 '<div class="notice notice-error"><p>%s</p></div>',
201 esc_html__( 'Vigilant requires WordPress 5.0 or higher.', 'vigilante' )
202 );
203 });
204 return false;
205 }
206
207 return true;
208 }
209
210 /**
211 * Create backup of important files
212 *
213 * @param Vigilante_Settings $settings Settings instance.
214 */
215 private static function create_activation_backup( $settings ) {
216 require_once VIGILANTE_INCLUDES_DIR . 'class-backup-manager.php';
217
218 $backup_manager = new Vigilante_Backup_Manager();
219 $result = $backup_manager->create_backups();
220
221 if ( is_wp_error( $result ) ) {
222 // Store error for admin notice
223 set_transient( 'vigilante_backup_error', $result->get_error_message(), 60 );
224 }
225 }
226
227 /**
228 * Apply htaccess protection
229 *
230 * @param Vigilante_Settings $settings Settings instance.
231 */
232 private static function apply_htaccess_protection( $settings ) {
233 // Only apply if Apache server
234 if ( ! self::is_apache() ) {
235 return;
236 }
237
238 require_once VIGILANTE_INCLUDES_DIR . 'class-htaccess-protection.php';
239
240 $htaccess = new Vigilante_Htaccess_Protection( $settings );
241 $htaccess->apply_rules();
242 }
243
244 /**
245 * Apply security headers to htaccess
246 *
247 * @param Vigilante_Settings $settings Settings instance.
248 */
249 private static function apply_security_headers( $settings ) {
250 // Only apply if Apache server
251 if ( ! self::is_apache() ) {
252 return;
253 }
254
255 require_once VIGILANTE_INCLUDES_DIR . 'class-security-headers.php';
256
257 $security_headers = new Vigilante_Security_Headers( $settings );
258 $security_headers->apply_rules();
259 }
260
261 /**
262 * Apply wp-config security
263 *
264 * @param Vigilante_Settings $settings Settings instance.
265 */
266 private static function apply_wpconfig_security( $settings ) {
267 require_once VIGILANTE_INCLUDES_DIR . 'class-wpconfig-security.php';
268
269 $wpconfig = new Vigilante_Wpconfig_Security( $settings );
270 $wpconfig->apply_security_constants();
271 }
272
273 /**
274 * Enforce HTTPS in WordPress settings
275 *
276 * @param Vigilante_Settings $settings Settings instance.
277 */
278 private static function enforce_https( $settings ) {
279 $options = $settings->get_section( 'security_headers' );
280
281 if ( empty( $options['force_https'] ) ) {
282 return;
283 }
284
285 // Check if already HTTPS
286 $site_url = get_option( 'siteurl' );
287 $home_url = get_option( 'home' );
288
289 // Update to HTTPS if not already
290 if ( strpos( $site_url, 'https://' ) === false ) {
291 update_option( 'siteurl', str_replace( 'http://', 'https://', $site_url ) );
292 }
293
294 if ( strpos( $home_url, 'https://' ) === false ) {
295 update_option( 'home', str_replace( 'http://', 'https://', $home_url ) );
296 }
297 }
298
299 /**
300 * Remove sensitive files from WordPress root
301 *
302 * @param Vigilante_Settings $settings Settings instance.
303 */
304 private static function remove_sensitive_files( $settings ) {
305 $advanced = $settings->get_section( 'advanced' );
306
307 // Remove readme.html
308 if ( ! empty( $advanced['remove_readme'] ) ) {
309 $readme_path = ABSPATH . 'readme.html';
310 if ( file_exists( $readme_path ) ) {
311 wp_delete_file( $readme_path );
312 }
313 }
314
315 // Remove license.txt / licencia.txt (Spanish locale)
316 if ( ! empty( $advanced['remove_license'] ) ) {
317 $license_files = array( 'license.txt', 'licencia.txt' );
318 foreach ( $license_files as $license_file ) {
319 $license_path = ABSPATH . $license_file;
320 if ( file_exists( $license_path ) ) {
321 wp_delete_file( $license_path );
322 }
323 }
324 }
325 }
326
327 /**
328 * Generate initial baseline hashes for critical config files
329 *
330 * Called once during activation, after Vigilante has written its own
331 * blocks to wp-config.php and .htaccess. The baseline stores the
332 * normalized hash (excluding Vigilante blocks) so that subsequent
333 * scans can detect unauthorized external modifications.
334 *
335 * @param Vigilante_Settings $settings Settings instance.
336 */
337 private static function generate_critical_baseline( $settings ) {
338 if ( ! class_exists( 'Vigilante_File_Integrity' ) ) {
339 require_once VIGILANTE_INCLUDES_DIR . 'class-file-integrity.php';
340 }
341
342 $database = new Vigilante_Database();
343 $activity_log = null; // Not needed for baseline generation
344
345 $fi = new Vigilante_File_Integrity( $settings, $database, $activity_log );
346 $fi->regenerate_all_baselines();
347 }
348
349 /**
350 * Schedule cron events
351 */
352 private static function schedule_events() {
353 // Daily maintenance
354 if ( ! wp_next_scheduled( 'vigilante_daily_maintenance' ) ) {
355 wp_schedule_event( time(), 'daily', 'vigilante_daily_maintenance' );
356 }
357
358 // Hourly checks
359 if ( ! wp_next_scheduled( 'vigilante_hourly_checks' ) ) {
360 wp_schedule_event( time(), 'hourly', 'vigilante_hourly_checks' );
361 }
362
363 // Weekly security analyzer scan
364 if ( ! wp_next_scheduled( 'vigilante_analyzer_weekly_scan' ) ) {
365 wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'vigilante_analyzer_weekly_scan' );
366 }
367
368 // Daily plugin status check (closed-in-wp.org detection)
369 if ( ! wp_next_scheduled( 'vigilante_plugin_status_check' ) ) {
370 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'vigilante_plugin_status_check' );
371 }
372 }
373
374 /**
375 * Send activation notification email
376 *
377 * @param Vigilante_Settings $settings Settings instance.
378 */
379 private static function send_activation_email( $settings ) {
380 $email_settings = $settings->get_section( 'email' );
381
382 if ( empty( $email_settings['send_activation_email'] ) ) {
383 return;
384 }
385
386 if ( ! class_exists( 'Vigilante_Email_Template' ) ) {
387 require_once VIGILANTE_INCLUDES_DIR . 'class-email-template.php';
388 }
389
390 $to = Vigilante_Email_Template::get_admin_recipients();
391
392 $site_name = get_bloginfo( 'name' );
393 $site_url = get_site_url();
394
395 $subject = sprintf(
396 /* translators: %s: Site name */
397 __( '[%s] Vigilant Activated', 'vigilante' ),
398 $site_name
399 );
400
401 $body = Vigilante_Email_Template::p( __( 'Vigilant has been activated on your website. All security modules are now enabled with default settings.', 'vigilante' ) );
402 $body .= Vigilante_Email_Template::data_table( array(
403 __( 'Site', 'vigilante' ) => $site_name,
404 __( 'URL', 'vigilante' ) => $site_url,
405 __( 'Date', 'vigilante' ) => wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ),
406 ) );
407 $body .= Vigilante_Email_Template::info_box( __( 'Please review the settings in your WordPress admin panel.', 'vigilante' ) );
408 $body .= Vigilante_Email_Template::button( admin_url( 'admin.php?page=vigilante' ), __( 'Go to Vigilant', 'vigilante' ) );
409
410 Vigilante_Email_Template::send( $to, $subject, __( 'Plugin activated', 'vigilante' ), $body );
411 }
412
413 /**
414 * Apply comment security settings to WordPress options
415 *
416 * @param Vigilante_Settings $settings Settings instance.
417 */
418 private static function apply_comment_security( $settings ) {
419 $options = $settings->get_section( 'wp_hardening' );
420
421 // Disable pingbacks
422 if ( ! empty( $options['disable_pingbacks'] ) ) {
423 update_option( 'default_pingback_flag', 0 );
424 update_option( 'default_ping_status', 'closed' );
425 }
426
427 // Disable trackbacks
428 if ( ! empty( $options['disable_trackbacks'] ) ) {
429 update_option( 'default_ping_status', 'closed' );
430 }
431
432 // Require comment moderation
433 if ( ! empty( $options['require_comment_moderation'] ) ) {
434 update_option( 'comment_moderation', 1 );
435 }
436 }
437
438 /**
439 * Check if server is Apache
440 *
441 * @return bool
442 */
443 private static function is_apache() {
444 if ( ! function_exists( 'apache_get_modules' ) ) {
445 // Check server software
446 $server = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
447 return stripos( $server, 'apache' ) !== false || stripos( $server, 'litespeed' ) !== false;
448 }
449 return true;
450 }
451 }