HEX
Server: Apache
System: Linux vps.teamads.com 4.18.0-553.126.1.el8_10.x86_64 #1 SMP Thu May 28 06:44:09 EDT 2026 x86_64
User: teamadsc (1024)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/teamadsc/public_html/wp-content/plugins/wp-defender/src/component/audit/class-user-audit.php
<?php
/**
 * Handles user-related audit events.
 *
 * @package WP_Defender\Component\Audit
 */

namespace WP_Defender\Component\Audit;

use WP_User;
use WP_Error;
use stdClass;
use WP_Defender\Traits\User;
use WP_Defender\Model\Audit_Log;

/**
 * Handle user-related audit events such as login, logout, registration, and profile updates.
 */
class User_Audit extends Audit_Event {

	use User;

	public const ACTION_LOGIN = 'login', ACTION_LOGOUT = 'logout', ACTION_REGISTERED = 'registered',
		ACTION_LOST_PASS      = 'lost_password', ACTION_RESET_PASS = 'reset_password',
		ACTION_CREATED        = 'created', ACTION_ADDED = 'added',
		ACTION_GRANTED        = 'granted', ACTION_REVOKED = 'revoked';

	public const CONTEXT_SESSION = 'session', CONTEXT_USERS = 'users', CONTEXT_PROFILE = 'profile',
		CONTEXT_MULTISITE_USERS  = 'multisite_users', CONTEXT_SUPER_ADMIN = 'super-admin';

	/**
	 * Cached old user data used for tracking changes to user profiles.
	 *
	 * This array stores old user data when a user's profile is updated.
	 * The data is keyed by user ID and contains the user's metadata before the update.
	 *
	 * @var array
	 */
	private static array $cached_old_user_data = array();

	/**
	 * Returns an array of hooks associated with various user actions to capture and log them.
	 *
	 * @return array
	 */
	public function get_hooks(): array {

		$hooks = array(
			'user_profile_update_errors'   => array(
				'args'     => array( 'errors', 'update', 'user' ),
				'callback' => array( $this, 'cache_old_user_data' ),
			),
			'wp_login_failed'              => array(
				'args'        => array( 'username' ),
				'text'        => sprintf(
				/* translators: 1: Blog name, 2: Username */
					esc_html__( '%1$s User login fail. Username: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}'
				),
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'context'     => self::CONTEXT_SESSION,
				'action_type' => self::ACTION_LOGIN,
			),
			'wp_login'                     => array(
				'args'        => array( 'userlogin', 'user' ),
				'text'        => sprintf(
				/* translators: 1: Blog name, 2: Username */
					esc_html__( '%1$s User login success: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{userlogin}}'
				),
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'context'     => self::CONTEXT_SESSION,
				'action_type' => self::ACTION_LOGIN,
			),
			'wpmu_2fa_login'               => array(
				'args'         => array( 'user_id', '2fa_slug' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: 2fa method slug, 3: Username. */
					esc_html__( '%1$s 2fa with %2$s method login success for user: %3$s', 'wpdef' ),
					'{{blog_name}}',
					'{{2fa_slug}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SESSION,
				'action_type'  => self::ACTION_LOGIN,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			'wp_logout'                    => array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Username */
					esc_html__( '%1$s User logout success: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'action_type'  => self::ACTION_LOGOUT,
				'context'      => self::CONTEXT_SESSION,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			'user_register'                => array(
				'args'        => array( 'user_id' ),
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'context'     => self::CONTEXT_USERS,
				'action_type' => self::ACTION_REGISTERED,
				'callback'    => array( $this, 'created_new_user_callback' ),
			),
			'delete_user'                  => array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: User ID, 4: Username */
					esc_html__( '%1$s %2$s deleted a user: ID: %3$s, username: %4$s', 'wpdef' ),
					'{{blog_name}}',
					'{{wp_user}}',
					'{{user_id}}',
					'{{username}}'
				),
				'context'      => self::CONTEXT_USERS,
				'action_type'  => self::ACTION_DELETED,
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			'remove_user_from_blog'        => array(
				'args'        => array( 'user_id', 'blog_id' ),
				'context'     => self::CONTEXT_USERS,
				'action_type' => self::ACTION_DELETED,
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'callback'    => array( self::class, 'remove_user_from_blog_callback' ),
			),
			'wpmu_delete_user'             => array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: User ID, 4: Username */
					esc_html__( '%1$s %2$s deleted a user: ID: %3$s, username: %4$s', 'wpdef' ),
					'{{blog_name}}',
					'{{wp_user}}',
					'{{user_id}}',
					'{{username}}'
				),
				'context'      => self::CONTEXT_USERS,
				'action_type'  => self::ACTION_DELETED,
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			'profile_update'               => array(
				'args'        => array( 'user_id', 'old_user_data', 'userdata' ),
				'action_type' => self::ACTION_UPDATED,
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'context'     => self::CONTEXT_PROFILE,
				'callback'    => array( self::class, 'profile_update_callback' ),
			),
			'retrieve_password'            => array(
				'args'         => array( 'username' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Username */
					esc_html__( '%1$s Password requested to reset for user: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}'
				),
				'action_type'  => self::ACTION_LOST_PASS,
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_PROFILE,
				'program_args' => array(
					'user' => array(
						'callable' => 'get_user_by',
						'params'   => array(
							'login',
							'{{username}}',
						),
					),
				),
			),
			'after_password_reset'         => array(
				'args'        => array( 'user' ),
				'text'        => sprintf(
				/* translators: 1: Blog name, 2: Username. */
					esc_html__( '%1$s Password reset for user: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{user_login}}'
				),
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'action_type' => self::ACTION_RESET_PASS,
				'context'     => self::CONTEXT_PROFILE,
				'custom_args' => array(
					'user_login' => '{{user->user_login}}',
				),
			),
			'set_user_role'                => array(
				'args'         => array( 'user_ID', 'new_role', 'old_role' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Source of action. For e.g. Hub or a logged-in user, 3: Username, 4: Old user role, 5: New user role */
					esc_html__( "%1\$s %2\$s changed user %3\$s's role from %4\$s to %5\$s", 'wpdef' ),
					'{{blog_name}}',
					'{{wp_user}}',
					'{{username}}',
					'{{from_role}}',
					'{{new_role}}'
				),
				'action_type'  => self::ACTION_UPDATED,
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_PROFILE,
				'custom_args'  => array(
					'from_role' => '{{old_role->0}}',
				),
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_ID}}',
						),
						'result_property' => 'user_login',
					),
				),
				'false_when'   => array(
					array(
						'{{old_role}}',
						array(),
						'==',
					),
				),
			),
			'wpdef_session_lock'           => array(
				'args'         => array( 'user_id', 'session_lock_type' ),
				'text'         => sprintf(
					/* translators: 1: Blog name, 2: Username, 3: Session lock type. */
					esc_html__( '%1$s User session ended for %2$s due to a change in %3$s', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}',
					'{{session_lock_type}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SESSION,
				'action_type'  => self::ACTION_LOGIN,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			'wpdef_session_timeout'        => array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
					/* translators: 1: Blog name, 2: Username. */
					esc_html__( '%1$s User session ended for %2$s due to an idle session', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SESSION,
				'action_type'  => self::ACTION_LOGIN,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
			// Since 5.6.0.
			'wpmudev_sso_set_current_user' => array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name, 2: Username. */
					esc_html__( '%1$s Hub SSO login success: %2$s', 'wpdef' ),
					'{{blog_name}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SESSION,
				'action_type'  => self::ACTION_LOGIN,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array(
							'id',
							'{{user_id}}',
						),
						'result_property' => 'user_login',
					),
				),
			),
		);

		if ( is_multisite() ) {
			$hooks['granted_super_admin'] = array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
					esc_html__( '%1$s %2$s granted Super Admin privileges to %3$s', 'wpdef' ),
					'{{blog_name}}',
					'{{wp_user}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SUPER_ADMIN,
				'action_type'  => self::ACTION_GRANTED,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array( 'id', '{{user_id}}' ),
						'result_property' => 'user_login',
					),
				),
			);
			$hooks['revoked_super_admin'] = array(
				'args'         => array( 'user_id' ),
				'text'         => sprintf(
				/* translators: 1: Blog name. 2: Actor username. 3: Affected username. */
					esc_html__( '%1$s %2$s revoked Super Admin privileges from %3$s', 'wpdef' ),
					'{{blog_name}}',
					'{{wp_user}}',
					'{{username}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_SUPER_ADMIN,
				'action_type'  => self::ACTION_REVOKED,
				'program_args' => array(
					'username' => array(
						'callable'        => 'get_user_by',
						'params'          => array( 'id', '{{user_id}}' ),
						'result_property' => 'user_login',
					),
				),
			);
			$hooks['wpmu_new_user']    = array(
				'args'        => array( 'user_id' ),
				'event_type'  => Audit_Log::EVENT_TYPE_USER,
				'context'     => self::CONTEXT_MULTISITE_USERS,
				'action_type' => self::ACTION_CREATED,
				'callback'    => array( $this, 'created_new_user_callback' ),
			);
			$hooks['add_user_to_blog'] = array(
				'args'         => array( 'user_id', 'role', 'blog_id' ),
				'text'         => sprintf(
					/* translators: 1: Blog name, 2: Username, 3: User role. */
					esc_html__( 'User added to %1$s, Username: %2$s, Role: %3$s', 'wpdef' ),
					'{{site_name}}',
					'{{username}}',
					'{{role}}'
				),
				'event_type'   => Audit_Log::EVENT_TYPE_USER,
				'context'      => self::CONTEXT_MULTISITE_USERS,
				'action_type'  => self::ACTION_ADDED,
				'program_args' => array(
					'site_name' => array(
						'callable'        => 'get_blog_details',
						'params'          => array( '{{blog_id}}' ),
						'result_property' => 'blogname',
					),
					'username'  => array(
						'callable'        => 'get_user_by',
						'params'          => array( 'id', '{{user_id}}' ),
						'result_property' => 'user_login',
					),
				),
			);
		}

		return $hooks;
	}

	/**
	 * Log when user is created on MU.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return bool|array
	 */
	public function created_new_user_callback( int $user_id ): bool|array {
		$user          = get_user_by( 'id', $user_id );
		$username      = $user->user_login ?? '';
		$blog_name     = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : 'the site';
		$userdata_role = defender_get_data_from_request( 'role', 'p' );

		if ( ! isset( $user->roles[0], $userdata_role ) || $userdata_role !== $user->roles[0] ) {
			return false;
		}
		$role   = $this->get_first_user_role( $user );
		$action = defender_get_data_from_request( 'action', 'p' );
		if ( is_admin() ) {
			return array(
				sprintf(
				/* translators: 1: Blog name, 2: Username, 3: User role. */
					esc_html__( 'User %1$s %2$s, Username: %3$s, Role: %4$s', 'wpdef' ),
					'created' === $action ? 'created on' : 'added to',
					$blog_name,
					$username,
					$role
				),
				self::ACTION_CREATED,
			);
		} else {
			return array(
				sprintf(
								/* translators: 1: Blog name, 2: Username, 3: User role */
					esc_html__( '%1$s A new user registered: Username: %2$s, Role: %3$s', 'wpdef' ),
					$blog_name,
					$username,
					$role
				),
			);
		}
	}

	/**
	 * Log when user is removed from a blog.
	 *
	 * @return bool|array
	 */
	public function remove_user_from_blog_callback() {
		$action = defender_get_data_from_request( 'action', 'p' );
		if ( 'doremove' !== $action ) {
			return false;
		}

		$args                 = func_get_args();
		$user_id              = $args[1]['user_id'];
		$blog_id              = $args[1]['blog_id'];
		$user                 = get_user_by( 'id', $user_id );
		$username             = $user->user_login ?? '';
		$current_user_display = $this->get_user_display( get_current_user_id() );
		$blog_name            = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';

		return array(
			sprintf(
			/* translators: 1: Blog name, 2: User's display name, 3: User ID, 4: Username, 5: Blog ID */
				esc_html__( '%1$s %2$s removed a user: ID: %3$s, username: %4$s from blog %5$s', 'wpdef' ),
				$blog_name,
				$current_user_display,
				$user_id,
				$username,
				$blog_id
			),
			self::ACTION_DELETED,
		);
	}

	/**
	 * Callback for logging when a user's profile is updated. It distinguishes between self-updates and updates made by
	 * other users.
	 *
	 * @param int     $user_id       User ID.
	 * @param WP_User $old_user_data Object containing user's data prior to update.
	 * @param array   $userdata      The raw array of data passed to wp_insert_user().
	 *
	 * @return bool|array
	 */
	public function profile_update_callback( int $user_id, WP_User $old_user_data, array $userdata ): bool|array {
		$from = defender_get_data_from_request( 'from', 'p' );
		if ( 'profile' !== $from ) {
			return false;
		}
		$diff_assoc = array_diff_assoc( $userdata, self::$cached_old_user_data[ $user_id ] ?? array() );
		if ( array() === $diff_assoc ) {
			return false;
		}
		$current_user = get_user_by( 'id', $user_id );
		if ( array_key_exists( 'user_pass', $diff_assoc ) && $old_user_data->user_pass !== $userdata['user_pass'] ) {
			$password_audit = new Password_Audit();
			return $password_audit->build_log( $current_user );
		}
		$blog_name       = is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '';
		$current_user_id = get_current_user_id();

		if ( $current_user_id === $user_id ) {

			return array(
				sprintf(
				/* translators: 1: Blog name, 2: User's nicename */
					esc_html__( '%1$s User %2$s updated his/her profile', 'wpdef' ),
					$blog_name,
					$current_user->user_nicename
				),
				self::ACTION_UPDATED,
			);
		} elseif ( 0 !== $current_user_id ) {

			return array(
				sprintf(
				/* translators: 1: Blog name, 2: User's display name, 3: User's nicename */
					esc_html__( "%1\$s %2\$s updated user %3\$s's profile information", 'wpdef' ),
					$blog_name,
					$this->get_user_display( $current_user_id ),
					$current_user->user_nicename
				),
				self::ACTION_UPDATED,
			);
		}

		return false;
	}

	/**
	 * Provides a dictionary for translating action constants into human-readable strings.
	 *
	 * @return array
	 */
	public function dictionary(): array {
		return array(
			self::ACTION_LOST_PASS  => esc_html__( 'lost password', 'wpdef' ),
			self::ACTION_REGISTERED => esc_html__( 'registered', 'wpdef' ),
			self::ACTION_LOGIN      => esc_html__( 'login', 'wpdef' ),
			self::ACTION_LOGOUT     => esc_html__( 'logout', 'wpdef' ),
			self::ACTION_RESET_PASS => esc_html__( 'password reset', 'wpdef' ),
			self::ACTION_CREATED    => esc_html__( 'created', 'wpdef' ),
			self::ACTION_ADDED      => esc_html__( 'added', 'wpdef' ),
			self::ACTION_GRANTED    => esc_html__( 'granted', 'wpdef' ),
			self::ACTION_REVOKED    => esc_html__( 'revoked', 'wpdef' ),
		);
	}

	/**
	 * Static method to retrieve the role of a user by their ID
	 *
	 * @param  int $user_id  The ID of the user whose role is to be fetched.
	 *
	 * @return string
	 */
	public static function get_user_role( $user_id ): string {
		$user = get_user_by( 'id', $user_id );
		if ( $user instanceof WP_User ) {
			$_this = new self();

			return $_this->get_first_user_role( $user );
		} else {
			return '';
		}
	}

	/**
	 * Check if it is a create new user request.
	 *
	 * @since 2.8.0
	 * @return bool
	 */
	public static function is_create_user_action(): bool {
		$action = defender_get_data_from_request( 'action', 'p' );
		if ( 'createuser' === $action ) {
			return true;
		}

		return false;
	}

	/**
	 * Cache old user data from when a user's profile is updated.
	 *
	 * @param WP_Error $errors WP_Error object (passed by reference).
	 * @param bool     $update Whether this is a user update.
	 * @param stdClass $user   User object (passed by reference).
	 *
	 * @return WP_Error The errors that occurred during the action.
	 */
	public function cache_old_user_data( WP_Error $errors, bool $update, stdClass $user ): WP_Error {
		if ( $update ) {
			$id           = $user->ID;
			$current_user = get_userdata( $id );
			$user_array   = $current_user->to_array();
			$meta         = array(
				'first_name'           => get_user_meta( $current_user->ID, 'first_name', true ),
				'last_name'            => get_user_meta( $current_user->ID, 'last_name', true ),
				'nickname'             => get_user_meta( $current_user->ID, 'nickname', true ),
				'description'          => get_user_meta( $current_user->ID, 'description', true ),
				'rich_editing'         => get_user_meta( $current_user->ID, 'rich_editing', true ),
				'syntax_highlighting'  => get_user_meta( $current_user->ID, 'syntax_highlighting', true ),
				'comment_shortcuts'    => get_user_meta( $current_user->ID, 'comment_shortcuts', true ) === 'true' ? 'true' : '',
				'locale'               => get_user_meta( $current_user->ID, 'locale', true ),
				'admin_color'          => get_user_meta( $current_user->ID, 'admin_color', true ),
				'use_ssl'              => get_user_meta( $current_user->ID, 'use_ssl', true ),
				'show_admin_bar_front' => get_user_meta( $current_user->ID, 'show_admin_bar_front', true ),
			);

			self::$cached_old_user_data[ $id ] = array_merge_recursive( $user_array, $meta );
		}

		return $errors;
	}
}