PluginProbe
SiteLock Security – WP Hardening, Login Security & Malware Scans / 5.1.2
SiteLock Security – WP Hardening, Login Security & Malware Scans v5.1.2
trunk 1.2.1 2.0 2.1.0 2.1.1 3.0.0 3.1.0 3.1.1 3.1.2 3.2.1 3.3.0 3.4.0 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 5.0.0 All 31 releases
sitelock / sitelock.php

sitelock.php in SiteLock Security – WP Hardening, Login Security & Malware Scans 5.1.2, at sitelock.php

839 lines 29.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The plugin bootstrap file
5 *
6 * This file is read by WordPress to generate the plugin information in the plugin
7 * admin area. This file also includes all of the dependencies used by the plugin,
8 * registers the activation and deactivation functions, and defines a function
9 * that starts the plugin.
10 *
11 * @link https://www.sitelock.com
12 * @since 1.9.0
13 * @package Sitelock
14 *
15 * @wordpress-plugin
16 * Plugin Name: SiteLock Security – WP Hardening, Login Security & Malware Scans
17 * Plugin URI: https://www.sitelock.com/wordpress
18 * Description: Free, lightweight WordPress security. WP Hardening, login protection and Site Health & on‑demand checks without slowing your site. Setup in minutes.
19 * Version: 5.1.2
20 * Author: SiteLockSecurity
21 * Author URI: https://www.sitelock.com
22 * License: GPLv2 or later
23 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
24 */
25
26 // If this file is called directly, abort.
27 if (!defined('WPINC')) {
28 die;
29 }
30
31 // name of HTTP header with an initial IP
32 define('SITELOCK_IP_HEADER', "HTTP_INCAP_CLIENT_IP");
33
34 try {
35 //stop process if there is no header
36 if (empty(sanitize_text_field(wp_unslash($_SERVER['SITELOCK_IP_HEADER'] ?? "")))) {
37 throw new Exception('No header defined', 1);
38 }
39
40 //validate header value
41 if (function_exists('filter_var')) {
42 $sitelock_ip = filter_var(sanitize_text_field(wp_unslash($_SERVER['SITELOCK_IP_HEADER'])), FILTER_VALIDATE_IP);
43 if (false === $sitelock_ip) {
44 throw new Exception('The value is not a valid IP address', 2);
45 }
46 } else {
47 $sitelock_ip = sanitize_text_field(wp_unslash($_SERVER['SITELOCK_IP_HEADER']));
48
49 if (false === preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $sitelock_ip)) {
50 throw new Exception('The value is not a valid IP address', 2);
51 }
52 }
53
54 //At this point the initial IP value is exist and validated
55 $_SERVER['REMOTE_ADDR'] = $sitelock_ip;
56 } catch (Exception $e) {
57 }
58
59 if ( ! defined( 'ABSPATH' ) ) {
60 exit; // Exit if accessed directly
61 }
62
63 if ( ! defined( 'SITELOCK_PLUGIN_DIR' ) ) {
64 define( 'SITELOCK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
65 }
66 // Include helper globally
67 require_once plugin_dir_path( __FILE__ ) . 'includes/sitelock-filesystem-helpers.php';
68
69 require_once plugin_dir_path(__FILE__) . 'includes/class-sitelock-crypto.php';
70
71 require_once plugin_dir_path(__FILE__) . 'includes/class-sitelock-secure-cookie.php';
72
73 /**
74 * The code that runs during plugin activation.
75 * This action is documented in includes/class-sitelock-activator.php
76 */
77 function sitelock_activate()
78 {
79 require_once plugin_dir_path(__FILE__) . 'includes/class-sitelock-activator.php';
80 Sitelock_Activator::activate();
81
82 /* Create transient data */
83 set_transient('slwp-plugin-activation-notice', true, 5);
84 }
85
86 /**
87 * The code that runs during plugin deactivation.
88 * This action is documented in includes/class-sitelock-deactivator.php
89 */
90 function sitelock_deactivate()
91 {
92 require_once plugin_dir_path(__FILE__) . 'includes/class-sitelock-deactivator.php';
93 Sitelock_Deactivator::deactivate();
94 }
95
96 register_activation_hook(__FILE__, 'sitelock_activate');
97 register_deactivation_hook(__FILE__, 'sitelock_deactivate');
98
99 /**
100 * The core plugin class that is used to define internationalization,
101 * admin-specific hooks, and public-facing site hooks.
102 */
103 require_once plugin_dir_path(__FILE__) . 'includes/class-sitelock.php';
104
105
106 register_activation_hook(__FILE__, ['SiteLock_Admin_Monitor', 'on_activation']);
107 register_deactivation_hook(__FILE__, ['SiteLock_Admin_Monitor', 'on_deactivation']);
108
109 register_activation_hook(__FILE__, ['Sitelock_Login_Logger', 'on_activation']);
110 register_deactivation_hook(__FILE__, ['Sitelock_Login_Logger', 'on_deactivation']);
111
112 /**
113 * Enforce 2FA after WP has validated username + password.
114 *
115 * This runs via wp_authenticate_user (receives a WP_User object or WP_Error).
116 *
117 * @param WP_User|WP_Error $user WP_User on success, WP_Error on earlier failure.
118 * @param string $password Raw password input.
119 * @return WP_User|WP_Error
120 */
121 function sitelock_check_2fa_after_login($user, $password) {
122 // If WP already returned an error (bad creds etc.), just pass it through.
123 if (is_wp_error($user)) {
124 return $user;
125 }
126
127 // Safety: ensure we have a WP_User object.
128 if (!($user instanceof WP_User)) {
129 return $user;
130 }
131
132 // Only enforce 2FA for users who can edit posts
133 if (!user_can($user, 'edit_posts')) {
134 return $user;
135 }
136
137 // IMPORTANT: The wp_authenticate_user filter fires BEFORE WordPress checks the password.
138 // We must manually validate the password here to avoid 2FA bypass with valid username + valid TOTP.
139 $raw_password = (string) $password;
140 if (!wp_check_password($raw_password, $user->user_pass, $user->ID)) {
141 // Return an auth error so WP treats credentials as invalid.
142 return new WP_Error('incorrect_password', __('Invalid username or password.', 'sitelock-wordpress-plugin'));
143 }
144
145 // Custom helper that returns whether 2FA is enabled/configured for user
146 $user_2fa_status = sitelock_get_user_2fa_status($user);
147 $user_id = $user->ID;
148
149 // If 2FA is required and configured, prevent completing the login and show 2FA form.
150 if (!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) {
151
152 // Save pending user id in session (used by 2FA page)
153 $cookie_set = sitelock_set_pending_user_cookie($user_id);
154
155 if (!$cookie_set) {
156 return new WP_Error(
157 'sitelock_2fa_error',
158 esc_html('Could not initiate Two-Factor Authentication due to a secure session error. Please try again or contact the administrator.')
159 );
160 }
161
162 // Create a nonce for the POST form
163 $nonce = wp_create_nonce('sitelock_2fa_verify');
164
165 $action_url = sitelock_build_url_with_query_params(site_url('/wp-login.php?action=sitelock-2fa'));
166
167 // Render the 2FA form HTML (your existing renderer)
168 sitelock_render_2fa_form($action_url, $nonce);
169
170 // Halt execution so WP doesn't proceed to complete the authentication.
171 exit;
172 }
173
174 // No 2FA required — allow login to continue.
175 return $user;
176 }
177 add_filter('wp_authenticate_user', 'sitelock_check_2fa_after_login', 10, 2);
178
179 /**
180 * Build a URL with additional query parameters from the request.
181 *
182 * @param string $base_url The base URL to which query parameters will be added.
183 * @param array $exclude_keys Array of keys to exclude from the query parameters.
184 * @return string The URL with appended query parameters.
185 */
186 function sitelock_build_url_with_query_params($base_url, $exclude_keys = [])
187 {
188 // Ensure the base URL is valid
189 $redirect_to = esc_url_raw($base_url);
190
191 // Default to admin URL if the base URL is empty
192 if (empty($redirect_to)) {
193 $redirect_to = admin_url();
194 }
195
196 // Keys to skip from the request
197 $skip_keys = array_merge([
198 'log', 'pwd', 'rememberme', 'wp-submit', 'testcookie', 'action',
199 'sitelock_2fa_nonce', 'totp_code', 'recovery_code', '_wp_http_referer',
200 ], $exclude_keys);
201
202 $extra = [];
203 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is verified elsewhere in the code.
204 foreach ($_REQUEST as $key => $value) {
205 if (in_array($key, $skip_keys, true)) {
206 continue;
207 }
208 if (is_scalar($value)) {
209 if ($key === 'redirect_to') {
210 $extra['redirect_to'] = esc_url_raw(wp_unslash($value));
211 } else {
212 $extra[sanitize_key($key)] = sanitize_text_field(wp_unslash($value));
213 }
214 }
215 }
216
217 // Append additional query parameters to the URL
218 if ($extra) {
219 $redirect_to = add_query_arg($extra, $redirect_to);
220 }
221
222 // Safeguard: Prevent redirecting to admin.php without parameters (blank page)
223 // We check if the path ends in admin.php and has no query string
224 $parsed = parse_url($redirect_to);
225 $path = isset($parsed['path']) ? $parsed['path'] : '';
226 $query = isset($parsed['query']) ? $parsed['query'] : '';
227
228 if (basename($path) === 'admin.php' && empty($query)) {
229 return admin_url();
230 }
231
232 return $redirect_to;
233 }
234
235
236 function sitelock_render_2fa_form($action_url, $nonce) {
237 include plugin_dir_path(__FILE__) . 'pages/2fa-form-template.php';
238 }
239
240 register_activation_hook(__FILE__, ['Sitelock_Login_Logger', 'on_activation']);
241 register_deactivation_hook(__FILE__, ['Sitelock_Login_Logger', 'on_deactivation']);
242
243 // Show Notice on the Admin Dashboard After Login
244 function sitelock_admin_dashboard_notice() {
245 $current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
246 if ( $current_screen && strpos( $current_screen->id, 'sitelock-your-2fa' ) !== false ) {
247 return;
248 }
249 $current_user = wp_get_current_user();
250 if ($message = get_transient('sitelock_admin_notice_'.$current_user->ID)) {
251 $class = ''; // Initialize the style variable
252 if (sitelock_is_plugin_page()) {
253 $class = 'sitelock-admin-notice-custom';
254 }
255 echo '<div class="notice notice-warning is-dismissible ' . esc_attr($class) . '">
256 <p><strong>' . wp_kses_post($message) . '</strong></p>
257 <p><a href="' . esc_url(site_url('/wp-admin/admin.php?page=sitelock-your-2fa')) . '" class="button button-primary" style="width: auto; display: inline-block; text-align: center;">' . esc_html__('Setup 2FA', 'sitelock-wordpress-plugin') . '</a></p>
258 </div>';
259
260
261 $has_2fa = get_user_meta($current_user->ID, 'sitelock_2fa_enabled', true);
262 if($has_2fa) {
263 delete_transient('sitelock_admin_notice_'.$current_user->ID);
264 }
265 }
266 }
267 add_action('admin_notices', 'sitelock_admin_dashboard_notice');
268
269 function sitelock_get_user_2fa_status($user) {
270 $sitelock_two_fa_settings = get_option('sitelock_2fa_settings', [
271 'enable_2fa' => false,
272 'mandatory_roles' => [],
273 'grace_period' => 7
274 ]);
275 $sitelock_two_fa_settings['enable_2fa'] = isset($sitelock_two_fa_settings['enable_2fa']) ? $sitelock_two_fa_settings['enable_2fa'] : false;
276 $sitelock_two_fa_settings['mandatory_roles'] = isset($sitelock_two_fa_settings['mandatory_roles']) ? $sitelock_two_fa_settings['mandatory_roles'] : [];
277 $sitelock_two_fa_settings['grace_period'] = isset($sitelock_two_fa_settings['grace_period']) ? $sitelock_two_fa_settings['grace_period'] : 7;
278
279 // Check if 2FA is mandatory for the user's role
280 $user_roles = $user->roles;
281 $role_requires_2fa = array_intersect($user_roles, $sitelock_two_fa_settings['mandatory_roles']);
282 $grace_period_expiration = get_option('sitelock_2fa_grace_period', 0);
283 $grace_period_expired = $grace_period_expiration && $grace_period_expiration < time();
284
285 // Check if user has set up 2FA
286 $has_2fa = get_user_meta($user->ID, 'sitelock_2fa_enabled', true);
287
288 // Calculate remaining days for display if valid
289 $remaining_days = 0;
290 if (!$grace_period_expired && $grace_period_expiration) {
291 $remaining = $grace_period_expiration - time();
292 $remaining_days = max(1, ceil($remaining / DAY_IN_SECONDS));
293 }
294
295 return [
296 '2fa_enabled' => $sitelock_two_fa_settings['enable_2fa'],
297 'role_requires_2fa' => $role_requires_2fa,
298 'grace_period_expired' => $grace_period_expired,
299 'remaining_days' => $remaining_days,
300 'has_2fa' => $has_2fa
301 ];
302 }
303
304 // Secure 2FA enforcement logic
305 function sitelock_force_2fa_setup() {
306 if (!is_user_logged_in()) {
307 return;
308 }
309
310 $user = wp_get_current_user();
311 $user_2fa_status = sitelock_get_user_2fa_status($user);
312
313 if (!sitelock_should_enforce_2fa($user_2fa_status)) {
314 delete_transient('sitelock_2fa_setup_notice_'.$user->ID);
315 return;
316 }
317
318 if (sitelock_is_allowed_request()) {
319 return;
320 }
321
322 sitelock_enforce_2fa_redirect($user, $user_2fa_status);
323 }
324
325 /**
326 * Check if 2FA enforcement applies to the current user.
327 *
328 * @param array $user_2fa_status User 2FA status array.
329 * @return bool
330 */
331 function sitelock_should_enforce_2fa($user_2fa_status) {
332 // Basic requirement check
333 if (empty($user_2fa_status['2fa_enabled']) ||
334 empty($user_2fa_status['role_requires_2fa']) ||
335 !empty($user_2fa_status['has_2fa'])) {
336 return false;
337 }
338
339 // PRIORITY 1: If settings were just saved in this session, allow access regardless of grace period
340 if (Sitelock_Secure_Cookie::get('sitelock_2fa_settings_saved')) {
341 return false;
342 }
343
344 // PRIORITY 2: If user skipped during a valid grace period, allow access
345 $skipped = Sitelock_Secure_Cookie::get('sitelock_2fa_skipped');
346 if (!$user_2fa_status['grace_period_expired'] && $skipped) {
347 return false;
348 }
349
350 return true;
351 }
352
353 /**
354 * Check if the current request is allowed during forced 2FA setup.
355 * Handles AJAX checks internally (exits if blocked).
356 *
357 * @return bool
358 */
359 function sitelock_is_allowed_request() {
360 // 1. AJAX Handling (Whitelist 2FA actions & essentials)
361 if (defined('DOING_AJAX') && DOING_AJAX) {
362 $allowed_actions = [
363 'sitelock_verify_2fa',
364 'sitelock_disable_2fa',
365 'sitelock_regenerate_backup_codes',
366 'heartbeat',
367 'query-attachments'
368 ];
369 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Processing form data without nonce verification.
370 $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
371
372 if (in_array($action, $allowed_actions, true)) {
373 return true;
374 }
375
376 // Block all other AJAX
377 wp_send_json_error(['message' => '2FA Setup Required'], 403);
378 exit;
379 }
380
381 // 2. Admin Post Handling (Allow saving 2FA settings)
382 global $pagenow;
383 if ($pagenow === 'admin-post.php') {
384 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Processing form data without nonce verification.
385 $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
386 if ($action === 'sitelock_security_form_data') {
387 return true;
388 }
389 }
390
391 // 3. Page Check (Strict Page Whitelist)
392 // Allow the 2FA Setup page itself
393 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Processing form data without nonce verification.
394 $is_2fa_page = ($pagenow === 'admin.php' && isset($_GET['page']) && $_GET['page'] === 'sitelock-your-2fa');
395
396 return $is_2fa_page;
397 }
398
399 /**
400 * Enforce 2FA by setting a notice and redirecting the user.
401 *
402 * @param WP_User $user The user object.
403 * @param array $user_2fa_status User 2FA status array.
404 */
405 function sitelock_enforce_2fa_redirect($user, $user_2fa_status) {
406 $target_url = site_url('/wp-admin/admin.php?page=sitelock-your-2fa&force_setup=1');
407
408 if ($user_2fa_status['grace_period_expired']) {
409 set_transient('sitelock_2fa_setup_notice_'.$user->ID,
410 'Your grace period for setting up 2FA has expired. You must enable 2FA to continue accessing your account.');
411 } else {
412 set_transient('sitelock_2fa_setup_notice_'.$user->ID,
413 'Two-Factor Authentication is required for your account. Please set it up now.');
414 }
415
416 wp_safe_redirect($target_url);
417 exit;
418 }
419
420 add_action('admin_init', 'sitelock_force_2fa_setup');
421
422 /**
423 * Restrict REST API for users who need 2FA.
424 */
425 function sitelock_restrict_rest_api($result) {
426 // If a previous authentication check already failed, return that result.
427 if (is_wp_error($result)) {
428 return $result;
429 }
430
431 if (!is_user_logged_in()) {
432 return $result;
433 }
434
435 $user = wp_get_current_user();
436 $user_2fa_status = sitelock_get_user_2fa_status($user);
437 $skipped = Sitelock_Secure_Cookie::get('sitelock_2fa_skipped');
438
439 if ($user_2fa_status['2fa_enabled'] && $user_2fa_status['role_requires_2fa'] && !$user_2fa_status['has_2fa']) {
440
441 // Priority Checks
442 if (Sitelock_Secure_Cookie::get('sitelock_2fa_settings_saved')) {
443 return $result;
444 }
445 if (!$user_2fa_status['grace_period_expired'] && $skipped) {
446 return $result;
447 }
448
449 return new WP_Error('rest_forbidden', __('Two-Factor Authentication Setup Required', 'sitelock-wordpress-plugin'), ['status' => 403]);
450 }
451
452 return $result;
453 }
454 add_filter('rest_authentication_errors', 'sitelock_restrict_rest_api');
455
456 // Check 2FA Status on Login and Store Notice
457 function sitelock_validate_2fa_status() {
458 $user = wp_get_current_user();
459 $user_2fa_status = sitelock_get_user_2fa_status($user);
460 // Show warning if 2FA is mandatory but user hasn't set it up
461 if ($user_2fa_status['2fa_enabled'] && $user_2fa_status['role_requires_2fa'] && !$user_2fa_status['has_2fa']) {
462 if ($user_2fa_status['grace_period_expired']) {
463 $admin_notice = 'Your 2FA grace period has expired. Please set up 2FA.';
464 } else {
465 $admin_notice = '2FA is mandatory for your role. Please set it up before the grace period ends.';
466 }
467 set_transient('sitelock_admin_notice_'.$user->ID, wp_kses_post($admin_notice));
468 } else {
469 delete_transient('sitelock_admin_notice_'.$user->ID);
470 }
471 }
472 add_filter('admin_init', 'sitelock_validate_2fa_status');
473
474 /**
475 * Cleanup specific SiteLock cookies on logout.
476 */
477 function sitelock_clear_cookies_on_logout() {
478 if (class_exists('Sitelock_Secure_Cookie')) {
479 Sitelock_Secure_Cookie::delete('sitelock_2fa_skipped');
480 Sitelock_Secure_Cookie::delete('sitelock_2fa_settings_saved');
481 }
482 }
483 add_action('wp_logout', 'sitelock_clear_cookies_on_logout');
484
485 /**
486 * Disable Application Passwords for users with 2FA enabled.
487 *
488 * Application Passwords bypass standard login forms and thus bypass 2FA.
489 * To maintain security, we disable them for any user who has 2FA active.
490 *
491 * @param bool $available Whether Application Passwords are available.
492 * @param WP_User $user The user being checked.
493 * @return bool
494 */
495 function sitelock_disable_app_passwords_for_2fa_users($available, $user) {
496 if (!$available) {
497 return false;
498 }
499
500 // If we don't have a user object, we can't check 2FA status.
501 if (!($user instanceof WP_User)) {
502 return $available;
503 }
504
505 $user_2fa_status = sitelock_get_user_2fa_status($user);
506
507 // If 2FA is enabled and configured for this user, disable Application Passwords.
508 // We also disable it if 2FA is mandatory for their role, even if not yet configured,
509 // to prevent using App Passwords to bypass the setup requirement.
510 if ((!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
511 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']))) {
512 return false;
513 }
514
515 return $available;
516 }
517 add_filter('wp_is_application_passwords_available_for_user', 'sitelock_disable_app_passwords_for_2fa_users', 10, 2);
518
519 /**
520 * Render a notice explaining why Application Passwords are disabled.
521 *
522 * This hooks into the user profile to show a message where the Application Passwords
523 * section would normally be.
524 *
525 * @param WP_User $user The user being edited.
526 */
527 function sitelock_render_app_password_notice($user) {
528 // Check if App Passwords are effectively disabled for this user by our filter
529 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
530 $available = apply_filters('wp_is_application_passwords_available_for_user', true, $user);
531
532 // If they are available, we don't need to show a notice (WP shows the form).
533 if ($available) {
534 return;
535 }
536
537 // Double check it was US who disabled it (by checking 2FA status again)
538 // This prevents us from showing a confusing message if it was disabled by something else.
539 $user_2fa_status = sitelock_get_user_2fa_status($user);
540 $blocked_by_us = (!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
541 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']));
542
543 if (!$blocked_by_us) {
544 return;
545 }
546
547 include plugin_dir_path(__FILE__) . 'pages/2fa-app-password-notice.php';
548 }
549 add_action('show_user_profile', 'sitelock_render_app_password_notice');
550 add_action('edit_user_profile', 'sitelock_render_app_password_notice');
551
552 /**
553 * Block XML-RPC for users with 2FA enabled.
554 *
555 * XML-RPC does not support 2FA, so allowing it would enable a bypass
556 * using just the username and password.
557 *
558 * @param WP_User|WP_Error $user WP_User on success, WP_Error on failure.
559 * @param string $username Username.
560 * @param string $password Password.
561 * @return WP_User|WP_Error
562 */
563 function sitelock_block_xmlrpc_for_2fa($user, $username, $password) {
564 // If authentication already failed, don't interfere.
565 if (is_wp_error($user)) {
566 return $user;
567 }
568
569 // Only check during XML-RPC requests.
570 if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) {
571 return $user;
572 }
573
574 // Check 2FA status
575 $user_2fa_status = sitelock_get_user_2fa_status($user);
576 $blocked_by_us = (!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
577 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']));
578
579 if ($blocked_by_us) {
580 return new WP_Error('xmlrpc_2fa_blocked', __('XML-RPC is disabled for accounts with Two-Factor Authentication enabled.', 'sitelock-wordpress-plugin'));
581 }
582
583 return $user;
584 }
585 add_filter('authenticate', 'sitelock_block_xmlrpc_for_2fa', 30, 3);
586
587
588
589
590
591 /**
592 * Begins execution of the plugin.
593 *
594 * Since everything within the plugin is registered via hooks,
595 * then kicking off the plugin from this point in the file does
596 * not affect the page life cycle.
597 *
598 * @since 1.9.0
599 */
600 function sitelock_run()
601 {
602
603 $plugin = new Sitelock();
604 $plugin->run();
605
606 return $plugin->get_version();
607
608 }
609
610 $sitelock_plugin_version = sitelock_run();
611
612
613 /**
614 * Handles the auth connection
615 */
616 $sitelockapi = new Sitelock_API($sitelock_plugin_version);
617
618 add_action('admin_post_handle_auth_key', array($sitelockapi->auth, 'handle_auth'));
619
620 // Pass the nonce to the function call
621 add_action('admin_post_activate_email_key', function() use ($sitelockapi) {
622 $nonce = wp_create_nonce('activate_email_key_action'); // Generate nonce within the function
623 $sitelockapi->auth->activate_email_key($nonce);
624 });
625
626 /**
627 * WP Head stuff
628 */
629 add_action('wp_head', 'sitelock_add_meta_tag');
630
631
632
633 /**
634 * Manage Columns Addition
635 */
636 add_action('wp_footer', 'sitelock_add_this_script_footer');
637
638
639 /**
640 * Add admin notice
641 */
642 add_action('admin_notices', 'sitelock_plugin_activation_notice');
643
644
645 /**
646 * Admin Notice on Activation.
647 *
648 * @since 3.5.0
649 */
650 function sitelock_plugin_activation_notice()
651 {
652 /* Check transient, if available display notice */
653 if (get_transient('slwp-plugin-activation-notice')) {
654 ?>
655 <div class="updated notice is-dismissible">
656 <p>Thank you for installing the SiteLock Security plugin. <a
657 href="<?php echo esc_url_raw(admin_url('admin.php?page=sitelock')); ?>">Click here</a> to get started.
658 </p>
659 </div>
660 <?php
661
662 /**
663 * Delete transient, only display this notice once.
664 */
665 delete_transient('slwp-plugin-activation-notice');
666 }
667 }
668 function sitelock_delete_plugin_options() {
669 global $wpdb;
670
671 // Delete all options starting with 'sitelock_' regardless of autoload status.
672 // wp_load_alloptions() only returns autoloaded options, so options marked
673 // autoload='no' (e.g. by fix_option_autoload()) would be silently skipped.
674 // A direct DB query ensures all sitelock_ options are found and removed.
675 $option_names = $wpdb->get_col(
676 $wpdb->prepare(
677 "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s",
678 'sitelock\_%'
679 )
680 );
681 foreach ($option_names as $option_name) {
682 delete_option($option_name);
683 }
684
685 // Delete all user meta keys starting with 'sitelock_'
686 $users = get_users();
687
688 // Loop through each user and delete metadata with the prefix 'sitelock_'
689 foreach ($users as $user) {
690 $user_id = $user->ID;
691
692 // Get all user meta for the current user
693 $user_meta = get_user_meta($user_id);
694
695 // Loop through user meta keys and delete those starting with 'sitelock_'
696 foreach ($user_meta as $meta_key => $meta_value) {
697 if (strpos($meta_key, 'sitelock_') === 0) {
698 delete_metadata('user', $user_id, $meta_key, '', true);
699 }
700 }
701 }
702 }
703 function sitelock_remove_htaccess_rules() {
704 // Ensure insert_with_markers is available
705 if (!function_exists('insert_with_markers')) {
706 require_once ABSPATH . 'wp-admin/includes/misc.php';
707 }
708
709 $files_to_clean = [
710 ABSPATH . '.htaccess',
711 ABSPATH . 'wp-content/uploads/.htaccess'
712 ];
713
714 foreach ($files_to_clean as $htaccess_file) {
715 if (file_exists($htaccess_file) && sitelock_filesystem_is_writable($htaccess_file)) {
716 // 1. Remove markers
717 insert_with_markers($htaccess_file, 'SitelockRules', []);
718
719 // 1.5. Force remove standard markers if insert_with_markers left them
720 $content = file_get_contents($htaccess_file);
721 if (strpos($content, '# BEGIN SitelockRules') !== false) {
722 $content = preg_replace('/[\r\n]*# BEGIN SitelockRules.*?# END SitelockRules[\r\n]*/s', "\n", $content);
723 file_put_contents($htaccess_file, $content);
724 }
725
726 // 2. Remove legacy regex (if any)
727 $content = file_get_contents($htaccess_file);
728 if (strpos($content, '# SitelockRulesStart') !== false || strpos($content, '#SitelockRulesStart') !== false) {
729 $content = preg_replace('/#\s?SitelockRulesStart.*?#\s?SitelockRulesEnd\s*/s', '', $content);
730 file_put_contents($htaccess_file, $content);
731 }
732
733 // 3. Remove "File created by" comment and cleanup
734 $content = file_get_contents($htaccess_file);
735 $content = str_replace("# File created by Sitelock Security Plugin\n", "", $content);
736 $content = str_replace("# File created by Sitelock Security Plugin", "", $content);
737 $content = trim($content);
738
739 if (empty($content)) {
740 // If empty, delete the file
741 @unlink($htaccess_file);
742 } else {
743 // Otherwise save trimmed content
744 file_put_contents($htaccess_file, $content);
745 }
746 }
747 }
748 }
749
750 function sitelock_plugin_deactivate() {
751 sitelock_delete_plugin_options(); // Clean database
752 sitelock_remove_htaccess_rules(); // Remove .htaccess modifications
753 }
754
755 register_uninstall_hook(__FILE__, 'sitelock_plugin_deactivate');
756 register_deactivation_hook(__FILE__, 'sitelock_plugin_deactivate');
757
758 function sitelock_sanitize_function( $input ) {
759 return sanitize_text_field( $input ); // Or use another appropriate sanitizer
760 }
761
762 function sitelock_get_language_tokens() {
763 $json_path = plugin_dir_path(__FILE__) . 'languages/en.json';
764
765 if (!file_exists($json_path)) {
766 return []; // Or handle error
767 }
768
769 $json = file_get_contents($json_path);
770 $tokens = json_decode($json, true); // decode as associative array
771
772 return $tokens;
773 }
774
775 add_action('admin_enqueue_scripts', 'sitelock_admin_notice_css');
776
777 /**
778 * Checks if the current screen is part of the SiteLock plugin's admin pages.
779 *
780 * @return bool True if the current screen is a SiteLock plugin page, false otherwise.
781 */
782 function sitelock_is_plugin_page() {
783 $screen = get_current_screen();
784 if (!$screen) return false;
785
786 // Replace with your plugin's screen ID or part of it
787 return strpos($screen->id, 'sitelock') !== false;
788 }
789 function sitelock_admin_notice_css() {
790 // Only apply on your plugin admin page(s)
791 if (sitelock_is_plugin_page()) {
792 echo '<style>
793 /* Target all warning/error notices */
794 .notice.notice-warning,
795 .notice.notice-error {
796 max-width: 1115px; /* Set your desired max width */
797 margin: 10px 15px 0 10px; /* Set your desired margin */
798 }
799 </style>';
800 }
801 }
802
803 function sitelock_remove_admin_footer_text($text) {
804 // Check if we are on SiteLock plugin's admin page
805 if (sitelock_is_plugin_page()) {
806 return ''; // Remove left-side footer text
807 }
808 return $text; // Return original text for other pages
809 }
810
811 function sitelock_remove_update_footer_text($text) {
812 // Check if we are on SiteLock plugin's admin page
813 if (sitelock_is_plugin_page()) {
814 return ''; // Remove right-side version text
815 }
816 return $text; // Return original text for other pages
817 }
818
819 add_filter('admin_footer_text', 'sitelock_remove_admin_footer_text');
820 add_filter('update_footer', 'sitelock_remove_update_footer_text', 11);
821
822 /**
823 * Logger class for SiteLock plugin.
824 *
825 * Handles logging messages to a file with rotation and security measures.
826 */
827 require_once plugin_dir_path( __FILE__ ) . 'includes/logging/class-sitelock-logger.php';
828
829 /**
830 * Global helper function for Logging.
831 *
832 * Usage: sitelock_log( 'error', 'Title', 'Detailed description', [ 'foo' => 'bar' ], __CLASS__ );
833 *
834 * @return bool
835 */
836 function sitelock_log( $level, $title, $message = '', $context = array(), $class = '' ) {
837 return SiteLock_Logger::instance()->log( $level, $title, $message, $context, $class );
838 }
839