PluginProbe
SiteLock Security – WP Hardening, Login Security & Malware Scans / 5.1.1
SiteLock Security – WP Hardening, Login Security & Malware Scans v5.1.1
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.1, at sitelock.php

829 lines 28.6 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.1
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 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 $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
370
371 if (in_array($action, $allowed_actions, true)) {
372 return true;
373 }
374
375 // Block all other AJAX
376 wp_send_json_error(['message' => '2FA Setup Required'], 403);
377 exit;
378 }
379
380 // 2. Admin Post Handling (Allow saving 2FA settings)
381 global $pagenow;
382 if ($pagenow === 'admin-post.php') {
383 $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';
384 if ($action === 'sitelock_security_form_data') {
385 return true;
386 }
387 }
388
389 // 3. Page Check (Strict Page Whitelist)
390 // Allow the 2FA Setup page itself
391 $is_2fa_page = ($pagenow === 'admin.php' && isset($_GET['page']) && $_GET['page'] === 'sitelock-your-2fa');
392
393 return $is_2fa_page;
394 }
395
396 /**
397 * Enforce 2FA by setting a notice and redirecting the user.
398 *
399 * @param WP_User $user The user object.
400 * @param array $user_2fa_status User 2FA status array.
401 */
402 function sitelock_enforce_2fa_redirect($user, $user_2fa_status) {
403 $target_url = site_url('/wp-admin/admin.php?page=sitelock-your-2fa&force_setup=1');
404
405 if ($user_2fa_status['grace_period_expired']) {
406 set_transient('sitelock_2fa_setup_notice_'.$user->ID,
407 'Your grace period for setting up 2FA has expired. You must enable 2FA to continue accessing your account.');
408 } else {
409 set_transient('sitelock_2fa_setup_notice_'.$user->ID,
410 'Two-Factor Authentication is required for your account. Please set it up now.');
411 }
412
413 wp_safe_redirect($target_url);
414 exit;
415 }
416
417 add_action('admin_init', 'sitelock_force_2fa_setup');
418
419 /**
420 * Restrict REST API for users who need 2FA.
421 */
422 function sitelock_restrict_rest_api($result) {
423 // If a previous authentication check already failed, return that result.
424 if (is_wp_error($result)) {
425 return $result;
426 }
427
428 if (!is_user_logged_in()) {
429 return $result;
430 }
431
432 $user = wp_get_current_user();
433 $user_2fa_status = sitelock_get_user_2fa_status($user);
434 $skipped = Sitelock_Secure_Cookie::get('sitelock_2fa_skipped');
435
436 if ($user_2fa_status['2fa_enabled'] && $user_2fa_status['role_requires_2fa'] && !$user_2fa_status['has_2fa']) {
437
438 // Priority Checks
439 if (Sitelock_Secure_Cookie::get('sitelock_2fa_settings_saved')) {
440 return $result;
441 }
442 if (!$user_2fa_status['grace_period_expired'] && $skipped) {
443 return $result;
444 }
445
446 return new WP_Error('rest_forbidden', __('Two-Factor Authentication Setup Required', 'sitelock-wordpress-plugin'), ['status' => 403]);
447 }
448
449 return $result;
450 }
451 add_filter('rest_authentication_errors', 'sitelock_restrict_rest_api');
452
453 // Check 2FA Status on Login and Store Notice
454 function sitelock_validate_2fa_status() {
455 $user = wp_get_current_user();
456 $user_2fa_status = sitelock_get_user_2fa_status($user);
457 // Show warning if 2FA is mandatory but user hasn't set it up
458 if ($user_2fa_status['2fa_enabled'] && $user_2fa_status['role_requires_2fa'] && !$user_2fa_status['has_2fa']) {
459 if ($user_2fa_status['grace_period_expired']) {
460 $admin_notice = 'Your 2FA grace period has expired. Please set up 2FA.';
461 } else {
462 $admin_notice = '2FA is mandatory for your role. Please set it up before the grace period ends.';
463 }
464 set_transient('sitelock_admin_notice_'.$user->ID, wp_kses_post($admin_notice));
465 } else {
466 delete_transient('sitelock_admin_notice_'.$user->ID);
467 }
468 }
469 add_filter('admin_init', 'sitelock_validate_2fa_status');
470
471 /**
472 * Cleanup specific SiteLock cookies on logout.
473 */
474 function sitelock_clear_cookies_on_logout() {
475 if (class_exists('Sitelock_Secure_Cookie')) {
476 Sitelock_Secure_Cookie::delete('sitelock_2fa_skipped');
477 Sitelock_Secure_Cookie::delete('sitelock_2fa_settings_saved');
478 }
479 }
480 add_action('wp_logout', 'sitelock_clear_cookies_on_logout');
481
482 /**
483 * Disable Application Passwords for users with 2FA enabled.
484 *
485 * Application Passwords bypass standard login forms and thus bypass 2FA.
486 * To maintain security, we disable them for any user who has 2FA active.
487 *
488 * @param bool $available Whether Application Passwords are available.
489 * @param WP_User $user The user being checked.
490 * @return bool
491 */
492 function sitelock_disable_app_passwords_for_2fa_users($available, $user) {
493 if (!$available) {
494 return false;
495 }
496
497 // If we don't have a user object, we can't check 2FA status.
498 if (!($user instanceof WP_User)) {
499 return $available;
500 }
501
502 $user_2fa_status = sitelock_get_user_2fa_status($user);
503
504 // If 2FA is enabled and configured for this user, disable Application Passwords.
505 // We also disable it if 2FA is mandatory for their role, even if not yet configured,
506 // to prevent using App Passwords to bypass the setup requirement.
507 if ((!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
508 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']))) {
509 return false;
510 }
511
512 return $available;
513 }
514 add_filter('wp_is_application_passwords_available_for_user', 'sitelock_disable_app_passwords_for_2fa_users', 10, 2);
515
516 /**
517 * Render a notice explaining why Application Passwords are disabled.
518 *
519 * This hooks into the user profile to show a message where the Application Passwords
520 * section would normally be.
521 *
522 * @param WP_User $user The user being edited.
523 */
524 function sitelock_render_app_password_notice($user) {
525 // Check if App Passwords are effectively disabled for this user by our filter
526 $available = apply_filters('wp_is_application_passwords_available_for_user', true, $user);
527
528 // If they are available, we don't need to show a notice (WP shows the form).
529 if ($available) {
530 return;
531 }
532
533 // Double check it was US who disabled it (by checking 2FA status again)
534 // This prevents us from showing a confusing message if it was disabled by something else.
535 $user_2fa_status = sitelock_get_user_2fa_status($user);
536 $blocked_by_us = (!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
537 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']));
538
539 if (!$blocked_by_us) {
540 return;
541 }
542
543 include plugin_dir_path(__FILE__) . 'pages/2fa-app-password-notice.php';
544 }
545 add_action('show_user_profile', 'sitelock_render_app_password_notice');
546 add_action('edit_user_profile', 'sitelock_render_app_password_notice');
547
548 /**
549 * Block XML-RPC for users with 2FA enabled.
550 *
551 * XML-RPC does not support 2FA, so allowing it would enable a bypass
552 * using just the username and password.
553 *
554 * @param WP_User|WP_Error $user WP_User on success, WP_Error on failure.
555 * @param string $username Username.
556 * @param string $password Password.
557 * @return WP_User|WP_Error
558 */
559 function sitelock_block_xmlrpc_for_2fa($user, $username, $password) {
560 // If authentication already failed, don't interfere.
561 if (is_wp_error($user)) {
562 return $user;
563 }
564
565 // Only check during XML-RPC requests.
566 if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) {
567 return $user;
568 }
569
570 // Check 2FA status
571 $user_2fa_status = sitelock_get_user_2fa_status($user);
572 $blocked_by_us = (!empty($user_2fa_status['2fa_enabled']) && !empty($user_2fa_status['has_2fa'])) ||
573 (!empty($user_2fa_status['role_requires_2fa']) && !empty($user_2fa_status['grace_period_expired']));
574
575 if ($blocked_by_us) {
576 return new WP_Error('xmlrpc_2fa_blocked', __('XML-RPC is disabled for accounts with Two-Factor Authentication enabled.', 'sitelock-wordpress-plugin'));
577 }
578
579 return $user;
580 }
581 add_filter('authenticate', 'sitelock_block_xmlrpc_for_2fa', 30, 3);
582
583
584
585
586
587 /**
588 * Begins execution of the plugin.
589 *
590 * Since everything within the plugin is registered via hooks,
591 * then kicking off the plugin from this point in the file does
592 * not affect the page life cycle.
593 *
594 * @since 1.9.0
595 */
596 function sitelock_run()
597 {
598
599 $plugin = new Sitelock();
600 $plugin->run();
601
602 return $plugin->get_version();
603
604 }
605
606 $sitelock_plugin_version = sitelock_run();
607
608
609 /**
610 * Handles the auth connection
611 */
612 $sitelockapi = new Sitelock_API($sitelock_plugin_version);
613
614 add_action('admin_post_handle_auth_key', array($sitelockapi->auth, 'handle_auth'));
615
616 // Pass the nonce to the function call
617 add_action('admin_post_activate_email_key', function() use ($sitelockapi) {
618 $nonce = wp_create_nonce('activate_email_key_action'); // Generate nonce within the function
619 $sitelockapi->auth->activate_email_key($nonce);
620 });
621
622 /**
623 * WP Head stuff
624 */
625 add_action('wp_head', 'sitelock_add_meta_tag');
626
627
628
629 /**
630 * Manage Columns Addition
631 */
632 add_action('wp_footer', 'sitelock_add_this_script_footer');
633
634
635 /**
636 * Add admin notice
637 */
638 add_action('admin_notices', 'sitelock_plugin_activation_notice');
639
640
641 /**
642 * Admin Notice on Activation.
643 *
644 * @since 3.5.0
645 */
646 function sitelock_plugin_activation_notice()
647 {
648 /* Check transient, if available display notice */
649 if (get_transient('slwp-plugin-activation-notice')) {
650 ?>
651 <div class="updated notice is-dismissible">
652 <p>Thank you for installing the SiteLock Security plugin. <a
653 href="<?php echo esc_url_raw(admin_url('admin.php?page=sitelock')); ?>">Click here</a> to get started.
654 </p>
655 </div>
656 <?php
657
658 /**
659 * Delete transient, only display this notice once.
660 */
661 delete_transient('slwp-plugin-activation-notice');
662 }
663 }
664 function sitelock_delete_plugin_options() {
665 global $wpdb;
666
667 // Delete all options starting with 'sitelock_'
668 $options = wp_load_alloptions();
669 foreach ($options as $option_name => $option_value) {
670 if (strpos($option_name, 'sitelock_') === 0) {
671 delete_option($option_name);
672 }
673 }
674
675 // Delete all user meta keys starting with 'sitelock_'
676 $users = get_users();
677
678 // Loop through each user and delete metadata with the prefix 'sitelock_'
679 foreach ($users as $user) {
680 $user_id = $user->ID;
681
682 // Get all user meta for the current user
683 $user_meta = get_user_meta($user_id);
684
685 // Loop through user meta keys and delete those starting with 'sitelock_'
686 foreach ($user_meta as $meta_key => $meta_value) {
687 if (strpos($meta_key, 'sitelock_') === 0) {
688 delete_metadata('user', $user_id, $meta_key, '', true);
689 }
690 }
691 }
692 }
693 function sitelock_remove_htaccess_rules() {
694 // Ensure insert_with_markers is available
695 if (!function_exists('insert_with_markers')) {
696 require_once ABSPATH . 'wp-admin/includes/misc.php';
697 }
698
699 $files_to_clean = [
700 ABSPATH . '.htaccess',
701 ABSPATH . 'wp-content/uploads/.htaccess'
702 ];
703
704 foreach ($files_to_clean as $htaccess_file) {
705 if (file_exists($htaccess_file) && sitelock_filesystem_is_writable($htaccess_file)) {
706 // 1. Remove markers
707 insert_with_markers($htaccess_file, 'SitelockRules', []);
708
709 // 1.5. Force remove standard markers if insert_with_markers left them
710 $content = file_get_contents($htaccess_file);
711 if (strpos($content, '# BEGIN SitelockRules') !== false) {
712 $content = preg_replace('/[\r\n]*# BEGIN SitelockRules.*?# END SitelockRules[\r\n]*/s', "\n", $content);
713 file_put_contents($htaccess_file, $content);
714 }
715
716 // 2. Remove legacy regex (if any)
717 $content = file_get_contents($htaccess_file);
718 if (strpos($content, '# SitelockRulesStart') !== false || strpos($content, '#SitelockRulesStart') !== false) {
719 $content = preg_replace('/#\s?SitelockRulesStart.*?#\s?SitelockRulesEnd\s*/s', '', $content);
720 file_put_contents($htaccess_file, $content);
721 }
722
723 // 3. Remove "File created by" comment and cleanup
724 $content = file_get_contents($htaccess_file);
725 $content = str_replace("# File created by Sitelock Security Plugin\n", "", $content);
726 $content = str_replace("# File created by Sitelock Security Plugin", "", $content);
727 $content = trim($content);
728
729 if (empty($content)) {
730 // If empty, delete the file
731 @unlink($htaccess_file);
732 } else {
733 // Otherwise save trimmed content
734 file_put_contents($htaccess_file, $content);
735 }
736 }
737 }
738 }
739
740 function sitelock_plugin_deactivate() {
741 sitelock_delete_plugin_options(); // Clean database
742 sitelock_remove_htaccess_rules(); // Remove .htaccess modifications
743 }
744
745 register_uninstall_hook(__FILE__, 'sitelock_plugin_deactivate');
746 register_deactivation_hook(__FILE__, 'sitelock_plugin_deactivate');
747
748 function sitelock_sanitize_function( $input ) {
749 return sanitize_text_field( $input ); // Or use another appropriate sanitizer
750 }
751
752 function sitelock_get_language_tokens() {
753 $json_path = plugin_dir_path(__FILE__) . 'languages/en.json';
754
755 if (!file_exists($json_path)) {
756 return []; // Or handle error
757 }
758
759 $json = file_get_contents($json_path);
760 $tokens = json_decode($json, true); // decode as associative array
761
762 return $tokens;
763 }
764
765 add_action('admin_enqueue_scripts', 'sitelock_admin_notice_css');
766
767 /**
768 * Checks if the current screen is part of the SiteLock plugin's admin pages.
769 *
770 * @return bool True if the current screen is a SiteLock plugin page, false otherwise.
771 */
772 function sitelock_is_plugin_page() {
773 $screen = get_current_screen();
774 if (!$screen) return false;
775
776 // Replace with your plugin's screen ID or part of it
777 return strpos($screen->id, 'sitelock') !== false;
778 }
779 function sitelock_admin_notice_css() {
780 // Only apply on your plugin admin page(s)
781 if (sitelock_is_plugin_page()) {
782 echo '<style>
783 /* Target all warning/error notices */
784 .notice.notice-warning,
785 .notice.notice-error {
786 max-width: 1115px; /* Set your desired max width */
787 margin: 10px 15px 0 10px; /* Set your desired margin */
788 }
789 </style>';
790 }
791 }
792
793 function sitelock_remove_admin_footer_text($text) {
794 // Check if we are on SiteLock plugin's admin page
795 if (sitelock_is_plugin_page()) {
796 return ''; // Remove left-side footer text
797 }
798 return $text; // Return original text for other pages
799 }
800
801 function sitelock_remove_update_footer_text($text) {
802 // Check if we are on SiteLock plugin's admin page
803 if (sitelock_is_plugin_page()) {
804 return ''; // Remove right-side version text
805 }
806 return $text; // Return original text for other pages
807 }
808
809 add_filter('admin_footer_text', 'sitelock_remove_admin_footer_text');
810 add_filter('update_footer', 'sitelock_remove_update_footer_text', 11);
811
812 /**
813 * Logger class for SiteLock plugin.
814 *
815 * Handles logging messages to a file with rotation and security measures.
816 */
817 require_once plugin_dir_path( __FILE__ ) . 'includes/logging/class-sitelock-logger.php';
818
819 /**
820 * Global helper function for Logging.
821 *
822 * Usage: sitelock_log( 'error', 'Title', 'Detailed description', [ 'foo' => 'bar' ], __CLASS__ );
823 *
824 * @return bool
825 */
826 function sitelock_log( $level, $title, $message = '', $context = array(), $class = '' ) {
827 return SiteLock_Logger::instance()->log( $level, $title, $message, $context, $class );
828 }
829