forked from github/Quit.mwForum
I've kind of overslept the change in the numbering system introduced with 2.27.1. Odd numbers no longer indicate development builds, therefore the "devel" branch has been removed.
733 lines
27 KiB
Perl
733 lines
27 KiB
Perl
#!/usr/bin/perl
|
|
#------------------------------------------------------------------------------
|
|
# mwForum - Web-based discussion forum
|
|
# Copyright (c) 1999-2012 Markus Wichitill
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#------------------------------------------------------------------------------
|
|
|
|
use strict;
|
|
use warnings;
|
|
no warnings qw(uninitialized redefine);
|
|
|
|
# Imports
|
|
use MwfMain;
|
|
use List::Util qw(first);
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# Init
|
|
my ($m, $cfg, $lng, $user, $userId) = MwfMain->new(@_);
|
|
|
|
# Check if user is admin
|
|
$user->{admin} or $m->error('errNoAccess');
|
|
|
|
# Print header
|
|
$m->printHeader();
|
|
|
|
# Get CGI parameters
|
|
my $page = $m->paramInt('pg') || 1;
|
|
my $search = $m->paramStr('search') || "";
|
|
my $field = $m->paramStrId('field') || $m->getVar('usrAdmFld', $userId) || 'realName';
|
|
my $sort = $m->paramStrId('sort') || $m->getVar('usrAdmSrt', $userId) || 'userName';
|
|
my $order = $m->paramStrId('order') || $m->getVar('usrAdmOrd', $userId) || 'desc';
|
|
my $hideEmpty = $m->paramDefined('search')
|
|
? $m->paramBool('hide') : $m->getVar('usrAdmHid', $userId) || 0;
|
|
|
|
# Enforce valid parameters
|
|
$sort = 'userName' if $sort !~ /^(?:userName|id|lastOnTime|field)\z/;
|
|
$order = 'desc' if $order !~ /^(?:asc|desc)\z/;
|
|
|
|
# What to search through when searching for "identity"
|
|
my @identity = qw(userName realName oldNames email homepage icq lastIp comment);
|
|
push @identity, grep($cfg->{$_}, qw(extra1 extra2 extra3));
|
|
|
|
# Shared formatting functions
|
|
my $defTime = sub { $m->formatTime($_[0]{$field}, $m->{user}{timezone}) };
|
|
my $defUrl = sub { my $v = $_[0]{$field};
|
|
$v =~ /^https?:\/\/[^\s\\\[\]{}<>)|^`'"]+\z/ ? "<a href='$v'>$v</a>" : $v;
|
|
};
|
|
|
|
# Install case-i LIKE function for SQLite
|
|
$m->{dbh}->func('LIKE', 2, sub { my $a = shift(); my $b = shift();
|
|
utf8::decode($a); utf8::decode($b); index(lc($a), lc($b)) > -1 }, 'create_function')
|
|
if $m->{sqlite} && $cfg->{sqliteLike} && length($search);
|
|
|
|
# Parse badges
|
|
my %badges = ();
|
|
if ($field eq '_badges') {
|
|
for my $line (@{$cfg->{badges}}) {
|
|
my ($id, $smallIcon, $bigIcon) = $line =~ /(\w+)\s+\w+\s+(\S+)\s+(\S+)/;
|
|
$badges{$id} = $smallIcon ne '-' ? $smallIcon : $bigIcon;
|
|
}
|
|
}
|
|
|
|
# Define views
|
|
my $like = $m->{pgsql} ? 'ILIKE' : 'LIKE';
|
|
my $percent = $m->{sqlite} && $cfg->{sqliteLike} ? "" : "%";
|
|
my $defFields = join(", ", 'users.id', 'users.userName',
|
|
$sort eq 'lastOnTime' ? 'users.lastOnTime' : ());
|
|
my $defFrom = "FROM users AS users WHERE users.id IN (:pageUserIds)";
|
|
my $defJoin = $hideEmpty ? 'INNER' : 'LEFT';
|
|
my $defGroup = $defFields;
|
|
my $defSort = $sort ne 'field' ? "users.$sort" : 'users.userName';
|
|
my $searchEsc = $m->escHtml($search);
|
|
my $searchLike = $m->dbEscLike($searchEsc);
|
|
my $views = [
|
|
{ name => " Identity",
|
|
field => '_identity',
|
|
notes => "no view-specific sorting; searching by username, real name, old usernames, email, "
|
|
. "website, messenger, last IP, comments, and custom profile fields 1-3 when used; "
|
|
. "no empty entry hiding",
|
|
searchParam => $percent . $searchLike . $percent,
|
|
searchUsers => sub {
|
|
my $where = !length($search) ? "" :
|
|
"WHERE ". join(" $like :search OR ", @identity) . " $like :search";
|
|
$m->fetchAllArray("
|
|
SELECT id FROM users AS users $where ORDER BY $defSort $order", $_[0])
|
|
},
|
|
fetchUsers => sub { $m->fetchAllHash("
|
|
SELECT $defFields, realName, email $defFrom ORDER BY $defSort $order", $_[0]) },
|
|
columns => [
|
|
{ name => "Email", value => sub { "<a href='mailto:$_[0]{email}'>$_[0]{email}</a>" } },
|
|
{ name => "Real Name", value => sub { $_[0]{realName} } },
|
|
{ name => "Old Names", value => sub { $_[0]{oldNames} } },
|
|
],
|
|
},
|
|
{ name => "Bans",
|
|
field => '_bans',
|
|
notes => "view-specific sorting by ban time; searching by both reasons; only banned users are shown",
|
|
searchParam => $percent . $searchLike . $percent,
|
|
searchUsers => sub {
|
|
my $where = length($search) ? "WHERE reason $like :search OR intReason $like :search" : "";
|
|
my $fldSort = $sort eq 'field' ? 'bans.banTime' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id
|
|
FROM users AS users
|
|
INNER JOIN userBans AS bans
|
|
ON bans.userId = users.id
|
|
$where
|
|
ORDER BY $fldSort $order, users.userName", $_[0])
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'bans.banTime' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT $defFields, bans.banTime, bans.duration, bans.reason, bans.intReason
|
|
FROM users AS users
|
|
INNER JOIN userBans AS bans
|
|
ON bans.userId = users.id
|
|
WHERE users.id IN (:pageUserIds)
|
|
ORDER BY $fldSort $order, users.userName", $_[0])
|
|
},
|
|
columns => [
|
|
{ name => "Ban Time", value => sub {
|
|
my $v = $_[0]{banTime}; $v ? $m->formatTime($v, $m->{user}{timezone}) : "" } },
|
|
{ name => "Dur.", value => sub {
|
|
my $v = $_[0]{duration}; return "" if !defined($v); return "∞" if $v == 0; $v } },
|
|
{ name => "Reason", value => sub { $_[0]{reason} } },
|
|
{ name => "Internal", value => sub { $_[0]{intReason} } },
|
|
],
|
|
},
|
|
{ name => "Badges",
|
|
field => '_badges',
|
|
notes => "no view-specific sorting; searching by identifier prefix",
|
|
searchParam => $searchLike . $percent,
|
|
searchUsers => sub {
|
|
my $where = length($search) ? "WHERE userBadges.badge LIKE :search" : "";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, GROUP_CONCAT(userBadges.badge) AS badges
|
|
FROM users AS users
|
|
$defJoin JOIN userBadges AS userBadges ON userBadges.userId = users.id
|
|
$where
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
fetchUsers => sub {
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName, GROUP_CONCAT(userBadges.badge) AS badges
|
|
FROM users AS users
|
|
$defJoin JOIN userBadges AS userBadges ON userBadges.userId = users.id
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "Badges",
|
|
value => sub {
|
|
join(" ", map("<img class='uba' src='$cfg->{dataPath}/$badges{$_}' alt='$_'>",
|
|
sort(split(',', $_[0]{badges}))));
|
|
}
|
|
},
|
|
],
|
|
},
|
|
{ name => "Notifications",
|
|
field => '_notes',
|
|
notes => "no view-specific sorting; no searching",
|
|
searchUsers => sub {
|
|
$m->fetchAllArray("
|
|
SELECT users.id
|
|
FROM users AS users
|
|
$defJoin JOIN notes AS notes ON notes.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order");
|
|
},
|
|
fetchUsers => sub {
|
|
$m->dbDo("SET group_concat_max_len = 32768") if $m->{mysql};
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName, GROUP_CONCAT('<div>' || notes.body || '</div>') AS notes
|
|
FROM users AS users
|
|
$defJoin JOIN notes AS notes ON notes.userId = users.id
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "Notifications",
|
|
value => sub { my $v = $_[0]{notes}; $v =~ s!</div>,<div>!</div>\n<div>!g; $v } },
|
|
],
|
|
},
|
|
{ name => "Style Snippets",
|
|
field => '_snippets',
|
|
notes => "no view-specific sorting; searching by identifier prefix",
|
|
searchParam => $searchLike . $percent,
|
|
searchUsers => sub {
|
|
my $where = length($search) ? "WHERE vars.name LIKE :search" : "";
|
|
$m->fetchAllArray("
|
|
SELECT users.id
|
|
FROM users AS users
|
|
$defJoin JOIN userVariables AS vars
|
|
ON vars.userId = users.id AND vars.name LIKE 'sty%'
|
|
$where
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
fetchUsers => sub {
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName, GROUP_CONCAT(vars.name) AS snippets
|
|
FROM users AS users
|
|
$defJoin JOIN userVariables AS vars
|
|
ON vars.userId = users.id AND vars.name LIKE 'sty%'
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
columns => [ { name => "Style Snippets", value => sub { $_[0]{snippets} } } ],
|
|
},
|
|
{ name => "Groups",
|
|
field => '_groups',
|
|
notes => "no view-specific sorting; searching by group title",
|
|
searchParam => $searchLike,
|
|
searchUsers => sub {
|
|
my $where = length($search) ? "WHERE groups.title = :search" : "";
|
|
$m->fetchAllArray("
|
|
SELECT users.id
|
|
FROM users AS users
|
|
$defJoin JOIN groupMembers AS groupMembers ON groupMembers.userId = users.id
|
|
$defJoin JOIN groups AS groups ON groups.id = groupMembers.groupId
|
|
$where
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
fetchUsers => sub {
|
|
$m->dbDo("SET group_concat_max_len = 32768") if $m->{mysql};
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName, GROUP_CONCAT('#' || groups.title || '#') AS groups
|
|
FROM users AS users
|
|
$defJoin JOIN groupMembers AS groupMembers ON groupMembers.userId = users.id
|
|
$defJoin JOIN groups AS groups ON groups.id = groupMembers.groupId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $defSort $order", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "Groups",
|
|
value => sub { my $v = $_[0]{groups}; $v =~ s!#,#!, !g; $v =~ s!#!!g; $v } },
|
|
],
|
|
},
|
|
{ name => "Ignored By",
|
|
field => '_ignored',
|
|
notes => "view-specific sorting by number of ignoring users; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'ignoreNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(ignores.userId) AS ignoreNum
|
|
FROM users AS users
|
|
$defJoin JOIN userIgnores AS ignores ON ignores.ignoredId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'ignoreNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(ignores.userId) AS ignoreNum, GROUP_CONCAT(ignorers.userName) AS ignorerNames
|
|
FROM users AS users
|
|
$defJoin JOIN userIgnores AS ignores ON ignores.ignoredId = users.id
|
|
$defJoin JOIN users AS ignorers ON ignorers.id = ignores.userId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{ignoreNum} } },
|
|
{ name => "Ignored By", value => sub { my $v = $_[0]{ignorerNames}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Ignored Users",
|
|
field => '_ignoring',
|
|
notes => "view-specific sorting by number of ignored users; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'ignoreNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(ignores.userId) AS ignoreNum
|
|
FROM users AS users
|
|
$defJoin JOIN userIgnores AS ignores ON ignores.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'ignoreNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(ignores.userId) AS ignoreNum, GROUP_CONCAT(ignoreds.userName) AS ignoredNames
|
|
FROM users AS users
|
|
$defJoin JOIN userIgnores AS ignores ON ignores.userId = users.id
|
|
$defJoin JOIN users AS ignoreds ON ignoreds.id = ignores.ignoredId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{ignoreNum} } },
|
|
{ name => "Ignored Users", value => sub { my $v = $_[0]{ignoredNames}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Watched By",
|
|
field => '_watched',
|
|
notes => "view-specific sorting by number of watching users; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(watches.userId) AS watchNum
|
|
FROM users AS users
|
|
$defJoin JOIN watchUsers AS watches ON watches.watchedId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(watches.userId) AS watchNum, GROUP_CONCAT(watchers.userName) AS watcherNames
|
|
FROM users AS users
|
|
$defJoin JOIN watchUsers AS watches ON watches.watchedId = users.id
|
|
$defJoin JOIN users AS watchers ON watchers.id = watches.userId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{watchNum} } },
|
|
{ name => "Watched By", value => sub { my $v = $_[0]{watcherNames}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Watched Users",
|
|
field => '_watching',
|
|
notes => "view-specific sorting by number of watched users; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(watches.userId) AS watchNum
|
|
FROM users AS users
|
|
$defJoin JOIN watchUsers AS watches ON watches.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(watches.userId) AS watchNum, GROUP_CONCAT(watched.userName) AS watchedNames
|
|
FROM users AS users
|
|
$defJoin JOIN watchUsers AS watches ON watches.userId = users.id
|
|
$defJoin JOIN users AS watched ON watched.id = watches.watchedId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{watchNum} } },
|
|
{ name => "Watched Users", value => sub { my $v = $_[0]{watchedNames}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Watched Words",
|
|
field => '_words',
|
|
notes => "view-specific sorting by number of watched words; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(watches.word) AS watchNum
|
|
FROM users AS users
|
|
$defJoin JOIN watchWords AS watches ON watches.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'watchNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(watches.word) AS watchNum, GROUP_CONCAT(watches.word) AS words
|
|
FROM users AS users
|
|
$defJoin JOIN watchWords AS watches ON watches.userId = users.id
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{watchNum} } },
|
|
{ name => "Words", value => sub { my $v = $_[0]{words}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Board Subscriptions",
|
|
field => '_boardsubs',
|
|
notes => "view-specific sorting by number of subscribed boards; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'subsNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(subs.userId) AS subsNum
|
|
FROM users AS users
|
|
$defJoin JOIN boardSubscriptions AS subs ON subs.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
$m->dbDo("SET group_concat_max_len = 32768") if $m->{mysql};
|
|
my $fldSort = $sort eq 'field' ? 'subsNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(subs.userId) AS subsNum, GROUP_CONCAT(REPLACE(boards.title, ',', '')) AS title
|
|
FROM users AS users
|
|
$defJoin JOIN boardSubscriptions AS subs ON subs.userId = users.id
|
|
$defJoin JOIN boards AS boards ON boards.id = subs.boardId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{subsNum} } },
|
|
{ name => "Board Subscriptions", value => sub { my $v = $_[0]{title}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Topic Subscriptions",
|
|
field => '_topicsubs',
|
|
notes => "view-specific sorting by number of subscribed topics; no searching",
|
|
searchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'subsNum' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT users.id, COUNT(subs.userId) AS subsNum
|
|
FROM users AS users
|
|
$defJoin JOIN topicSubscriptions AS subs ON subs.userId = users.id
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName");
|
|
},
|
|
fetchUsers => sub {
|
|
$m->dbDo("SET group_concat_max_len = 32768") if $m->{mysql};
|
|
my $fldSort = $sort eq 'field' ? 'subsNum' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT users.id, users.userName,
|
|
COUNT(subs.userId) AS subsNum, GROUP_CONCAT(REPLACE(topics.subject, ',', '')) AS subject
|
|
FROM users AS users
|
|
$defJoin JOIN topicSubscriptions AS subs ON subs.userId = users.id
|
|
$defJoin JOIN topics AS topics ON topics.id = subs.topicId
|
|
WHERE users.id IN (:pageUserIds)
|
|
GROUP BY $defGroup
|
|
ORDER BY $fldSort $order, users.userName", $_[0]);
|
|
},
|
|
columns => [
|
|
{ name => "#", value => sub { $_[0]{subsNum} } },
|
|
{ name => "Topic Subscriptions", value => sub { my $v = $_[0]{subject}; $v =~ s!,!, !g; $v } }
|
|
],
|
|
},
|
|
{ name => "Birthdate",
|
|
field => '_birthdate',
|
|
notes => "no searching",
|
|
searchUsers => sub {
|
|
my $where = $hideEmpty ? "WHERE birthday <> ''" : "";
|
|
$m->fetchAllArray("
|
|
SELECT id FROM users AS users $where ORDER BY birthyear $order, birthday $order", $_[0])
|
|
},
|
|
fetchUsers => sub { $m->fetchAllHash("
|
|
SELECT $defFields, birthyear, birthday $defFrom
|
|
ORDER BY birthyear $order, birthday $order", $_[0]) },
|
|
columns => [
|
|
{ name => "Birthdate",
|
|
value => sub { ($_[0]->{birthyear} ? $_[0]->{birthyear} : "0000") . "-$_[0]->{birthday}" } },
|
|
],
|
|
},
|
|
{ name => "Active Time",
|
|
field => '_active',
|
|
notes => "no searching",
|
|
searchUsers => sub {
|
|
my $where = $hideEmpty ? "WHERE lastOnTime - regTime > 0" : "";
|
|
my $fldSort = $sort eq 'field' ? 'activeSeconds' : "users.$sort";
|
|
$m->fetchAllArray("
|
|
SELECT id, lastOnTime - regTime AS activeSeconds
|
|
FROM users AS users
|
|
$where
|
|
ORDER BY $fldSort $order", $_[0])
|
|
},
|
|
fetchUsers => sub {
|
|
my $fldSort = $sort eq 'field' ? 'activeSeconds' : "users.$sort";
|
|
$m->fetchAllHash("
|
|
SELECT $defFields, lastOnTime - regTime AS activeSeconds,
|
|
ROUND((lastOnTime - regTime) / 86400) AS activeDays
|
|
$defFrom
|
|
ORDER BY $fldSort $order", $_[0])
|
|
},
|
|
columns => [
|
|
{ name => "Active Time (days)", value => sub { $_[0]->{activeDays} } },
|
|
{ name => "Active Time (seconds)", value => sub { $_[0]->{activeSeconds} } },
|
|
],
|
|
},
|
|
{ name => "Avatar",
|
|
field => 'avatar',
|
|
columns => [
|
|
{
|
|
name => "Avatar",
|
|
value => sub {
|
|
my $v = $_[0]{avatar};
|
|
return if !$v;
|
|
index($v, "gravatar:") == 0
|
|
? "<img src='//gravatar.com/avatar/" . $m->md5(substr($v, 9)) .
|
|
"?s=$m->{cfg}{avatarWidth}' alt=''>"
|
|
: "<img src='$m->{cfg}{attachUrlPath}/avatars/$v' alt=''>";
|
|
},
|
|
},
|
|
{
|
|
name => "Type",
|
|
value => sub {
|
|
my $v = $_[0]{avatar};
|
|
return "Gravatar" if index($v, "gravatar:") == 0;
|
|
return "Gallery" if index($v, "gallery/") == 0;
|
|
return "Upload" if $v;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{ name => "Email",
|
|
field => 'email',
|
|
columns => [
|
|
{ name => "Email", value => sub { "<a href='mailto:$_[0]{email}'>$_[0]{email}</a>" } },
|
|
],
|
|
},
|
|
{ name => "Registration Time",
|
|
field => 'regTime',
|
|
type => 'int',
|
|
columns => [ { name => "Registration Time", value => $defTime } ],
|
|
},
|
|
{ name => "Last Online Time",
|
|
field => 'lastOnTime',
|
|
type => 'int',
|
|
columns => [ { name => "Last Online Time", value => $defTime } ],
|
|
},
|
|
{ name => "Prev. Online Time",
|
|
field => 'prevOnTime',
|
|
type => 'int',
|
|
columns => [ { name => "Prev. Online Time", value => $defTime } ],
|
|
},
|
|
{ name => "Website",
|
|
field => 'homepage',
|
|
columns => [ { name => "Website", value => $defUrl } ],
|
|
},
|
|
{ name => "OpenID",
|
|
field => 'openId',
|
|
columns => [ { name => "OpenID", value => $defUrl } ],
|
|
},
|
|
{ name => "Username", field => 'userName', columns => [] },
|
|
{ name => "Administrator", field => 'admin', type => 'int' },
|
|
{ name => "Birthyear", field => 'birthyear', type => 'int' },
|
|
{ name => "Disable Email", field => 'dontEmail', type => 'int' },
|
|
{ name => "Email Notifications", field => 'msgNotify', type => 'int' },
|
|
{ name => "Reply Notifications", field => 'notify', type => 'int' },
|
|
{ name => "Privacy", field => 'privacy', type => 'int' },
|
|
{ name => "Temporary Login", field => 'tempLogin', type => 'int' },
|
|
{ name => "Show Board Desc.", field => 'boardDescs', type => 'int' },
|
|
{ name => "Show Decoration", field => 'showDeco', type => 'int' },
|
|
{ name => "Show Avatars", field => 'showAvatars', type => 'int' },
|
|
{ name => "Show Embed. Images", field => 'showImages', type => 'int' },
|
|
{ name => "Show Signatures", field => 'showSigs', type => 'int' },
|
|
{ name => "Collapse Branches", field => 'collapse', type => 'int' },
|
|
{ name => "Font Size", field => 'fontSize', type => 'int' },
|
|
{ name => "Threading Indent", field => 'indent', type => 'int' },
|
|
{ name => "Posts Per Page", field => 'postsPP', type => 'int' },
|
|
{ name => "Topics Per Page", field => 'topicsPP', type => 'int' },
|
|
{ name => "Post Number", field => 'postNum', type => 'int' },
|
|
{ name => "Bounce Counter", field => 'bounceNum', type => 'int' },
|
|
{ name => "Renames Left", field => 'renamesLeft', type => 'int' },
|
|
{ name => "Old Usernames", field => 'oldNames' },
|
|
{ name => "Real Name", field => 'realName' },
|
|
{ name => "Title", field => 'title' },
|
|
{ name => "Occupation", field => 'occupation' },
|
|
{ name => "Hobbies", field => 'hobbies' },
|
|
{ name => "Location", field => 'location' },
|
|
{ name => "Messenger", field => 'icq' },
|
|
{ name => "Signature", field => 'signature' },
|
|
{ name => "Miscellaneous", field => 'blurb' },
|
|
{ name => "Custom 1", field => 'extra1' },
|
|
{ name => "Custom 2", field => 'extra2' },
|
|
{ name => "Custom 3", field => 'extra3' },
|
|
{ name => "Birthday", field => 'birthday' },
|
|
{ name => "Timezone", field => 'timezone' },
|
|
{ name => "Language", field => 'language' },
|
|
{ name => "Style", field => 'style' },
|
|
{ name => "Font Face", field => 'fontFace' },
|
|
{ name => "User Agent", field => 'userAgent' },
|
|
{ name => "IP Address", field => 'lastIp' },
|
|
{ name => "PGP Key ID", field => 'gpgKeyId' },
|
|
{ name => "Comments", field => 'comment' },
|
|
];
|
|
|
|
my $view = (first{ $_->{field} eq $field } @$views) || $views->[1];
|
|
$field = $view->{field};
|
|
|
|
# Save options
|
|
$m->setVar('usrAdmFld', $field, $userId);
|
|
$m->setVar('usrAdmSrt', $sort, $userId);
|
|
$m->setVar('usrAdmOrd', $order, $userId);
|
|
$m->setVar('usrAdmHid', $hideEmpty, $userId);
|
|
|
|
# Searching
|
|
my $searchParam = "";
|
|
my $searchStr = "";
|
|
if (length($search)) {
|
|
if ($view->{searchUsers}) {
|
|
$searchParam = $view->{searchParam};
|
|
}
|
|
elsif ($view->{type} eq 'int') {
|
|
$searchParam = int($search || 0);
|
|
$searchStr = "AND $field = :search";
|
|
}
|
|
else {
|
|
$searchParam = $percent . $searchLike . $percent;
|
|
$searchStr = "AND $field $like :search";
|
|
}
|
|
}
|
|
|
|
# Hiding empty fields
|
|
my $hideEmptyStr = "";
|
|
if ($hideEmpty && !$view->{searchUsers}) {
|
|
if ($view->{type} eq 'int') { $hideEmptyStr = "AND $field > 0" }
|
|
else { $hideEmptyStr = "AND $field <> ''" }
|
|
}
|
|
|
|
# Ordering
|
|
my $orderStr = "";
|
|
if ($sort eq 'id') { $orderStr = "users.id $order" }
|
|
elsif ($sort eq 'userName') { $orderStr = "users.userName $order" }
|
|
elsif ($sort eq 'lastOnTime') { $orderStr = "users.lastOnTime $order" }
|
|
elsif ($sort eq 'field') { $orderStr = "users.$field $order, users.userName" }
|
|
|
|
# Get ids of users
|
|
my $users = $view->{searchUsers} ? $view->{searchUsers}({ search => $searchParam }) :
|
|
$m->fetchAllArray("
|
|
SELECT id
|
|
FROM users AS users
|
|
WHERE 1 = 1
|
|
$searchStr
|
|
$hideEmptyStr
|
|
ORDER BY $orderStr",
|
|
{ search => $searchParam });
|
|
|
|
# Print page bar
|
|
my $usersPP = $cfg->{usersPP} || 100;
|
|
my $pageNum = int(@$users / $usersPP) + (@$users % $usersPP != 0);
|
|
my @pageLinks = $pageNum < 2 ? () : $m->pageLinks('user_admin', [], $page, $pageNum);
|
|
my @navLinks = ({ url => $m->url('forum_show'), txt => 'comUp', ico => 'up' });
|
|
my @adminLinks = ({ url => $m->url('user_set'), txt => "Set", ico => 'edit' });
|
|
$m->printPageBar(mainTitle => "User Administration", navLinks => \@navLinks,
|
|
pageLinks => \@pageLinks, adminLinks => \@adminLinks);
|
|
|
|
# Get users on page
|
|
my @pageUsers = @$users[($page - 1) * $usersPP .. $m->min($page * $usersPP, scalar @$users) - 1];
|
|
my @pageUserIds = map($_->[0], @pageUsers);
|
|
$users = $view->{fetchUsers} ? $view->{fetchUsers}({ pageUserIds => \@pageUserIds }) :
|
|
$m->fetchAllHash("
|
|
SELECT $defFields, $field
|
|
FROM users AS users
|
|
WHERE id IN (:pageUserIds)
|
|
ORDER BY $orderStr",
|
|
{ pageUserIds => \@pageUserIds });
|
|
|
|
# Determine checkbox, radiobutton and listbox states
|
|
my $hideEmptyChk = $hideEmpty ? 'checked' : "",
|
|
my %state = ( $sort => 'selected', $order => 'selected', "field$field" => 'selected' );
|
|
|
|
# Print user list form
|
|
print
|
|
"<form action='user_admin$m->{ext}' method='get'>\n",
|
|
"<div class='frm'>\n",
|
|
"<div class='hcl'><span class='htt'>List Users</span></div>\n",
|
|
"<div class='ccl'>\n",
|
|
"<div class='cli'>\n",
|
|
"<label>View\n",
|
|
"<select name='field' size='1'>\n",
|
|
map("<option value='$_->{field}' $state{\"field$_->{field}\"}>$_->{name}</option>\n",
|
|
sort({ $a->{name} cmp $b->{name} } @$views)),
|
|
"</select></label>\n",
|
|
"<label>Sort\n",
|
|
"<select name='sort' size='1'>\n",
|
|
"<option value='userName' $state{userName}>Username</option>\n",
|
|
"<option value='id' $state{id}>User ID</option>\n",
|
|
"<option value='lastOnTime' $state{lastOnTime}>Last Online</option>\n",
|
|
"<option value='field' $state{field}>View</option>\n",
|
|
"</select></label>\n",
|
|
"<label>Order\n",
|
|
"<select name='order' size='1'>\n",
|
|
"<option value='asc' $state{asc}>Asc</option>\n",
|
|
"<option value='desc' $state{desc}>Desc</option>\n",
|
|
"</select></label>\n",
|
|
"<label>Search\n",
|
|
"<input type='text' name='search' style='width: 100px' value='$searchEsc' autofocus></label>\n",
|
|
"<label><input type='checkbox' name='hide' value='1' $hideEmptyChk>Hide empty</label>\n",
|
|
$m->submitButton("List", 'search'),
|
|
"</div>\n",
|
|
$view->{notes} ? "<div>View notes: $view->{notes}</div>" : "",
|
|
"</div>\n",
|
|
"</div>\n",
|
|
"</form>\n\n";
|
|
|
|
# Print user list header
|
|
my $defColumns = [ { name => $view->{name}, value => sub { $_[0]{$field} } } ];
|
|
my $columns = $view->{columns} ? $view->{columns} : $defColumns;
|
|
print
|
|
"<table class='tbl'>\n",
|
|
"<tr class='hrw'>\n",
|
|
"<th>Username</th>\n",
|
|
map("<th>$_->{name}</th>\n", @$columns),
|
|
"</tr>\n";
|
|
|
|
# Print user list
|
|
for my $listUser (@$users) {
|
|
my $infUrl = $m->url('user_info', uid => $listUser->{id});
|
|
print
|
|
"<tr class='crw'>\n",
|
|
"<td><a href='$infUrl'>$listUser->{userName}</a></td>\n",
|
|
map("<td>" . $_->{value}($listUser) . "</td>\n", @$columns),
|
|
"</tr>\n";
|
|
}
|
|
|
|
print "</table>\n\n";
|
|
|
|
# Log action and finish
|
|
$m->logAction(3, 'user', 'admin', $userId);
|
|
$m->printFooter();
|
|
$m->finish();
|