*/ public static function event_types(): array { return array( self::SESSION_ENDED => 'Session Ended', self::SESSIONS_ENDED => 'Sessions Ended', self::SESSION_IDLE_EXPIRED => 'Idle Session Ended', self::SESSION_LIMIT => 'Session Limit Enforced', ); } /** * Record changes to the policy option from ANY writer -- the Sessions * screen, WP-CLI `option update`, another plugin -- not just the form. */ public static function register(): void { add_action( 'add_option_' . SessionPolicy::OPTION, array( __CLASS__, 'policy_added' ), 10, 2 ); add_action( 'update_option_' . SessionPolicy::OPTION, array( __CLASS__, 'policy_changed' ), 10, 2 ); } /** * @param string $option The option name. * @param mixed $value What was stored. */ public static function policy_added( string $option, $value ): void { self::policy_changed( array(), $value ); } /** * @param mixed $old_value What was stored. * @param mixed $value What is stored now. */ public static function policy_changed( $old_value, $value ): void { $before = SessionPolicy::sanitize( $old_value ); $after = SessionPolicy::sanitize( $value ); if ( $before === $after ) { return; } EventLogger::log( 'setting_changed', sprintf( 'Session policy changed: idle timeout %s, %d role override(s)', $after['idle_minutes'] > 0 ? $after['idle_minutes'] . ' minutes' : 'off', count( $after['roles'] ) ), 'setting', null, array( 'option' => SessionPolicy::OPTION, 'old_value' => $before, 'new_value' => $after, ) ); } /** * One session ended from the Sessions screen. * * @param \WP_User $user Whose session. * @param array $session The session as stored. */ public static function session_ended( \WP_User $user, array $session ): void { EventLogger::log( self::SESSION_ENDED, sprintf( 'Session of %s ended by an administrator', $user->user_login ), 'user', (int) $user->ID, array_merge( array( 'via' => 'sessions_screen' ), self::describe( $session ) ) ); } /** * Every session of a user ended, or every session but the acting * user's own current one. * * @param \WP_User $user Whose sessions. * @param int $count How many were ended. * @param bool $kept_current Whether the acting user's current session was kept (their own "everywhere else"). * @param string $via Which screen: sessions_screen or profile_screen. */ public static function sessions_ended( \WP_User $user, int $count, bool $kept_current, string $via ): void { EventLogger::log( self::SESSIONS_ENDED, $kept_current ? sprintf( 'User %1$s logged out everywhere else (%2$d session(s))', $user->user_login, $count ) : sprintf( 'All sessions of %1$s ended by an administrator (%2$d session(s))', $user->user_login, $count ), 'user', (int) $user->ID, array( 'via' => $via, 'sessions' => $count, 'kept_current' => $kept_current, ) ); } /** * A session ended by the idle timeout. * * @param \WP_User $user Whose session. * @param array $session The session as stored. * @param int $idle_seconds The timeout that applied. */ public static function idle_expired( \WP_User $user, array $session, int $idle_seconds ): void { EventLogger::log( self::SESSION_IDLE_EXPIRED, sprintf( 'Session of %1$s ended after %2$d minutes idle', $user->user_login, intdiv( $idle_seconds, MINUTE_IN_SECONDS ) ), 'user', (int) $user->ID, array_merge( array( 'idle_timeout_minutes' => intdiv( $idle_seconds, MINUTE_IN_SECONDS ) ), self::describe( $session ) ), (int) $user->ID ); } /** * The oldest session ended because a new one took the user past the * limit. * * @param \WP_User $user Whose session. * @param array $session The evicted session as stored. * @param int $limit The limit that applied. */ public static function limit_evicted( \WP_User $user, array $session, int $limit ): void { EventLogger::log( self::SESSION_LIMIT, sprintf( 'Oldest session of %1$s ended: limit of %2$d concurrent session(s)', $user->user_login, $limit ), 'user', (int) $user->ID, array_merge( array( 'session_limit' => $limit ), self::describe( $session ) ), (int) $user->ID ); } /** * The parts of a stored session an event may carry. See the class * docblock for what is left out and why. * * @param array $session The session as stored. * @return array */ private static function describe( array $session ): array { return array( 'signed_in_at' => self::utc( $session['login'] ?? null ), 'last_activity' => self::utc( $session[ SessionStore::LAST_ACTIVITY_KEY ] ?? null ), 'session_ip' => self::ip_as_logged( $session['ip'] ?? null ), ); } /** * A session's IP address the way this plugin's own setting says to * keep one. Public: the Sessions screen shows it the same way. * * @param mixed $ip Core's stored address. */ public static function ip_as_logged( $ip ): ?string { if ( ! is_string( $ip ) || false === filter_var( $ip, FILTER_VALIDATE_IP ) ) { return null; } if ( (bool) get_option( EventLogger::OPTION_STORE_FULL_IPS, false ) ) { return $ip; } $truncated = IpAnonymizer::truncate( $ip ); return '' !== $truncated ? $truncated : null; } /** * @param mixed $timestamp A Unix timestamp or nothing. */ private static function utc( $timestamp ): ?string { return is_numeric( $timestamp ) && (int) $timestamp > 0 ? gmdate( 'Y-m-d H:i:s', (int) $timestamp ) : null; } }