From 2d7e55f103d34960b133e8fe3dcbbc95cc4f40e0 Mon Sep 17 00:00:00 2001 From: flashwave Date: Mon, 28 May 2018 01:24:16 +0200 Subject: [PATCH] member listing --- assets/less/mio/classes/flag.less | 288 ++++++++++++++++++ assets/less/mio/classes/input/select.less | 15 + assets/less/mio/classes/members/user.less | 93 ++++++ assets/less/mio/classes/members/users.less | 8 + assets/less/mio/main.less | 6 + .../2018_05_16_155840_initial_structure.php | 2 +- misuzu.php | 2 + public/manage/users.php | 1 + public/members.php | 132 ++++++++ public/profile.php | 2 +- views/mio/master.twig | 1 + views/mio/settings/login-history.twig | 6 +- views/mio/settings/sessions.twig | 6 +- views/mio/user/listing.twig | 126 ++++++++ views/mio/user/view.twig | 6 +- 15 files changed, 685 insertions(+), 9 deletions(-) create mode 100644 assets/less/mio/classes/flag.less create mode 100644 assets/less/mio/classes/input/select.less create mode 100644 assets/less/mio/classes/members/user.less create mode 100644 assets/less/mio/classes/members/users.less create mode 100644 public/members.php create mode 100644 views/mio/user/listing.twig diff --git a/assets/less/mio/classes/flag.less b/assets/less/mio/classes/flag.less new file mode 100644 index 00000000..be414f80 --- /dev/null +++ b/assets/less/mio/classes/flag.less @@ -0,0 +1,288 @@ +@flag-width: 16px; +@flag-height: 11px; + +.flag-position(@x, @y, @offsetX: 0px, @offsetY: 0px) { + background-position: top -(@flag-height * @x) + @offsetX left -(@flag-width * @y) + @offsetY; +} + +.flag { + display: inline-block; + width: @flag-width; + height: @flag-height; + background-image: url('https://static.flash.moe/images/flag-sprite-24.png'); + background-repeat: no-repeat; + font-size: 0; + .flag-position(23, 23); // xx + + &__container { + min-width: @flag-width; + min-height: @flag-height; + display: block; + } + + &--ad {.flag-position(0, 3);} + &--ae {.flag-position(0, 4);} + &--af {.flag-position(0, 5);} + &--ag {.flag-position(0, 6);} + &--ai {.flag-position(0, 8);} + &--al {.flag-position(0, 11);} + &--am {.flag-position(0, 12);} + &--an {.flag-position(0, 13);} + &--ao {.flag-position(0, 14);} + &--ar {.flag-position(0, 17);} + &--as {.flag-position(0, 18);} + &--at {.flag-position(0, 19);} + &--au {.flag-position(0, 20);} + &--aw {.flag-position(0, 22);} + &--ax {.flag-position(0, 23);} + &--az {.flag-position(0, 25);} + + &--ba {.flag-position(1, 0);} + &--bb {.flag-position(1, 1);} + &--bd {.flag-position(1, 3);} + &--be {.flag-position(1, 4);} + &--bf {.flag-position(1, 5);} + &--bg {.flag-position(1, 6);} + &--bh {.flag-position(1, 7);} + &--bi {.flag-position(1, 8);} + &--bj {.flag-position(1, 9);} + &--bm {.flag-position(1, 12);} + &--bn {.flag-position(1, 13);} + &--bo {.flag-position(1, 14);} + &--br {.flag-position(1, 17);} + &--bs {.flag-position(1, 18);} + &--bt {.flag-position(1, 19);} + &--bv {.flag-position(1, 21);} + &--bw {.flag-position(1, 22);} + &--by {.flag-position(1, 24);} + &--bz {.flag-position(1, 25);} + + &--ca {.flag-position(2, 0);} + &--cc {.flag-position(2, 2);} + &--cd {.flag-position(2, 3);} + &--cf {.flag-position(2, 5);} + &--cg {.flag-position(2, 6);} + &--ch {.flag-position(2, 7); width: 11px;} + &--ci {.flag-position(2, 8);} + &--ck {.flag-position(2, 10);} + &--cl {.flag-position(2, 11);} + &--cm {.flag-position(2, 12);} + &--cn {.flag-position(2, 13);} + &--co {.flag-position(2, 14);} + &--cr {.flag-position(2, 17);} + &--cs {.flag-position(2, 18);} + &--cu {.flag-position(2, 20);} + &--cv {.flag-position(2, 21);} + &--cx {.flag-position(2, 23);} + &--cy {.flag-position(2, 24);} + &--cz {.flag-position(2, 25);} + + &--de {.flag-position(3, 4);} + &--dj {.flag-position(3, 9);} + &--dk {.flag-position(3, 10);} + &--dm {.flag-position(3, 12);} + &--do {.flag-position(3, 14);} + &--dz {.flag-position(3, 25);} + + &--ec {.flag-position(4, 2);} + &--ee {.flag-position(4, 4);} + &--eg {.flag-position(4, 6);} + &--eh {.flag-position(4, 7);} + &--er {.flag-position(4, 17);} + &--es {.flag-position(4, 18);} + &--et {.flag-position(4, 19);} + + &--fi {.flag-position(5, 8);} + &--fj {.flag-position(5, 9);} + &--fk {.flag-position(5, 10);} + &--fm {.flag-position(5, 12);} + &--fo {.flag-position(5, 14);} + &--fr {.flag-position(5, 17);} + + &--ga {.flag-position(6, 0);} + &--gb {.flag-position(6, 1);} + &--gd {.flag-position(6, 3);} + &--ge {.flag-position(6, 4);} + &--gf {.flag-position(6, 5);} + &--gh {.flag-position(6, 7);} + &--gi {.flag-position(6, 8);} + &--gl {.flag-position(6, 11);} + &--gm {.flag-position(6, 12);} + &--gn {.flag-position(6, 13);} + &--gp {.flag-position(6, 15);} + &--gq {.flag-position(6, 16);} + &--gr {.flag-position(6, 17);} + &--gs {.flag-position(6, 18);} + &--gt {.flag-position(6, 19);} + &--gu {.flag-position(6, 20);} + &--gw {.flag-position(6, 22);} + &--gy {.flag-position(6, 24);} + + &--hd {.flag-position(7, 3);} + &--he {.flag-position(7, 4);} + &--hk {.flag-position(7, 10);} + &--hm {.flag-position(7, 12);} + &--hn {.flag-position(7, 13);} + &--hr {.flag-position(7, 17);} + &--ht {.flag-position(7, 19);} + &--hu {.flag-position(7, 20);} + + &--il {.flag-position(8, 11);} + &--in {.flag-position(8, 13);} + &--io {.flag-position(8, 14);} + &--iq {.flag-position(8, 16);} + &--ir {.flag-position(8, 17);} + &--is {.flag-position(8, 18);} + &--it {.flag-position(8, 19);} + + &--jm {.flag-position(9, 12);} + &--jo {.flag-position(9, 14);} + &--jp {.flag-position(9, 15);} + + &--ke {.flag-position(10, 4);} + &--kg {.flag-position(10, 6);} + &--kh {.flag-position(10, 7);} + &--ki {.flag-position(10, 8);} + &--km {.flag-position(10, 12);} + &--kn {.flag-position(10, 13);} + &--kp {.flag-position(10, 15);} + &--kr {.flag-position(10, 17);} + &--kw {.flag-position(10, 22);} + &--ky {.flag-position(10, 24);} + &--kz {.flag-position(10, 25);} + + &--la {.flag-position(11, 0);} + &--lb {.flag-position(11, 1);} + &--lc {.flag-position(11, 2);} + &--li {.flag-position(11, 8);} + &--lk {.flag-position(11, 10);} + &--lr {.flag-position(11, 17);} + &--ls {.flag-position(11, 18);} + &--lt {.flag-position(11, 19);} + &--lu {.flag-position(11, 20);} + &--lv {.flag-position(11, 21);} + &--ly {.flag-position(11, 24);} + + &--ma {.flag-position(12, 0);} + &--mc {.flag-position(12, 2);} + &--md {.flag-position(12, 3);} + &--me {.flag-position(12, 4, 1); height: 12px;} + &--mg {.flag-position(12, 6);} + &--mh {.flag-position(12, 7);} + &--mk {.flag-position(12, 10);} + &--ml {.flag-position(12, 11);} + &--mm {.flag-position(12, 12);} + &--mn {.flag-position(12, 13);} + &--mo {.flag-position(12, 14);} + &--mp {.flag-position(12, 15);} + &--mq {.flag-position(12, 16);} + &--mr {.flag-position(12, 17);} + &--ms {.flag-position(12, 18);} + &--mt {.flag-position(12, 19);} + &--mu {.flag-position(12, 20);} + &--mv {.flag-position(12, 21);} + &--mw {.flag-position(12, 22);} + &--mx {.flag-position(12, 23);} + &--my {.flag-position(12, 24);} + &--mz {.flag-position(12, 25);} + + &--na {.flag-position(13, 0);} + &--nc {.flag-position(13, 2);} + &--ne {.flag-position(13, 4);} + &--nf {.flag-position(13, 5);} + &--ng {.flag-position(13, 6);} + &--ni {.flag-position(13, 8);} + &--nl {.flag-position(13, 11);} + &--no {.flag-position(13, 14);} + &--np {.flag-position(13, 15); width: 9px;} + &--nr {.flag-position(13, 17);} + &--nu {.flag-position(13, 20);} + &--nz {.flag-position(13, 25);} + + &--ok {.flag-position(14, 10);} + + &--pa {.flag-position(15, 0);} + &--pe {.flag-position(15, 4);} + &--pf {.flag-position(15, 5);} + &--pg {.flag-position(15, 6);} + &--ph {.flag-position(15, 7);} + &--pk {.flag-position(15, 10);} + &--pl {.flag-position(15, 11);} + &--pm {.flag-position(15, 12);} + &--pn {.flag-position(15, 13);} + &--pr {.flag-position(15, 17);} + &--ps {.flag-position(15, 18);} + &--pt {.flag-position(15, 19);} + &--pw {.flag-position(15, 22);} + &--py {.flag-position(15, 24);} + + &--qa {.flag-position(16, 0);} + + &--re {.flag-position(17, 4);} + &--ro {.flag-position(17, 14);} + &--rs {.flag-position(17, 18);} + &--ru {.flag-position(17, 20);} + &--rw {.flag-position(17, 22);} + + &--sa {.flag-position(18, 0);} + &--sb {.flag-position(18, 1);} + &--sc {.flag-position(18, 2);} + &--sd {.flag-position(18, 3);} + &--se {.flag-position(18, 4);} + &--sg {.flag-position(18, 6);} + &--sh {.flag-position(18, 7);} + &--si {.flag-position(18, 8);} + &--sj {.flag-position(18, 9);} + &--sk {.flag-position(18, 10);} + &--sl {.flag-position(18, 11);} + &--sm {.flag-position(18, 12);} + &--sn {.flag-position(18, 13);} + &--so {.flag-position(18, 14);} + &--sr {.flag-position(18, 17);} + &--st {.flag-position(18, 19);} + &--sv {.flag-position(18, 21);} + &--sy {.flag-position(18, 24);} + &--sz {.flag-position(18, 25);} + + &--tc {.flag-position(19, 2);} + &--td {.flag-position(19, 3);} + &--tf {.flag-position(19, 5);} + &--tg {.flag-position(19, 6);} + &--th {.flag-position(19, 7);} + &--tj {.flag-position(19, 9);} + &--tk {.flag-position(19, 10);} + &--tl {.flag-position(19, 11);} + &--tm {.flag-position(19, 12);} + &--tn {.flag-position(19, 13);} + &--to {.flag-position(19, 14);} + &--tr {.flag-position(19, 17);} + &--tt {.flag-position(19, 19);} + &--tv {.flag-position(19, 21);} + &--tw {.flag-position(19, 22);} + &--tz {.flag-position(19, 25);} + + &--ua {.flag-position(20, 0);} + &--ug {.flag-position(20, 6);} + &--um {.flag-position(20, 12);} + &--us {.flag-position(20, 18);} + &--uy {.flag-position(20, 24);} + &--uz {.flag-position(20, 25);} + + &--va {.flag-position(21, 0);} + &--vc {.flag-position(21, 2);} + &--ve {.flag-position(21, 4);} + &--vg {.flag-position(21, 6);} + &--vi {.flag-position(21, 8);} + &--vn {.flag-position(21, 13);} + &--vu {.flag-position(21, 20);} + + &--wf {.flag-position(22, 5);} + &--ws {.flag-position(22, 18);} + + &--ye {.flag-position(24, 4);} + &--yt {.flag-position(24, 19);} + + &--za {.flag-position(25, 0);} + &--zm {.flag-position(25, 12);} + &--zw {.flag-position(25, 22);} +} diff --git a/assets/less/mio/classes/input/select.less b/assets/less/mio/classes/input/select.less new file mode 100644 index 00000000..761d05cd --- /dev/null +++ b/assets/less/mio/classes/input/select.less @@ -0,0 +1,15 @@ +.input__select { + border: 1px solid #aaa; + padding: 1px; + background: #fff; + color: #111; + + &:focus { + border-color: #9475b2; + } +} + +.mio--dark .input__select { + background: #111; + color: #fff; +} diff --git a/assets/less/mio/classes/members/user.less b/assets/less/mio/classes/members/user.less new file mode 100644 index 00000000..2835d984 --- /dev/null +++ b/assets/less/mio/classes/members/user.less @@ -0,0 +1,93 @@ +.members__user { + display: flex; + text-decoration: none; + color: inherit; + + margin: 1px; + padding: 5px; + background-color: #fbeeff; + border: 1px solid #9475b2; + box-shadow: 0 1px 2px #9475b2; + width: 396px; + transition: background-color .2s, box-shadow .2s; + z-index: 1; + + &:hover { + box-shadow: 0 1px 1em #9475b2; + background-color: #ecddee; + z-index: 2; + } + + &__avatar { + width: 100px; + height: 100px; + flex: 0 0 auto; + box-shadow: 0 1px 2px #9475b2; + margin-right: 5px; + } + + &__info { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1 1 auto; + } + + &__name { + font-size: 1.5em; + line-height: 1.3em; + } + + &__sub { + font-size: .9em; + display: flex; + align-items: baseline; + vertical-align: middle; + } + + &__title { + flex: 1 1 auto; + margin-left: 2px; + } + + &__country { + flex: 0 0 auto; + display: flex; + align-items: baseline; + vertical-align: middle; + + &__name { + margin-right: 2px; + } + + &__flag { + margin-right: 4px; + } + } + + &__supporter { + background-image: url('https://static.flash.moe/images/tenshi.png'); + width: 37px; + height: 11px; + font-size: 0; + margin-right: 2px; + } + + &__stats { + display: flex; + justify-content: space-evenly; + font-size: .9em; + line-height: 1.5em; + } + + &__stat { + width: 50%; + margin-right: 2px; + display: inline-flex; + + &__title { + font-weight: 700; + flex-grow: 1; + } + } +} diff --git a/assets/less/mio/classes/members/users.less b/assets/less/mio/classes/members/users.less new file mode 100644 index 00000000..d1e62af0 --- /dev/null +++ b/assets/less/mio/classes/members/users.less @@ -0,0 +1,8 @@ +.members__users { + + &__content { + margin: 1px; + display: flex; + flex-wrap: wrap; + } +} diff --git a/assets/less/mio/main.less b/assets/less/mio/main.less index a318b583..54c11b21 100644 --- a/assets/less/mio/main.less +++ b/assets/less/mio/main.less @@ -37,6 +37,7 @@ body { // Input elements @import "classes/input/button"; +@import "classes/input/select"; @import "classes/input/text"; @import "classes/input/textarea"; @import "classes/input/upload"; @@ -44,6 +45,7 @@ body { // Base styles @import "classes/avatar"; @import "classes/container"; +@import "classes/flag"; @import "classes/heading"; @import "classes/navigation"; @import "classes/pagination"; @@ -78,3 +80,7 @@ body { @import "classes/forum/posting"; @import "classes/forum/topic"; @import "classes/forum/topics"; + +// Member listing +@import "classes/members/user"; +@import "classes/members/users"; diff --git a/database/2018_05_16_155840_initial_structure.php b/database/2018_05_16_155840_initial_structure.php index a89b6586..37711cbd 100644 --- a/database/2018_05_16_155840_initial_structure.php +++ b/database/2018_05_16_155840_initial_structure.php @@ -44,7 +44,7 @@ function migrate_up(PDO $conn): void `user_twitchtv` VARCHAR(30) NOT NULL DEFAULT '', `user_osu` VARCHAR(20) NOT NULL DEFAULT '', `user_lastfm` VARCHAR(20) NOT NULL DEFAULT '', - `user_title` VARCHAR(64) NOT NULL DEFAULT '', + `user_title` VARCHAR(64) NULL DEFAULT NULL, `last_seen` TIMESTAMP NULL DEFAULT NULL, PRIMARY KEY (`user_id`), UNIQUE INDEX `users_username_unique` (`username`), diff --git a/misuzu.php b/misuzu.php index cc094b7a..43920ba5 100644 --- a/misuzu.php +++ b/misuzu.php @@ -1,6 +1,8 @@ bindValue('user_id', $userId); $getUser->execute(); diff --git a/public/members.php b/public/members.php new file mode 100644 index 00000000..a9d23425 --- /dev/null +++ b/public/members.php @@ -0,0 +1,132 @@ + 'Ascending', + 'DESC' => 'Descending', +]; + +$orderFields = [ + 'id' => [ + 'column' => 'user_id', + 'default-dir' => 'ASC', + 'title' => 'User ID', + ], + 'name' => [ + 'column' => 'username', + 'default-dir' => 'ASC', + 'title' => 'Username', + ], + 'country' => [ + 'column' => 'user_country', + 'default-dir' => 'ASC', + 'title' => 'Country', + ], + 'registered' => [ + 'column' => 'created_at', + 'default-dir' => 'DESC', + 'title' => 'Registration Date', + ], + 'last-online' => [ + 'column' => 'last_seen', + 'default-dir' => 'DESC', + 'title' => 'Last Online', + ], +]; + +if (empty($orderBy)) { + $orderBy = 'last-online'; +} elseif (!array_key_exists($orderBy, $orderFields)) { + echo render_error(400); + return; +} + +if (empty($orderDir)) { + $orderDir = $orderFields[$orderBy]['default-dir']; +} elseif (!array_key_exists($orderDir, $orderDirs)) { + echo render_error(400); + return; +} + +$db = Database::connection(); +$tpl = $app->getTemplating(); + +$getRole = $db->prepare(' + SELECT + `role_id`, `role_name`, `role_colour`, `role_description`, `created_at`, + ( + SELECT COUNT(`user_id`) + FROM `msz_user_roles` + WHERE `role_id` = r.`role_id` + ) as `role_user_count` + FROM `msz_roles` as r + WHERE `role_id` = :role_id +'); +$getRole->bindValue('role_id', $roleId); +$role = $getRole->execute() ? $getRole->fetch() : []; + +if (!$role) { + echo render_error(404); + return; +} + +$getRoles = $db->prepare(' + SELECT `role_id`, `role_name`, `role_colour` + FROM `msz_roles` + WHERE `role_secret` = 0 + ORDER BY `role_id` +'); +$roles = $getRoles->execute() ? $getRoles->fetchAll() : []; + +$getUsers = $db->prepare(" + SELECT + u.`user_id`, u.`username`, u.`user_country`, + u.`created_at` as `user_joined`, u.`last_seen` as `user_last_seen`, + COALESCE(u.`user_title`, r.`role_title`) as `user_title`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`, + ( + SELECT COUNT(`topic_id`) + FROM `msz_forum_topics` + WHERE `user_id` = u.`user_id` + AND `topic_deleted` IS NULL + ) as `user_topic_count`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `user_id` = u.`user_id` + AND `post_deleted` IS NULL + ) as `user_post_count` + FROM `msz_users` as u + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + LEFT JOIN `msz_user_roles` as ur + ON ur.`user_id` = u.`user_id` + WHERE ur.`role_id` = :role_id + ORDER BY u.`{$orderFields[$orderBy]['column']}` {$orderDir} + LIMIT :offset, :take +"); +$getUsers->bindValue('role_id', $role['role_id']); +$getUsers->bindValue('offset', $usersOffset); +$getUsers->bindValue('take', $usersTake); +$users = $getUsers->execute() ? $getUsers->fetchAll() : []; + +echo $tpl->render('user.listing', [ + 'roles' => $roles, + 'role' => $role, + 'users' => $users, + 'order_fields' => $orderFields, + 'order_directions' => $orderDirs, + 'order_field' => $orderBy, + 'order_direction' => $orderDir, + 'users_offset' => $usersOffset, + 'users_take' => $usersTake, +]); diff --git a/public/profile.php b/public/profile.php index 22370c2a..39967fb2 100644 --- a/public/profile.php +++ b/public/profile.php @@ -45,7 +45,7 @@ switch ($mode) { $getProfile = Database::connection()->prepare(' SELECT u.*, - r.`role_title` as `user_title`, + COALESCE(u.`user_title`, r.`role_title`) as `user_title`, COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`, ( SELECT COUNT(`topic_id`) diff --git a/views/mio/master.twig b/views/mio/master.twig index 05f7a188..c488ad00 100644 --- a/views/mio/master.twig +++ b/views/mio/master.twig @@ -5,6 +5,7 @@ 'News': '/news.php', 'Forum': '/forum/', 'Chat': 'https://chat.flashii.net', + 'Members': '/members.php', } %} diff --git a/views/mio/settings/login-history.twig b/views/mio/settings/login-history.twig index ceeecf3b..44fc1dea 100644 --- a/views/mio/settings/login-history.twig +++ b/views/mio/settings/login-history.twig @@ -1,12 +1,16 @@ {% extends '@mio/settings/master.twig' %} {% from '@mio/macros.twig' import pagination %} +{% set lhpagination = pagination(login_attempts_count, login_attempts_take, login_attempts_offset, '?m=login-history', 'settings__') %} + {% block settings_content %}

These are all the login attempts to your account. If any attempt that you don't recognise is marked as successful your account may be compromised, ask a staff member for advice in this case.

+ {{ lhpagination }} + {% for attempt in user_login_attempts %} {% endblock %} diff --git a/views/mio/settings/sessions.twig b/views/mio/settings/sessions.twig index 40d57129..8cf0f0b3 100644 --- a/views/mio/settings/sessions.twig +++ b/views/mio/settings/sessions.twig @@ -1,12 +1,16 @@ {% extends '@mio/settings/master.twig' %} {% from '@mio/macros.twig' import pagination %} +{% set spagination = pagination(sessions_count, sessions_take, sessions_offset, '?m=sessions', 'settings__') %} + {% block settings_content %}

These are the active logins to your account, clicking the Kill button will force a logout on that session. Your current login is highlighted with a darker purple so you don't accidentally force yourself to logout.

+ {{ spagination }} + {% for session in user_sessions %}
@@ -60,6 +64,6 @@
{% endfor %} - {{ pagination(sessions_count, sessions_take, sessions_offset, '?m=sessions', 'settings__') }} + {{ spagination }}
{% endblock %} diff --git a/views/mio/user/listing.twig b/views/mio/user/listing.twig new file mode 100644 index 00000000..04f50a4f --- /dev/null +++ b/views/mio/user/listing.twig @@ -0,0 +1,126 @@ +{% extends '@mio/user/master.twig' %} +{% from '@mio/macros.twig' import navigation, pagination %} + +{% set canonical_url = '/members.php' ~ (role.role_id != 1 ? '?r=' ~ role.role_id : '') %} +{% set full_url = '/members.php?r=' ~ role.role_id ~ '&ss=' ~ order_field ~ '&sd=' ~ order_direction %} +{% set title = role.role_id == 1 ? 'Members' : 'Role ยป ' ~ role.role_name %} +{% set manage_link = '/manage/users.php?v=listing' %} + +{% block content %} +
+ + + + + + + +
+ + + + {{ pagination(role.role_user_count, users_take, users_offset, full_url) }} + {{ navigation(mio_navigation, '/members.php') }} +{% endblock %} diff --git a/views/mio/user/view.twig b/views/mio/user/view.twig index 93018490..588e76a1 100644 --- a/views/mio/user/view.twig +++ b/views/mio/user/view.twig @@ -106,11 +106,7 @@ Last Seen
{% endif %}