package VNDB::Handler::Staff; use strict; use warnings; use TUWF qw(:html :xml uri_escape xml_escape); use VNDB::Func; use List::Util qw(first); TUWF::register( qr{s([1-9]\d*)(?:\.([1-9]\d*))?} => \&page, qr{s(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit|/new)} => \&edit, qr{s/([a-z0]|all)} => \&list, qr{xml/staff\.xml} => \&staffxml, ); sub page { my($self, $id, $rev) = @_; my $method = $rev ? 'dbStaffGetRev' : 'dbStaffGet'; my $s = $self->$method( id => $id, what => 'extended aliases roles', $rev ? ( rev => $rev ) : () )->[0]; return $self->resNotFound if !$s->{id}; my $metadata = { 'og:title' => $s->{name}, 'og:description' => $s->{desc}, }; $self->htmlHeader(title => $s->{name}, noindex => $rev, metadata => $metadata); $self->htmlMainTabs('s', $s) if $id; return if $self->htmlHiddenMessage('s', $s); if($rev) { my $prev = $rev && $rev > 1 && $self->dbStaffGetRev(id => $id, rev => $rev-1, what => 'extended aliases')->[0]; $self->htmlRevision('s', $prev, $s, [ name => 'Name (romaji)', diff => 1 ], [ original => 'Original name', diff => 1 ], [ gender => 'Gender', serialize => sub { $self->{genders}{$_[0]} } ], [ lang => 'Language', serialize => sub { "$_[0] ($self->{languages}{$_[0]})" } ], [ l_site => 'Official page', diff => 1 ], [ l_wp => 'Wikipedia link', htmlize => sub { $_[0] ? sprintf '%1$s', xml_escape $_[0] : '[empty]' }], [ l_twitter => 'Twitter account', diff => 1 ], [ l_anidb => 'AniDB creator ID', serialize => sub { $_[0] // '' } ], [ desc => 'Description', diff => qr/[ ,\n\.]/ ], [ aliases => 'Aliases', join => '
', split => sub { map xml_escape(sprintf('%s%s', $_->{name}, $_->{original} ? ' ('.$_->{original}.')' : '')), @{$_[0]}; }], ); } div class => 'mainbox staffpage'; $self->htmlItemMessage('s', $s); h1 $s->{name}; h2 class => 'alttitle', $s->{original} if $s->{original}; # info table table class => 'stripe'; thead; Tr; td colspan => 2; b style => 'margin-right: 10px', $s->{name}; b class => 'grayedout', style => 'margin-right: 10px', $s->{original} if $s->{original}; cssicon "gen $s->{gender}", $self->{genders}{$s->{gender}} if $s->{gender} ne 'unknown'; end; end; end; Tr; td class => 'key', 'Language'; td $self->{languages}{$s->{lang}}; end; if(@{$s->{aliases}}) { Tr; td class => 'key', @{$s->{aliases}} == 1 ? 'Alias' : 'Aliases'; td; table class => 'aliases'; for my $alias (@{$s->{aliases}}) { Tr class => 'nostripe'; td $alias->{original} ? () : (colspan => 2), class => 'key'; txt $alias->{name}; end; td $alias->{original} if $alias->{original}; end; } end; end; end; } my @links = ( $s->{l_site} ? [ 'Official page', $s->{l_site} ] : (), $s->{l_wp} ? [ 'Wikipedia', "http://en.wikipedia.org/wiki/$s->{l_wp}" ] : (), $s->{l_twitter} ? [ 'Twitter', "https://twitter.com/$s->{l_twitter}" ] : (), $s->{l_anidb} ? [ 'AniDB', "http://anidb.net/cr$s->{l_anidb}" ] : (), ); if(@links) { Tr; td class => 'key', 'Links'; td; for(@links) { a href => $_->[1], $_->[0]; br if $_ != $links[$#links]; } end; end; } end 'table'; # description p class => 'description'; lit bb2html $s->{desc}, 0, 1; end; end; _roles($self, $s); _cast($self, $s); $self->htmlFooter; } sub _roles { my($self, $s) = @_; return if !@{$s->{roles}}; h1 class => 'boxtitle', 'Credits'; $self->htmlBrowse( items => $s->{roles}, class => 'staffroles', header => [ [ 'Title' ], [ 'Released' ], [ 'Role' ], [ 'As' ], [ 'Note' ], ], row => sub { my($r, $n, $l) = @_; Tr; td class => 'tc1'; a href => "/v$l->{vid}", title => $l->{t_original}||$l->{title}, shorten $l->{title}, 60; end; td class => 'tc2'; lit fmtdatestr $l->{c_released}; end; td class => 'tc3', $self->{staff_roles}{$l->{role}}; td class => 'tc4', title => $l->{original}||$l->{name}, $l->{name}; td class => 'tc5', $l->{note}; end; }, ); } sub _cast { my($self, $s) = @_; return if !@{$s->{cast}}; h1 class => 'boxtitle', sprintf 'Voiced characters (%d)', scalar @{$s->{cast}}; $self->htmlBrowse( items => $s->{cast}, class => 'staffroles', header => [ [ 'Title' ], [ 'Released' ], [ 'Cast' ], [ 'As' ], [ 'Note' ], ], row => sub { my($r, $n, $l) = @_; Tr; td class => 'tc1'; a href => "/v$l->{vid}", title => $l->{t_original}||$l->{title}, shorten $l->{title}, 60; end; td class => 'tc2'; lit fmtdatestr $l->{c_released}; end; td class => 'tc3'; a href => "/c$l->{cid}", title => $l->{c_original}, $l->{c_name}; end; td class => 'tc4', title => $l->{original}||$l->{name}, $l->{name}; td class => 'tc5', $l->{note}; end; }, ); } sub edit { my($self, $sid, $rev) = @_; my $s = $sid && $self->dbStaffGetRev(id => $sid, what => 'extended aliases roles', $rev ? (rev => $rev) : ())->[0]; return $self->resNotFound if $sid && !$s->{id}; $rev = undef if !$s || $s->{lastrev}; return $self->htmlDenied if !$self->authCan('edit') || $sid && (($s->{locked} || $s->{hidden}) && !$self->authCan('dbmod')); my %b4 = !$sid ? () : ( (map { $_ => $s->{$_} } qw|name original gender lang desc l_wp l_site l_twitter l_anidb ihid ilock|), primary => $s->{aid}, aliases => [ map +{ aid => $_->{aid}, name => $_->{name}, orig => $_->{original} }, sort { $a->{name} cmp $b->{name} || $a->{original} cmp $b->{original} } @{$s->{aliases}} ], ); my $frm; if ($self->reqMethod eq 'POST') { return if !$self->authCheckCode; $frm = $self->formValidate ( { post => 'name', maxlength => 200 }, { post => 'original', required => 0, maxlength => 200, default => '' }, { post => 'primary', required => 0, template => 'id', default => 0 }, { post => 'desc', required => 0, maxlength => 5000, default => '' }, { post => 'gender', required => 0, default => 'unknown', enum => [qw|unknown m f|] }, { post => 'lang', enum => [ keys %{$self->{languages}} ] }, { post => 'l_wp', required => 0, maxlength => 150, default => '' }, { post => 'l_site', required => 0, template => 'weburl', maxlength => 250, default => '' }, { post => 'l_twitter', required => 0, maxlength => 16, default => '', regex => [ qr/^\S+$/, 'Invalid twitter username' ] }, { post => 'l_anidb', required => 0, template => 'id', default => undef }, { post => 'aliases', template => 'json', json_sort => ['name','orig'], json_fields => [ { field => 'name', required => 1, maxlength => 200 }, { field => 'orig', required => 0, maxlength => 200, default => '' }, { field => 'aid', required => 0, template => 'id', default => 0 }, ]}, { post => 'editsum', template => 'editsum' }, { post => 'ihid', required => 0 }, { post => 'ilock', required => 0 }, ); if(!$frm->{_err}) { my %old_aliases = $sid ? ( map +($_->{aid} => 1), @{$self->dbStaffAliasIds($sid)} ) : (); $frm->{primary} = 0 unless exists $old_aliases{$frm->{primary}}; # reset aid to zero for newly added aliases. $_->{aid} *= $old_aliases{$_->{aid}} ? 1 : 0 for(@{$frm->{aliases}}); # Make sure no aliases that have been linked to a VN are removed. my %new_aliases = map +($_, 1), grep $_, $frm->{primary}, map $_->{aid}, @{$frm->{aliases}}; $frm->{_err} = [ "Can't remove an alias that is still linked to a VN." ] if grep !$new_aliases{$_->{aid}}, @{$s->{roles}}, @{$self->{cast}}; } if(!$frm->{_err}) { $frm->{ihid} = $frm->{ihid} ?1:0; $frm->{ilock} = $frm->{ilock}?1:0; $frm->{aid} = $frm->{primary} if $sid; $frm->{desc} = $self->bbSubstLinks($frm->{desc}); return $self->resRedirect("/s$sid", 'post') if $sid && !form_compare(\%b4, $frm); my $nrev = $self->dbItemEdit(s => $sid ? ($s->{id}, $s->{rev}) : (undef, undef), %$frm); return $self->resRedirect("/s$nrev->{itemid}.$nrev->{rev}", 'post'); } } $frm->{$_} //= $b4{$_} for keys %b4; $frm->{editsum} //= sprintf 'Reverted to revision s%d.%d', $sid, $rev if $rev; $frm->{lang} = 'ja' if !$sid && !defined $frm->{lang}; my $title = $s ? "Edit $s->{name}" : 'Add staff member'; $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('s', $s, 'edit') if $s; $self->htmlEditMessage('s', $s, $title); $self->htmlForm({ frm => $frm, action => $s ? "/s$sid/edit" : '/s/new', editsum => 1 }, staffe_geninfo => [ 'General info', [ hidden => short => 'name' ], [ hidden => short => 'original' ], [ hidden => short => 'primary' ], [ json => short => 'aliases' ], $sid && @{$s->{aliases}} ? [ static => content => 'You may choose a different primary name.' ] : (), [ static => label => 'Names', content => sub { table id => 'names'; thead; Tr; td class => 'tc_id'; end; td class => 'tc_name', 'Name (romaji)'; td class => 'tc_original', 'Original'; td; end; end; end; tbody id => 'alias_tbl'; # filled with javascript end; end; }], [ static => content => '
' ], [ text => name => 'Staff note
English please!', short => 'desc', rows => 4 ], [ select => name => 'Gender',short => 'gender', options => [ map [ $_, $self->{genders}{$_} ], qw(unknown m f) ] ], [ select => name => 'Primary language', short => 'lang', options => [ map [ $_, "$_ ($self->{languages}{$_})" ], keys %{$self->{languages}} ] ], [ input => name => 'Official page', short => 'l_site' ], [ input => name => 'Wikipedia link', short => 'l_wp', pre => 'http://en.wikipedia.org/wiki/' ], [ input => name => 'Twitter username', short => 'l_twitter' ], [ input => name => 'AniDB creator ID', short => 'l_anidb' ], [ static => content => '
' ], ]); $self->htmlFooter; } sub list { my ($self, $char) = @_; my $f = $self->formValidate( { get => 'p', required => 0, default => 1, template => 'page' }, { get => 'q', required => 0, default => '' }, { get => 'fil', required => 0, default => '' }, ); return $self->resNotFound if $f->{_err}; my ($list, $np) = $self->filFetchDB(staff => $f->{fil}, {}, { $char ne 'all' ? ( char => $char ) : (), $f->{q} ? ($f->{q} =~ /^=(.+)$/ ? (exact => $1) : (search => $f->{q})) : (), results => 150, page => $f->{p} }); return $self->resRedirect('/s'.$list->[0]{id}, 'temp') if $f->{q} && @$list && (!first { $_->{id} != $list->[0]{id} } @$list) && $f->{p} == 1 && !$f->{fil}; # redirect to the staff page if all results refer to the same entry my $quri = join(';', $f->{q} ? 'q='.uri_escape($f->{q}) : (), $f->{fil} ? "fil=$f->{fil}" : ()); $quri = '?'.$quri if $quri; my $pageurl = "/s/$char$quri"; $self->htmlHeader(title => 'Browse staff'); form action => '/s/all', 'accept-charset' => 'UTF-8', method => 'get'; div class => 'mainbox'; h1 'Browse staff'; $self->htmlSearchBox('s', $f->{q}); p class => 'browseopts'; for ('all', 'a'..'z', 0) { a href => "/s/$_$quri", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? 'ALL' : $_ ? uc $_ : '#'; } end; p class => 'filselect'; a id => 'filselect', href => '#s'; lit ' Filters'; end; end; input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil}; end; end 'form'; $self->htmlBrowseNavigate($pageurl, $f->{p}, $np, 't'); div class => 'mainbox staffbrowse'; h1 $f->{q} ? 'Search results' : 'Staff list'; if(!@$list) { p 'No results found'; } else { # spread the results over 3 equivalent-sized lists my $perlist = @$list/3 < 1 ? 1 : @$list/3; for my $c (0..(@$list < 3 ? $#$list : 2)) { ul; for ($perlist*$c..($perlist*($c+1))-1) { li; my $gender = $list->[$_]{gender}; cssicon 'lang '.$list->[$_]{lang}, $self->{languages}{$list->[$_]{lang}}; a href => "/s$list->[$_]{id}", title => $list->[$_]{original}, $list->[$_]{name}; end; } end; } } clearfloat; end 'div'; $self->htmlBrowseNavigate($pageurl, $f->{p}, $np, 'b'); $self->htmlFooter; } sub staffxml { my $self = shift; my $q = $self->formValidate({ get => 'q', required => 0, maxlength => 500 }); return $self->resNotFound if $q->{_err} || !$q->{q}; my($list, $np) = $self->dbStaffGet( $q->{q} =~ /^s([1-9]\d*)/ ? (id => $1) : $q->{q} =~ /^=(.+)/ ? (exact => $1) : (search => $q->{q}, sort => 'search'), results => 10, page => 1, ); $self->resHeader('Content-type' => 'text/xml; charset=UTF-8'); xml; tag 'staff', more => $np ? 'yes' : 'no'; for(@$list) { tag 'item', sid => $_->{id}, id => $_->{aid}, orig => $_->{original}, $_->{name}; } end; } 1;