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

463 lines 16.4 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 /*
286 * Only rewrite the URLs when the request doing the activation is itself
287 * running over HTTPS, which proves the site answers over it. Without this
288 * check, activating on an HTTP-only site pointed it at an address that
289 * may not respond, locking the owner out of their own admin. is_ssl() is
290 * also false under WP-CLI, where there is no request to learn from, so a
291 * command-line activation leaves the URLs alone as well.
292 */
293 if ( ! is_ssl() ) {
294 return;
295 }
296
297 // Check if already HTTPS
298 $site_url = get_option( 'siteurl' );
299 $home_url = get_option( 'home' );
300
301 // Update to HTTPS if not already
302 if ( strpos( $site_url, 'https://' ) === false ) {
303 update_option( 'siteurl', str_replace( 'http://', 'https://', $site_url ) );
304 }
305
306 if ( strpos( $home_url, 'https://' ) === false ) {
307 update_option( 'home', str_replace( 'http://', 'https://', $home_url ) );
308 }
309 }
310
311 /**
312 * Remove sensitive files from WordPress root
313 *
314 * @param Vigilante_Settings $settings Settings instance.
315 */
316 private static function remove_sensitive_files( $settings ) {
317 $advanced = $settings->get_section( 'advanced' );
318
319 // Remove readme.html
320 if ( ! empty( $advanced['remove_readme'] ) ) {
321 $readme_path = ABSPATH . 'readme.html';
322 if ( file_exists( $readme_path ) ) {
323 wp_delete_file( $readme_path );
324 }
325 }
326
327 // Remove license.txt / licencia.txt (Spanish locale)
328 if ( ! empty( $advanced['remove_license'] ) ) {
329 $license_files = array( 'license.txt', 'licencia.txt' );
330 foreach ( $license_files as $license_file ) {
331 $license_path = ABSPATH . $license_file;
332 if ( file_exists( $license_path ) ) {
333 wp_delete_file( $license_path );
334 }
335 }
336 }
337 }
338
339 /**
340 * Generate initial baseline hashes for critical config files
341 *
342 * Called once during activation, after Vigilante has written its own
343 * blocks to wp-config.php and .htaccess. The baseline stores the
344 * normalized hash (excluding Vigilante blocks) so that subsequent
345 * scans can detect unauthorized external modifications.
346 *
347 * @param Vigilante_Settings $settings Settings instance.
348 */
349 private static function generate_critical_baseline( $settings ) {
350 if ( ! class_exists( 'Vigilante_File_Integrity' ) ) {
351 require_once VIGILANTE_INCLUDES_DIR . 'class-file-integrity.php';
352 }
353
354 $database = new Vigilante_Database();
355 $activity_log = null; // Not needed for baseline generation
356
357 $fi = new Vigilante_File_Integrity( $settings, $database, $activity_log );
358 $fi->regenerate_all_baselines();
359 }
360
361 /**
362 * Schedule cron events
363 */
364 private static function schedule_events() {
365 // Daily maintenance
366 if ( ! wp_next_scheduled( 'vigilante_daily_maintenance' ) ) {
367 wp_schedule_event( time(), 'daily', 'vigilante_daily_maintenance' );
368 }
369
370 // Hourly checks
371 if ( ! wp_next_scheduled( 'vigilante_hourly_checks' ) ) {
372 wp_schedule_event( time(), 'hourly', 'vigilante_hourly_checks' );
373 }
374
375 // Weekly security analyzer scan
376 if ( ! wp_next_scheduled( 'vigilante_analyzer_weekly_scan' ) ) {
377 wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'vigilante_analyzer_weekly_scan' );
378 }
379
380 // Daily plugin status check (closed-in-wp.org detection)
381 if ( ! wp_next_scheduled( 'vigilante_plugin_status_check' ) ) {
382 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'vigilante_plugin_status_check' );
383 }
384 }
385
386 /**
387 * Send activation notification email
388 *
389 * @param Vigilante_Settings $settings Settings instance.
390 */
391 private static function send_activation_email( $settings ) {
392 $email_settings = $settings->get_section( 'email' );
393
394 if ( empty( $email_settings['send_activation_email'] ) ) {
395 return;
396 }
397
398 if ( ! class_exists( 'Vigilante_Email_Template' ) ) {
399 require_once VIGILANTE_INCLUDES_DIR . 'class-email-template.php';
400 }
401
402 $to = Vigilante_Email_Template::get_admin_recipients();
403
404 $site_name = get_bloginfo( 'name' );
405 $site_url = get_site_url();
406
407 $subject = sprintf(
408 /* translators: %s: Site name */
409 __( '[%s] Vigilant Activated', 'vigilante' ),
410 $site_name
411 );
412
413 $body = Vigilante_Email_Template::p( __( 'Vigilant has been activated on your website. All security modules are now enabled with default settings.', 'vigilante' ) );
414 $body .= Vigilante_Email_Template::data_table( array(
415 __( 'Site', 'vigilante' ) => $site_name,
416 __( 'URL', 'vigilante' ) => $site_url,
417 __( 'Date', 'vigilante' ) => wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ),
418 ) );
419 $body .= Vigilante_Email_Template::info_box( __( 'Please review the settings in your WordPress admin panel.', 'vigilante' ) );
420 $body .= Vigilante_Email_Template::button( admin_url( 'admin.php?page=vigilante' ), __( 'Go to Vigilant', 'vigilante' ) );
421
422 Vigilante_Email_Template::send( $to, $subject, __( 'Plugin activated', 'vigilante' ), $body );
423 }
424
425 /**
426 * Apply comment security settings to WordPress options
427 *
428 * @param Vigilante_Settings $settings Settings instance.
429 */
430 private static function apply_comment_security( $settings ) {
431 $options = $settings->get_section( 'wp_hardening' );
432
433 // Disable pingbacks
434 if ( ! empty( $options['disable_pingbacks'] ) ) {
435 update_option( 'default_pingback_flag', 0 );
436 update_option( 'default_ping_status', 'closed' );
437 }
438
439 // Disable trackbacks
440 if ( ! empty( $options['disable_trackbacks'] ) ) {
441 update_option( 'default_ping_status', 'closed' );
442 }
443
444 // Require comment moderation
445 if ( ! empty( $options['require_comment_moderation'] ) ) {
446 update_option( 'comment_moderation', 1 );
447 }
448 }
449
450 /**
451 * Check if server is Apache
452 *
453 * @return bool
454 */
455 private static function is_apache() {
456 if ( ! function_exists( 'apache_get_modules' ) ) {
457 // Check server software
458 $server = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
459 return stripos( $server, 'apache' ) !== false || stripos( $server, 'litespeed' ) !== false;
460 }
461 return true;
462 }
463 }