summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2009-10-24 15:18:34 +0200
committerYorhel <git@yorhel.nl>2009-10-24 15:19:10 +0200
commit5251f791de0a0e1684107b373a08cfe3a17fc447 (patch)
tree53d39d0456b464bf4525020b8503ae2d0c98b1bc /lib
parent8243d0b30d015c2464c34b02dd4e3ecf1c599762 (diff)
parenta8921887bddb7242a95e99b5177aff6ff6cd11be (diff)
Merge branch 'beta'2.8
+ ChangeLog update
Diffstat (limited to 'lib')
-rw-r--r--lib/LangFile.pm145
-rw-r--r--lib/Multi/Anime.pm13
-rw-r--r--lib/Multi/IRC.pm8
-rw-r--r--lib/Multi/Maintenance.pm35
-rw-r--r--lib/Multi/RG.pm296
-rw-r--r--lib/VNDB/DB/Discussions.pm12
-rw-r--r--lib/VNDB/DB/Misc.pm40
-rw-r--r--lib/VNDB/DB/Producers.pm37
-rw-r--r--lib/VNDB/DB/Releases.pm14
-rw-r--r--lib/VNDB/DB/Users.pm28
-rw-r--r--lib/VNDB/DB/VN.pm13
-rw-r--r--lib/VNDB/Func.pm4
-rw-r--r--lib/VNDB/Handler/Discussions.pm7
-rw-r--r--lib/VNDB/Handler/Misc.pm13
-rw-r--r--lib/VNDB/Handler/Producers.pm160
-rw-r--r--lib/VNDB/Handler/Releases.pm96
-rw-r--r--lib/VNDB/Handler/Tags.pm45
-rw-r--r--lib/VNDB/Handler/ULists.pm20
-rw-r--r--lib/VNDB/Handler/Users.pm6
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm6
-rw-r--r--lib/VNDB/Handler/VNEdit.pm45
-rw-r--r--lib/VNDB/Handler/VNPage.pm71
-rw-r--r--lib/VNDB/L10N.pm115
-rw-r--r--lib/VNDB/Plugin/TransAdmin.pm349
-rw-r--r--lib/VNDB/Util/Auth.pm4
-rw-r--r--lib/VNDB/Util/CommonHTML.pm66
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm20
27 files changed, 1240 insertions, 428 deletions
diff --git a/lib/LangFile.pm b/lib/LangFile.pm
new file mode 100644
index 00000000..e81f7f7a
--- /dev/null
+++ b/lib/LangFile.pm
@@ -0,0 +1,145 @@
+
+
+package LangFile;
+
+use strict;
+use warnings;
+use Fcntl qw(LOCK_SH LOCK_EX SEEK_SET);
+
+
+sub new {
+ my($class, $action, $file) = @_;
+ open my $F, $action eq 'read' ? '<:utf8' : '>:utf8', $file or die "Opening $file: $!";
+ flock($F, $action eq 'read' ? LOCK_SH : LOCK_EX) or die "Locking $file: $!";
+ seek($F, 0, SEEK_SET) or die "Seeking $file: $!";
+ return bless {
+ act => $action,
+ FH => $F,
+ # status vars for reading
+ intro => 1,
+ last => [],
+ }, $class;
+}
+
+
+sub read {
+ my $self = shift;
+ my $FH = $self->{FH};
+ my @lines;
+ my $state = '';
+ my($lang, $sync);
+
+ while((my $l = shift(@{$self->{last}}) || <$FH>)) {
+ $l =~ s/[\r\n\t\s]+$//;
+
+ # header
+ if($self->{intro}) {
+ push @lines, $l;
+ next if $l ne '/intro';
+ $self->{intro} = 0;
+ return [ 'space', @lines ];
+ }
+
+ # key
+ if(!$state && $l =~ /^:(.+)$/) {
+ return [ 'key', $1 ];
+ }
+
+ # space
+ if((!$state || $state eq 'space') && ($l =~ /^#/ || $l eq '')) {
+ $state = 'space';
+ push @lines, $l;
+ } elsif($state eq 'space') {
+ push @{$self->{last}}, "$l\n";
+ return [ 'space', @lines ];
+ }
+
+ # tl
+ if(!$state && $l =~ /^([a-z_-]{2})([ *]):(?: (.+)|)$/) {
+ $lang = $1;
+ $sync = $2 eq '*' ? 0 : 1;
+ push @lines, $3||'';
+ $state = 'tl';
+ } elsif($state eq 'tl' && $l =~ /^\s{5}(.+)$/) {
+ push @lines, $1;
+ } elsif($state eq 'tl' && $l eq '') {
+ push @lines, $l;
+ } elsif($state eq 'tl') {
+ my $trans = join "\n", @lines;
+ push @{$self->{last}}, "\n" while $trans =~ s/\n$//;
+ push @{$self->{last}}, $l;
+ return [ 'tl', $lang, $sync, $trans ];
+ }
+
+ die "Don\'t know what to do with \"$l\"" if !$state;
+ }
+ if($state eq 'space') {
+ return [ 'space', @lines ];
+ }
+ if($state eq 'tl') {
+ my $trans = join "\n", @lines;
+ push @{$self->{last}}, "\n" while $trans =~ s/\n$//;
+ return [ 'tl', $lang, $sync, $trans ];
+ }
+ return undef;
+}
+
+
+sub write {
+ my($self, @line) = @_;
+ my $FH = $self->{FH};
+
+ my $t = shift @line;
+
+ if($t eq 'space') {
+ print $FH "$_\n" for @line;
+ }
+
+ if($t eq 'key') {
+ print $FH ":$line[0]\n";
+ }
+
+ if($t eq 'tl') {
+ my($lang, $sync, $text) = @line;
+ $text =~ s/\n([^\n])/\n $1/g;
+ $text = " $text" if $text ne '';
+ printf $FH "%s%s:%s\n", $lang, $sync ? ' ' : '*', $text;
+ }
+}
+
+
+sub close {
+ my $self = shift;
+ close $self->{FH};
+}
+
+1;
+
+__END__
+=pod
+
+=head1 NAME
+
+LangFile - Simple object oriented interface for the parsing and creation of lang.txt
+
+=head1 USAGE
+
+ use LangFile;
+ my $read = LangFile->new(read => "data/lang.txt");
+ my $write = LangFile->new(write => "lang-copy.txt");
+
+ while((my $line = $read->read())) {
+ # $line is an arrayref in one of the following formats:
+ # [ 'space', @lines ]
+ # unparsed lines, like the header, newlines and comments
+ # [ 'key', $key ]
+ # key line, $key is key name
+ # [ 'tl', $lang, $sync, $text ]
+ # translation line(s), $lang = language tag, $sync = 1/0, $text = translation (can include newlines)
+ # $line is undef on EOF, $read->next() die()s on a parsing error
+
+ # create an identical copy of $read in $write
+ $write->write(@$line);
+ }
+ $write->close;
+
diff --git a/lib/Multi/Anime.pm b/lib/Multi/Anime.pm
index 03134925..df2f6424 100644
--- a/lib/Multi/Anime.pm
+++ b/lib/Multi/Anime.pm
@@ -70,6 +70,17 @@ sub spawn {
lm => 0, # timestamp of last outgoing message, 0=no running msg
aid => 0, # anime ID of the last sent ANIME command
tag => int(rand()*50000),
+ # anime types as returned by AniDB (lowercased)
+ anime_types => {
+ 'unknown' => undef, # NULL
+ 'tv series' => 'tv',
+ 'ova' => 'ova',
+ 'movie' => 'mov',
+ 'other' => 'oth',
+ 'web' => 'web',
+ 'tv special' => 'spe',
+ 'music video' => 'mv',
+ },
},
);
}
@@ -224,7 +235,7 @@ sub receivepacket { # input, wheelid
$col[2] = undef if !$col[2] || $col[2] =~ /^0,/;
$col[3] = $1 if $col[3] =~ /^([0-9]+)/; # remove multi-year stuff
$col[3] = undef if !$col[3];
- $col[4] = (grep lc($VNDB::S{anime_types}[$_]) eq lc($col[4]), 0..$#{$VNDB::S{anime_types}})[0];
+ $col[4] = $_[HEAP]{anime_types}{ lc($col[4]) };
$col[5] = undef if !$col[5];
$col[6] = undef if !$col[6];
$_[KERNEL]->post(pg => do => 'UPDATE anime
diff --git a/lib/Multi/IRC.pm b/lib/Multi/IRC.pm
index 5018d2aa..4225e6b8 100644
--- a/lib/Multi/IRC.pm
+++ b/lib/Multi/IRC.pm
@@ -266,12 +266,12 @@ sub notify { # name, pid, payload
return if !$_[HEAP]{$k};
my $q = $_[ARG0] eq 'newrevision' ? q|SELECT
- CASE WHEN c.type = 0 THEN 'v' WHEN c.type = 1 THEN 'r' ELSE 'p' END AS type, c.rev, c.comments, c.id AS lastrev,
+ CASE WHEN c.type, c.rev, c.comments, c.id AS lastrev,
COALESCE(vr.vid, rr.rid, pr.pid) AS id, COALESCE(vr.title, rr.title, pr.name) AS title, u.username
FROM changes c
- LEFT JOIN vn_rev vr ON c.type = 0 AND c.id = vr.id
- LEFT JOIN releases_rev rr ON c.type = 1 AND c.id = rr.id
- LEFT JOIN producers_rev pr ON c.type = 2 AND c.id = pr.id
+ LEFT JOIN vn_rev vr ON c.type = 'v' AND c.id = vr.id
+ LEFT JOIN releases_rev rr ON c.type = 'r' AND c.id = rr.id
+ LEFT JOIN producers_rev pr ON c.type = 'p' AND c.id = pr.id
JOIN users u ON u.id = c.requester
WHERE c.id > ? AND c.requester <> 1
ORDER BY c.added|
diff --git a/lib/Multi/Maintenance.pm b/lib/Multi/Maintenance.pm
index e29c7b52..4f816e56 100644
--- a/lib/Multi/Maintenance.pm
+++ b/lib/Multi/Maintenance.pm
@@ -17,13 +17,13 @@ sub spawn {
package_states => [
$p => [qw|
_start shutdown set_daily daily set_monthly monthly log_stats
- vncache tagcache vnpopularity
- usercache statscache revcache logrotate
+ vncache tagcache vnpopularity cleangraphs
+ usercache statscache logrotate
|],
],
heap => {
- daily => [qw|vncache tagcache vnpopularity|],
- monthly => [qw|usercache statscache revcache logrotate|],
+ daily => [qw|vncache tagcache vnpopularity cleangraphs|],
+ monthly => [qw|usercache statscache logrotate|],
@_,
},
);
@@ -50,7 +50,7 @@ sub set_daily {
# (GMT because we're calculating on the UNIX timestamp, I can easily add an
# offset if necessary, but it doesn't really matter what time this cron
# runs, as long as it's run on a daily basis)
- $_[KERNEL]->alarm(daily => int(time/86400+1)*86400);
+ $_[KERNEL]->alarm(daily => int((time+3)/86400+1)*86400);
}
@@ -70,7 +70,7 @@ sub set_monthly {
# We do this by simply incrementing the timestamp with one day and checking gmtime()
# for a month change. This might not be very reliable, but should be enough for
# our purposes.
- my $nextday = int(time/86400+1)*86400;
+ my $nextday = int((time+3)/86400+1)*86400;
my $thismonth = (gmtime)[5]*100+(gmtime)[4]; # year*100 + month, for easy comparing
$nextday += 86400 while (gmtime $nextday)[5]*100+(gmtime $nextday)[4] <= $thismonth;
$_[KERNEL]->alarm(monthly => $nextday);
@@ -99,24 +99,33 @@ sub log_stats { # num, res, action, time
sub vncache {
- # this takes about 30s to complete. We really need to search for an alternative
+ # this takes about 40s to complete. We really need to search for an alternative
# method of keeping the c_* columns in the vn table up-to-date.
$_[KERNEL]->post(pg => do => 'SELECT update_vncache(0)', undef, 'log_stats', 'vncache');
}
sub tagcache {
- # this still takes "only" about 3 seconds max. Let's hope that doesn't increase too much.
+ # takes about 18 seconds max. ouch, but still kind-of acceptable
$_[KERNEL]->post(pg => do => 'SELECT tag_vn_calc()', undef, 'log_stats', 'tagcache');
}
sub vnpopularity {
- # still takes at most 2 seconds. Againt, let's hope that doesn't increase...
+ # still takes at most 2 seconds. let's hope that doesn't increase...
$_[KERNEL]->post(pg => do => 'SELECT update_vnpopularity()', undef, 'log_stats', 'vnpopularity');
}
+sub cleangraphs {
+ # should be pretty fast
+ $_[KERNEL]->post(pg => do => q|
+ DELETE FROM relgraphs vg
+ WHERE NOT EXISTS(SELECT 1 FROM vn WHERE rgraph = vg.id)
+ AND NOT EXISTS(SELECT 1 FROM producers WHERE rgraph = vg.id)
+ |, undef, 'log_stats', 'cleangraphs');
+}
+
#
# M O N T H L Y J O B S
@@ -164,14 +173,6 @@ sub statscache {
}
-sub revcache {
- # This -really- shouldn't be necessary...
- # Currently takes about 25 seconds to complete
- $_[KERNEL]->post(pg => do => q|SELECT update_rev('vn', ''), update_rev('releases', ''), update_rev('producers', '')|,
- undef, 'log_stats', 'revcache');
-}
-
-
sub logrotate {
my $dir = sprintf '%s/old', $VNDB::M{log_dir};
mkdir $dir if !-d $dir;
diff --git a/lib/Multi/RG.pm b/lib/Multi/RG.pm
index e427caf2..b3f9bb46 100644
--- a/lib/Multi/RG.pm
+++ b/lib/Multi/RG.pm
@@ -9,6 +9,8 @@ use strict;
use warnings;
use POE 'Wheel::Run', 'Filter::Stream';
use Encode 'encode_utf8';
+use XML::Parser;
+use XML::Writer;
use Time::HiRes 'time';
@@ -17,15 +19,13 @@ sub spawn {
POE::Session->create(
package_states => [
$p => [qw|
- _start shutdown check_rg creategraph getrel builddot buildgraph savegraph
+ _start shutdown check_rg creategraph getrel builddot savegraph finish
proc_stdin proc_stdout proc_stderr proc_closed proc_child
|],
],
heap => {
font => 'Arial',
fsize => [ 9, 7, 10 ], # nodes, edges, node_title
- imgdir => '/www/vndb/static/rg',
- moy => [qw| Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec |],
dot => '/usr/bin/dot',
check_delay => 3600,
@_,
@@ -51,10 +51,12 @@ sub shutdown {
sub check_rg {
- return if $_[HEAP]{vid};
- $_[KERNEL]->call(pg => query =>
- 'SELECT v.id FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE rgraph IS NULL AND hidden = FALSE LIMIT 1',
- undef, 'creategraph');
+ return if $_[HEAP]{id};
+ $_[KERNEL]->call(pg => query => q|
+ SELECT 'v' AS type, v.id FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE rgraph IS NULL AND hidden = FALSE
+ UNION
+ SELECT 'p', p.id FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE rgraph IS NULL AND hidden = FALSE
+ LIMIT 1|, undef, 'creategraph');
}
@@ -62,148 +64,138 @@ sub creategraph { # num, res
return $_[KERNEL]->delay('check_rg', $_[HEAP]{check_delay}) if $_[ARG0] == 0;
$_[HEAP]{start} = time;
- $_[HEAP]{vid} = $_[ARG1][0]{id};
- $_[HEAP]{rels} = {}; # relations (key=vid1-vid2, value=relation)
- $_[HEAP]{nodes} = {}; # nodes (key=vid, value= 0:found, 1:processed)
-
- $_[KERNEL]->post(pg => query =>
- 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?',
- [ $_[HEAP]{vid} ], 'getrel', $_[HEAP]{vid});
+ $_[HEAP]{id} = $_[ARG1][0]{id};
+ $_[HEAP]{type} = $_[ARG1][0]{type};
+ $_[HEAP]{rels} = {}; # relations (key=id1-id2, value=relation)
+ $_[HEAP]{nodes} = {}; # nodes (key=id, value= 0:found, 1:processed)
+
+ $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v'
+ ? 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?'
+ : 'SELECT pid2 AS id, relation FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE p.id = ?',
+ [ $_[HEAP]{id} ], 'getrel', $_[HEAP]{id});
}
-sub getrel { # num, res, vid
+sub getrel { # num, res, id
my $id = $_[ARG2];
$_[HEAP]{nodes}{$id} = 1;
for($_[ARG0] > 0 ? @{$_[ARG1]} : ()) {
- $_[HEAP]{rels}{$id.'-'.$_->{id}} = reverserel($_->{relation}) if $id < $_->{id};
- $_[HEAP]{rels}{$_->{id}.'-'.$id} = $_->{relation} if $id > $_->{id};
+ $_[HEAP]{rels}{$id.'-'.$_->{id}} = $VNDB::S{ $_[HEAP]{type} eq 'v' ? 'vn_relations' : 'prod_relations' }{$_->{relation}}[1] if $id < $_->{id};
+ $_[HEAP]{rels}{$_->{id}.'-'.$id} = $_->{relation} if $id > $_->{id};
if(!exists $_[HEAP]{nodes}{$_->{id}}) {
$_[HEAP]{nodes}{$_->{id}} = 0;
- $_[KERNEL]->post(pg => query =>
- 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?',
+ $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v'
+ ? 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?'
+ : 'SELECT pid2 AS id, relation FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE p.id = ?',
[ $_->{id} ], 'getrel', $_->{id});
}
}
- # do we have all relations now? get VN info
+ # do we have all relations now? get node info
if(!grep !$_, values %{$_[HEAP]{nodes}}) {
- $_[KERNEL]->post(pg => query =>
- 'SELECT v.id, vr.title, v.c_released AS date, v.c_languages AS lang
- FROM vn v JOIN vn_rev vr ON vr.id = v.latest
- WHERE v.id IN('.join(', ', map '?', keys %{$_[HEAP]{nodes}}).')',
+ my $ids = join(', ', map '?', keys %{$_[HEAP]{nodes}});
+ $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v'
+ ? "SELECT v.id, vr.title, v.c_released AS date, v.c_languages AS lang FROM vn v JOIN vn_rev vr ON vr.id = v.latest WHERE v.id IN($ids) ORDER BY v.c_released"
+ : "SELECT p.id, pr.name, pr.lang, pr.type FROM producers p JOIN producers_rev pr ON pr.id = p.latest WHERE p.id IN($ids) ORDER BY pr.name",
[ keys %{$_[HEAP]{nodes}} ], 'builddot');
}
}
sub builddot { # num, res
- my $vns = $_[ARG1];
+ my $nodes = $_[ARG1];
my $gv =
qq|graph rgraph {\n|.
- qq|\tratio = "compress"\n|.
- qq|\tgraph [ bgcolor="#ffffff00" ]\n|.
qq|\tnode [ fontname = "$_[HEAP]{font}", shape = "plaintext",|.
- qq| fontsize = $_[HEAP]{fsize}[0], style = "setlinewidth(0.5)", fontcolor = "#cccccc", color = "#225588" ]\n|.
+ qq| fontsize = $_[HEAP]{fsize}[0], fontcolor = "#333333", color = "#111111" ]\n|.
qq|\tedge [ labeldistance = 2.5, labelangle = -20, labeljust = 1, minlen = 2, dir = "both",|.
- qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#225588", fontcolor = "#cccccc" ]\n|;
-
- # insert all nodes, ordered by release date
- for (sort { $a->{date} <=> $b->{date} } @$vns) {
- my $date = sprintf '%08d', $_->{date};
- $date =~ s#^([0-9]{4})([0-9]{2}).+#$1==0?'N/A':$1==9999?'TBA':(($2&&$2<13?($_[HEAP]{moy}[$2-1].' '):'').$1)#e;
-
- my $title = $_->{title};
- $title = substr($title, 0, 27).'...' if length($title) > 30;
- $title =~ s/&/&amp;/g;
- $title =~ s/>/&gt;/g;
- $title =~ s/</&lt;/g;
-
- my $tooltip = $_->{title};
- $tooltip =~ s/\\/\\\\/g;
- $tooltip =~ s/"/\\"/g;
-
- $gv .= sprintf
- qq|\tv%d [ URL = "/v%d", tooltip = "%s" label=<|.
- q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#00000033">|.
- q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|.
- q|<TR><TD> %s </TD><TD> %s </TD></TR>|.
- qq|</TABLE>> ]\n|,
- $_->{id}, $_->{id}, encode_utf8($tooltip), $_[HEAP]{fsize}[2], encode_utf8($title), $date, $_->{lang}||'N/A';
- }
+ qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#111111", fontcolor = "#333333" ]\n|;
- # @rels = ([ vid1, vid2, relation, date1, date2 ], ..), for easier processing
- my @rels = map {
- /^([0-9]+)-([0-9]+)$/;
- my $vn1 = (grep $1 == $_->{id}, @$vns)[0];
- my $vn2 = (grep $2 == $_->{id}, @$vns)[0];
- [ $1, $2, $_[HEAP]{rels}{$_}, $vn1->{date}, $vn2->{date} ]
- } keys %{$_[HEAP]{rels}};
+ # insert all nodes
+ $gv .= $_[HEAP]{type} eq 'v' ? _vnnode($_, $_[HEAP]) : _prodnode($_, $_[HEAP]) for @$nodes;
- # insert all edges, ordered by release date again
- for (sort { ($a->[3]>$a->[4]?$a->[4]:$a->[3]) <=> ($b->[3]>$b->[4]?$b->[4]:$b->[3]) } @rels) {
- # [older game] -> [newer game]
- if($_->[4] > $_->[3]) {
- ($_->[0], $_->[1]) = ($_->[1], $_->[0]);
- $_->[2] = reverserel($_->[2]);
- }
- my $label =
- $VNDB::S{vn_relations}[$_->[2]][1]
- ? qq|headlabel = "$VNDB::S{vn_relations}[$_->[2]][0]", taillabel = "$VNDB::S{vn_relations}[$_->[2]-1][0]"| :
- $VNDB::S{vn_relations}[$_->[2]+1][1]
- ? qq|headlabel = "$VNDB::S{vn_relations}[$_->[2]][0]", taillabel = "$VNDB::S{vn_relations}[$_->[2]+1][0]"|
- : qq|label = " $VNDB::S{vn_relations}[$_->[2]][0]"|;
- $gv .= qq|\tv$$_[1] -- v$$_[0] [ $label ]\n|;
- }
+ # ...and relations
+ $gv .= $_[HEAP]{type} eq 'v' ? _vnrels($_[HEAP]{rels}, $nodes) : _prodrels($_[HEAP]{rels}, $nodes);
$gv .= "}\n";
- # get ID
- $_[KERNEL]->post(pg => query => 'INSERT INTO relgraph (cmap) VALUES (\'\') RETURNING id', undef, 'buildgraph', \$gv);
-}
-
-
-sub buildgraph { # num, res, \$gv
- $_[HEAP]{gid} = $_[ARG1][0]{id};
- $_[HEAP]{graph} = sprintf('%s/%02d/%d.png', $_[HEAP]{imgdir}, $_[ARG1][0]{id} % 100, $_[ARG1][0]{id});
- $_[HEAP]{cmap} = '';
-
- # roughly equivalent to:
- # cat layout.txt | dot -Tpng -o graph.png -Tcmapx
+ # Pass our dot file to graphviz
+ $_[HEAP]{svg} = '';
$_[HEAP]{proc} = POE::Wheel::Run->new(
Program => $_[HEAP]{dot},
- ProgramArgs => [ '-Tpng', '-o', $_[HEAP]{graph}, '-Tcmapx' ],
+ ProgramArgs => [ '-Tsvg' ],
StdioFilter => POE::Filter::Stream->new(),
StdinEvent => 'proc_stdin',
StdoutEvent => 'proc_stdout',
StderrEvent => 'proc_stderr',
CloseEvent => 'proc_closed',
);
- $_[HEAP]{proc}->put(${$_[ARG2]});
+ $_[HEAP]{proc}->put($gv);
}
sub savegraph {
- my $vids = join ',', sort map int, keys %{$_[HEAP]{nodes}};
+ # Before saving the SVG output, we'll modify it a little:
+ # - Remove comments
+ # - Add svg: prefix to all tags
+ # - Remove xmlns declarations (this is set in the html)
+ # - Remove <title> elements (unused)
+ # - Remove id attributes (unused)
+ # - Remove first <polygon> element (emulates the background color)
+ # - Replace stroke and fill attributes with classes (so that coloring is done in CSS)
+ my $svg = '';
+ my $w = XML::Writer->new(OUTPUT => \$svg);
+ my $p = XML::Parser->new;
+ $p->setHandlers(
+ Start => sub {
+ my($expat, $el, %attr) = @_;
+ return if $el eq 'title' || $expat->in_element('title');
+ return if $el eq 'polygon' && $expat->depth == 2;
+
+ $attr{class} = 'border' if $attr{stroke} && $attr{stroke} eq '#111111';
+ $attr{class} = 'nodebg' if $attr{fill} && $attr{fill} eq '#222222';
+
+ delete @attr{qw|stroke fill id xmlns xmlns:xlink|};
+ $el eq 'path' || $el eq 'polygon'
+ ? $w->emptyTag("svg:$el", %attr)
+ : $w->startTag("svg:$el", %attr);
+ },
+ End => sub {
+ my($expat, $el) = @_;
+ return if $el eq 'title' || $expat->in_element('title');
+ return if $el eq 'polygon' && $expat->depth == 2;
+ $w->endTag("svg:$el") if $el ne 'path' && $el ne 'polygon';
+ },
+ Char => sub {
+ my($expat, $str) = @_;
+ return if $expat->in_element('title');
+ $w->characters($str) if $str !~ /^[\s\t\r\n]*$/s;
+ }
+ );
+ $p->parsestring($_[HEAP]{svg});
+ $w->end();
+
+ # save the processed SVG in the database and fetch graph ID
+ $_[KERNEL]->post(pg => query => 'INSERT INTO relgraphs (svg) VALUES (?) RETURNING id', [ $svg ], 'finish');
+}
- # chmod graph
- chmod 0666, $_[HEAP]{graph};
- # save the image map in the database
- $_[KERNEL]->post(pg => do => 'UPDATE relgraph SET cmap = ? WHERE id = ?',
- [ "<!-- V:$vids -->\n$_[HEAP]{cmap}", $_[HEAP]{gid} ]);
+sub finish { # num, res
+ my $id = $_[ARG1][0]{id};
+ my $ids = join ',', sort map int, keys %{$_[HEAP]{nodes}};
+ my $table = $_[HEAP]{type} eq 'v' ? 'vn' : 'producers';
- # update the VN table
- $_[KERNEL]->post(pg => do => "UPDATE vn SET rgraph = ? WHERE id IN($vids)", [ $_[HEAP]{gid} ]);
+ # update the table
+ $_[KERNEL]->post(pg => do => "UPDATE $table SET rgraph = ? WHERE id IN($ids)", [ $id ]);
# log
- $_[KERNEL]->call(core => log => 'Generated relation graph in %.2fs, V: %s', time-$_[HEAP]{start}, $vids);
+ $_[KERNEL]->call(core => log => 'Generated relation graph #%d in %.2fs, %s: %s', $id, time-$_[HEAP]{start}, uc $_[HEAP]{type}, $ids);
# clean up
- delete @{$_[HEAP]}{qw| start vid nodes rels gid graph cmap proc |};
+ delete @{$_[HEAP]}{qw| start id type nodes rels svg proc |};
# check for more things to do
$_[KERNEL]->yield('check_rg');
@@ -216,7 +208,7 @@ sub proc_stdin {
$_[HEAP]{proc}->shutdown_stdin;
}
sub proc_stdout {
- $_[HEAP]{cmap} .= $_[ARG0];
+ $_[HEAP]{svg} .= $_[ARG0];
}
sub proc_stderr {
$_[KERNEL]->call(core => log => 'GraphViz STDERR: %s', $_[ARG0]);
@@ -230,9 +222,107 @@ sub proc_child {
-# non-POE helper function
-sub reverserel { # relation
- return $VNDB::S{vn_relations}[$_[0]][1] ? $_[0]-1 : $VNDB::S{vn_relations}[$_[0]+1][1] ? $_[0]+1 : $_[0];
+# non-POE helper functions
+
+sub _vnnode {
+ my($n, $heap) = @_;
+
+ my $date = sprintf '%08d', $n->{date};
+ $date =~ s{^([0-9]{4})([0-9]{2})([0-9]{2})$}{
+ $1 == 0 ? 'unknown'
+ : $1 == 9999 ? 'TBA'
+ : $2 == 99 ? $1
+ : $3 == 99 ? "$1-$2" : "$1-$2-$3"
+ }e;
+
+ my $title = $n->{title};
+ $title = substr($title, 0, 27).'...' if length($title) > 30;
+ $title =~ s/&/&amp;/g;
+ $title =~ s/>/&gt;/g;
+ $title =~ s/</&lt;/g;
+
+ my $tooltip = $n->{title};
+ $tooltip =~ s/\\/\\\\/g;
+ $tooltip =~ s/"/\\"/g;
+
+ return sprintf
+ qq|\tv%d [ URL = "/v%d", tooltip = "%s", label=<|.
+ q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#222222">|.
+ q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|.
+ q|<TR><TD> %s </TD><TD> %s </TD></TR>|.
+ qq|</TABLE>> ]\n|,
+ $_->{id}, $_->{id}, encode_utf8($tooltip), $heap->{fsize}[2], encode_utf8($title), $date, $n->{lang}||'N/A';
+}
+
+
+sub _vnrels {
+ my($rels, $vns) = @_;
+ my $r = '';
+
+ # @rels = ([ vid1, vid2, relation, date1, date2 ], ..), for easier processing
+ my @rels = map {
+ /^([0-9]+)-([0-9]+)$/;
+ my $vn1 = (grep $1 == $_->{id}, @$vns)[0];
+ my $vn2 = (grep $2 == $_->{id}, @$vns)[0];
+ [ $1, $2, $rels->{$_}, $vn1->{date}, $vn2->{date} ]
+ } keys %$rels;
+
+ # insert all edges, ordered by release date again
+ for (sort { ($a->[3]>$a->[4]?$a->[4]:$a->[3]) <=> ($b->[3]>$b->[4]?$b->[4]:$b->[3]) } @rels) {
+ # [older game] -> [newer game]
+ if($_->[4] > $_->[3]) {
+ ($_->[0], $_->[1]) = ($_->[1], $_->[0]);
+ $_->[2] = $VNDB::S{vn_relations}{$_->[2]}[1];
+ }
+ my $rev = $VNDB::S{vn_relations}{$_->[2]}[1];
+ my $label = $rev ne $_->[2]
+ ? qq|headlabel = "\$____vnrel_$_->[2]____\$", taillabel = "\$____vnrel_${rev}____\$"|
+ : qq|label = "\$____vnrel_$_->[2]____\$"|;
+ $r .= qq|\tv$$_[1] -- v$$_[0] [ $label ]\n|;
+ }
+ return $r;
+}
+
+
+sub _prodnode {
+ my($n, $heap) = @_;
+
+ my $name = $n->{name};
+ $name = substr($name, 0, 27).'...' if length($name) > 30;
+ $name =~ s/&/&amp;/g;
+ $name =~ s/>/&gt;/g;
+ $name =~ s/</&lt;/g;
+
+ my $tooltip = $n->{name};
+ $tooltip =~ s/\\/\\\\/g;
+ $tooltip =~ s/"/\\"/g;
+
+ return sprintf
+ qq|\tp%d [ URL = "/p%d", tooltip = "%s", label=<|.
+ q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#222222">|.
+ q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|.
+ q|<TR><TD ALIGN="CENTER"> $_lang_%s_$ </TD><TD ALIGN="CENTER"> $_ptype_%s_$ </TD></TR>|.
+ qq|</TABLE>> ]\n|,
+ $_->{id}, $_->{id}, encode_utf8($tooltip), $heap->{fsize}[2], encode_utf8($name), $n->{lang}, $n->{type};
+}
+
+
+sub _prodrels {
+ my($rels, $prods) = @_;
+ my $r = '';
+
+ for (keys %$rels) {
+ /^([0-9]+)-([0-9]+)$/;
+ my $p1 = (grep $1 == $_->{id}, @$prods)[0];
+ my $p2 = (grep $2 == $_->{id}, @$prods)[0];
+
+ my $rev = $VNDB::S{prod_relations}{$rels->{$_}}[1];
+ my $label = $rev ne $rels->{$_}
+ ? qq|headlabel = "\$____prodrel_${rev}____\$", taillabel = "\$____prodrel_$rels->{$_}____\$"|
+ : qq|label = "\$____prodrel_$rels->{$_}____\$"|;
+ $r .= qq|\tp$p1->{id} -- p$p2->{id} [ $label ]\n|;
+ }
+ return $r;
}
diff --git a/lib/VNDB/DB/Discussions.pm b/lib/VNDB/DB/Discussions.pm
index 26beaaee..60487098 100644
--- a/lib/VNDB/DB/Discussions.pm
+++ b/lib/VNDB/DB/Discussions.pm
@@ -5,7 +5,7 @@ use strict;
use warnings;
use Exporter 'import';
-our @EXPORT = qw|dbThreadGet dbThreadEdit dbThreadAdd dbPostGet dbPostEdit dbPostAdd dbThreadCount|;
+our @EXPORT = qw|dbThreadGet dbThreadEdit dbThreadAdd dbPostGet dbPostEdit dbPostAdd dbThreadCount dbPostRead|;
# Options: id, type, iid, results, page, what, notusers
@@ -243,5 +243,15 @@ sub dbPostAdd {
}
+sub dbPostRead { # thread id, user id, last post number
+ my($s, $tid, $uid, $num) = @_;
+ $s->dbExec(q|
+ UPDATE threads_boards
+ SET lastread = ?
+ WHERE tid = ? AND type = 'u' AND iid = ?|,
+ $num, $tid, $uid);
+}
+
+
1;
diff --git a/lib/VNDB/DB/Misc.pm b/lib/VNDB/DB/Misc.pm
index eeb860b0..b3522d72 100644
--- a/lib/VNDB/DB/Misc.pm
+++ b/lib/VNDB/DB/Misc.pm
@@ -6,7 +6,7 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw|
- dbStats dbRevisionInsert dbItemInsert dbRevisionGet dbItemMod dbLanguages dbRandomQuote
+ dbStats dbRevisionInsert dbItemInsert dbRevisionGet dbItemMod dbRandomQuote
|;
@@ -24,12 +24,12 @@ sub dbStats {
# This function leaves the DB in an inconsistent state, the actual revision
# will have to be inserted directly after calling this function, otherwise
# the commit will fail.
-# Arguments: type [0..2], item ID, edit summary
+# Arguments: type [vrp], item ID, edit summary
# Returns: local revision, global revision
sub dbRevisionInsert {
my($self, $type, $iid, $editsum, $uid) = @_;
- my $table = [qw|vn releases producers|]->[$type];
+ my $table = {qw|v vn r releases p producers|}->{$type};
my $c = $self->dbRow(q|
INSERT INTO changes (type, requester, ip, comments, rev)
@@ -43,7 +43,7 @@ sub dbRevisionInsert {
))
RETURNING id, rev|,
$type, $uid||$self->authInfo->{id}, $self->reqIP, $editsum,
- $table, [qw|v r p|]->[$type], $iid
+ $table, $type, $iid
);
$self->dbExec(q|UPDATE !s SET latest = ? WHERE id = ?|, $table, $c->{id}, $iid);
@@ -54,7 +54,7 @@ sub dbRevisionInsert {
# Comparable to RevisionInsert, but creates a new item with a corresponding
# change. Same things about inconsistent state, etc.
-# Argumments: type [0..2], edit summary, [uid]
+# Argumments: type [vrp], edit summary, [uid]
# Returns: item id, global revision
sub dbItemInsert {
my($self, $type, $editsum, $uid) = @_;
@@ -70,7 +70,7 @@ sub dbItemInsert {
INSERT INTO !s (latest)
VALUES (?)
RETURNING id|,
- [qw|vn releases producers|]->[$type], $cid
+ {qw|v vn r releases p producers|}->{$type}, $cid
)->{id};
return ($iid, $cid);
@@ -94,7 +94,7 @@ sub dbRevisionGet {
'((c.type = ? AND vr.vid = ?) OR (c.type = ? AND rv.vid = ?))' => [0, $o{iid}, 1, $o{iid}],
) : (
$o{type} ? (
- 'c.type = ?' => { v=>0, r=>1, p=>2 }->{$o{type}} ) : (),
+ 'c.type = ?' => $o{type} ) : (),
$o{iid} ? (
'!sr.!sid = ?' => [ $o{type}, $o{type}, $o{iid} ] ) : (),
),
@@ -113,14 +113,14 @@ sub dbRevisionGet {
my @join = (
$o{iid} || $o{what} =~ /item/ || $o{hidden} || $o{releases} ? (
- 'LEFT JOIN vn_rev vr ON c.type = 0 AND c.id = vr.id',
- 'LEFT JOIN releases_rev rr ON c.type = 1 AND c.id = rr.id',
- 'LEFT JOIN producers_rev pr ON c.type = 2 AND c.id = pr.id',
+ q{LEFT JOIN vn_rev vr ON c.type = 'v' AND c.id = vr.id},
+ q{LEFT JOIN releases_rev rr ON c.type = 'r' AND c.id = rr.id},
+ q{LEFT JOIN producers_rev pr ON c.type = 'p' AND c.id = pr.id},
) : (),
$o{hidden} || $o{releases} ? (
- 'LEFT JOIN vn v ON c.type = 0 AND vr.vid = v.id',
- 'LEFT JOIN releases r ON c.type = 1 AND rr.rid = r.id',
- 'LEFT JOIN producers p ON c.type = 2 AND pr.pid = p.id',
+ q{LEFT JOIN vn v ON c.type = 'v' AND vr.vid = v.id},
+ q{LEFT JOIN releases r ON c.type = 'r' AND rr.rid = r.id},
+ q{LEFT JOIN producers p ON c.type = 'p' AND pr.pid = p.id},
) : (),
$o{what} =~ /user/ ? 'JOIN users u ON c.requester = u.id' : (),
$o{releases} ? 'LEFT JOIN releases_vn rv ON c.id = rv.rid' : (),
@@ -160,20 +160,6 @@ sub dbItemMod {
}
-# Returns a list of languages actually in use
-sub dbLanguages {
- my $self = shift;
- return [
- map $_->{lang}, @{$self->dbAll(q|
- SELECT DISTINCT rl.lang
- FROM releases r
- JOIN releases_lang rl ON rl.rid = r.latest
- WHERE r.hidden = FALSE|
- )}
- ];
-}
-
-
# Returns a random quote (hashref with keys = vid, quote)
sub dbRandomQuote {
return $_[0]->dbRow(q|
diff --git a/lib/VNDB/DB/Producers.pm b/lib/VNDB/DB/Producers.pm
index 65d1fbca..7c7da3ba 100644
--- a/lib/VNDB/DB/Producers.pm
+++ b/lib/VNDB/DB/Producers.pm
@@ -9,7 +9,7 @@ our @EXPORT = qw|dbProducerGet dbProducerEdit dbProducerAdd|;
# options: results, page, id, search, char, rev
-# what: extended, changes, vn
+# what: extended changes vn relations relgraph
sub dbProducerGet {
my $self = shift;
my %o = (
@@ -40,10 +40,12 @@ sub dbProducerGet {
push @join, $o{rev} ? 'JOIN producers p ON p.id = pr.pid' : 'JOIN producers p ON pr.id = p.latest';
push @join, 'JOIN changes c ON c.id = pr.id' if $o{what} =~ /changes/ || $o{rev};
push @join, 'JOIN users u ON u.id = c.requester' if $o{what} =~ /changes/;
+ push @join, 'JOIN relgraphs pg ON pg.id = p.rgraph' if $o{what} =~ /relgraph/;
- my $select = 'p.id, pr.type, pr.name, pr.original, pr.lang';
+ my $select = 'p.id, pr.type, pr.name, pr.original, pr.lang, pr.id AS cid, p.rgraph';
$select .= ', pr.desc, pr.alias, pr.website, p.hidden, p.locked' if $o{what} =~ /extended/;
$select .= q|, extract('epoch' from c.added) as added, c.requester, c.comments, p.latest, pr.id AS cid, u.username, c.rev| if $o{what} =~ /changes/;
+ $select .= ', pg.svg' if $o{what} =~ /relgraph/;
my($r, $np) = $self->dbPage(\%o, q|
SELECT !s
@@ -61,7 +63,8 @@ sub dbProducerGet {
} 0..$#$r;
push @{$r->[$r{$_->{pid}}]{vn}}, $_ for (@{$self->dbAll(q|
- SELECT MAX(vp.pid) AS pid, v.id, MAX(vr.title) AS title, MAX(vr.original) AS original, MIN(rr.released) AS date
+ SELECT MAX(vp.pid) AS pid, v.id, MAX(vr.title) AS title, MAX(vr.original) AS original, MIN(rr.released) AS date,
+ MAX(CASE WHEN vp.developer = true THEN 1 ELSE 0 END) AS developer, MAX(CASE WHEN vp.publisher = true THEN 1 ELSE 0 END) AS publisher
FROM releases_producers vp
JOIN releases_rev rr ON rr.id = vp.rid
JOIN releases r ON r.latest = rr.id
@@ -77,6 +80,22 @@ sub dbProducerGet {
)});
}
+ if(@$r && $o{what} =~ /relations/) {
+ my %r = map {
+ $r->[$_]{relations} = [];
+ ($r->[$_]{cid}, $_)
+ } 0..$#$r;
+
+ push @{$r->[$r{$_->{pid1}}]{relations}}, $_ for(@{$self->dbAll(q|
+ SELECT rel.pid1, rel.pid2 AS id, rel.relation, pr.name, pr.original
+ FROM producers_relations rel
+ JOIN producers p ON rel.pid2 = p.id
+ JOIN producers_rev pr ON p.latest = pr.id
+ WHERE rel.pid1 IN(!l)|,
+ [ keys %r ]
+ )});
+ }
+
return wantarray ? ($r, $np) : $r;
}
@@ -85,7 +104,7 @@ sub dbProducerGet {
# returns: ( local revision, global revision )
sub dbProducerEdit {
my($self, $pid, %o) = @_;
- my($rev, $cid) = $self->dbRevisionInsert(2, $pid, $o{editsum}, $o{uid});
+ my($rev, $cid) = $self->dbRevisionInsert('p', $pid, $o{editsum}, $o{uid});
insert_rev($self, $cid, $pid, \%o);
return ($rev, $cid);
}
@@ -95,14 +114,14 @@ sub dbProducerEdit {
# returns: ( item id, global revision )
sub dbProducerAdd {
my($self, %o) = @_;
- my($pid, $cid) = $self->dbItemInsert(2, $o{editsum}, $o{uid});
+ my($pid, $cid) = $self->dbItemInsert('p', $o{editsum}, $o{uid});
insert_rev($self, $cid, $pid, \%o);
return ($pid, $cid);
}
# helper function, inserts a producer revision
-# Arguments: global revision, item id, { columns in producers_rev }
+# Arguments: global revision, item id, { columns in producers_rev }, relations
sub insert_rev {
my($self, $cid, $pid, $o) = @_;
$self->dbExec(q|
@@ -110,6 +129,12 @@ sub insert_rev {
VALUES (!l)|,
[ $cid, $pid, @$o{qw| name original website type lang desc alias|} ]
);
+
+ $self->dbExec(q|
+ INSERT INTO producers_relations (pid1, pid2, relation)
+ VALUES (?, ?, ?)|,
+ $cid, $_->[1], $_->[0]
+ ) for (@{$o->{relations}});
}
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
index 672596da..9260b787 100644
--- a/lib/VNDB/DB/Releases.pm
+++ b/lib/VNDB/DB/Releases.pm
@@ -117,7 +117,7 @@ sub dbReleaseGet {
if($o{what} =~ /producers/) {
push(@{$r->[$r{$_->{rid}}]{producers}}, $_) for (@{$self->dbAll(q|
- SELECT rp.rid, p.id, pr.name, pr.original, pr.type
+ SELECT rp.rid, rp.developer, rp.publisher, p.id, pr.name, pr.original, pr.type
FROM releases_producers rp
JOIN producers p ON rp.pid = p.id
JOIN producers_rev pr ON pr.id = p.latest
@@ -137,7 +137,7 @@ sub dbReleaseGet {
}
if($o{what} =~ /media/) {
- ($_->{medium}=~s/\s+//||1)&&push(@{$r->[$r{$_->{rid}}]{media}}, $_) for (@{$self->dbAll(q|
+ push(@{$r->[$r{$_->{rid}}]{media}}, $_) for (@{$self->dbAll(q|
SELECT rid, medium, qty
FROM releases_media
WHERE rid IN(!l)|,
@@ -154,7 +154,7 @@ sub dbReleaseGet {
# returns: ( local revision, global revision )
sub dbReleaseEdit {
my($self, $rid, %o) = @_;
- my($rev, $cid) = $self->dbRevisionInsert(1, $rid, $o{editsum}, $o{uid});
+ my($rev, $cid) = $self->dbRevisionInsert('r', $rid, $o{editsum}, $o{uid});
insert_rev($self, $cid, $rid, \%o);
return ($rev, $cid);
}
@@ -164,7 +164,7 @@ sub dbReleaseEdit {
# returns: ( item id, global revision )
sub dbReleaseAdd {
my($self, %o) = @_;
- my($rid, $cid) = $self->dbItemInsert(1, $o{editsum}, $o{uid});
+ my($rid, $cid) = $self->dbItemInsert('r', $o{editsum}, $o{uid});
insert_rev($self, $cid, $rid, \%o);
return ($rid, $cid);
}
@@ -189,9 +189,9 @@ sub insert_rev {
) for (@{$o->{languages}});
$self->dbExec(q|
- INSERT INTO releases_producers (rid, pid)
- VALUES (?, ?)|,
- $cid, $_
+ INSERT INTO releases_producers (rid, pid, developer, publisher)
+ VALUES (?, ?, ?, ?)|,
+ $cid, $_->[0], $_->[1]?1:0, $_->[2]?1:0
) for (@{$o->{producers}});
$self->dbExec(q|
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index b2cd1a31..593c6415 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -5,11 +5,11 @@ use strict;
use warnings;
use Exporter 'import';
-our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel dbSessionAdd dbSessionDel dbSessionCheck|;
+our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserMessageCount dbSessionAdd dbSessionDel dbSessionCheck|;
# %options->{ username passwd mail order uid ip registered search results page what }
-# what: stats mymessages
+# what: stats extended
sub dbUserGet {
my $s = shift;
my %o = (
@@ -43,8 +43,12 @@ sub dbUserGet {
);
my @select = (
- qw|id username mail rank salt c_votes c_changes show_nsfw show_list skin customcss ip c_tags ign_votes|,
- q|encode(passwd, 'hex') AS passwd|, q|extract('epoch' from registered) as registered|,
+ qw|id username c_votes c_changes show_list c_tags|,
+ q|extract('epoch' from registered) as registered|,
+ $o{what} =~ /extended/ ? (
+ qw|mail rank salt skin customcss show_nsfw ign_votes|,
+ q|encode(passwd, 'hex') AS passwd|
+ ) : (),
$o{what} =~ /stats/ ? (
'(SELECT COUNT(*) FROM rlists WHERE uid = u.id) AS releasecount',
'(SELECT COUNT(DISTINCT rv.vid) FROM rlists rl JOIN releases r ON rl.rid = r.id JOIN releases_vn rv ON rv.rid = r.latest WHERE uid = u.id) AS vncount',
@@ -53,8 +57,6 @@ sub dbUserGet {
'(SELECT COUNT(DISTINCT tag) FROM tags_vn WHERE uid = u.id) AS tagcount',
'(SELECT COUNT(DISTINCT vid) FROM tags_vn WHERE uid = u.id) AS tagvncount',
) : (),
- $o{what} =~ /mymessages/ ?
- '(SELECT COUNT(*) FROM threads_boards tb JOIN threads t ON t.id = tb.tid WHERE tb.type = \'u\' AND tb.iid = u.id AND t.hidden = FALSE) AS mymessages' : (),
);
my($r, $np) = $s->dbPage(\%o, q|
@@ -110,6 +112,20 @@ sub dbUserDel {
}
+# Returns number of unread messages
+sub dbUserMessageCount { # uid
+ my($s, $uid) = @_;
+ return $s->dbRow(q{
+ SELECT SUM(tbi.count) AS cnt FROM (
+ SELECT t.count-COALESCE(tb.lastread,0)
+ FROM threads_boards tb
+ JOIN threads t ON t.id = tb.tid AND NOT t.hidden
+ WHERE tb.type = 'u' AND tb.iid = ?
+ ) AS tbi (count)
+ }, $uid)->{cnt}||0;
+}
+
+
# Adds a session to the database
# If no expiration is supplied the database default is used
# uid, 40 character session token, expiration time (timestamp)
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
index b3603985..bb2c1275 100644
--- a/lib/VNDB/DB/VN.pm
+++ b/lib/VNDB/DB/VN.pm
@@ -5,6 +5,7 @@ use strict;
use warnings;
use Exporter 'import';
use VNDB::Func 'gtintype';
+use Encode 'decode_utf8';
our @EXPORT = qw|dbVNGet dbVNAdd dbVNEdit dbVNImageId dbVNCache dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|;
@@ -76,7 +77,7 @@ sub dbVNGet {
$o{what} =~ /changes/ ?
'JOIN users u ON u.id = c.requester' : (),
$o{what} =~ /relgraph/ ?
- 'JOIN relgraph rg ON rg.id = v.rgraph' : (),
+ 'JOIN relgraphs vg ON vg.id = v.rgraph' : (),
);
my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]};
@@ -86,7 +87,7 @@ sub dbVNGet {
qw|vr.alias vr.image vr.img_nsfw vr.length vr.desc vr.l_wp vr.l_encubed vr.l_renai vr.l_vnn| ) : (),
$o{what} =~ /changes/ ? (
qw|c.requester c.comments v.latest u.username c.rev c.causedby|, q|extract('epoch' from c.added) as added|) : (),
- $o{what} =~ /relgraph/ ? 'rg.cmap' : (),
+ $o{what} =~ /relgraph/ ? 'vg.svg' : (),
$o{what} =~ /ranking/ ? '(SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_popularity > v.c_popularity) AS ranking' : (),
$tag_ids ?
qq|(SELECT AVG(tvb.rating) FROM tags_vn_bayesian tvb WHERE tvb.tag IN($tag_ids) AND tvb.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvb.vid) AS tagscore| : (),
@@ -101,6 +102,10 @@ sub dbVNGet {
join(', ', @select), join(' ', @join), \%where, $o{order},
);
+ if($o{what} =~ /relgraph/) {
+ $_->{svg} = decode_utf8($_->{svg}) for @$r;
+ }
+
if(@$r && $o{what} =~ /(anime|relations|screenshots)/) {
my %r = map {
$r->[$_]{anime} = [];
@@ -155,7 +160,7 @@ sub dbVNGet {
# returns: ( local revision, global revision )
sub dbVNEdit {
my($self, $id, %o) = @_;
- my($rev, $cid) = $self->dbRevisionInsert(0, $id, $o{editsum}, $o{uid});
+ my($rev, $cid) = $self->dbRevisionInsert('v', $id, $o{editsum}, $o{uid});
insert_rev($self, $cid, $id, \%o);
return ($rev, $cid);
}
@@ -165,7 +170,7 @@ sub dbVNEdit {
# returns: ( item id, global revision )
sub dbVNAdd {
my($self, %o) = @_;
- my($id, $cid) = $self->dbItemInsert(0, $o{editsum}, $o{uid});
+ my($id, $cid) = $self->dbItemInsert('v', $o{editsum}, $o{uid});
insert_rev($self, $cid, $id, \%o);
return ($id, $cid);
}
diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm
index cd4c4b62..ad38215e 100644
--- a/lib/VNDB/Func.pm
+++ b/lib/VNDB/Func.pm
@@ -149,10 +149,10 @@ sub gtintype {
sub liststat {
my $l = shift;
return '' if !$l;
- my $rs = $YAWF::OBJ->{vn_rstat}[$l->{rstat}];
+ my $rs = mt('_rlst_rstat_'.$l->{rstat});
$rs = qq|<b class="done">$rs</b>| if $l->{rstat} == 2; # Obtained
$rs = qq|<b class="todo">$rs</b>| if $l->{rstat} < 2; # Unknown/pending
- my $vs = $YAWF::OBJ->{vn_vstat}[$l->{vstat}];
+ my $vs = mt('_rlst_vstat_'.$l->{vstat});
$vs = qq|<b class="done">$vs</b>| if $l->{vstat} == 2; # Finished
$vs = qq|<b class="todo">$vs</b>| if $l->{vstat} == 0 || $l->{vstat} == 4; # Unknown/dropped
return "$rs / $vs";
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm
index 1d74ac6a..12b55029 100644
--- a/lib/VNDB/Handler/Discussions.pm
+++ b/lib/VNDB/Handler/Discussions.pm
@@ -29,6 +29,11 @@ sub thread {
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';
@@ -293,7 +298,7 @@ sub board {
order => $type eq 'an' ? 't.id DESC' : 'tpl.date DESC',
);
- $self->htmlHeader(title => $title, noindex => !@$list);
+ $self->htmlHeader(title => $title, noindex => !@$list || $type eq 'u');
$self->htmlMainTabs($type, $obj, 'disc') if $iid;
div class => 'mainbox';
diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm
index 80a9e9ea..9625372b 100644
--- a/lib/VNDB/Handler/Misc.pm
+++ b/lib/VNDB/Handler/Misc.pm
@@ -66,10 +66,9 @@ sub homepage {
my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1, hidden => 1);
ul;
for (@$changes) {
- my $t = (qw|v r p|)[$_->{type}];
li;
- lit mt '_home_recentchanges_item', $t,
- sprintf('<a href="%s" title="%s">%s</a>', "/$t$_->{iid}.$_->{rev}",
+ lit mt '_home_recentchanges_item', $_->{type},
+ sprintf('<a href="%s" title="%s">%s</a>', "/$_->{type}$_->{iid}.$_->{rev}",
xml_escape($_->{ioriginal}||$_->{ititle}), xml_escape shorten $_->{ititle}, 33),
$_;
end;
@@ -295,6 +294,12 @@ sub docpage {
close $F;
$ii;
}eg;
+ s{^:TOP5CONTRIB:$}{
+ my $l = $self->dbUserGet(results => 6, order => 'c_changes DESC');
+ '<dl>'.join('', map $_->{id} == 1 ? () :
+ sprintf('<dt><a href="/u%d">%s</a></dt><dd>%d</dd>', $_->{id}, $_->{username}, $_->{c_changes}),
+ @$l).'</dl>';
+ }eg;
}
$self->htmlHeader(title => $title);
@@ -331,7 +336,7 @@ sub itemmod {
my $obj = $type eq 'v' ? $self->dbVNGet(id => $iid)->[0] :
$type eq 'r' ? $self->dbReleaseGet(id => $iid, what => 'vn extended')->[0] :
- $self->dbProducerGet(id => $iid)->[0];
+ $self->dbProducerGet(id => $iid, what => 'extended')->[0];
return 404 if !$obj->{id};
$self->dbItemMod($type, $iid, $act eq 'hide' ? (hidden => !$obj->{hidden}) : (locked => !$obj->{locked}));
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
index e6477567..4e75962b 100644
--- a/lib/VNDB/Handler/Producers.pm
+++ b/lib/VNDB/Handler/Producers.pm
@@ -3,11 +3,12 @@ package VNDB::Handler::Producers;
use strict;
use warnings;
-use YAWF ':html', ':xml';
+use YAWF ':html', ':xml', 'xml_escape';
use VNDB::Func;
YAWF::register(
+ qr{p([1-9]\d*)/rg} => \&rg,
qr{p([1-9]\d*)(?:\.([1-9]\d*))?} => \&page,
qr{p(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit|/new)}
=> \&edit,
@@ -16,12 +17,34 @@ YAWF::register(
);
+sub rg {
+ my($self, $pid) = @_;
+
+ my $p = $self->dbProducerGet(id => $pid, what => 'relgraph')->[0];
+ return 404 if !$p->{id} || !$p->{rgraph};
+
+ my $title = mt '_prodrg_title', $p->{name};
+ return if $self->htmlRGHeader($title, 'p', $p);
+
+ $p->{svg} =~ s/\$___(_prodrel_[a-z]+)____\$/mt $1/eg;
+ $p->{svg} =~ s/\$(_lang_[a-z]+)_\$/mt $1/eg;
+ $p->{svg} =~ s/\$(_ptype_[a-z]+)_\$/mt $1/eg;
+
+ div class => 'mainbox';
+ h1 $title;
+ p class => 'center';
+ lit $p->{svg};
+ end;
+ end;
+ $self->htmlFooter;
+}
+
sub page {
my($self, $pid, $rev) = @_;
my $p = $self->dbProducerGet(
id => $pid,
- what => 'vn extended'.($rev ? ' changes' : ''),
+ what => 'vn extended relations'.($rev ? ' changes' : ''),
$rev ? ( rev => $rev ) : ()
)->[0];
return 404 if !$p->{id};
@@ -31,7 +54,7 @@ sub page {
return if $self->htmlHiddenMessage('p', $p);
if($rev) {
- my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended')->[0];
+ my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended relations')->[0];
$self->htmlRevision('p', $prev, $p,
[ type => serialize => sub { mt "_ptype_$_[0]" } ],
[ name => diff => 1 ],
@@ -40,6 +63,12 @@ sub page {
[ lang => serialize => sub { "$_[0] (".mt("_lang_$_[0]").')' } ],
[ website => diff => 1 ],
[ desc => diff => 1 ],
+ [ relations => join => '<br />', split => sub {
+ my @r = map sprintf('%s: <a href="/p%d" title="%s">%s</a>',
+ mt("_prodrel_$_->{relation}"), $_->{id}, xml_escape($_->{original}||$_->{name}), xml_escape shorten $_->{name}, 40
+ ), sort { $a->{id} <=> $b->{id} } @{$_[0]};
+ return @r ? @r : (mt '_proddiff_none');
+ }],
);
}
@@ -56,6 +85,23 @@ sub page {
}
end;
+ if(@{$p->{relations}}) {
+ my %rel;
+ push @{$rel{$_->{relation}}}, $_
+ for (sort { $a->{name} cmp $b->{name} } @{$p->{relations}});
+ p class => 'center';
+ txt "\n";
+ for my $r (sort keys %rel) {
+ txt mt("_prodrel_$r").': ';
+ for (@{$rel{$r}}) {
+ a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 40;
+ txt ', ' if $_ ne $rel{$r}[$#{$rel{$r}}];
+ }
+ txt "\n";
+ }
+ end;
+ }
+
if($p->{desc}) {
p class => 'description';
lit bb2html $p->{desc};
@@ -75,6 +121,8 @@ sub page {
lit $self->{l10n}->datestr($_->{date});
end;
a href => "/v$_->{id}", title => $_->{original}, $_->{title};
+ b class => 'grayedout', ' ('.join(', ',
+ $_->{developer} ? mt '_prodpage_dev' : (), $_->{publisher} ? mt '_prodpage_pub' : ()).')';
end;
}
end;
@@ -89,36 +137,55 @@ sub page {
sub edit {
my($self, $pid, $rev) = @_;
- my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes extended', $rev ? (rev => $rev) : ())->[0];
+ my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes extended relations', $rev ? (rev => $rev) : ())->[0];
return 404 if $pid && !$p->{id};
$rev = undef if !$p || $p->{cid} == $p->{latest};
return $self->htmlDenied if !$self->authCan('edit')
|| $pid && ($p->{locked} && !$self->authCan('lock') || $p->{hidden} && !$self->authCan('del'));
- my %b4 = !$pid ? () : map { $_ => $p->{$_} } qw|type name original lang website desc alias|;
+ my %b4 = !$pid ? () : (
+ (map { $_ => $p->{$_} } qw|type name original lang website desc alias|),
+ prodrelations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{name}, sort { $a->{id} <=> $b->{id} } @{$p->{relations}}),
+ );
my $frm;
if($self->reqMethod eq 'POST') {
$frm = $self->formValidate(
- { name => 'type', enum => $self->{producer_types} },
- { name => 'name', maxlength => 200 },
- { name => 'original', required => 0, maxlength => 200, default => '' },
- { name => 'alias', required => 0, maxlength => 500, default => '' },
- { name => 'lang', enum => $self->{languages} },
- { name => 'website', required => 0, template => 'url', default => '' },
- { name => 'desc', required => 0, maxlength => 5000, default => '' },
- { name => 'editsum', maxlength => 5000 },
+ { name => 'type', enum => $self->{producer_types} },
+ { name => 'name', maxlength => 200 },
+ { name => 'original', required => 0, maxlength => 200, default => '' },
+ { name => 'alias', required => 0, maxlength => 500, default => '' },
+ { name => 'lang', enum => $self->{languages} },
+ { name => 'website', required => 0, template => 'url', default => '' },
+ { name => 'desc', required => 0, maxlength => 5000, default => '' },
+ { name => 'prodrelations', required => 0, maxlength => 5000, default => '' },
+ { name => 'editsum', maxlength => 5000 },
);
if(!$frm->{_err}) {
+ # parse
+ my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$pid || $2 != $pid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{prodrelations} ];
+
+ # normalize
+ $frm->{prodrelations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations};
+
return $self->resRedirect("/p$pid", 'post')
if $pid && !grep $frm->{$_} ne $b4{$_}, keys %b4;
+ $frm->{relations} = $relations;
$rev = 1;
+ my $cid;
if($pid) {
- ($rev) = $self->dbProducerEdit($pid, %$frm);
+ ($rev, $cid) = $self->dbProducerEdit($pid, %$frm);
} else {
- ($pid) = $self->dbProducerAdd(%$frm);
+ ($pid, $cid) = $self->dbProducerAdd(%$frm);
+ }
+
+ # update reverse relations
+ if(!$pid && $#$relations >= 0 || $pid && $frm->{prodrelations} ne $b4{prodrelations}) {
+ my %old = $pid ? (map { $_->{id} => $_->{relation} } @{$p->{relations}}) : ();
+ my %new = map { $_->[1] => $_->[0] } @$relations;
+ _updreverse($self, \%old, \%new, $pid, $cid, $rev);
}
return $self->resRedirect("/p$pid.$rev", 'post');
@@ -133,7 +200,8 @@ sub edit {
$self->htmlHeader(title => $title, noindex => 1);
$self->htmlMainTabs('p', $p, 'edit') if $pid;
$self->htmlEditMessage('p', $p, $title);
- $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, 'pedit_geninfo' => [mt('_pedit_form_generalinfo'),
+ $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 },
+ 'pedit_geninfo' => [ mt('_pedit_form_generalinfo'),
[ select => name => mt('_pedit_form_type'), short => 'type',
options => [ map [ $_, mt "_ptype_$_" ], sort @{$self->{producer_types}} ] ],
[ input => name => mt('_pedit_form_name'), short => 'name' ],
@@ -145,10 +213,70 @@ sub edit {
options => [ map [ $_, "$_ (".mt("_lang_$_").')' ], sort @{$self->{languages}} ] ],
[ input => name => mt('_pedit_form_website'), short => 'website' ],
[ text => name => mt('_pedit_form_desc').'<br /><b class="standout">'.mt('_inenglish').'</b>', short => 'desc', rows => 6 ],
+ ], 'pedit_rel' => [ mt('_pedit_form_rel'),
+ [ hidden => short => 'prodrelations' ],
+ [ static => nolabel => 1, content => sub {
+ h2 mt '_pedit_rel_sel';
+ table;
+ tbody id => 'relation_tbl';
+ # to be filled using javascript
+ end;
+ end;
+
+ h2 mt '_pedit_rel_add';
+ table;
+ Tr id => 'relation_new';
+ td class => 'tc_prod';
+ input type => 'text', class => 'text';
+ end;
+ td class => 'tc_rel';
+ Select;
+ option value => $_, mt "_prodrel_$_"
+ for (sort { $self->{prod_relations}{$a}[0] <=> $self->{prod_relations}{$b}[0] } keys %{$self->{prod_relations}});
+ end;
+ end;
+ td class => 'tc_add';
+ a href => '#', mt '_pedit_rel_addbut';
+ end;
+ end;
+ end;
+ }],
]);
$self->htmlFooter;
}
+# !IMPORTANT!: Don't forget to update this function when
+# adding/removing fields to/from producer entries!
+sub _updreverse {
+ my($self, $old, $new, $pid, $cid, $rev) = @_;
+ my %upd;
+
+ # compare %old and %new
+ for (keys %$old, keys %$new) {
+ if(exists $$old{$_} and !exists $$new{$_}) {
+ $upd{$_} = undef;
+ } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} ne $$new{$_})) {
+ $upd{$_} = $self->{prod_relations}{$$new{$_}}[1];
+ }
+ }
+
+ return if !keys %upd;
+
+ # edit all related producers
+ for my $i (keys %upd) {
+ my $r = $self->dbProducerGet(id => $i, what => 'extended relations')->[0];
+ my @newrel = map $_->{id} != $pid ? [ $_->{relation}, $_->{id} ] : (), @{$r->{relations}};
+ push @newrel, [ $upd{$i}, $pid ] if $upd{$i};
+ $self->dbProducerEdit($i,
+ relations => \@newrel,
+ editsum => "Reverse relation update caused by revision p$pid.$rev",
+ causedby => $cid,
+ uid => 1, # Multi - hardcoded
+ ( map { $_ => $r->{$_} } qw|type name original lang website desc alias| )
+ );
+ }
+}
+
sub list {
my($self, $char) = @_;
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index d916e056..7365f235 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -54,17 +54,16 @@ sub page {
[ notes => diff => 1 ],
[ platforms => join => ', ', split => sub { map mt("_plat_$_"), @{$_[0]} } ],
[ media => join => ', ', split => sub {
- map {
- my $med = $self->{media}{$_->{medium}};
- $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0]
- } @{$_[0]};
+ map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]}
} ],
[ resolution => serialize => sub { $self->{resolutions}[$_[0]][0] } ],
[ voiced => serialize => sub { mt '_voiced_'.$_[0] } ],
[ ani_story => serialize => sub { mt '_animated_'.$_[0] } ],
[ ani_ero => serialize => sub { mt '_animated_'.$_[0] } ],
[ producers => join => '<br />', split => sub {
- map sprintf('<a href="/p%d" title="%s">%s</a>', $_->{id}, $_->{original}||$_->{name}, shorten $_->{name}, 50), @{$_[0]};
+ map sprintf('<a href="/p%d" title="%s">%s</a> (%s)', $_->{id}, $_->{original}||$_->{name}, shorten($_->{name}, 50),
+ join(', ', $_->{developer} ? mt '_reldiff_developer' :(), $_->{publisher} ? mt '_reldiff_publisher' :())
+ ), @{$_[0]};
} ],
);
}
@@ -154,11 +153,9 @@ sub _infotable {
if(@{$r->{media}}) {
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_relinfo_media', scalar @{$r->{media}};
- # TODO: TL the media
- td join ', ', map {
- my $med = $self->{media}{$_->{medium}};
- $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0]
- } @{$r->{media}};
+ td join ', ', map
+ $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1),
+ @{$r->{media}};
end;
}
@@ -199,16 +196,19 @@ sub _infotable {
end;
}
- if(@{$r->{producers}}) {
- Tr ++$i % 2 ? (class => 'odd') : ();
- td mt '_relinfo_producer', scalar @{$r->{producers}};
- td;
- for (@{$r->{producers}}) {
- a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 60;
- br if $_ != $r->{producers}[$#{$r->{producers}}];
- }
- end;
- end;
+ for my $t (qw|developer publisher|) {
+ my @prod = grep $_->{$t}, @{$r->{producers}};
+ if(@prod) {
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td mt "_relinfo_$t", scalar @prod;
+ td;
+ for (@prod) {
+ a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 60;
+ br if $_ != $prod[$#prod];
+ }
+ end;
+ end;
+ }
}
if($r->{gtin}) {
@@ -241,14 +241,14 @@ sub _infotable {
td;
Select id => 'listsel', name => 'listsel';
option mt !$rl ? '_relinfo_user_notlist' :
- ('_relinfo_user_inlist', $self->{vn_rstat}[$rl->{rstat}], $self->{vn_vstat}[$rl->{vstat}]);
+ ('_relinfo_user_inlist', mt('_rlst_rstat_'.$rl->{rstat}), mt('_rlst_vstat_'.$rl->{vstat}));
optgroup label => mt '_relinfo_user_setr';
- option value => "r$_", $self->{vn_rstat}[$_]
- for (0..$#{$self->{vn_rstat}});
+ option value => "r$_", mt '_rlst_rstat_'.$_
+ for (@{$self->{rlst_rstat}});
end;
optgroup label => mt '_relinfo_user_setv';
- option value => "v$_", $self->{vn_vstat}[$_]
- for (0..$#{$self->{vn_vstat}});
+ option value => "v$_", mt '_rlst_vstat_'.$_
+ for (@{$self->{rlst_vstat}});
end;
option value => 'del', mt '_relinfo_user_del' if $rl;
end;
@@ -289,7 +289,10 @@ sub edit {
(map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released
notes minage platforms patch resolution voiced freeware doujin ani_story ani_ero|),
media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}),
- producers => join('|||', map "$_->{id},$_->{name}", sort { $a->{id} <=> $b->{id} } @{$r->{producers}}),
+ producers => join('|||', map
+ sprintf('%d,%d,%s', $_->{id}, ($_->{developer}?1:0)+($_->{publisher}?2:0), $_->{name}),
+ sort { $a->{id} <=> $b->{id} } @{$r->{producers}}
+ ),
);
$b4{vn} = join('|||', map "$_->{vid},$_->{title}", @$vn);
my $frm;
@@ -325,7 +328,7 @@ sub edit {
if(!$frm->{_err}) {
# de-serialize
$media = [ map [ split / / ], split /,/, $frm->{media} ];
- $producers = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{producers} ];
+ $producers = [ map { /^([0-9]+),([1-3])/ ? [ $1, $2&1?1:0, $2&2?1:0] : () } split /\|\|\|/, $frm->{producers} ];
$new_vn = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{vn} ];
$frm->{platforms} = [ grep $_, @{$frm->{platforms}} ];
$frm->{$_} = $frm->{$_} ? 1 : 0 for (qw|patch freeware doujin|);
@@ -335,7 +338,7 @@ sub edit {
my $same = $rid &&
(join(',', sort @{$b4{platforms}}) eq join(',', sort @{$frm->{platforms}})) &&
- (join(',', sort @$producers) eq join(',', sort map $_->{id}, @{$r->{producers}})) &&
+ (join(',', map join(' ', @$_), sort { $a->[0] <=> $b->[0] } @$producers) eq join(',', sort map sprintf('%d %d %d',$_->{id}, $_->{developer}?1:0, $_->{publisher}?1:0), sort { $a->{id} <=> $b->{id} } @{$r->{producers}})) &&
(join(',', sort @$new_vn) eq join(',', sort map $_->{vid}, @$vn)) &&
(join(',', sort @{$b4{languages}}) eq join(',', sort @{$frm->{languages}})) &&
!grep !/^(platforms|producers|vn|languages)$/ && $frm->{$_} ne $b4{$_}, keys %b4;
@@ -370,7 +373,7 @@ sub edit {
$frm->{original} = $v->{original} if !defined $frm->{original} && !$r;
my $title = mt $rid ? ($copy ? '_redit_title_copy' : '_redit_title_edit', $r->{title}) : ('_redit_title_add', $v->{title});
- $self->htmlHeader(js => 'forms', title => $title, noindex => 1);
+ $self->htmlHeader(title => $title, noindex => 1);
$self->htmlMainTabs('r', $r, $copy ? 'copy' : 'edit') if $rid;
$self->htmlMainTabs('v', $v, 'edit') if $vid;
$self->htmlEditMessage('r', $r, $title, $copy);
@@ -435,7 +438,7 @@ sub _form {
h2 mt '_redit_form_media';
div id => 'media_div';
Select;
- option value => $_, class => $self->{media}{$_}[1] ? 'qty' : 'noqty', $self->{media}{$_}[0]
+ option value => $_, class => $self->{media}{$_} ? 'qty' : 'noqty', mt "_med_$_", 1
for (sort keys %{$self->{media}});
end;
end;
@@ -446,13 +449,17 @@ sub _form {
[ hidden => short => 'producers' ],
[ static => nolabel => 1, content => sub {
h2 mt('_redit_form_prod_sel');
- div id => 'producerssel';
- end;
+ table; tbody id => 'producer_tbl'; end; end;
h2 mt('_redit_form_prod_add');
- div;
- input type => 'text', class => 'text';
- a href => '#', 'add';
- end;
+ table; Tr;
+ td class => 'tc_name'; input id => 'producer_input', type => 'text', class => 'text'; end;
+ td class => 'tc_role'; Select id => 'producer_role';
+ option value => 1, mt '_redit_form_prod_dev';
+ option value => 2, selected => 'selected', mt '_redit_form_prod_pub';
+ option value => 3, mt '_redit_form_prod_both';
+ end; end;
+ td class => 'tc_add'; a id => 'producer_add', href => '#', mt '_redit_form_prod_addbut'; end;
+ end; end;
}],
],
@@ -460,12 +467,11 @@ sub _form {
[ hidden => short => 'vn' ],
[ static => nolabel => 1, content => sub {
h2 mt('_redit_form_vn_sel');
- div id => 'vnsel';
- end;
+ table; tbody id => 'vn_tbl'; end; end;
h2 mt('_redit_form_vn_add');
div;
- input type => 'text', class => 'text';
- a href => '#', 'add';
+ input id => 'vn_input', type => 'text', class => 'text';
+ a href => '#', id => 'vn_add', mt '_redit_form_vn_addbut';
end;
}],
],
@@ -484,7 +490,7 @@ sub browse {
{ name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} },
{ name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} },
{ name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] },
- { name => 'tp', required => 0, default => -1, enum => [ -1, @{$self->{release_types}} ] },
+ { name => 'tp', required => 0, default => '', enum => [ '', @{$self->{release_types}} ] },
{ name => 'pa', required => 0, default => 0, enum => [ 0..2 ] },
{ name => 'fw', required => 0, default => 0, enum => [ 0..2 ] },
{ name => 'do', required => 0, default => 0, enum => [ 0..2 ] },
@@ -503,7 +509,7 @@ sub browse {
$f->{ln}[0] ? (languages => $f->{ln}) : (),
$f->{me}[0] ? (media => $f->{me}) : (),
$f->{re}[0] ? (resolutions => $f->{re} ) : (),
- $f->{tp} >= 0 ? (type => $f->{tp}) : (),
+ $f->{tp} ? (type => $f->{tp}) : (),
$f->{ma_a} || $f->{ma_m} ? (minage => [$f->{ma_m}, $f->{ma_a}]) : (),
$f->{pa} ? (patch => $f->{pa}) : (),
$f->{fw} ? (freeware => $f->{fw}) : (),
@@ -614,7 +620,7 @@ sub _filters {
end;
end;
$self->htmlFormPart($f, [ select => short => 'tp', name => mt('_rbrowse_type'),
- options => [ [-1, mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]);
+ options => [ ['', mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]);
$self->htmlFormPart($f, [ select => short => 'pa', name => mt('_rbrowse_patch'),
options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_patchonly'], [2, mt '_rbrowse_patchnone']]]);
$self->htmlFormPart($f, [ select => short => 'fw', name => mt('_rbrowse_freeware'),
@@ -629,7 +635,7 @@ sub _filters {
txt mt '_rbrowse_languages';
b ' ('.mt('_rbrowse_boolor').')';
end;
- for my $i (sort @{$self->dbLanguages}) {
+ for my $i (@{$self->{languages}}) {
span;
input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", grep($_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : ();
label for => "lang_$i";
@@ -660,7 +666,7 @@ sub _filters {
for my $i (sort keys %{$self->{media}}) {
span;
input type => 'checkbox', name => 'me', value => $i, id => "med_$i", grep($_ eq $i, @{$f->{me}}) ? (checked => 'checked') : ();
- label for => "med_$i", $self->{media}{$i}[0];
+ label for => "med_$i", mt "_med_$i", 1;
end;
}
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
index ae240d38..4c969436 100644
--- a/lib/VNDB/Handler/Tags.pm
+++ b/lib/VNDB/Handler/Tags.pm
@@ -36,7 +36,7 @@ sub tagpage {
);
return 404 if $f->{_err};
my $tagspoil = $self->reqCookie('tagspoil');
- $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 1 if $f->{m} == -1;
+ $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 0 if $f->{m} == -1;
my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->dbTagVNs(
tag => $tag,
@@ -422,7 +422,7 @@ sub vntagmod {
my $frm;
my $title = mt '_tagv_title', $v->{title};
- $self->htmlHeader(title => $title, noindex => 1, js => 'forms');
+ $self->htmlHeader(title => $title, noindex => 1);
$self->htmlMainTabs('v', $v, 'tagmod');
div class => 'mainbox';
h1 $title;
@@ -438,43 +438,44 @@ sub vntagmod {
$self->htmlForm({ frm => $frm, action => "/v$vid/tagmod", nosubmit => 1 }, tagmod => [ mt('_tagv_frm_title'),
[ hidden => short => 'taglinks', value => '' ],
[ static => nolabel => 1, content => sub {
- table id => 'tagtable';
+ table class => 'tgl';
thead;
Tr;
td '';
- td colspan => 2, class => 'tc2_1', mt '_tagv_col_you';
- td colspan => 2, class => 'tc3_1', mt '_tagv_col_others';
+ td colspan => 2, class => 'tc_you', mt '_tagv_col_you';
+ td colspan => 2, class => 'tc_others', mt '_tagv_col_others';
end;
Tr;
- my $i=0;
- td class => 'tc'.++$i, mt '_tagv_col_'.$_ for(qw|tag rating spoiler rating spoiler|);
+ td class => 'tc_tagname', mt '_tagv_col_tag';
+ td class => 'tc_myvote', mt '_tagv_col_rating';
+ td class => 'tc_myspoil', mt '_tagv_col_spoiler';
+ td class => 'tc_allvote', mt '_tagv_col_rating';
+ td class => 'tc_allspoil', mt '_tagv_col_spoiler';
end;
end;
tfoot; Tr;
td colspan => 5;
input type => 'submit', class => 'submit', value => mt('_tagv_save'), style => 'float: right';
- input type => 'text', class => 'text', name => 'addtag', value => '';
- input type => 'button', class => 'submit', value => mt '_tagv_add';
+ input id => 'tagmod_tag', type => 'text', class => 'text', value => '';
+ input id => 'tagmod_add', type => 'button', class => 'submit', value => mt '_tagv_add';
br;
p;
lit mt '_tagv_addmsg';
end;
end;
end; end;
- tbody;
+ tbody id => 'tagtable';
for my $t (sort { $a->{name} cmp $b->{name} } @$tags) {
my $m = (grep $_->{tag} == $t->{id}, @$my)[0] || {};
- Tr;
- td class => 'tc1';
- a href => "/g$t->{id}", $t->{name};
- end;
- td class => 'tc2', $m->{vote}||0;
- td class => 'tc3', defined $m->{spoiler} ? $m->{spoiler} : -1;
- td class => 'tc4';
+ Tr id => "tgl_$t->{id}";
+ td class => 'tc_tagname'; a href => "/g$t->{id}", $t->{name}; end;
+ td class => 'tc_myvote', $m->{vote}||0;
+ td class => 'tc_myspoil', defined $m->{spoiler} ? $m->{spoiler} : -1;
+ td class => 'tc_allvote';
tagscore !$m->{vote} ? $t->{rating} : $t->{cnt} == 1 ? 0 : ($t->{rating}*$t->{cnt} - $m->{vote}) / ($t->{cnt}-1);
i ' ('.($t->{cnt} - ($m->{vote} ? 1 : 0)).')';
end;
- td class => 'tc5', sprintf '%.2f', $t->{spoiler};
+ td class => 'tc_allspoil', sprintf '%.2f', $t->{spoiler};
end;
}
end;
@@ -529,7 +530,7 @@ sub usertags {
header => [
sub {
td class => 'tc1';
- b id => 'relhidall';
+ b id => 'expandall';
lit '<i>&#9656;</i> '.mt('_tagu_col_num').' ';
end;
lit $f->{s} eq 'cnt' && $f->{o} eq 'a' ? "\x{25B4}" : qq|<a href="/u$u->{id}/tags?o=a;s=cnt">\x{25B4}</a>|;
@@ -542,7 +543,7 @@ sub usertags {
row => sub {
my($s, $n, $l) = @_;
Tr $n % 2 ? (class => 'odd') : ();
- td class => 'tc1 relhid_but', id => "tag$l->{id}";
+ td class => 'tc1 collapse_but', id => "tag$l->{id}";
lit "<i>&#9656;</i> $l->{cnt}";
end;
td class => 'tc2', colspan => 2;
@@ -550,7 +551,7 @@ sub usertags {
end;
end;
for(@{$l->{vns}}) {
- Tr class => "relhid tag$l->{id}";
+ Tr class => "collapse collapse_tag$l->{id}";
td class => 'tc1_1';
tagscore $_->{vote};
end;
@@ -652,7 +653,7 @@ sub tagxml {
my($list, $np) = $self->dbTagGet(
$q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q),
- results => 10,
+ results => 15,
page => 1,
);
diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm
index 7d1a9304..6a0d6c9e 100644
--- a/lib/VNDB/Handler/ULists.pm
+++ b/lib/VNDB/Handler/ULists.pm
@@ -69,7 +69,7 @@ sub rlist {
return $self->htmlDenied() if !$uid;
my $f = $self->formValidate(
- { name => 'e', required => 1, enum => [ 'del', map("r$_", 0..$#{$self->{vn_rstat}}), map("v$_", 0..$#{$self->{vn_vstat}}) ] },
+ { name => 'e', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
);
return 404 if $f->{_err};
@@ -212,7 +212,7 @@ sub vnlist {
if($own && $self->reqMethod eq 'POST') {
my $frm = $self->formValidate(
{ name => 'sel', required => 0, default => 0, multi => 1, template => 'int' },
- { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", 0..$#{$self->{vn_rstat}}), map("v$_", 0..$#{$self->{vn_vstat}}) ] },
+ { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
);
if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) {
$self->dbVNListDel($uid, $frm->{sel}) if $frm->{batchedit} eq 'del';
@@ -229,7 +229,7 @@ sub vnlist {
uid => $uid,
results => 50,
page => $f->{p},
- order => $f->{s}.' '.($f->{o} eq 'd' ? 'DESC' : 'ASC'),
+ order => $f->{s}.' '.($f->{o} eq 'd' ? 'DESC' : 'ASC').($f->{s} eq 'vote' ? ', title ASC' : ''),
voted => $f->{v},
$f->{c} ne 'all' ? (char => $f->{c}) : (),
);
@@ -285,7 +285,7 @@ sub _vnlist_browse {
pageurl => $url->('page'),
header => [
[ mt('_rlist_col_title') => 'title', 3 ],
- sub { td class => 'tc2', id => 'relhidall'; lit '<i>&#9656;</i>'.mt('_rlist_col_releases').'*'; end; },
+ sub { td class => 'tc2', id => 'expandall'; lit '<i>&#9656;</i>'.mt('_rlist_col_releases').'*'; end; },
[ mt('_rlist_col_vote') => 'vote' ],
],
row => sub {
@@ -294,7 +294,7 @@ sub _vnlist_browse {
td class => 'tc1', colspan => 3;
a href => "/v$i->{vid}", title => $i->{original}||$i->{title}, shorten $i->{title}, 70;
end;
- td class => 'tc2'.(@{$i->{rels}} ? ' relhid_but' : ''), id => 'vid'.$i->{vid};
+ td class => 'tc2'.(@{$i->{rels}} ? ' collapse_but' : ''), id => 'vid'.$i->{vid};
lit '<i>&#9656;</i>';
my $obtained = grep $_->{rstat}==2, @{$i->{rels}};
my $finished = grep $_->{vstat}==2, @{$i->{rels}};
@@ -307,7 +307,7 @@ sub _vnlist_browse {
end;
for (@{$i->{rels}}) {
- Tr class => "relhid vid$i->{vid}";
+ Tr class => "collapse relhid collapse_vid$i->{vid}";
td class => 'tc1'.($own ? ' own' : '');
input type => 'checkbox', name => 'sel', value => $_->{rid}
if $own;
@@ -333,12 +333,12 @@ sub _vnlist_browse {
Select id => 'batchedit', name => 'batchedit';
option mt '_rlist_selection';
optgroup label => mt '_rlist_changerel';
- option value => "r$_", $self->{vn_rstat}[$_]
- for (0..$#{$self->{vn_rstat}});
+ option value => "r$_", mt "_rlst_rstat_$_"
+ for (@{$self->{rlst_rstat}});
end;
optgroup label => mt '_rlist_changeplay';
- option value => "v$_", $self->{vn_vstat}[$_]
- for (0..$#{$self->{vn_vstat}});
+ option value => "v$_", mt "_rlst_vstat_$_"
+ for (@{$self->{rlst_vstat}});
end;
option value => 'del', mt '_rlist_del';
end;
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index d6193ddc..39aecbfc 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -267,7 +267,7 @@ sub edit {
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];
+ my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'extended')->[0];
return 404 if !$u->{id};
# check POST data
@@ -386,7 +386,7 @@ sub posts {
[ '' ],
[ '' ],
[ mt '_uposts_col_date' ],
- sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_uposts_col_title'; end; }
+ sub { td; a href => '#', id => 'expandlist', mt '_js_expand'; txt mt '_uposts_col_title'; end; }
],
row => sub {
my($s, $n, $l) = @_;
@@ -396,7 +396,7 @@ sub posts {
td class => 'tc3', $self->{l10n}->date($l->{date});
td class => 'tc4'; a href => "/t$l->{tid}.$l->{num}", $l->{title}; end;
end;
- Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden';
+ Tr class => $n % 2 ? 'collapse msgsum odd hidden' : 'collapse msgsum hidden';
td colspan => 4;
lit bb2html $l->{msg}, 150;
end;
diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm
index 2a6d6cd7..bca21151 100644
--- a/lib/VNDB/Handler/VNBrowse.pm
+++ b/lib/VNDB/Handler/VNBrowse.pm
@@ -25,7 +25,7 @@ sub list {
{ name => 'pl', required => 0, multi => 1, enum => $self->{platforms}, default => '' },
{ name => 'ti', required => 0, default => '', maxlength => 200 },
{ name => 'te', required => 0, default => '', maxlength => 200 },
- { name => 'sp', required => 0, default => $self->reqCookie('tagspoil') =~ /^([0-2])$/ ? $1 : 1, enum => [0..2] },
+ { name => 'sp', required => 0, default => $self->reqCookie('tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] },
);
return 404 if $f->{_err};
$f->{q} ||= $f->{sq};
@@ -70,7 +70,7 @@ sub list {
$self->resRedirect('/v'.$list->[0]{id}, 'temp')
if $f->{q} && @$list == 1;
- $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q}, js => 'forms');
+ $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q});
_filters($self, $f, $char, \@ignored);
my $url = "/v/$char?q=$f->{q};ti=$f->{ti};te=$f->{te}";
@@ -162,7 +162,7 @@ sub _filters {
txt mt '_vnbrowse_lang';
b ' ('.mt('_vnbrowse_boolor').')';
end;
- for my $i (sort @{$self->dbLanguages}) {
+ for my $i (@{$self->{languages}}) {
span;
input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i",
(scalar grep $_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : ();
diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm
index f09d6ddd..ed7068dc 100644
--- a/lib/VNDB/Handler/VNEdit.pm
+++ b/lib/VNDB/Handler/VNEdit.pm
@@ -28,7 +28,7 @@ sub edit {
my %b4 = !$vid ? () : (
(map { $_ => $v->{$_} } qw|title original desc alias length l_wp l_encubed l_renai l_vnn img_nsfw|),
anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}),
- relations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}),
+ vnrelations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}),
screenshots => join(' ', map sprintf('%d,%d,%d', $_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$v->{screenshots}}),
);
@@ -46,7 +46,7 @@ sub edit {
{ name => 'l_vnn', required => 0, default => $b4{l_vnn}||0, template => 'int' },
{ name => 'anime', required => 0, default => '' },
{ name => 'img_nsfw', required => 0, default => 0 },
- { name => 'relations', required => 0, default => '', maxlength => 5000 },
+ { name => 'vnrelations', required => 0, default => '', maxlength => 5000 },
{ name => 'screenshots', required => 0, default => '', maxlength => 1000 },
{ name => 'editsum', maxlength => 5000 },
);
@@ -57,11 +57,11 @@ sub edit {
if(!$frm->{_err}) {
# parse and re-sort fields that have multiple representations of the same information
my $anime = { map +($_=>1), grep /^[0-9]+$/, split /[ ,]+/, $frm->{anime} };
- my $relations = [ map { /^([0-9]+),([0-9]+),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{relations} ];
+ my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{vnrelations} ];
my $screenshots = [ map /^[0-9]+,[01],[0-9]+$/ ? [split /,/] : (), split / +/, $frm->{screenshots} ];
$frm->{anime} = join ' ', sort { $a <=> $b } keys %$anime;
- $frm->{relations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations};
+ $frm->{vnrelations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations};
$frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0;
$frm->{screenshots} = join ' ', map sprintf('%d,%d,%d', $_->[0], $_->[1]?1:0, $_->[2]), sort { $a->[0] <=> $b->[0] } @$screenshots;
@@ -83,7 +83,7 @@ sub edit {
($nvid, $cid) = $self->dbVNAdd(%args) if !$vid;
# update reverse relations & relation graph
- if(!$vid && $#$relations >= 0 || $vid && $frm->{relations} ne $b4{relations}) {
+ if(!$vid && $#$relations >= 0 || $vid && $frm->{vnrelations} ne $b4{vnrelations}) {
my %old = $vid ? (map { $_->{id} => $_->{relation} } @{$v->{relations}}) : ();
my %new = map { $_->[1] => $_->[0] } @$relations;
_updreverse($self, \%old, \%new, $nvid, $cid, $nrev);
@@ -97,7 +97,7 @@ sub edit {
$frm->{editsum} = sprintf 'Reverted to revision v%d.%d', $vid, $rev if $rev && !defined $frm->{editsum};
my $title = $vid ? mt('_vnedit_title_edit', $v->{title}) : mt '_vnedit_title_add';
- $self->htmlHeader(js => 'forms', title => $title, noindex => 1);
+ $self->htmlHeader(title => $title, noindex => 1);
$self->htmlMainTabs('v', $v, 'edit') if $vid;
$self->htmlEditMessage('v', $v, $title);
_form($self, $v, $frm);
@@ -183,7 +183,7 @@ sub _form {
],
vn_rel => [ mt('_vnedit_rel'),
- [ hidden => short => 'relations' ],
+ [ hidden => short => 'vnrelations' ],
[ static => nolabel => 1, content => sub {
h2 mt '_vnedit_rel_sel';
table;
@@ -193,22 +193,22 @@ sub _form {
end;
h2 mt '_vnedit_rel_add';
- # TODO: localize JS relartion selector
table;
Tr id => 'relation_new';
- td class => 'tc1';
+ td class => 'tc_vn';
input type => 'text', class => 'text';
end;
- td class => 'tc2';
- txt ' is a ';
+ td class => 'tc_rel';
+ txt mt('_vnedit_rel_isa').' ';
Select;
- option value => $_, $self->{vn_relations}[$_][0] for (0..$#{$self->{vn_relations}});
+ option value => $_, mt "_vnrel_$_"
+ for (sort { $self->{vn_relations}{$a}[0] <=> $self->{vn_relations}{$b}[0] } keys %{$self->{vn_relations}});
end;
- txt ' of';
+ txt ' '.mt '_vnedit_rel_of';
end;
- td class => 'tc3', $v ? $v->{title} : '';
- td class => 'tc4';
- a href => '#', 'add';
+ td class => 'tc_title', $v ? $v->{title} : '';
+ td class => 'tc_add';
+ a href => '#', mt '_vnedit_rel_addbut';
end;
end;
end;
@@ -219,10 +219,9 @@ sub _form {
[ hidden => short => 'screenshots' ],
[ static => nolabel => 1, content => sub {
div class => 'warning';
- lit mt '_vnedit_scr_msg';
+ lit mt '_vnedit_scrmsg';
end;
br;
- # TODO: localize screenshot uploader
table;
tbody id => 'scr_table', '';
end;
@@ -250,11 +249,9 @@ sub _updreverse {
# compare %old and %new
for (keys %$old, keys %$new) {
if(exists $$old{$_} and !exists $$new{$_}) {
- $upd{$_} = -1;
- } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} != $$new{$_})) {
- $upd{$_} = $$new{$_};
- if ($self->{vn_relations}[$upd{$_} ][1]) { $upd{$_}-- }
- elsif($self->{vn_relations}[$upd{$_}+1][1]) { $upd{$_}++ }
+ $upd{$_} = undef;
+ } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} ne $$new{$_})) {
+ $upd{$_} = $self->{vn_relations}{$$new{$_}}[1];
}
}
@@ -264,7 +261,7 @@ sub _updreverse {
for my $i (keys %upd) {
my $r = $self->dbVNGet(id => $i, what => 'extended relations anime screenshots')->[0];
my @newrel = map $_->{id} != $vid ? [ $_->{relation}, $_->{id} ] : (), @{$r->{relations}};
- push @newrel, [ $upd{$i}, $vid ] if $upd{$i} != -1;
+ push @newrel, [ $upd{$i}, $vid ] if $upd{$i};
$self->dbVNEdit($i,
relations => \@newrel,
editsum => "Reverse relation update caused by revision v$vid.$rev",
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
index 361963a8..ec5f4afc 100644
--- a/lib/VNDB/Handler/VNPage.pm
+++ b/lib/VNDB/Handler/VNPage.pm
@@ -27,16 +27,17 @@ sub rg {
return 404 if !$v->{id} || !$v->{rgraph};
my $title = mt '_vnrg_title', $v->{title};
- $self->htmlHeader(title => $title);
- $self->htmlMainTabs('v', $v, 'rg');
+ return if $self->htmlRGHeader($title, 'v', $v);
+
+ $v->{svg} =~ s/\$___(_vnrel_[a-z]+)____\$/mt $1/eg;
+
div class => 'mainbox';
h1 $title;
- lit $v->{cmap};
p class => 'center';
- img src => sprintf('%s/rg/%02d/%d.png', $self->{url_static}, $v->{rgraph}%100, $v->{rgraph}),
- alt => $title, usemap => '#rgraph';
+ lit $v->{svg};
end;
end;
+ $self->htmlFooter;
}
@@ -148,15 +149,16 @@ sub page {
my $t = $self->dbTagStats(vid => $v->{id}, order => 'avg(tv.vote) DESC', minrating => 0, results => 999);
if(@$t) {
div id => 'tagops';
- a href => '#', mt '_vnpage_tags_spoil0';
- a href => '#', class => 'tsel', mt '_vnpage_tags_spoil1';
+ # NOTE: order of these links is hardcoded in JS
+ a href => '#', class => 'tsel', mt '_vnpage_tags_spoil0';
+ a href => '#', mt '_vnpage_tags_spoil1';
a href => '#', mt '_vnpage_tags_spoil2';
a href => '#', class => 'sec', mt '_vnpage_tags_summary';
a href => '#', mt '_vnpage_tags_all';
end;
div id => 'vntags';
for (@$t) {
- span class => sprintf 'tagspl%.0f %s', $_->{spoiler}, $_->{spoiler} > 1 ? 'hidden' : '';
+ span class => sprintf 'tagspl%.0f %s', $_->{spoiler}, $_->{spoiler} > 0 ? 'hidden' : '';
a href => "/g$_->{id}", style => sprintf('font-size: %dpx', $_->{rating}*3.5+6), $_->{name};
b class => 'grayedout', sprintf ' %.1f', $_->{rating};
end;
@@ -199,7 +201,7 @@ sub _revision {
}],
[ relations => join => '<br />', split => sub {
my @r = map sprintf('%s: <a href="/v%d" title="%s">%s</a>',
- $self->{vn_relations}[$_->{relation}][0], $_->{id}, xml_escape($_->{original}||$_->{title}), xml_escape shorten $_->{title}, 40
+ mt("_vnrel_$_->{relation}"), $_->{id}, xml_escape($_->{original}||$_->{title}), xml_escape shorten $_->{title}, 40
), sort { $a->{id} <=> $b->{id} } @{$_[0]};
return @r ? @r : (mt '_vndiff_none');
}],
@@ -229,27 +231,42 @@ sub _revision {
sub _producers {
my($self, $i, $r) = @_;
- return if !grep @{$_->{producers}}, @$r;
my %lang;
my @lang = grep !$lang{$_}++, map @{$_->{languages}}, @$r;
- Tr ++$$i % 2 ? (class => 'odd') : ();
- td mt '_vnpage_producers';
- td;
- for my $l (@lang) {
- my %p = map { $_->{id} => $_ } map @{$_->{producers}}, grep grep($_ eq $l, @{$_->{languages}}), @$r;
- my @p = values %p;
- next if !@p;
- cssicon "lang $l", mt "_lang_$l";
- for (@p) {
+ if(grep $_->{developer}, map @{$_->{producers}}, @$r) {
+ my %dev = map $_->{developer} ? ($_->{id} => $_) : (), map @{$_->{producers}}, @$r;
+ my @dev = values %dev;
+ Tr ++$$i % 2 ? (class => 'odd') : ();
+ td mt "_vnpage_developer";
+ td;
+ for (@dev) {
a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 30;
- txt ' & ' if $_ != $p[$#p];
+ txt ' & ' if $_ != $dev[$#dev];
}
- txt "\n";
- }
- end;
- end;
+ end;
+ end;
+ }
+
+ if(grep $_->{publisher}, map @{$_->{producers}}, @$r) {
+ Tr ++$$i % 2 ? (class => 'odd') : ();
+ td mt "_vnpage_publisher";
+ td;
+ for my $l (@lang) {
+ my %p = map $_->{publisher} ? ($_->{id} => $_) : (), map @{$_->{producers}}, grep grep($_ eq $l, @{$_->{languages}}), @$r;
+ my @p = values %p;
+ next if !@p;
+ cssicon "lang $l", mt "_lang_$l";
+ for (@p) {
+ a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 30;
+ txt ' & ' if $_ != $p[$#p];
+ }
+ txt "\n";
+ }
+ end;
+ end;
+ }
}
@@ -266,7 +283,7 @@ sub _relations {
td class => 'relations';
dl;
for(sort keys %rel) {
- dt $self->{vn_relations}[$_][0];
+ dt mt "_vnrel_$_";
dd;
for (@{$rel{$_}}) {
a href => "/v$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 40;
@@ -306,7 +323,7 @@ sub _anime {
txt '] ';
end;
acronym title => $_->{title_kanji}||$_->{title_romaji}, shorten $_->{title_romaji}, 50;
- b ' ('.(defined $_->{type} ? $self->{anime_types}[$_->{type}].', ' : '').$_->{year}.')';
+ b ' ('.(defined $_->{type} ? mt("_animetype_$_->{type}").', ' : '').$_->{year}.')';
txt "\n";
}
}
@@ -395,7 +412,7 @@ sub _releases {
end;
td class => 'tc5';
if($self->authInfo->{id}) {
- a href => "/r$rel->{id}", id => "rlsel_$rel->{id}";
+ a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel';
lit $rel->{ulist} ? liststat $rel->{ulist} : '--';
end;
} else {
diff --git a/lib/VNDB/L10N.pm b/lib/VNDB/L10N.pm
index d4ff872c..8698cb74 100644
--- a/lib/VNDB/L10N.pm
+++ b/lib/VNDB/L10N.pm
@@ -5,12 +5,13 @@ use warnings;
{
package VNDB::L10N;
use base 'Locale::Maketext';
+ use LangFile;
sub fallback_languages { ('en') };
# used for the language switch interface, language tags must
# be the same as in the languages hash in global.pl
- sub languages { ('en', 'ru') }
+ sub languages { qw{ cs en hu ru } }
sub maketext {
my $r = eval { shift->SUPER::maketext(@_) };
@@ -21,59 +22,29 @@ use warnings;
# can be called as either a subroutine or a method
sub loadfile {
- my %lang = (
- en => \%VNDB::L10N::en::Lexicon,
- ru => \%VNDB::L10N::ru::Lexicon,
- );
-
- open my $F, '<:utf8', $VNDB::ROOT.'/data/lang.txt' or die "Opening language file: $!\n";
- my($empty, $line, $key, $lang) = (0, 0);
- while(<$F>) {
- chomp;
- $line++;
-
- # ignore intro
- if(!defined $key) {
- $key = 0 if /^\/intro$/;
- next;
+ my %lang = do {
+ no strict 'refs';
+ map +($_, \%{"VNDB::L10N::${_}::Lexicon"}), languages
+ };
+ my $r = LangFile->new(read => "$VNDB::ROOT/data/lang.txt");
+ my $key;
+ while(my $l = $r->read) {
+ my($t, @l) = @$l;
+ $key = $l[0] if $t eq 'key';
+ if($t eq 'tl') {
+ my($lang, undef, $text) = @l;
+ next if !$text;
+ die "Unknown language \"$l->[1]\"\n" if !$lang{$lang};
+ die "Unknown key for translation \"$lang: $text\"\n" if !$key;
+ $lang{$lang}{$key} = $text;
}
- # ignore comments
- next if /^#/;
- # key
- if(/^:(.+)$/) {
- $key = $1;
- $lang = undef;
- $empty = 0;
- next;
- }
- # locale string
- if(/^([a-z_-]{2,7})[ *]: (.+)$/) {
- $lang = $1;
- die "Unknown language on #$line: $lang\n" if !$lang{$lang};
- die "Unknown key for locale on #$line\n" if !$key;
- $lang{$lang}{$key} = $2;
- $empty = 0;
- next;
- }
- # multi-line locale string
- if($lang && /^\s+([^\s].*)$/) {
- $lang{$lang}{$key} .= ''.("\n"x$empty)."\n$1";
- $empty = 0;
- next;
- }
- # empty string (count them in case they're part of a multi-line locale string)
- if(/^\s*$/) {
- $empty++;
- next;
- }
- # something we didn't expect
- die "Don't know what to do with line $line\n" unless /^([a-z_-]{2,7})[ *]:/;
}
- close $F;
+ $r->close;
}
}
+
{
package VNDB::L10N::en;
use base 'VNDB::L10N';
@@ -88,8 +59,9 @@ use warnings;
# Argument: unix timestamp
# Returns: age
sub age {
- my $a = time-$_[1];
- return sprintf '%d %s ago',
+ my($self, $time) = @_;
+ my $a = time-$time;
+ my @f =
$a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'years' ) :
$a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'months' ) :
$a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'weeks' ) :
@@ -97,6 +69,7 @@ use warnings;
$a > 60*60*2 ? ( $a/60/60, 'hours' ) :
$a > 60*2 ? ( $a/60, 'min' ) :
( $a, 'sec' );
+ return $self->maketext("_age_$f[1]", int $f[0]);
}
# argument: unix timestamp and optional format (compact/full)
@@ -155,6 +128,30 @@ use warnings;
{
+ package VNDB::L10N::cs;
+ use base 'VNDB::L10N::en';
+ our %Lexicon;
+
+ sub quant {
+ my($self, $num, $single, $couple, $lots) = @_;
+ return $lots if ($num % 100) >= 11 && ($num % 100) <= 14;
+ return $single if ($num % 10) == 1;
+ return $couple if ($num % 10) >= 2 && ($num % 10) <= 4;
+ return $lots;
+ }
+}
+
+
+
+{
+ package VNDB::L10N::hu;
+ use base 'VNDB::L10N::en';
+ our %Lexicon;
+}
+
+
+
+{
package VNDB::L10N::ru;
use base 'VNDB::L10N::en';
our %Lexicon;
@@ -165,25 +162,9 @@ use warnings;
return $couple if ($num % 10) >= 2 && ($num % 10) <= 4 && !(($num % 100) >= 12 && ($num % 100) <= 14);
return $lots;
}
-
- sub age {
- my $self = shift;
- my $a = time-shift;
- use utf8;
- my @l = (
- $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'год', 'года', 'лет' ) :
- $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'месяц', 'месяца', 'месяцев' ) :
- $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'неделя', 'недели', 'недель' ) :
- $a > 60*60*24*2 ? ( $a/60/60/24, 'день', 'дня', 'дней' ) :
- $a > 60*60*2 ? ( $a/60/60, 'час', 'часа', 'часов' ) :
- $a > 60*2 ? ( $a/60, 'минута', 'минуты', 'минут' ) :
- ( $a, 'секунда', 'секунды', 'секунд' )
- );
- return sprintf '%d %s назад', $l[0], $self->quant(@l);
- }
-
}
+
1;
diff --git a/lib/VNDB/Plugin/TransAdmin.pm b/lib/VNDB/Plugin/TransAdmin.pm
new file mode 100644
index 00000000..ed5ebb36
--- /dev/null
+++ b/lib/VNDB/Plugin/TransAdmin.pm
@@ -0,0 +1,349 @@
+# This plugin provides a quick and dirty user interface to editing lang.txt,
+# to use it, add the following to your data/config.pl:
+#
+# if($INC{"YAWF.pm"}) {
+# require VNDB::Plugin::TransAdmin;
+# $VNDB::S{transadmin} = {
+# <userid> => 'all' || <language> || <arrayref with languages>
+# };
+# }
+#
+# And then open /tladmin in your browser.
+# Also make sure data/lang.txt and data/docs/* are writable by the httpd process.
+# English is considered the 'main' language, and cannot be edited using this interface.
+
+package VNDB::Plugin::TransAdmin;
+
+use strict;
+use warnings;
+use YAWF ':html';
+use LangFile;
+use VNDB::Func;
+
+
+my $langfile = "$VNDB::ROOT/data/lang.txt";
+
+
+YAWF::register(
+ qr{tladmin(?:/([a-z]+))?} => \&tladmin
+);
+
+
+sub uri_escape {
+ local $_ = shift;
+ s/ /%20/g;
+ s/\?/%3F/g;
+ s/;/%3B/g;
+ s/&/%26/g;
+ return $_;
+}
+
+
+sub _allowed {
+ my($self, $lang) = @_;
+ my $a = $self->{transadmin}{ $self->authInfo->{id} };
+ return $a eq 'all' || $a eq $lang || ref($a) eq 'ARRAY' && grep $_ eq $lang, @$a;
+}
+
+
+sub tladmin {
+ my($self, $lang) = @_;
+
+ $lang ||= '';
+ my $intro = $lang =~ s/intro//;
+ return 404 if $lang && ($lang eq 'en' || !grep $_ eq $lang, $self->{l10n}->languages);
+ my $sect = $self->reqParam('sect')||'';
+ my $doc = $self->reqParam('doc')||'';
+
+ my $uid = $self->authInfo->{id};
+ return $self->htmlDenied if !$uid || !$self->{transadmin}{$uid};
+
+ if(!-w $langfile || !-w "$VNDB::ROOT/data/docs" || grep /\.[a-z]{2}$/ && !-w $_, glob "$VNDB::ROOT/data/docs/*") {
+ $self->htmlHeader(title => 'Language file not writable', noindex => 1);
+ div class => 'mainbox';
+ h1 'Language file not writable';
+ div class => 'warning', 'Sorry, I do not have enough permission to write to the language files.';
+ end;
+ $self->htmlFooter;
+ return;
+ }
+
+ _savelang($self, $lang) if $lang && $sect && $self->reqMethod eq 'POST' && _allowed($self, $lang);
+ _savedoc($self, $lang, $doc) if $lang && $doc && $self->reqMethod eq 'POST' && _allowed($self, $lang);
+ my($sects, $page) = _readlang($lang, $sect) if $lang;
+
+ $self->htmlHeader(title => 'Quick-and-dirty Translation Editor', noindex => 1);
+ div class => 'mainbox';
+ a class => 'addnew', href => '/tladmin/intro', 'README';
+ h1 'Quick-and-dirty Translation Editor';
+ h2 class => 'alttitle', 'Step #1: Choose a language';
+ p class => 'browseopts';
+ a $lang eq $_ ? (class => 'optselected') : (), href => "/tladmin/$_", mt "_lang_$_"
+ for grep !/en/, $self->{l10n}->languages;
+ end;
+ _sections($self, $lang, $sect, $sects) if $lang;
+ _docs($lang, $doc) if $lang;
+ end;
+
+ _intro() if $intro;
+ _section($self, $lang, $sect, $page) if $lang && $sect;
+ _doc($self, $lang, $doc) if $lang && $doc;
+
+ $self->htmlFooter;
+}
+
+
+sub _savelang {
+ my($self, $lang) = @_;
+
+ # do everything in-memory, so we don't need write access to a temporary file
+ # (this has the downside that in the event something goes wrong, everything will be wiped)
+ my $f = LangFile->new(read => $langfile);
+ my @read;
+ push @read, $_ while (local $_ = $f->read);
+ $f->close;
+
+ my @keys = $self->reqParam;
+ $f = LangFile->new(write => $langfile);
+ my $key;
+ for my $l (@read) {
+ $key = $l->[1] if $l->[0] eq 'key';
+ if($l->[0] eq 'tl' && $l->[1] eq $lang && grep $key eq $_, @keys) {
+ $l->[2] = !$self->reqParam("check$key");
+ $l->[3] = $self->reqParam($key);
+ $l->[3] =~ s/\r?\n/\n/g;
+ $l->[3] =~ s/\s+$//g;
+ }
+ $f->write(@$l);
+ }
+ $f->close;
+
+ # re-read the file and regenerate the JS in case we're not running as CGI
+ if($INC{"FCGI.pm"}) {
+ VNDB::L10N::loadfile();
+ VNDB::checkjs();
+ }
+}
+
+
+sub _readlang {
+ my($lang, $sect) = @_;
+ my @sect; # [ title, count, unsync ]
+ my @page; # [ 'comment'||'line', <comment>|| ( <key>, <en>, <sync>, <tl> ) ]
+
+ my $f = LangFile->new(read => $langfile);
+ my($key, $insect);
+ while(my $l = $f->read) {
+ my $t = shift @$l;
+
+ if($t eq 'space') {
+ if(join("\n", @$l) =~ /((#{30,90}\n)## +(.+) +##\n\2.+)^/ms) {
+ my $header = $1;
+ (my $title = $3) =~ s/\s+$//;
+ $title =~ s/\s+\([^)]+\)$//;
+ push @sect, [ $title, 0, 0 ];
+ $insect = $title eq $sect;
+ push @page, [ 'comment', $header ] if $insect;
+ } elsif($insect) {
+ push @page, [ 'comment', join "\n", @$l ];
+ }
+ }
+
+ $sect[$#sect][1]++ if $t eq 'key';
+ $sect[$#sect][2]++ if $t eq 'tl' && $l->[0] eq $lang && !$l->[1];
+
+ next if !$insect;
+ push @page, [ 'line', $l->[0] ] if $t eq 'key';
+ $page[$#page][2] = $l->[2] if $t eq 'tl' && $l->[0] eq 'en';
+ if($t eq 'tl' && $l->[0] eq $lang) {
+ $page[$#page][3] = $l->[1];
+ $page[$#page][4] = $l->[2];
+ }
+ }
+ $f->close;
+ return (\@sect, \@page);
+}
+
+
+sub _intro {
+ my $f = LangFile->new(read => $langfile);
+ my $intro = $f->read;
+ $intro = join "\n", @$intro[1..$#$intro];
+ $f->close;
+ div class => 'mainbox';
+ h1 'Introduction to the language file';
+ pre $intro;
+ end;
+}
+
+
+sub _sections {
+ my($self, $lang, $sect, $list) = @_;
+
+ br;
+ h2 class => 'alttitle', 'Step #2: Choose a section';
+ div style => 'margin: 0 40px';
+ for (@$list) {
+ div style => 'float: left; width: 200px;';
+ a href => "/tladmin/$lang?sect=".uri_escape($_->[0]), $_->[0] if $sect ne $_->[0];
+ txt $sect if $sect eq $_->[0];
+ txt " ";
+ txt "0/$_->[1]" if !$_->[2];
+ b class => 'standout', "$_->[2]/$_->[1]" if $_->[2];
+ end;
+ }
+ clearfloat;
+ end;
+ br;
+ br;
+}
+
+
+sub _section {
+ my($self, $lang, $sect, $page) = @_;
+
+ form action => "/tladmin/$lang?sect=".uri_escape($sect), method => 'POST', 'accept-charset' => 'utf-8';
+ div class => 'mainbox';
+ h1 $sect;
+
+ if(_allowed($self, $lang)) {
+ h2 class => 'alttitle', "Don't forget to hit the 'save' button to make your changes permament!";
+ } else {
+ div class => 'warning';
+ h2 'Read-only';
+ p "You can't edit this language.";
+ end;
+ }
+
+ for my $l (@$page) {
+ if($l->[0] eq 'comment') {
+ pre;
+ b class => 'grayedout', $l->[1]."\n";
+ end;
+ next;
+ }
+
+ my(undef, $key, $en, $sync, $tl) = @$l;
+ b class => $sync ? 'grayedout' : 'standout', ":$key";
+ br;
+ div style => 'margin-left: 25px; font: 12px Tahoma; width: 700px; overflow-x: auto; white-space: nowrap', $en;
+ my $multi = $en =~ y/\n//;
+
+ div style => 'width: 23px; float: left; text-align: right';
+ input type => 'checkbox', name => "check$key", id => "check$key", !$sync ? (checked => 'checked') : ();
+ end;
+ div style => 'float: left';
+ if($multi) {
+ $tl =~ s/&/&amp;/g;
+ $tl =~ s/</&lt;/g;
+ $tl =~ s/>/&gt;/g;
+ textarea name => $key, id => $key, rows => $multi+2, style => 'width: 700px; height: auto; white-space: nowrap; border: none', wrap => 'off';
+ lit $tl;
+ end;
+ } else {
+ input type => 'text', class => 'text', name => $key, id => $key, value => $tl, style => 'width: 700px; border: none';
+ }
+ end;
+ clearfloat;
+ }
+ if(_allowed($self, $lang)) {
+ br;br;
+ fieldset class => 'submit';
+ input type => 'submit', value => 'Save', class => 'submit';
+ end;
+ }
+ end;
+ end;
+}
+
+
+sub _savedoc {
+ my($self, $lang, $doc) = @_;
+
+ my $file = "$VNDB::ROOT/data/docs/$doc.$lang";
+
+ open my $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc" or die $!;
+ my $en = join '', <$f>;
+ close $f;
+
+ my $tl = $self->reqParam('tl');
+ $tl =~ s/\r?\n/\n/g;
+
+ return -e $file && unlink $file if $tl eq $en;
+
+ open $f, '>:utf8', $file or die $!;
+ print $f $tl;
+ close $f;
+ chmod 0666, $file;
+}
+
+
+sub _docs {
+ my($lang, $doc) = @_;
+
+ my @d = map /\.[a-z]{2}$/ || /\/8$/ ? () : s{^.+\/([^/]+)$}{$1} && $_, glob "$VNDB::ROOT/data/docs/*";
+
+ h2 class => 'alttitle', '...or a doc page';
+ div style => 'margin: 0 40px';
+ for (sort { $a =~ /^\d+$/ && $b =~ /^\d+$/ ? $a <=> $b : $a cmp $b } @d) {
+ div style => 'float: left; width: 60px;';
+ a href => "/tladmin/$lang?doc=$_", $_ if $_ ne $doc;
+ txt $_ if $_ eq $doc;
+ end;
+ }
+ clearfloat;
+ end;
+}
+
+
+sub _doc {
+ my($self, $lang, $doc) = @_;
+
+ open my $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc" or die $!;
+ my $en = join '', <$f>;
+ close $f;
+
+ my $tl = $en;
+ if(open $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc.$lang") {
+ $tl = join '', <$f>;
+ close $f;
+ }
+ $tl =~ s/&/&amp;/g;
+ $tl =~ s/</&lt;/g;
+ $tl =~ s/>/&gt;/g;
+
+
+ form action => "/tladmin/$lang?doc=$doc", method => 'POST', 'accept-charset' => 'utf-8';
+ div class => 'mainbox';
+ a class => 'addnew', href => "/d$doc", "View current page" if $doc =~ /^\d+$/;
+ h1 "Translating page $doc";
+ h2 class => 'alttitle', 'Left = English, Right = translation';
+
+ if(!_allowed($self, $lang)) {
+ div class => 'warning';
+ h2 'Read-only';
+ p "You can't edit this language.";
+ end;
+ }
+
+ div style => 'width: 48%; margin-right: 10px; overflow-y: auto; float: left';
+ pre style => 'font: 12px Tahoma', $en;
+ end;
+ textarea name => 'tl', id => 'tl', rows => ($en =~ y/\n//),
+ style => 'border: none; float: left; width: 49%; white-space: nowrap', wrap => 'off';
+ lit $tl;
+ end;
+ clearfloat;
+ if(_allowed($self, $lang)) {
+ br;
+ fieldset class => 'submit';
+ input type => 'submit', value => 'Save', class => 'submit';
+ end;
+ }
+ end;
+ end;
+}
+
+
+1;
+
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
index c4daffd9..a3bf7c29 100644
--- a/lib/VNDB/Util/Auth.pm
+++ b/lib/VNDB/Util/Auth.pm
@@ -26,7 +26,7 @@ sub authInit {
my $token = substr($cookie, 0, 40);
my $uid = substr($cookie, 40);
return _rmcookie($self) if $uid !~ /^\d+$/ || !$self->dbSessionCheck($uid, $token);
- $self->{_auth} = $self->dbUserGet(uid => $uid, what => 'mymessages')->[0];
+ $self->{_auth} = $self->dbUserGet(uid => $uid, what => 'extended')->[0];
}
@@ -95,7 +95,7 @@ sub _authCheck {
return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass;
- my $d = $self->dbUserGet(username => $user, what => 'mymessages')->[0];
+ my $d = $self->dbUserGet(username => $user, what => 'extended')->[0];
return 0 if !defined $d->{id} || !$d->{rank};
if(_authEncryptPass($self, $pass, $d->{salt}) eq $d->{passwd}) {
diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm
index b1eb6432..ad99d32d 100644
--- a/lib/VNDB/Util/CommonHTML.pm
+++ b/lib/VNDB/Util/CommonHTML.pm
@@ -12,7 +12,7 @@ use POSIX 'ceil';
our @EXPORT = qw|
htmlMainTabs htmlDenied htmlHiddenMessage htmlBrowse htmlBrowseNavigate
- htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory htmlSearchBox
+ htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory htmlSearchBox htmlRGHeader
|;
@@ -101,7 +101,7 @@ sub htmlMainTabs {
end;
}
- if($type eq 'v' && $obj->{rgraph}) {
+ if($type =~ /[vp]/ && $obj->{rgraph}) {
li $sel eq 'rg' ? (class => 'tabselected') : ();
a href => "/$id/rg", mt '_mtabs_relations';
end;
@@ -501,16 +501,15 @@ sub htmlHistory {
sub { td colspan => 2, class => 'tc1', mt '_hist_col_rev' },
[ mt '_hist_col_date' ],
[ mt '_hist_col_user' ],
- sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_hist_col_page'; end; }
+ sub { td; a href => '#', id => 'expandlist', mt '_js_expand'; txt mt '_hist_col_page'; end; }
],
row => sub {
my($s, $n, $i) = @_;
- my $tc = [qw|v r p|]->[$i->{type}];
- my $revurl = "/$tc$i->{iid}.$i->{rev}";
+ my $revurl = "/$i->{type}$i->{iid}.$i->{rev}";
Tr $n % 2 ? ( class => 'odd' ) : ();
td class => 'tc1_1';
- a href => $revurl, "$tc$i->{iid}";
+ a href => $revurl, "$i->{type}$i->{iid}";
end;
td class => 'tc1_2';
a href => $revurl, ".$i->{rev}";
@@ -524,7 +523,7 @@ sub htmlHistory {
end;
end;
if($i->{comments}) {
- Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden';
+ Tr class => $n % 2 ? 'collapse msgsum odd hidden' : 'collapse msgsum hidden';
td colspan => 5;
lit bb2html $i->{comments}, 150;
end;
@@ -538,13 +537,20 @@ sub htmlHistory {
sub htmlSearchBox {
my($self, $sel, $v) = @_;
+ # escape search query for use as a query string value
+ (my $q = $v||'') =~ s/&/%26/g;
+ $q =~ s/\?/%3F/g;
+ $q =~ s/;/%3B/g;
+ $q =~ s/ /%20/g;
+ $q = "?q=$q" if $q;
+
fieldset class => 'search';
p class => 'searchtabs';
- a href => '/v/all', $sel eq 'v' ? (class => 'sel') : (), mt '_searchbox_vn';
- a href => '/r', $sel eq 'r' ? (class => 'sel') : (), mt '_searchbox_releases';
- a href => '/p/all', $sel eq 'p' ? (class => 'sel') : (), mt '_searchbox_producers';
- a href => '/g', $sel eq 'g' ? (class => 'sel') : (), mt '_searchbox_tags';
- a href => '/u/all', $sel eq 'u' ? (class => 'sel') : (), mt '_searchbox_users';
+ a href => "/v/all$q", $sel eq 'v' ? (class => 'sel') : (), mt '_searchbox_vn';
+ a href => "/r$q", $sel eq 'r' ? (class => 'sel') : (), mt '_searchbox_releases';
+ a href => "/p/all$q", $sel eq 'p' ? (class => 'sel') : (), mt '_searchbox_producers';
+ a href => '/g'.($q?"/list$q":''), $sel eq 'g' ? (class => 'sel') : (), mt '_searchbox_tags';
+ a href => "/u/all$q", $sel eq 'u' ? (class => 'sel') : (), mt '_searchbox_users';
end;
input type => 'text', name => 'q', id => 'q', class => 'text', value => $v;
input type => 'submit', class => 'submit', value => mt '_searchbox_submit';
@@ -552,5 +558,41 @@ sub htmlSearchBox {
}
+sub htmlRGHeader {
+ my($self, $title, $type, $obj) = @_;
+
+ if(($self->reqHeader('Accept')||'') !~ /application\/xhtml\+xml/) {
+ $self->htmlHeader(title => $title);
+ $self->htmlMainTabs($type, $obj, 'rg');
+ div class => 'mainbox';
+ h1 $title;
+ div class => 'warning';
+ h2 mt '_rg_notsupp';
+ p mt '_rg_notsupp_msg';
+ end;
+ end;
+ $self->htmlFooter;
+ return 1;
+ }
+ $self->resHeader('Content-Type' => 'application/xhtml+xml; charset=UTF-8');
+
+ # This is a REALLY ugly hack, need find a proper solution in YAWF
+ no warnings 'redefine';
+ my $sub = \&YAWF::XML::html;
+ *YAWF::XML::html = sub () {
+ lit q|<!DOCTYPE html PUBLIC
+ "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
+ "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">|;
+ tag 'html',
+ xmlns => "http://www.w3.org/1999/xhtml",
+ 'xmlns:svg' => 'http://www.w3.org/2000/svg',
+ 'xmlns:xlink' => 'http://www.w3.org/1999/xlink';
+ };
+ $self->htmlHeader(title => $title);
+ *YAWF::XML::html = $sub;
+ $self->htmlMainTabs($type, $obj, 'rg');
+ return 0;
+}
+
1;
diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm
index 85971ba9..084b9a4e 100644
--- a/lib/VNDB/Util/LayoutHTML.pm
+++ b/lib/VNDB/Util/LayoutHTML.pm
@@ -10,7 +10,7 @@ use VNDB::Func;
our @EXPORT = qw|htmlHeader htmlFooter|;
-sub htmlHeader { # %options->{ title, js, noindex, search }
+sub htmlHeader { # %options->{ title, noindex, search }
my($self, %o) = @_;
my $skin = $self->reqParam('skin') || $self->authInfo->{skin} || $self->{skin_default};
$skin = $self->{skin_default} if !$self->{skins}{$skin} || !-d "$VNDB::ROOT/static/s/$skin";
@@ -22,12 +22,6 @@ sub htmlHeader { # %options->{ title, js, noindex, search }
Link rel => 'shortcut icon', href => '/favicon.ico', type => 'image/x-icon';
Link rel => 'stylesheet', href => $self->{url_static}.'/s/'.$skin.'/style.css?'.$self->{version}, type => 'text/css', media => 'all';
Link rel => 'search', type => 'application/opensearchdescription+xml', title => 'VNDB VN Search', href => $self->{url}.'/opensearch.xml';
- if($o{js}) {
- script type => 'text/javascript', src => $self->{url_static}.'/f/forms.js?'.$self->{version}; end;
- }
- script type => 'text/javascript', src => $self->{url_static}.'/f/script.js?'.$self->{version};
- # most browsers don't like a self-closing <script> tag...
- end;
if($self->authInfo->{customcss}) {
(my $css = $self->authInfo->{customcss}) =~ s/\n/ /g;
style type => 'text/css', $css;
@@ -55,12 +49,8 @@ sub _menu {
div class => 'menubox';
h2;
- span;
- for (grep $self->{l10n}->language_tag() ne $_, $self->{l10n}->languages()) {
- a href => "?l10n=$_";
- cssicon "lang $_", mt "_lang_$_"; # NOTE: should actually be in the destination language...
- end;
- }
+ a href => "#", id => 'lang_select';
+ cssicon "lang ".$self->{l10n}->language_tag(), mt "_lang_".$self->{l10n}->language_tag();
end;
txt mt '_menu';
end;
@@ -89,6 +79,7 @@ sub _menu {
div class => 'menubox';
if($self->authInfo->{id}) {
+ my $msg = $self->dbUserMessageCount($self->authInfo->{id});
my $uid = sprintf '/u%d', $self->authInfo->{id};
h2;
a href => $uid, ucfirst $self->authInfo->{username};
@@ -99,7 +90,7 @@ sub _menu {
a href => "$uid/edit", mt '_menu_myprofile'; br;
a href => "$uid/list", mt '_menu_myvnlist'; br;
a href => "$uid/wish", mt '_menu_mywishlist'; br;
- a href => "/t$uid", mt '_menu_mymessages', $self->authInfo->{mymessages}; br;
+ a href => "/t$uid", $msg ? (class => 'standout') : (), mt '_menu_mymessages', $msg; br;
a href => "$uid/hist", mt '_menu_mychanges'; br;
a href => "$uid/tags", mt '_menu_mytags'; br;
br;
@@ -163,6 +154,7 @@ sub htmlFooter {
a href => $self->{source_url}, mt '_footer_source';
end;
end; # /div maincontent
+ script type => 'text/javascript', src => $self->{url_static}.'/f/script.js?'.$self->{version}, '';
end; # /body
end; # /html