PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.74
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.74
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Activity_Log / Event_Logger.php

Event_Logger.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.74, at includes/extensions/Activity_Log/Event_Logger.php

831 lines 25.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Event Logger - WordPress hook listeners for Activity Log.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 /**
15 * Captures WordPress events and logs them to the database.
16 */
17 final class Activity_Log_Event_Logger
18 {
19 /**
20 * Singleton instance.
21 *
22 * @var Activity_Log_Event_Logger|null
23 */
24 private static ?Activity_Log_Event_Logger $instance = null;
25
26 /**
27 * Extension settings.
28 *
29 * @var array<string, mixed>
30 */
31 private array $settings;
32
33 /**
34 * Get singleton instance.
35 *
36 * @param array<string, mixed> $settings Extension settings.
37 * @return Activity_Log_Event_Logger
38 */
39 public static function instance(array $settings = []): Activity_Log_Event_Logger
40 {
41 if (self::$instance === null) {
42 self::$instance = new self($settings);
43 }
44 return self::$instance;
45 }
46
47 /**
48 * Constructor - registers WordPress hooks.
49 *
50 * @param array<string, mixed> $settings Extension settings.
51 */
52 private function __construct(array $settings)
53 {
54 $this->settings = $settings;
55
56 // Check if logging is enabled
57 if (empty($this->settings['enabled'])) {
58 return;
59 }
60
61 // Register hooks based on enabled modules
62 $this->register_auth_hooks();
63 $this->register_user_hooks();
64 $this->register_content_hooks();
65 $this->register_plugin_hooks();
66 $this->register_theme_hooks();
67
68 // Custom event action for other extensions
69 add_action('kng_activity_log/event', [$this, 'log_custom_event'], 10, 1);
70 }
71
72 // =========================================================================
73 // Hook Registration
74 // =========================================================================
75
76 /**
77 * Register authentication hooks.
78 *
79 * @return void
80 */
81 private function register_auth_hooks(): void
82 {
83 if (!$this->is_module_enabled('auth')) {
84 return;
85 }
86
87 add_action('wp_login', [$this, 'on_login'], 10, 2);
88 add_action('wp_logout', [$this, 'on_logout'], 10, 1);
89 add_action('wp_login_failed', [$this, 'on_login_failed'], 10, 2);
90 }
91
92 /**
93 * Register user hooks.
94 *
95 * @return void
96 */
97 private function register_user_hooks(): void
98 {
99 if (!$this->is_module_enabled('users')) {
100 return;
101 }
102
103 add_action('user_register', [$this, 'on_user_created'], 10, 2);
104 add_action('profile_update', [$this, 'on_user_updated'], 10, 3);
105 add_action('delete_user', [$this, 'on_user_deleted'], 10, 3);
106 add_action('set_user_role', [$this, 'on_user_role_changed'], 10, 3);
107 }
108
109 /**
110 * Register content hooks.
111 *
112 * @return void
113 */
114 private function register_content_hooks(): void
115 {
116 if (!$this->is_module_enabled('content')) {
117 return;
118 }
119
120 add_action('transition_post_status', [$this, 'on_post_status_change'], 10, 3);
121 add_action('before_delete_post', [$this, 'on_post_deleted'], 10, 2);
122 }
123
124 /**
125 * Register plugin hooks.
126 *
127 * @return void
128 */
129 private function register_plugin_hooks(): void
130 {
131 if (!$this->is_module_enabled('plugins')) {
132 return;
133 }
134
135 add_action('activated_plugin', [$this, 'on_plugin_activated'], 10, 2);
136 add_action('deactivated_plugin', [$this, 'on_plugin_deactivated'], 10, 2);
137 }
138
139 /**
140 * Register theme hooks.
141 *
142 * @return void
143 */
144 private function register_theme_hooks(): void
145 {
146 if (!$this->is_module_enabled('themes')) {
147 return;
148 }
149
150 add_action('switch_theme', [$this, 'on_theme_switched'], 10, 3);
151 }
152
153 // =========================================================================
154 // Auth Event Handlers
155 // =========================================================================
156
157 /**
158 * Handle successful login.
159 *
160 * @param string $user_login User login name.
161 * @param \WP_User $user User object.
162 * @return void
163 */
164 public function on_login(string $user_login, \WP_User $user): void
165 {
166 if ($this->is_excluded_user($user)) {
167 return;
168 }
169
170 $this->log([
171 'event_key' => Activity_Log_Event_Types::AUTH_LOGIN_SUCCESS,
172 'user_id' => $user->ID,
173 'user_login' => $user_login,
174 'user_role' => $this->get_primary_role($user),
175 'object_type' => 'user',
176 'object_id' => (string) $user->ID,
177 'object_title' => $user->display_name,
178 'message' => sprintf(
179 /* translators: %s: user login name */
180 __('User %s logged in', 'king-addons'),
181 $user_login
182 ),
183 ]);
184 }
185
186 /**
187 * Handle logout.
188 *
189 * @param int $user_id User ID.
190 * @return void
191 */
192 public function on_logout(int $user_id): void
193 {
194 $user = get_userdata($user_id);
195 if (!$user || $this->is_excluded_user($user)) {
196 return;
197 }
198
199 $this->log([
200 'event_key' => Activity_Log_Event_Types::AUTH_LOGOUT,
201 'user_id' => $user_id,
202 'user_login' => $user->user_login,
203 'user_role' => $this->get_primary_role($user),
204 'object_type' => 'user',
205 'object_id' => (string) $user_id,
206 'object_title' => $user->display_name,
207 'message' => sprintf(
208 /* translators: %s: user login name */
209 __('User %s logged out', 'king-addons'),
210 $user->user_login
211 ),
212 ]);
213 }
214
215 /**
216 * Handle failed login.
217 *
218 * @param string $username Username attempted.
219 * @param \WP_Error $error Error object.
220 * @return void
221 */
222 public function on_login_failed(string $username, \WP_Error $error): void
223 {
224 $error_code = $error->get_error_code();
225
226 $this->log([
227 'event_key' => Activity_Log_Event_Types::AUTH_LOGIN_FAILED,
228 'severity' => Activity_Log_Event_Types::SEVERITY_WARNING,
229 'object_type' => 'user',
230 'object_title' => $username,
231 'message' => sprintf(
232 /* translators: 1: username, 2: error code */
233 __('Failed login attempt for "%1$s" (%2$s)', 'king-addons'),
234 $username,
235 $error_code
236 ),
237 'data' => [
238 'error_code' => $error_code,
239 'error_message' => $error->get_error_message(),
240 ],
241 ]);
242 }
243
244 // =========================================================================
245 // User Event Handlers
246 // =========================================================================
247
248 /**
249 * Handle user creation.
250 *
251 * @param int $user_id User ID.
252 * @param array $userdata User data array.
253 * @return void
254 */
255 public function on_user_created(int $user_id, array $userdata = []): void
256 {
257 $user = get_userdata($user_id);
258 if (!$user) {
259 return;
260 }
261
262 $current_user = wp_get_current_user();
263
264 $this->log([
265 'event_key' => Activity_Log_Event_Types::USER_CREATED,
266 'user_id' => $current_user->ID ?: null,
267 'user_login' => $current_user->user_login ?: null,
268 'user_role' => $current_user->ID ? $this->get_primary_role($current_user) : null,
269 'object_type' => 'user',
270 'object_id' => (string) $user_id,
271 'object_title' => $user->display_name,
272 'message' => sprintf(
273 /* translators: %s: new user login name */
274 __('User %s created', 'king-addons'),
275 $user->user_login
276 ),
277 'data' => [
278 'new_user_role' => $this->get_primary_role($user),
279 'new_user_email' => $user->user_email,
280 ],
281 ]);
282 }
283
284 /**
285 * Handle user update.
286 *
287 * @param int $user_id User ID.
288 * @param \WP_User $old_user_data Old user data.
289 * @param array $userdata New user data.
290 * @return void
291 */
292 public function on_user_updated(int $user_id, \WP_User $old_user_data, array $userdata = []): void
293 {
294 $user = get_userdata($user_id);
295 if (!$user) {
296 return;
297 }
298
299 $current_user = wp_get_current_user();
300 if ($this->is_excluded_user($current_user)) {
301 return;
302 }
303
304 $this->log([
305 'event_key' => Activity_Log_Event_Types::USER_UPDATED,
306 'user_id' => $current_user->ID,
307 'user_login' => $current_user->user_login,
308 'user_role' => $this->get_primary_role($current_user),
309 'object_type' => 'user',
310 'object_id' => (string) $user_id,
311 'object_title' => $user->display_name,
312 'message' => sprintf(
313 /* translators: %s: user login name */
314 __('User %s updated', 'king-addons'),
315 $user->user_login
316 ),
317 ]);
318 }
319
320 /**
321 * Handle user deletion.
322 *
323 * @param int $user_id User ID being deleted.
324 * @param int|null $reassign User ID to reassign posts to.
325 * @param \WP_User $user User object being deleted.
326 * @return void
327 */
328 public function on_user_deleted(int $user_id, ?int $reassign, \WP_User $user): void
329 {
330 $current_user = wp_get_current_user();
331
332 $this->log([
333 'event_key' => Activity_Log_Event_Types::USER_DELETED,
334 'severity' => Activity_Log_Event_Types::SEVERITY_CRITICAL,
335 'user_id' => $current_user->ID,
336 'user_login' => $current_user->user_login,
337 'user_role' => $this->get_primary_role($current_user),
338 'object_type' => 'user',
339 'object_id' => (string) $user_id,
340 'object_title' => $user->display_name,
341 'message' => sprintf(
342 /* translators: %s: user login name */
343 __('User %s deleted', 'king-addons'),
344 $user->user_login
345 ),
346 'data' => [
347 'deleted_user_email' => $user->user_email,
348 'deleted_user_role' => $this->get_primary_role($user),
349 'reassign_to' => $reassign,
350 ],
351 ]);
352 }
353
354 /**
355 * Handle user role change.
356 *
357 * @param int $user_id User ID.
358 * @param string $new_role New role.
359 * @param array $old_roles Old roles.
360 * @return void
361 */
362 public function on_user_role_changed(int $user_id, string $new_role, array $old_roles): void
363 {
364 $user = get_userdata($user_id);
365 if (!$user) {
366 return;
367 }
368
369 $current_user = wp_get_current_user();
370 $old_role = !empty($old_roles) ? $old_roles[0] : '';
371
372 // Determine severity - admin grant is critical
373 $severity = ($new_role === 'administrator')
374 ? Activity_Log_Event_Types::SEVERITY_CRITICAL
375 : Activity_Log_Event_Types::SEVERITY_NOTICE;
376
377 $this->log([
378 'event_key' => Activity_Log_Event_Types::USER_ROLE_CHANGED,
379 'severity' => $severity,
380 'user_id' => $current_user->ID,
381 'user_login' => $current_user->user_login,
382 'user_role' => $this->get_primary_role($current_user),
383 'object_type' => 'user',
384 'object_id' => (string) $user_id,
385 'object_title' => $user->display_name,
386 'message' => sprintf(
387 /* translators: 1: user login, 2: old role, 3: new role */
388 __('User %1$s role changed from %2$s to %3$s', 'king-addons'),
389 $user->user_login,
390 $old_role,
391 $new_role
392 ),
393 'data' => [
394 'old_role' => $old_role,
395 'new_role' => $new_role,
396 ],
397 ]);
398 }
399
400 // =========================================================================
401 // Content Event Handlers
402 // =========================================================================
403
404 /**
405 * Handle post status transition.
406 *
407 * @param string $new_status New post status.
408 * @param string $old_status Old post status.
409 * @param \WP_Post $post Post object.
410 * @return void
411 */
412 public function on_post_status_change(string $new_status, string $old_status, \WP_Post $post): void
413 {
414 // Skip revisions and auto-drafts
415 if (wp_is_post_revision($post->ID) || wp_is_post_autosave($post->ID)) {
416 return;
417 }
418
419 if ($post->post_status === 'auto-draft') {
420 return;
421 }
422
423 // Skip if statuses are the same (no actual change)
424 if ($new_status === $old_status) {
425 return;
426 }
427
428 $current_user = wp_get_current_user();
429 if ($this->is_excluded_user($current_user)) {
430 return;
431 }
432
433 // Determine event type
434 $event_key = null;
435
436 if ($old_status === 'new' || $old_status === 'auto-draft') {
437 $event_key = Activity_Log_Event_Types::CONTENT_CREATED;
438 } elseif ($new_status === 'trash') {
439 $event_key = Activity_Log_Event_Types::CONTENT_TRASHED;
440 } elseif ($old_status === 'trash') {
441 $event_key = Activity_Log_Event_Types::CONTENT_RESTORED;
442 } else {
443 $event_key = Activity_Log_Event_Types::CONTENT_UPDATED;
444 }
445
446 $this->log([
447 'event_key' => $event_key,
448 'user_id' => $current_user->ID,
449 'user_login' => $current_user->user_login,
450 'user_role' => $this->get_primary_role($current_user),
451 'object_type' => $post->post_type,
452 'object_id' => (string) $post->ID,
453 'object_title' => $post->post_title,
454 'message' => sprintf(
455 /* translators: 1: post type, 2: post title, 3: old status, 4: new status */
456 __('%1$s "%2$s" status changed from %3$s to %4$s', 'king-addons'),
457 ucfirst($post->post_type),
458 $post->post_title,
459 $old_status,
460 $new_status
461 ),
462 'data' => [
463 'old_status' => $old_status,
464 'new_status' => $new_status,
465 ],
466 ]);
467 }
468
469 /**
470 * Handle permanent post deletion.
471 *
472 * @param int $post_id Post ID.
473 * @param \WP_Post $post Post object.
474 * @return void
475 */
476 public function on_post_deleted(int $post_id, \WP_Post $post): void
477 {
478 if (wp_is_post_revision($post_id)) {
479 return;
480 }
481
482 $current_user = wp_get_current_user();
483 if ($this->is_excluded_user($current_user)) {
484 return;
485 }
486
487 $this->log([
488 'event_key' => Activity_Log_Event_Types::CONTENT_DELETED,
489 'severity' => Activity_Log_Event_Types::SEVERITY_CRITICAL,
490 'user_id' => $current_user->ID,
491 'user_login' => $current_user->user_login,
492 'user_role' => $this->get_primary_role($current_user),
493 'object_type' => $post->post_type,
494 'object_id' => (string) $post_id,
495 'object_title' => $post->post_title,
496 'message' => sprintf(
497 /* translators: 1: post type, 2: post title */
498 __('%1$s "%2$s" permanently deleted', 'king-addons'),
499 ucfirst($post->post_type),
500 $post->post_title
501 ),
502 ]);
503 }
504
505 // =========================================================================
506 // Plugin Event Handlers
507 // =========================================================================
508
509 /**
510 * Handle plugin activation.
511 *
512 * @param string $plugin Plugin path.
513 * @param bool $network_wide Network-wide activation.
514 * @return void
515 */
516 public function on_plugin_activated(string $plugin, bool $network_wide): void
517 {
518 $current_user = wp_get_current_user();
519 $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
520 $plugin_name = $plugin_data['Name'] ?? $plugin;
521
522 $this->log([
523 'event_key' => Activity_Log_Event_Types::PLUGIN_ACTIVATED,
524 'severity' => Activity_Log_Event_Types::SEVERITY_WARNING,
525 'user_id' => $current_user->ID,
526 'user_login' => $current_user->user_login,
527 'user_role' => $this->get_primary_role($current_user),
528 'object_type' => 'plugin',
529 'object_id' => $plugin,
530 'object_title' => $plugin_name,
531 'message' => sprintf(
532 /* translators: %s: plugin name */
533 __('Plugin "%s" activated', 'king-addons'),
534 $plugin_name
535 ),
536 'data' => [
537 'network_wide' => $network_wide,
538 'version' => $plugin_data['Version'] ?? '',
539 ],
540 ]);
541 }
542
543 /**
544 * Handle plugin deactivation.
545 *
546 * @param string $plugin Plugin path.
547 * @param bool $network_wide Network-wide deactivation.
548 * @return void
549 */
550 public function on_plugin_deactivated(string $plugin, bool $network_wide): void
551 {
552 $current_user = wp_get_current_user();
553 $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
554 $plugin_name = $plugin_data['Name'] ?? $plugin;
555
556 $this->log([
557 'event_key' => Activity_Log_Event_Types::PLUGIN_DEACTIVATED,
558 'severity' => Activity_Log_Event_Types::SEVERITY_WARNING,
559 'user_id' => $current_user->ID,
560 'user_login' => $current_user->user_login,
561 'user_role' => $this->get_primary_role($current_user),
562 'object_type' => 'plugin',
563 'object_id' => $plugin,
564 'object_title' => $plugin_name,
565 'message' => sprintf(
566 /* translators: %s: plugin name */
567 __('Plugin "%s" deactivated', 'king-addons'),
568 $plugin_name
569 ),
570 'data' => [
571 'network_wide' => $network_wide,
572 ],
573 ]);
574 }
575
576 // =========================================================================
577 // Theme Event Handlers
578 // =========================================================================
579
580 /**
581 * Handle theme switch.
582 *
583 * @param string $new_name New theme name.
584 * @param \WP_Theme $new_theme New theme object.
585 * @param \WP_Theme $old_theme Old theme object.
586 * @return void
587 */
588 public function on_theme_switched(string $new_name, \WP_Theme $new_theme, \WP_Theme $old_theme): void
589 {
590 $current_user = wp_get_current_user();
591
592 $this->log([
593 'event_key' => Activity_Log_Event_Types::THEME_SWITCHED,
594 'severity' => Activity_Log_Event_Types::SEVERITY_WARNING,
595 'user_id' => $current_user->ID,
596 'user_login' => $current_user->user_login,
597 'user_role' => $this->get_primary_role($current_user),
598 'object_type' => 'theme',
599 'object_id' => $new_theme->get_stylesheet(),
600 'object_title' => $new_name,
601 'message' => sprintf(
602 /* translators: 1: old theme name, 2: new theme name */
603 __('Theme switched from "%1$s" to "%2$s"', 'king-addons'),
604 $old_theme->get('Name'),
605 $new_name
606 ),
607 'data' => [
608 'old_theme' => $old_theme->get_stylesheet(),
609 'old_theme_name' => $old_theme->get('Name'),
610 ],
611 ]);
612 }
613
614 // =========================================================================
615 // Custom Event Handler
616 // =========================================================================
617
618 /**
619 * Log custom event from other extensions.
620 *
621 * @param array<string, mixed> $event Event data.
622 * @return void
623 */
624 public function log_custom_event(array $event): void
625 {
626 $this->log($event);
627 }
628
629 // =========================================================================
630 // Core Logging Method
631 // =========================================================================
632
633 /**
634 * Log an event to the database.
635 *
636 * @param array<string, mixed> $data Event data.
637 * @return void
638 */
639 private function log(array $data): void
640 {
641 // Add context
642 $data['context'] = $this->get_context();
643 $data['source'] = $data['source'] ?? 'core';
644
645 // Add IP and user agent
646 $data['ip'] = $this->get_client_ip();
647 $data['user_agent'] = $this->get_user_agent();
648
649 // Set default severity if not specified
650 if (empty($data['severity'])) {
651 $data['severity'] = Activity_Log_Event_Types::get_default_severity($data['event_key'] ?? '');
652 }
653
654 // Insert into database
655 Activity_Log_DB::insert($data);
656 }
657
658 // =========================================================================
659 // Helper Methods
660 // =========================================================================
661
662 /**
663 * Check if a module is enabled.
664 *
665 * @param string $module Module name.
666 * @return bool
667 */
668 private function is_module_enabled(string $module): bool
669 {
670 $modules = $this->settings['modules'] ?? [];
671 return !isset($modules[$module]) || $modules[$module] === true;
672 }
673
674 /**
675 * Check if user is excluded from logging.
676 *
677 * @param \WP_User $user User object.
678 * @return bool
679 */
680 private function is_excluded_user(\WP_User $user): bool
681 {
682 if (empty($user->ID)) {
683 return false;
684 }
685
686 // Check excluded roles
687 $excluded_roles = $this->settings['excluded_roles'] ?? [];
688 if (!empty($excluded_roles)) {
689 foreach ($user->roles as $role) {
690 if (in_array($role, $excluded_roles, true)) {
691 return true;
692 }
693 }
694 }
695
696 // Check excluded user IDs
697 $excluded_users = $this->settings['excluded_users'] ?? [];
698 if (in_array($user->ID, $excluded_users, true)) {
699 return true;
700 }
701
702 return false;
703 }
704
705 /**
706 * Get user's primary role.
707 *
708 * @param \WP_User $user User object.
709 * @return string
710 */
711 private function get_primary_role(\WP_User $user): string
712 {
713 return !empty($user->roles) ? $user->roles[0] : '';
714 }
715
716 /**
717 * Get current context.
718 *
719 * @return string
720 */
721 private function get_context(): string
722 {
723 if (defined('WP_CLI') && WP_CLI) {
724 return 'cli';
725 }
726
727 if (defined('REST_REQUEST') && REST_REQUEST) {
728 return 'rest';
729 }
730
731 if (defined('DOING_CRON') && DOING_CRON) {
732 return 'cron';
733 }
734
735 if (is_admin()) {
736 return 'admin';
737 }
738
739 return 'frontend';
740 }
741
742 /**
743 * Get client IP address.
744 *
745 * @return string|null
746 */
747 private function get_client_ip(): ?string
748 {
749 $ip_storage = $this->settings['ip_storage'] ?? 'full';
750
751 if ($ip_storage === 'none') {
752 return null;
753 }
754
755 $ip = '';
756
757 // Check for proxy headers if enabled
758 if (!empty($this->settings['trust_proxy_headers'])) {
759 $headers = [
760 'HTTP_X_FORWARDED_FOR',
761 'HTTP_X_REAL_IP',
762 'HTTP_CLIENT_IP',
763 ];
764
765 foreach ($headers as $header) {
766 if (!empty($_SERVER[$header])) {
767 $ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$header])));
768 $ip = trim($ips[0]);
769 break;
770 }
771 }
772 }
773
774 if (empty($ip) && !empty($_SERVER['REMOTE_ADDR'])) {
775 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
776 }
777
778 if (empty($ip)) {
779 return null;
780 }
781
782 // Apply masking if configured
783 if ($ip_storage === 'masked') {
784 return $this->mask_ip($ip);
785 }
786
787 if ($ip_storage === 'hashed') {
788 return wp_hash($ip);
789 }
790
791 return $ip;
792 }
793
794 /**
795 * Mask IP address (last octet for IPv4, last 80 bits for IPv6).
796 *
797 * @param string $ip IP address.
798 * @return string
799 */
800 private function mask_ip(string $ip): string
801 {
802 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
803 return preg_replace('/\.\d+$/', '.xxx', $ip) ?? $ip;
804 }
805
806 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
807 return preg_replace('/:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+$/', ':xxxx:xxxx:xxxx:xxxx:xxxx', $ip) ?? $ip;
808 }
809
810 return $ip;
811 }
812
813 /**
814 * Get user agent string.
815 *
816 * @return string|null
817 */
818 private function get_user_agent(): ?string
819 {
820 if (empty($this->settings['store_user_agent'])) {
821 return null;
822 }
823
824 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
825 return sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']));
826 }
827
828 return null;
829 }
830 }
831