' . esc_html__( 'Your sessions', '404ops-activity-log' ) . ''; self::render_own_sessions(); echo '

' . esc_html__( 'Signed-in users', '404ops-activity-log' ) . '

'; self::render_user_table(); echo '

' . esc_html__( 'Session policy', '404ops-activity-log' ) . '

'; self::render_policy_form(); } /** * The notice for the result an action redirected back with. Only a * known code is ever shown; anything else in the URL shows nothing. */ private static function render_result_notice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $code = isset( $_GET[ SessionActions::RESULT_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ SessionActions::RESULT_ARG ] ) ) : ''; if ( ! in_array( $code, SessionActions::RESULTS, true ) ) { return; } $messages = array( 'destroyed_one' => __( 'The session was logged out.', '404ops-activity-log' ), 'destroyed_user' => __( 'All of that user\'s sessions were logged out.', '404ops-activity-log' ), 'destroyed_others' => __( 'You are now logged out everywhere else. This session was kept.', '404ops-activity-log' ), 'policy_saved' => __( 'Session policy saved.', '404ops-activity-log' ), 'bad_request' => __( 'That request was not understood.', '404ops-activity-log' ), 'bad_nonce' => __( 'The link has expired. Reload the page and try again.', '404ops-activity-log' ), 'forbidden' => __( 'You are not allowed to manage that user\'s sessions.', '404ops-activity-log' ), 'own_current' => __( 'That is the session you are using now. To end it, use Log Out.', '404ops-activity-log' ), 'gone' => __( 'That session no longer exists. It may have expired or been logged out already.', '404ops-activity-log' ), 'unsupported' => __( 'This site stores sessions in a custom session manager, so single sessions cannot be logged out from here.', '404ops-activity-log' ), 'no_current_session' => __( 'This request is not signed in with a browser session, so there is nothing to keep.', '404ops-activity-log' ), ); $is_error = ! in_array( $code, array( 'destroyed_one', 'destroyed_user', 'destroyed_others', 'policy_saved' ), true ); echo '

' . esc_html( $messages[ $code ] ) . '

'; } /** * The acting user's own sessions and "log out everywhere else". */ private static function render_own_sessions(): void { $me = wp_get_current_user(); $others = max( 0, count( \WP_Session_Tokens::get_instance( $me->ID )->get_all() ) - 1 ); echo '

' . esc_html( sprintf( /* translators: %d: number of other sessions. */ _n( 'You are signed in in %d other place.', 'You are signed in in %d other places.', $others, '404ops-activity-log' ), $others ) ) . '

'; if ( $others > 0 ) { self::action_form( SessionActions::OP_DESTROY_OTHERS, (int) $me->ID, '', __( 'Log out everywhere else', '404ops-activity-log' ), __( 'Ends every other session of yours. This session stays signed in.', '404ops-activity-log' ) ); } } /** * Every user with a stored session on this page of the list. */ private static function render_user_table(): void { if ( ! SessionStore::supported() ) { echo '

' . esc_html( sprintf( /* translators: %s: PHP class name of the session manager. */ __( 'Sessions on this site are stored by a custom session manager (%s), which this screen cannot list. "Log out everywhere else" and the idle timeout still work; single-session logout and the concurrent-session limit do not.', '404ops-activity-log' ), SessionStore::manager_class() ) ) . '

'; return; } if ( is_multisite() && ! current_user_can( 'manage_network_users' ) ) { echo '

' . esc_html__( 'On a network, sessions are shared by every site, so only a network administrator can manage other users\' sessions.', '404ops-activity-log' ) . '

'; return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pagination only. $page = isset( $_GET['sessions_page'] ) ? max( 1, absint( $_GET['sessions_page'] ) ) : 1; $result = SessionStore::users_with_sessions( $page, self::PER_PAGE ); $now = time(); $mine = SessionStore::current_verifier(); $rows = 0; echo ''; echo ''; foreach ( array( __( 'User', '404ops-activity-log' ), __( 'Signed in', '404ops-activity-log' ), __( 'Last active', '404ops-activity-log' ), __( 'Expires', '404ops-activity-log' ), __( 'IP Address', '404ops-activity-log' ), __( 'Browser', '404ops-activity-log' ), __( 'Actions', '404ops-activity-log' ), ) as $heading ) { echo ''; } echo ''; foreach ( $result['users'] as $user ) { $sessions = SessionStore::sessions( (int) $user->ID ); if ( array() === $sessions ) { continue; } $is_me = get_current_user_id() === (int) $user->ID; $can_manage = $is_me || SessionActions::may_manage( $user ); $idle = SessionPolicy::idle_seconds_for( $user ); $first = true; foreach ( $sessions as $verifier => $session ) { ++$rows; $is_current = $is_me && '' !== $mine && hash_equals( $mine, (string) $verifier ); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; $first = false; } } echo '
' . esc_html( $heading ) . '
'; if ( $first ) { echo wp_kses_post( self::user_cell( $user ) ); // The current user's other-session action is offered above the table. if ( ! $is_me && $can_manage ) { self::action_form( SessionActions::OP_DESTROY_USER, (int) $user->ID, '', __( 'Log out all sessions', '404ops-activity-log' ), '' ); } } echo '' . esc_html( self::when( $session['login'] ?? null ) ) . '' . esc_html( self::last_active( $session, $idle, $now ) ) . '' . esc_html( self::when( $session['expiration'] ?? null ) ) . '' . esc_html( SessionLog::ip_as_logged( $session['ip'] ?? null ) ?? '—' ) . '' . esc_html( self::browser( $session['ua'] ?? null ) ) . ''; if ( $is_current ) { echo '' . esc_html__( 'This session', '404ops-activity-log' ) . ''; } elseif ( $can_manage ) { self::action_form( SessionActions::OP_DESTROY_ONE, (int) $user->ID, SessionStore::handle( (int) $user->ID, (string) $verifier ), __( 'Log out', '404ops-activity-log' ), '' ); } echo '
'; if ( 0 === $rows ) { echo '

' . esc_html__( 'Nobody else is signed in.', '404ops-activity-log' ) . '

'; } $pages = (int) ceil( $result['total'] / self::PER_PAGE ); if ( $pages > 1 ) { echo '

'; for ( $i = 1; $i <= $pages; $i++ ) { $url = add_query_arg( array( 'page' => '404ops-activity-log', 'tab' => 'sessions', 'sessions_page' => $i, ), admin_url( 'admin.php' ) ); echo '' . esc_html( (string) $i ) . ' '; } echo '

'; } } /** * The first cell of a user's group of rows. * * @param \WP_User $user The user. */ private static function user_cell( \WP_User $user ): string { $html = '' . esc_html( $user->user_login ) . ''; if ( '' !== (string) $user->display_name && $user->display_name !== $user->user_login ) { $html .= '
' . esc_html( (string) $user->display_name ); } $html .= '
' . esc_html( implode( ', ', array_map( 'translate_user_role', self::role_names( $user ) ) ) ) . ''; return $html; } /** * @param \WP_User $user The user. * @return string[] Display names of their roles. */ private static function role_names( \WP_User $user ): array { $names = wp_roles()->get_names(); $out = array(); foreach ( (array) $user->roles as $role ) { $out[] = $names[ $role ] ?? $role; } return $out; } /** * One small POST form for one action. * * @param string $op SessionActions::OP_*. * @param int $user_id The target user. * @param string $handle The session handle, for OP_DESTROY_ONE. * @param string $label Button text. * @param string $description Help text; '' for none. */ private static function action_form( string $op, int $user_id, string $handle, string $label, string $description ): void { echo '
'; echo ''; echo ''; echo ''; if ( '' !== $handle ) { echo ''; } wp_nonce_field( SessionActions::nonce_action( $op, $user_id ), '_wpnonce', false ); echo ''; if ( '' !== $description ) { echo ' ' . esc_html( $description ) . ''; } echo '
'; } /** * The policy form. */ private static function render_policy_form(): void { $policy = SessionPolicy::get(); $editable = SessionActions::may_edit_policy(); if ( ! $editable ) { echo '

' . esc_html__( 'On a network, the session policy can be changed only by a network administrator, because sessions are shared by every site.', '404ops-activity-log' ) . '

'; } echo '
'; echo ''; echo ''; echo ''; wp_nonce_field( SessionActions::nonce_action( SessionActions::OP_SAVE_POLICY, 0 ), '_wpnonce', false ); $disabled = $editable ? '' : ' disabled'; echo '
'; echo ''; echo ''; echo '

' . esc_html( sprintf( /* translators: %d: minimum idle timeout in minutes. */ __( '0 turns it off. Otherwise at least %d minutes. A session with no activity for this long is logged out on its next request, and the login screen says why. While a timeout is on, the time of each session\'s last activity is recorded inside the session itself and removed with it.', '404ops-activity-log' ), SessionPolicy::MIN_IDLE_MINUTES ) ) . '

'; echo '
'; echo ''; echo ''; foreach ( wp_roles()->get_names() as $slug => $name ) { $slug = (string) $slug; $entry = $policy['roles'][ $slug ] ?? array( 'idle' => null, 'limit' => 0, ); $label = translate_user_role( $name ); echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Role', '404ops-activity-log' ) . '' . esc_html__( 'Idle timeout (minutes)', '404ops-activity-log' ) . '' . esc_html__( 'Most sessions at once', '404ops-activity-log' ) . '
' . esc_html( $label ) . '
'; echo '

' . esc_html__( 'Idle timeout: blank uses the default above, 0 exempts the role. Most sessions at once: 0 is unlimited; signing in beyond the limit logs out that user\'s oldest session. A user with several roles gets the strictest setting among them. Every logout this causes is recorded in the activity log.', '404ops-activity-log' ) . '

'; if ( $editable ) { submit_button( __( 'Save Session Policy', '404ops-activity-log' ), 'primary', 'ops404_al_save_session_policy', true ); } echo '
'; } /** * @param mixed $timestamp Unix time or nothing. */ private static function when( $timestamp ): string { if ( ! is_numeric( $timestamp ) || (int) $timestamp <= 0 ) { return '—'; } return (string) wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $timestamp ); } /** * Last activity, and whether the idle timeout has already doomed it. * * @param array $session The session. * @param int $idle The owner's idle timeout in seconds, 0 = none. * @param int $now Current time. */ private static function last_active( array $session, int $idle, int $now ): string { $last = $session[ SessionStore::LAST_ACTIVITY_KEY ] ?? null; if ( ! is_numeric( $last ) ) { return 0 === $idle ? __( 'Not tracked', '404ops-activity-log' ) : '—'; } $text = self::when( $last ); if ( $idle > 0 && $now - (int) $last >= $idle + SessionPolicy::throttle_seconds( $idle ) ) { $text .= ' — ' . __( 'idle, ends on next use', '404ops-activity-log' ); } return $text; } /** * @param mixed $ua Core's stored user agent. */ private static function browser( $ua ): string { if ( ! is_string( $ua ) || '' === $ua ) { return '—'; } $ua = function_exists( 'mb_substr' ) ? mb_substr( $ua, 0, self::UA_LENGTH ) : substr( $ua, 0, self::UA_LENGTH ); return sanitize_text_field( $ua ); } }