diff options
Diffstat (limited to 'lib/VNDB/Handler/Discussions.pm')
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm new file mode 100644 index 00000000..286e3ee5 --- /dev/null +++ b/lib/VNDB/Handler/Discussions.pm @@ -0,0 +1,399 @@ + +package VNDB::Handler::Discussions; + +use strict; +use warnings; +use YAWF ':html', 'xml_escape'; +use POSIX 'ceil'; +use VNDB::Func; + + +YAWF::register( + qr{t([1-9]\d*)(?:/([1-9]\d*))?} => \&thread, + qr{t([1-9]\d*)\.([1-9]\d*)} => \&redirect, + qr{t/(db|an|[vpu])([1-9]\d*)?} => \&tagbrowse, + qr{t([1-9]\d*)/reply} => \&edit, + qr{t([1-9]\d*)\.([1-9]\d*)/edit} => \&edit, + qr{t/(db|an|[vpu])([1-9]\d*)?/new} => \&edit, + qr{t} => \&index, +); + + +sub thread { + my($self, $tid, $page) = @_; + $page ||= 1; + + my $t = $self->dbThreadGet(id => $tid, what => 'tagtitles')->[0]; + return 404 if !$t->{id} || $t->{hidden} && !$self->authCan('boardmod'); + + my $p = $self->dbPostGet(tid => $tid, results => 25, page => $page); + return 404 if !$p->[0]; + + $self->htmlHeader(title => $t->{title}); + + div class => 'mainbox'; + h1 $t->{title}; + h2 'Posted in'; + ul; + for (sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$t->{tags}}) { + li; + a href => "/t/$_->{type}", $self->{discussion_tags}{$_->{type}}; + if($_->{iid}) { + txt ' > '; + a style => 'font-weight: bold', href => "/t/$_->{type}$_->{iid}", "$_->{type}$_->{iid}"; + txt ':'; + a href => "/$_->{type}$_->{iid}", title => $_->{original}, $_->{title}; + } + end; + } + end; + end; + + $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 't', 1); + div class => 'mainbox thread'; + table; + for my $i (0..$#$p) { + local $_ = $p->[$i]; + my $class = $i % 2 == 0 ? 'odd ' : ''; + $class .= 'deleted' if $_->{hidden}; + Tr class => $class; + td class => 'tc1'; + a href => "/t$tid.$_->{num}", name => $_->{num}, "#$_->{num}"; + if(!$_->{hidden}) { + txt ' by '; + lit userstr $_; + br; + lit date $_->{date}, 'full'; + } + end; + td class => 'tc2'; + if($self->authCan('boardmod') || $self->authInfo->{id} && $_->{uid} == $self->authInfo->{id} && !$_->{hidden}) { + i class => 'edit'; + txt '< '; + a href => "/t$tid.$_->{num}/edit", 'edit'; + txt ' >'; + end; + } + if($_->{hidden}) { + i class => 'deleted', 'Post deleted.'; + } else { + lit bb2html $_->{msg}; + i class => 'lastmod', 'Last modified on '.date($_->{edited}, 'full') if $_->{edited}; + } + end; + end; + } + end; + end; + $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 'b', 1); + + if($t->{locked}) { + div class => 'mainbox'; + h1 'Reply'; + p class => 'center', 'This thread has been locked, you can\'t reply to it anymore.'; + end; + } elsif($t->{count} < $page*25 && $self->authCan('board')) { + form action => "/t$tid/reply", method => 'post', 'accept-charset' => 'UTF-8'; + div class => 'mainbox'; + fieldset class => 'submit'; + h2 'Quick reply'; + textarea name => 'msg', id => 'msg', rows => 4, cols => 50, ''; + br; + input type => 'submit', value => 'Reply', class => 'submit'; + end; + end; + end; + } elsif(!$self->authCan('board')) { + div class => 'mainbox'; + h1 'Reply'; + p class => 'center', 'You must be logged in to reply to this thread.'; + end; + } + + $self->htmlFooter; +} + + +sub redirect { + my($self, $tid, $num) = @_; + $self->resRedirect("/t$tid".($num > 25 ? '/'.ceil($num/25) : '').'#'.$num, 'perm'); +} + + +# Arguments, action +# tid reply +# tid, 1 edit thread +# tid, num edit post +# type, (iid) start new thread +sub edit { + my($self, $tid, $num) = @_; + $num ||= 0; + + # in case we start a new thread, parse tag + my $tag = ''; + if($tid !~ /^\d+$/) { + return 404 if $tid =~ /(db|an)/ && $num || $tid =~ /[vpu]/ && !$num; + $tag = $tid.($num||''); + $tid = 0; + $num = 0; + } + + # get thread and post, if any + my $t = $tid && $self->dbThreadGet(id => $tid, what => 'tags')->[0]; + return 404 if $tid && !$t->{id}; + + my $p = $num && $self->dbPostGet(tid => $tid, num => $num)->[0]; + return 404 if $num && !$p->{num}; + + # are we allowed to perform this action? + return $self->htmlDenied if !$self->authCan('board') + || ($tid && ($t->{locked} || $t->{hidden}) && !$self->authCan('boardmod')) + || ($num && $p->{uid} != $self->authInfo->{id} && !$self->authCan('boardmod')); + + # check form etc... + my $frm; + if($self->reqMethod eq 'POST') { + $frm = $self->formValidate( + !$tid || $num == 1 ? ( + { name => 'title', maxlength => 50 }, + { name => 'tags', maxlength => 50 }, + ) : (), + $self->authCan('boardmod') ? ( + { name => 'locked', required => 0 }, + { name => 'hidden', required => 0 }, + { name => 'nolastmod', required => 0 }, + ) : (), + { name => 'msg', maxlenght => 5000 }, + ); + + # parse and validate the tags + my @tags; + if(!$frm->{_err} && $frm->{tags}) { + for (split /[ ,]/, $frm->{tags}) { + my($ty, $id) = ($1, $2) if /^([a-z]{1,2})([0-9]*)$/; + push @tags, [ $ty, $id ]; + push @{$frm->{_err}}, [ 'tags', 'wrongtag', $_ ] if + !$ty || !$self->{discussion_tags}{$ty} + || $ty eq 'an' && ($id || !$self->authCan('boardmod')) + || $ty eq 'db' && $id + || $ty eq 'v' && (!$id || !$self->dbVNGet(id => $id)->[0]{id}) + || $ty eq 'p' && (!$id || !$self->dbProducerGet(id => $id)->[0]{id}) + || $ty eq 'u' && (!$id || !$self->dbUserGet(uid => $id)->[0]{id}); + } + } + + if(!$frm->{_err}) { + my($ntid, $nnum) = ($tid, $num); + + # create/edit thread + if(!$tid || $num == 1) { + my %thread = ( + title => $frm->{title}, + tags => \@tags, + hidden => $frm->{hidden}, + locked => $frm->{locked}, + ); + $self->dbThreadEdit($tid, %thread) if $tid; + $ntid = $self->dbThreadAdd(%thread) if !$tid; + } + + # create/edit post + my %post = ( + msg => $frm->{msg}, + hidden => $num != 1 && $frm->{hidden}, + lastmod => !$num || $frm->{nolastmod} ? 0 : time, + ); + $self->dbPostEdit($tid, $num, %post) if $num; + $nnum = $self->dbPostAdd($ntid, %post) if !$num; + + $self->multiCmd("ircnotify t$ntid.$nnum") if !$num && !$frm->{hidden}; + + return $self->resRedirect("/t$ntid".($nnum > 25 ? '/'.ceil($nnum/25) : '').'#'.$nnum, 'post'); + } + } + + # fill out form if we have some data + if($p) { + $frm->{msg} ||= $p->{msg}; + $frm->{hidden} = $p->{hidden} if $num != 1 && !exists $frm->{hidden}; + if($num == 1) { + $frm->{tags} ||= join ' ', sort map $_->[1]?$_->[0].$_->[1]:$_->[0], @{$t->{tags}}; + $frm->{title} ||= $t->{title}; + $frm->{locked} = $t->{locked} if !exists $frm->{locked}; + $frm->{hidden} = $t->{hidden} if !exists $frm->{hidden}; + } + } + $frm->{tags} ||= $tag; + $frm->{nolastmod} = 1 if $num && $self->authCan('boardmod') && !exists $frm->{nolastmod}; + + # generate html + my $title = !$tid ? 'Start new thread' : + !$num ? 'Reply to '.$t->{title} : + 'Edit post'; + my $url = !$tid ? "/t/$tag/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; + $self->htmlHeader(title => $title, noindex => 1); + $self->htmlForm({ frm => $frm, action => $url }, $title => [ + [ static => label => 'Username', content => userstr($self->authInfo->{id}, $self->authInfo->{username}) ], + !$tid || $num == 1 ? ( + [ input => short => 'title', name => 'Thread title' ], + [ input => short => 'tags', name => 'Tags' ], + [ static => content => 'Read <a href="/d9.2">d9.2</a> for information about how to use tags' ], + $self->authCan('boardmod') ? ( + [ check => name => 'Locked', short => 'locked' ], + ) : (), + ) : ( + [ static => label => 'Topic', content => qq|<a href="/t$tid">|.xml_escape($t->{title}).'</a>' ], + ), + $self->authCan('boardmod') ? ( + [ check => name => 'Hidden', short => 'hidden' ], + $num ? ( + [ check => name => 'Don\'t update last modified field', short => 'nolastmod' ], + ) : (), + ) : (), + [ text => name => 'Message', short => 'msg', rows => 10 ], + [ static => content => 'See <a href="/d9.3">d9.3</a> for the allowed formatting codes' ], + ]); + $self->htmlFooter; +} + + +sub tagbrowse { + my($self, $type, $iid) = @_; + $iid ||= ''; + return 404 if $type =~ /(db|an)/ && $iid; + + my $f = $self->formValidate( + { name => 'p', required => 0, default => 1, template => 'int' }, + ); + return 404 if $f->{_err}; + + my $obj = !$iid ? undef : + $type eq 'u' ? $self->dbUserGet(uid => $iid)->[0] : + $type eq 'p' ? $self->dbProducerGet(id => $iid)->[0] : + $self->dbVNGet(id => $iid)->[0]; + return 404 if $iid && !$obj; + my $ititle = $obj && ($obj->{title}||$obj->{name}||$obj->{username}); + my $title = !$obj ? $self->{discussion_tags}{$type} : 'Related discussions for '.$ititle; + + my($list, $np) = $self->dbThreadGet( + type => $type, + $iid ? (iid => $iid) : (), + results => 50, + page => $f->{p}, + what => 'firstpost lastpost tagtitles', + order => $type eq 'an' ? 't.id DESC' : 'tpl.date DESC', + ); + + $self->htmlHeader(title => $title, noindex => !@$list); + + $self->htmlMainTabs($type, $obj, 'disc') if $iid; + div class => 'mainbox'; + h1 $title; + p; + a href => '/t', 'Discussion board'; + txt ' > '; + a href => "/t/$type", $self->{discussion_tags}{$type}; + if($iid) { + txt ' > '; + a style => 'font-weight: bold', href => "/t/$type$iid", "$type$iid"; + txt ':'; + a href => "/$type$iid", $ititle; + } + end; + p class => 'center'; + if(!@$list) { + b 'No related threads found'; + br; br; + a href => "/t/$type$iid/new", 'Why not create one yourself?'; + } else { + a href => '/t/'.($iid ? $type.$iid : 'db').'/new', 'Start a new thread'; + } + end; + end; + + _threadlist($self, $list, $f, $np, "/t/$type$iid") if @$list; + + $self->htmlFooter; +} + + +sub index { + my $self = shift; + + $self->htmlHeader(title => 'Discussion board index'); + div class => 'mainbox'; + h1 'Discussion board index'; + p class => 'browseopts'; + a href => '/t/'.$_, $self->{discussion_tags}{$_} + for (qw|an db v p u|); + end; + end; + + for (qw|an db v p u|) { + my $list = $self->dbThreadGet( + type => $_, + results => 5, + page => 1, + what => 'firstpost lastpost tagtitles', + order => 'tpl.date DESC', + ); + h1 class => 'boxtitle'; + a href => "/t/$_", $self->{discussion_tags}{$_}; + end; + _threadlist($self, $list, {p=>1}, 0, "/t"); + } + + $self->htmlFooter; +} + + +sub _threadlist { + my($self, $list, $f, $np, $url) = @_; + $self->htmlBrowse( + items => $list, + options => $f, + nextpage => $np, + pageurl => $url, + class => 'discussions', + header => [ + [ 'Topic' ], [ 'Replies' ], [ 'Starter' ], [ 'Last post' ] + ], + row => sub { + my($self, $n, $o) = @_; + Tr $n % 2 ? ( class => 'odd' ) : (); + td class => 'tc1'; + a $o->{locked} ? ( class => 'locked' ) : (), href => "/t$o->{id}", shorten $o->{title}, 50; + end; + td class => 'tc2', $o->{count}-1; + td class => 'tc3'; + lit userstr $o->{fuid}, $o->{fusername}; + end; + td class => 'tc4', rowspan => 2; + lit userstr $o->{luid}, $o->{lusername}; + lit ' @ '; + a href => "/t$o->{id}.$o->{count}"; + lit date $o->{ldate}; + end; + end; + end; + Tr $n % 2 ? ( class => 'odd' ) : (); + td colspan => 4, class => 'tags'; + txt ' > '; + my $i = 1; + for(sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$o->{tags}}) { + last if $i++ > 5; + txt ', ' if $i > 2; + a href => "/t/$_->{type}".($_->{iid}||''), + title => $_->{original}||$self->{discussion_tags}{$_->{type}}, + shorten $_->{title}||$self->{discussion_tags}{$_->{type}}, 30; + } + txt ', ...' if @{$o->{tags}} > 5; + end; + end; + } + ); +} + + +1; + |