Users - the agents who sign in to ITFlow. Each row is who * they are, their role, whether they can sign in and how (MFA, browsers that * skip the code), the clients they can see and when they last signed in. The * pills split them: everyone, admins, no MFA, disabled - and the archived. * Invited is the people asked to join who aren't in yet: the invites waiting * or run out (functions/user_invite.php), to send again, get the link to * pass on, or cancel. A link made to pass on is shown once, at the top. * * The admin's own row is shown but not changed here: that is their Account * pages, so nobody locks themselves out by mistake. */ // Default Column Sortby Filter $sort = "user_name"; $order = "ASC"; require_once "includes/inc_all_admin.php"; // filter_header.php fills in canned_date=custom when no range was asked for - not a filter here, so not carried into links if (($_GET['canned_date'] ?? '') === 'custom' && empty($_GET['dtf'])) { unset($_GET['canned_date']); } $roles = userRoles(); $role_filter = isset($roles[intval($_GET['role'] ?? 0)]) ? intval($_GET['role']) : 0; $filters = "user_type = 1 AND (user_name LIKE '%$q%' OR user_email LIKE '%$q%' OR role_name LIKE '%$q%')" . ($role_filter ? " AND user_role_id = $role_filter" : ''); $joins = "LEFT JOIN user_roles ON user_role_id = role_id LEFT JOIN user_settings ON user_settings.user_id = users.user_id"; // The views $shows = [ '' => ['label' => 'Users', 'icon' => 'fa-users', 'where' => 'user_archived_at IS NULL', 'title' => 'Everyone not archived'], 'admins' => ['label' => 'Admins', 'icon' => 'fa-user-shield', 'where' => 'user_archived_at IS NULL AND role_is_admin = 1', 'title' => 'Every client and every setting'], 'nomfa' => ['label' => 'No MFA', 'icon' => 'fa-unlock', 'where' => "user_archived_at IS NULL AND user_status = 1 AND (user_token IS NULL OR user_token = '')", 'title' => 'Active, and signing in with a password alone'], 'disabled' => ['label' => 'Disabled', 'icon' => 'fa-ban', 'where' => 'user_archived_at IS NULL AND user_status <> 1', 'title' => 'Cannot sign in'], 'invited' => ['label' => 'Invited', 'icon' => 'fa-paper-plane', 'where' => null, 'title' => 'Asked to join and not in yet - their invite waiting, or run out'], 'archived' => ['label' => 'Archived', 'icon' => 'fa-archive', 'where' => 'user_archived_at IS NOT NULL', 'title' => 'Gone - restore one with a new password'], ]; if (!userInvitesReady()) { unset($shows['invited']); } $show = isset($shows[$_GET['show'] ?? '']) ? ($_GET['show'] ?? '') : ''; // The invites - not users yet, so a list of their own $invite_filters = "invite_accepted_at IS NULL AND invite_cancelled_at IS NULL AND (invite_name LIKE '%$q%' OR invite_email LIKE '%$q%' OR role_name LIKE '%$q%')" . ($role_filter ? " AND invite_role_id = $role_filter" : ''); $invite_joins = "LEFT JOIN user_roles ON role_id = invite_role_id LEFT JOIN users inviter ON inviter.user_id = invite_created_by AND invite_created_by > 0"; // Only these sort - an unknown column would take the page down $last_login_sql = "(SELECT MAX(log_id) FROM logs WHERE log_user_id = users.user_id AND log_type = 'Login' AND log_action = 'Success')"; $sortable_columns = [ 'user_name' => "user_name $order", 'user_email' => "user_email $order", 'role_name' => "role_is_admin DESC, role_name $order, user_name ASC", 'user_status' => "user_status $order, user_name ASC", 'last_login' => "$last_login_sql IS NULL, $last_login_sql $order, user_name ASC", 'user_created_at' => "user_created_at $order, user_name ASC", ]; // ...and the same headers sort the invites: when it runs out for Sign in, when it was sent for Added $invite_sortable_columns = [ 'user_name' => "invite_name $order", 'user_email' => "invite_email $order", 'role_name' => "role_is_admin DESC, role_name $order, invite_name ASC", 'user_status' => "invite_expires_at $order, invite_name ASC", 'user_created_at' => "invite_created_at $order, invite_name ASC", ]; if (!isset(($show === 'invited' ? $invite_sortable_columns : $sortable_columns)[$sort])) { $sort = 'user_name'; } $users = []; $invites = []; if ($show === 'invited') { $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS user_invites.*, role_name, role_is_admin, inviter.user_name AS invite_by_name FROM user_invites $invite_joins WHERE $invite_filters ORDER BY {$invite_sortable_columns[$sort]}, invite_id ASC LIMIT $record_from, $record_to"); $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); while ($row = mysqli_fetch_assoc($sql)) { $invites[] = $row; } } else { $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS users.user_id, user_name, user_email, user_status, user_token, user_avatar, user_created_at, user_archived_at, user_role_id, role_name, role_is_admin, user_config_force_mfa FROM users $joins WHERE $filters AND {$shows[$show]['where']} ORDER BY {$sortable_columns[$sort]}, users.user_id ASC LIMIT $record_from, $record_to"); $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); while ($row = mysqli_fetch_assoc($sql)) { $users[intval($row['user_id'])] = $row; } } $logins = userLastLogins(array_keys($users)); $access = userClientAccess(array_keys($users)); $remembered = userRememberCounts(array_keys($users)); // How many in each view, over what the search and role leave $user_views = array_filter($shows, function ($s) { return $s['where'] !== null; }); $view_counts = []; $row = mysqli_fetch_row(mysqli_query($mysqli, "SELECT " . implode(', ', array_map(function ($s) { return "COALESCE(SUM({$s['where']}), 0)"; }, $user_views)) . " FROM users $joins WHERE $filters")); foreach (array_keys($user_views) as $i => $key) { $view_counts[$key] = intval($row[$i]); } if (isset($shows['invited'])) { $view_counts['invited'] = intval(mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(*) FROM user_invites $invite_joins WHERE $invite_filters"))[0]); } // Agent user limit (set in config.php, 0 / unset = unlimited) - an invite waiting holds a seat $agent_user_limit = getAgentUserLimit(); $agent_user_count = getActiveAgentUserCount(); $agent_invites_holding = userInvitesHoldingSeats(); $agent_user_limit_reached = $agent_user_limit > 0 && $agent_user_count + $agent_invites_holding >= $agent_user_limit; // An invite link to pass on - shown this once $invite_link = userInviteTakeLink(); $invite_mail_ready = userInviteMailReady(); $filter_url = function ($changes) { $params = array_merge($_GET, $changes); unset($params['page']); return '?' . http_build_query(array_filter($params, function ($v) { return $v !== '' && $v !== null; })); }; $sort_link = function ($column, $label) use ($url_query_strings_sort, $sort, $disp, $order_icon) { return '' . $label . ($sort === $column ? " $order_icon" : '') . ''; }; $csrf = $_SESSION['csrf_token']; ?>
| = $sort_link('user_name', 'Invited') ?> = $sort_link('user_email', 'by email') ?> | = $sort_link('role_name', 'Role') ?> | = $sort_link('user_status', 'Invite') ?> | Clients | = $sort_link('user_created_at', 'Invited by') ?> | |
|---|---|---|---|---|---|
|
= $invite_name ?>
= escapeHtml($invite['invite_email']) ?>
= $invite_role ?>
= $invite_pill ?>
|
= $invite_role ?> |
= $invite_pill ?>
MFA required
= escapeHtml($invite_expired ? 'Ran out ' . date('M j', strtotime($invite['invite_expires_at'])) : $invite_label['title']) ?>
|
= escapeHtml(userClientAccessText($invite_access, $invite_admin)) ?> |
= escapeHtml(userInviteBy($invite) !== '' ? userInviteBy($invite) : 'Someone no longer here') ?> · = escapeHtml(timeAgo($invite['invite_created_at'])) ?>
Emailed = escapeHtml(timeAgo($invite['invite_sent_at'])) ?>= $invite_sent > 1 ? " · $invite_sent times" : '' ?>
A link to pass on
|
|