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*)?} => \&board, 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 => 'boardtitles')->[0]; return 404 if !$t->{id} || $t->{hidden} && !$self->authCan('boardmod'); my $p = $self->dbPostGet(tid => $tid, results => 25, page => $page, what => 'user'); return 404 if !$p->[0]; # mark as read when this thread is posted in the board of the currently logged in user my $uid = $self->authInfo->{id}; $self->dbPostRead($t->{id}, $uid, $p->[$#$p]{num}) if $uid && grep $_->{type} eq 'u' && $_->{iid} == $uid, @{$t->{boards}}; $self->htmlHeader(title => $t->{title}); div class => 'mainbox'; h1 $t->{title}; h2 mt '_thread_postedin'; ul; for (sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$t->{boards}}) { li; a href => "/t/$_->{type}", mt "_dboard_$_->{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 ? 'odd ' : ''; $class .= 'deleted' if $_->{hidden}; Tr class => $class; td class => 'tc1'; a href => "/t$tid.$_->{num}", name => $_->{num}, "#$_->{num}"; if(!$_->{hidden}) { lit ' '.mt "_thread_byuser", $_; br; lit $self->{l10n}->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", mt '_thread_editpost'; txt ' >'; end; } if($_->{hidden}) { i class => 'deleted', mt '_thread_deletedpost'; } else { lit bb2html $_->{msg}; i class => 'lastmod', mt '_thread_lastmodified', $_->{edited} if $_->{edited}; } end; end; } end; end; $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 'b', 1); if($t->{locked}) { div class => 'mainbox'; h1 mt '_thread_noreply_title'; p class => 'center', mt '_thread_noreply_locked'; 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; txt mt '_thread_quickreply_title'; b class => 'standout', ' ('.mt('_inenglish').')'; end; textarea name => 'msg', id => 'msg', rows => 4, cols => 50, ''; br; input type => 'submit', value => mt('_thread_quickreply_submit'), class => 'submit'; input type => 'submit', value => mt('_thread_quickreply_full'), class => 'submit', name => 'fullreply'; end; end; end; } elsif(!$self->authCan('board')) { div class => 'mainbox'; h1 mt '_thread_noreply_title'; p class => 'center', mt '_thread_noreply_login'; 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 boards my $board = ''; if($tid !~ /^\d+$/) { return 404 if $tid =~ /(db|an)/ && $num || $tid =~ /[vpu]/ && !$num; $board = $tid.($num||''); $tid = 0; $num = 0; } # get thread and post, if any my $t = $tid && $self->dbThreadGet(id => $tid, what => 'boards')->[0]; return 404 if $tid && !$t->{id}; my $p = $num && $self->dbPostGet(tid => $tid, num => $num, what => 'user')->[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 => 'boards', maxlength => 50 }, ) : (), $self->authCan('boardmod') ? ( { name => 'locked', required => 0 }, { name => 'hidden', required => 0 }, { name => 'nolastmod', required => 0 }, ) : (), { name => 'msg', maxlenght => 5000 }, { name => 'fullreply', required => 0 }, ); $frm->{_err} = 1 if $frm->{fullreply}; # check for double-posting push @{$frm->{_err}}, 'doublepost' if !$num && !$frm->{_err} && $self->dbPostGet( uid => $self->authInfo->{id}, tid => $tid, mindate => time - 30, results => 1, $tid ? () : (num => 1))->[0]{num}; # parse and validate the boards my @boards; if(!$frm->{_err} && $frm->{boards}) { for (split /[ ,]/, $frm->{boards}) { my($ty, $id) = ($1, $2) if /^([a-z]{1,2})([0-9]*)$/; push @boards, [ $ty, $id ]; push @{$frm->{_err}}, [ 'boards', 'wrongboard', $_ ] if !$ty || !grep($_ eq $ty, @{$self->{discussion_boards}}) || $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}, boards => \@boards, 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; 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->{boards} ||= join ' ', sort map $_->[1]?$_->[0].$_->[1]:$_->[0], @{$t->{boards}}; $frm->{title} ||= $t->{title}; $frm->{locked} = $t->{locked} if !exists $frm->{locked}; $frm->{hidden} = $t->{hidden} if !exists $frm->{hidden}; } } delete $frm->{_err} unless ref $frm->{_err}; $frm->{boards} ||= $board; $frm->{nolastmod} = 1 if $num && $self->authCan('boardmod') && !exists $frm->{nolastmod}; # generate html my $title = mt !$tid ? '_postedit_newthread' : !$num ? ('_postedit_replyto', $t->{title}) : '_postedit_edit'; my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; $self->htmlHeader(title => $title, noindex => 1); $self->htmlForm({ frm => $frm, action => $url }, 'postedit' => [$title, [ static => label => mt('_postedit_form_username'), content => $self->{l10n}->userstr($self->authInfo->{id}, $self->authInfo->{username}) ], !$tid || $num == 1 ? ( [ input => short => 'title', name => mt('_postedit_form_title') ], [ input => short => 'boards', name => mt('_postedit_form_boards') ], [ static => content => mt('_postedit_form_boards_info') ], $self->authCan('boardmod') ? ( [ check => name => mt('_postedit_form_locked'), short => 'locked' ], ) : (), ) : ( [ static => label => mt('_postedit_form_topic'), content => qq||.xml_escape($t->{title}).'' ], ), $self->authCan('boardmod') ? ( [ check => name => mt('_postedit_form_hidden'), short => 'hidden' ], $num ? ( [ check => name => mt('_postedit_form_nolastmod'), short => 'nolastmod' ], ) : (), ) : (), [ text => name => mt('_postedit_form_msg').'
'.mt('_inenglish').'', short => 'msg', rows => 25, cols => 75 ], [ static => content => mt('_postedit_form_msg_format') ], ]); $self->htmlFooter; } sub board { 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 ? mt("_dboard_$type") : mt '_disboard_item_title', $ititle; my($list, $np) = $self->dbThreadGet( type => $type, $iid ? (iid => $iid) : (), results => 50, page => $f->{p}, what => 'firstpost lastpost boardtitles', order => $type eq 'an' ? 't.id DESC' : 'tpl.date DESC', ); $self->htmlHeader(title => $title, noindex => !@$list || $type eq 'u'); $self->htmlMainTabs($type, $obj, 'disc') if $iid; div class => 'mainbox'; h1 $title; p; a href => '/t', mt '_disboard_rootlink'; txt ' > '; a href => "/t/$type", mt "_dboard_$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 mt '_disboard_nothreads'; br; br; a href => "/t/$type$iid/new", mt '_disboard_createyourown'; } else { a href => '/t/'.($iid ? $type.$iid : 'db').'/new', mt '_disboard_startnew'; } end; end; _threadlist($self, $list, $f, $np, "/t/$type$iid") if @$list; $self->htmlFooter; } sub index { my $self = shift; $self->htmlHeader(title => mt '_disindex_title'); div class => 'mainbox'; h1 mt '_disindex_title'; p class => 'browseopts'; a href => '/t/'.$_, mt "_dboard_$_" 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 boardtitles', order => 'tpl.date DESC', ); h1 class => 'boxtitle'; a href => "/t/$_", mt "_dboard_$_"; 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 => [ [ mt '_threadlist_col_topic' ], [ mt '_threadlist_col_replies' ], [ mt '_threadlist_col_starter' ], [ mt '_threadlist_col_lastpost' ], ], 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 $self->{l10n}->userstr($o->{fuid}, $o->{fusername}); end; td class => 'tc4'; lit $self->{l10n}->userstr($o->{luid}, $o->{lusername}); lit ' @ '; a href => "/t$o->{id}.$o->{count}"; lit $self->{l10n}->date($o->{ldate}); end; end; end; Tr $n % 2 ? ( class => 'odd' ) : (); td colspan => 4, class => 'boards'; txt ' > '; my $i = 1; for(sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$o->{boards}}) { last if $i++ > 5; txt ', ' if $i > 2; a href => "/t/$_->{type}".($_->{iid}||''), title => $_->{original}||mt("_dboard_$_->{type}"), shorten $_->{title}||mt("_dboard_$_->{type}"), 30; } txt ', ...' if @{$o->{boards}} > 5; end; end; } ); } 1;