package VNDB::Handler::Users;
use strict;
use warnings;
use YAWF ':html';
use Digest::MD5 'md5_hex';
use VNDB::Func;
YAWF::register(
qr{u([1-9]\d*)} => \&userpage,
qr{u/login} => \&login,
qr{u/logout} => \&logout,
qr{u/newpass} => \&newpass,
qr{u/newpass/sent} => \&newpass_sent,
qr{u/register} => \®ister,
qr{u([1-9]\d*)/edit} => \&edit,
qr{u([1-9]\d*)/del(/[od])?} => \&delete,
qr{u/(all|[0a-z])} => \&list,
);
sub userpage {
my($self, $uid) = @_;
my $u = $self->dbUserGet(uid => $uid, what => 'stats')->[0];
return 404 if !$u->{id};
my $votes = $u->{c_votes} && $self->dbVoteStats(uid => $uid);
$self->htmlHeader(title => ucfirst($u->{username})."'s profile");
$self->htmlMainTabs('u', $u);
div class => 'mainbox userpage';
h1 ucfirst($u->{username})."'s profile";
table;
Tr;
td class => 'key', ' ';
td ' ';
end;
my $i = 0;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Username';
td;
txt ucfirst($u->{username}).' (';
a href => "/u$uid", "u$uid";
txt ')';
end;
end;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Registered';
td date $u->{registered};
end;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Edits';
td;
if($u->{c_changes}) {
a href => "/u$uid/hist", $u->{c_changes};
} else {
txt '-';
}
end;
end;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Votes';
td;
if(!$u->{show_list}) {
txt 'hidden';
} elsif($votes) {
my($total, $count) = (0, 0);
for (1..@$votes) {
$total += $_*$votes->[$_-1];
$count += $votes->[$_-1];
}
a href => "/u$uid/list?v=1", $count;
txt sprintf ' (%.2f average)', $total/$count;
} else {
txt '-';
}
end;
end;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'List stats';
td !$u->{show_list} ? 'hidden' :
sprintf '%d releases of %d visual novels', $u->{releasecount}, $u->{vncount};
end;
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Forum stats';
td sprintf '%d posts, %d new threads', $u->{postcount}, $u->{threadcount};
end;
end;
end;
if($u->{show_list} && $votes) {
div class => 'mainbox';
h1 'Vote statistics';
$self->htmlVoteStats(u => $u, $votes);
end;
}
if($u->{c_changes}) {
my $list = $self->dbRevisionGet(what => 'item user', uid => $uid, results => 5);
h1 class => 'boxtitle';
a href => "/u$uid/hist", 'Recent changes';
end;
$self->htmlHistory($list, { p => 1 }, 0, "/u$uid/hist");
}
$self->htmlFooter;
}
sub login {
my $self = shift;
return $self->resRedirect('/') if $self->authInfo->{id};
my $frm;
if($self->reqMethod eq 'POST') {
$frm = $self->formValidate(
{ name => 'usrname', required => 1, minlength => 2, maxlength => 15, template => 'pname' },
{ name => 'usrpass', required => 1, minlength => 4, maxlength => 64, template => 'asciiprint' },
);
(my $ref = $self->reqHeader('Referer')||'/') =~ s/^\Q$self->{url}//;
$ref = '/' if $ref =~ /^\/u\//;
return if !$frm->{_err} && $self->authLogin($frm->{usrname}, $frm->{usrpass}, $ref);
$frm->{_err} = [ 'login_failed' ] if !$frm->{_err};
}
$self->htmlHeader(title => 'Login', noindex => 1);
$self->htmlForm({ frm => $frm, action => '/u/login' }, Login => [
[ input => name => 'Username', short => 'usrname' ],
[ static => content => 'No account yet?' ],
[ passwd => name => 'Password', short => 'usrpass' ],
[ static => content => 'Forgot your password?' ],
]);
$self->htmlFooter;
}
sub logout {
shift->authLogout;
}
sub newpass {
my $self = shift;
return $self->resRedirect('/') if $self->authInfo->{id};
my($frm, $u);
if($self->reqMethod eq 'POST') {
$frm = $self->formValidate(
{ name => 'mail', required => 1, template => 'mail' },
);
if(!$frm->{_err}) {
$u = $self->dbUserGet(mail => $frm->{mail})->[0];
$frm->{_err} = [ 'nomail' ] if !$u || !$u->{id};
}
if(!$frm->{_err}) {
my @chars = ( 'A'..'Z', 'a'..'z', 0..9 );
my $pass = join '', map $chars[int rand $#chars+1], 0..8;
$self->dbUserEdit($u->{id}, passwd => md5_hex($pass));
my $body = <<'__';
Hello %s,
Your password has been reset, you can now login at http://vndb.org/ with the
following information:
Username: %1$s
Password: %s
Now don't forget your password again! :-)
vndb.org
__
$self->mail(
sprintf($body, $u->{username}, $pass),
To => $u->{mail},
From => 'VNDB ',
Subject => 'New password for '.$u->{username}
);
return $self->resRedirect('/u/newpass/sent', 'post');
}
}
$self->htmlHeader(title => 'Forgot Password', noindex => 1);
div class => 'mainbox';
h1 'Forgot Password';
p "Forgot your password and can't login to VNDB anymore?\n"
."Don't worry! Just give us the email address you used to register on VNDB,\n"
."and we'll send you a new password within a few minutes!";
end;
$self->htmlForm({ frm => $frm, action => '/u/newpass' }, 'Reset Password' => [
[ input => name => 'Email', short => 'mail' ],
]);
$self->htmlFooter;
}
sub newpass_sent {
my $self = shift;
return $self->resRedirect('/') if $self->authInfo->{id};
$self->htmlHeader(title => 'New Password', noindex => 1);
div class => 'mainbox';
h1 'New Password';
div class => 'notice';
h2 'Password Reset';
p;
txt "Your password has been reset and your new password should reach your mailbox in a few minutes.\n"
."You can always change your password again after logging in.\n\n";
lit 'Login - Home';
end;
end;
end;
$self->htmlFooter;
}
sub register {
my $self = shift;
return $self->resRedirect('/') if $self->authInfo->{id};
my $frm;
if($self->reqMethod eq 'POST') {
$frm = $self->formValidate(
{ name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
{ name => 'mail', template => 'mail' },
{ name => 'usrpass', minlength => 4, maxlength => 64, template => 'asciiprint' },
{ name => 'usrpass2', minlength => 4, maxlength => 64, template => 'asciiprint' },
);
push @{$frm->{_err}}, 'passmatch' if $frm->{usrpass} ne $frm->{usrpass2};
push @{$frm->{_err}}, 'usrexists' if $frm->{usrname} eq 'anonymous' || !$frm->{_err} && $self->dbUserGet(username => $frm->{usrname})->[0]{id};
push @{$frm->{_err}}, 'mailexists' if !$frm->{_err} && $self->dbUserGet(mail => $frm->{mail})->[0]{id};
if(!$frm->{_err}) {
$self->dbUserAdd($frm->{usrname}, md5_hex($frm->{usrpass}), $frm->{mail});
return $self->authLogin($frm->{usrname}, $frm->{usrpass}, '/');
}
}
$self->htmlHeader(title => 'Create an Account', noindex => 1);
div class => 'mainbox';
h1 'Create an Account';
h2 'Why should I register?';
p 'Creating an account is completely painless, the only thing we need to know is your prefered username '
.'and a password. You can just use any email address that isn\'t yours, as we don\'t even confirm '
.'that the address you gave us is really yours. Keep in mind, however, that you would probably '
.'want to remember your password if you do choose to give us an invalid email address...';
p 'Anyway, having an account here has a few advantages over being just a regular visitor:';
ul;
li 'You can contribute to the database by editing any entries and adding new ones';
li 'Keep track of all visual novels and releases you have, you\'d like to play, are playing, or have finished playing';
li 'Vote on the visual novels you liked or disliked';
li 'Contribute to the discussions on the boards';
li 'And boast about the fact that you have an account on the best visual novel database in the world!';
end;
end;
$self->htmlForm({ frm => $frm, action => '/u/register' }, 'New Account' => [
[ input => short => 'usrname', name => 'Username' ],
[ static => content => 'Requested username. Must be lowercase and can only consist of alphanumeric characters.' ],
[ input => short => 'mail', name => 'Email' ],
[ static => content => 'Your email address will only be used in case you lose your password. We will never send'
.' spam or newsletters unless you explicitly ask us for it.
' ],
[ passwd => short => 'usrpass', name => 'Password' ],
[ passwd => short => 'usrpass2', name => 'Confirm pass.' ],
]);
$self->htmlFooter;
}
sub edit {
my($self, $uid) = @_;
# are we allowed to edit this user?
return $self->htmlDenied if !$self->authInfo->{id} || $self->authInfo->{id} != $uid && !$self->authCan('usermod');
# fetch user info (cached if uid == loggedin uid)
my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid)->[0];
return 404 if !$u->{id};
# check POST data
my $frm;
if($self->reqMethod eq 'POST') {
$frm = $self->formValidate(
$self->authCan('usermod') ? (
{ name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
{ name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] },
) : (),
{ name => 'mail', template => 'mail' },
{ name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
{ name => 'usrpass2', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
{ name => 'skin', enum => [ '', keys %{$self->{skins}} ], required => 0, default => '' },
{ name => 'flags_list', required => 0, default => 0 },
{ name => 'flags_nsfw', required => 0, default => 0 },
);
push @{$frm->{_err}}, 'passmatch' if ($frm->{usrpass} || $frm->{usrpass2}) && $frm->{usrpass} ne $frm->{usrpass2};
if(!$frm->{_err}) {
my %o;
$o{username} = $frm->{usrname} if $frm->{usrname};
$o{rank} = $frm->{rank} if $frm->{rank};
$o{mail} = $frm->{mail};
$o{skin} = $frm->{skin};
$o{passwd} = md5_hex($frm->{usrpass}) if $frm->{usrpass};
$o{show_list} = $frm->{flags_list} ? 1 : 0;
$o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0;
$self->dbUserEdit($uid, %o);
return $self->resRedirect("/u$uid/edit?d=1", 'post') if $uid != $self->authInfo->{id} || !$frm->{usrpass};
return $self->authLogin($frm->{usrname}||$u->{username}, $frm->{usrpass}, "/u$uid/edit?d=1");
}
}
# fill out default values
$frm->{usrname} ||= $u->{username};
$frm->{rank} ||= $u->{rank};
$frm->{mail} ||= $u->{mail};
$frm->{skin} ||= $u->{skin};
$frm->{flags_list} = $u->{show_list} if !defined $frm->{flags_list};
$frm->{flags_nsfw} = $u->{show_nsfw} if !defined $frm->{flags_nsfw};
# create the page
my $title = $self->authInfo->{id} != $uid ? "Edit $u->{username}'s Account" : 'My Account';
$self->htmlHeader(title => $title, noindex => 1);
$self->htmlMainTabs('u', $u, 'edit');
if($self->reqParam('d')) {
div class => 'mainbox';
h1 'Settings saved';
div class => 'notice';
p 'Settings successfully saved.';
end;
end
}
$self->htmlForm({ frm => $frm, action => "/u$uid/edit" }, $title => [
[ part => title => 'General Info' ],
$self->authCan('usermod') ? (
[ input => short => 'usrname', name => 'Username' ],
[ select => short => 'rank', name => 'Rank', options => [
map [ $_, $self->{user_ranks}[$_][0] ], 1..$#{$self->{user_ranks}} ] ],
) : (
[ static => label => 'Username', content => $frm->{usrname} ],
),
[ input => short => 'mail', name => 'Email' ],
[ part => title => 'Change Password' ],
[ static => content => 'Leave blank to keep your current password' ],
[ passwd => short => 'usrpass', name => 'Password' ],
[ passwd => short => 'usrpass2', name => 'Confirm pass.' ],
[ part => title => 'Options' ],
[ select => short => 'skin', name => 'Prefered skin', options => [
map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_} ], sort { $self->{skins}{$a} cmp $self->{skins}{$b} } keys %{$self->{skins}} ] ],
[ check => short => 'flags_list', name =>
qq|Allow other people to see my visual novel list (/u$uid/list) |.
qq|and wishlist (/u$uid/wish)| ],
[ check => short => 'flags_nsfw', name => 'Disable warnings for images that are not safe for work.' ],
]);
$self->htmlFooter;
}
sub delete {
my($self, $uid, $act) = @_;
return $self->htmlDenied if !$self->authCan('usermod');
# confirm
if(!$act) {
my $u = $self->dbUserGet(uid => $uid)->[0];
return 404 if !$u->{id};
$self->htmlHeader(title => 'Delete user', noindex => 1);
$self->htmlMainTabs('u', $u, 'del');
div class => 'mainbox';
div class => 'warning';
h2 'Delete user';
p;
lit qq|Are you sure you want to remove $u->{username}'s account?
|
.qq|Yes, I'm not kidding!|;
end;
end;
end;
$self->htmlFooter;
}
# delete
elsif($act eq '/o') {
$self->dbUserDel($uid);
$self->resRedirect("/u$uid/del/d", 'post');
}
# done
elsif($act eq '/d') {
$self->htmlHeader(title => 'Delete user', noindex => 1);
div class => 'mainbox';
div class => 'notice';
p 'User deleted.';
end;
end;
$self->htmlFooter;
}
}
sub list {
my($self, $char) = @_;
my $f = $self->formValidate(
{ name => 's', required => 0, default => 'username', enum => [ qw|username registered votes changes| ] },
{ name => 'o', required => 0, default => 'a', enum => [ 'a','d' ] },
{ name => 'p', required => 0, default => 1, template => 'int' },
);
return 404 if $f->{_err};
$self->htmlHeader(title => 'Browse users');
div class => 'mainbox';
h1 'Browse users';
p class => 'browseopts';
for ('all', 'a'..'z', 0) {
a href => "/u/$_", $_ eq $char ? (class => 'optselected') : (), $_ ? uc $_ : '#';
}
end;
end;
my($list, $np) = $self->dbUserGet(
order => ($f->{s} eq 'changes' ? 'c_' : $f->{s} eq 'votes' ? 'NOT show_list, c_' : '').$f->{s}.($f->{o} eq 'a' ? ' ASC' : ' DESC'),
$char ne 'all' ? (
firstchar => $char ) : (),
results => 50,
page => $f->{p},
);
$self->htmlBrowse(
items => $list,
options => $f,
nextpage => $np,
pageurl => "/u/$char?o=$f->{o};s=$f->{s}",
sorturl => "/u/$char",
header => [
[ 'Username', 'username' ],
[ 'Registered', 'registered' ],
[ 'Votes', 'votes' ],
[ 'Edits', 'changes' ],
],
row => sub {
my($s, $n, $l) = @_;
Tr $n % 2 ? (class => 'odd') : ();
td class => 'tc1';
a href => '/u'.$l->{id}, $l->{username};
end;
td class => 'tc2', date $l->{registered};
td class => 'tc3';
lit !$l->{show_list} ? '-' : !$l->{c_votes} ? 0 :
qq|$l->{c_votes}|;
end;
td class => 'tc4';
lit !$l->{c_changes} ? 0 : qq|$l->{c_changes}|;
end;
end;
},
);
$self->htmlFooter;
}
1;