diff options
author | Yorhel <git@yorhel.nl> | 2011-01-02 14:17:57 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2011-01-02 14:17:57 +0100 |
commit | b4e3c35620916852a6028ab5f6644382553408f9 (patch) | |
tree | 303248a812f6a7dc9c25bec0d8af5836c87fec3f /lib | |
parent | 632df9599de8dbb25707b0bf8caea075c55cfa3f (diff) | |
parent | 98f4725013b6d7a65e1fd07f7f02785b12e8a9bd (diff) |
Merge branch 'beta'2.16
Conflicts:
ChangeLog
lib/VNDB/Handler/ULists.pm
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Multi/API.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/DB/Releases.pm | 11 | ||||
-rw-r--r-- | lib/VNDB/DB/Tags.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/DB/ULists.pm | 138 | ||||
-rw-r--r-- | lib/VNDB/DB/Users.pm | 39 | ||||
-rw-r--r-- | lib/VNDB/DB/VN.pm | 47 | ||||
-rw-r--r-- | lib/VNDB/Func.pm | 21 | ||||
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 6 | ||||
-rw-r--r-- | lib/VNDB/Handler/Misc.pm | 46 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 5 | ||||
-rw-r--r-- | lib/VNDB/Handler/Releases.pm | 89 | ||||
-rw-r--r-- | lib/VNDB/Handler/Tags.pm | 29 | ||||
-rw-r--r-- | lib/VNDB/Handler/ULists.pm | 237 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 72 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNBrowse.pm | 73 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNEdit.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 32 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 17 | ||||
-rw-r--r-- | lib/VNDB/Util/BrowseHTML.pm | 43 | ||||
-rw-r--r-- | lib/VNDB/Util/CommonHTML.pm | 6 | ||||
-rw-r--r-- | lib/VNDB/Util/FormHTML.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Util/LayoutHTML.pm | 22 | ||||
-rw-r--r-- | lib/VNDB/Util/Misc.pm | 100 |
23 files changed, 676 insertions, 369 deletions
diff --git a/lib/Multi/API.pm b/lib/Multi/API.pm index 4872acf9..3f1b24c4 100644 --- a/lib/Multi/API.pm +++ b/lib/Multi/API.pm @@ -648,7 +648,7 @@ sub get_release_res { if(grep /details/, @{$get->{info}}) { $_->{website} ||= undef; $_->{notes} ||= undef; - $_->{minage} *= 1 if defined $_->{minage}; + $_->{minage} = $_->{minage} < 0 ? undef : $_->{minage}*1; $_->{gtin} ||= undef; $_->{catalog} ||= undef; } diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm index b0fb9a89..ffffb2a6 100644 --- a/lib/VNDB/DB/Releases.pm +++ b/lib/VNDB/DB/Releases.pm @@ -32,6 +32,7 @@ sub dbReleaseGet { defined $o{type} ? ( 'rr.type = ?' => $o{type} ) : (), defined $o{date_before} ? ( 'rr.released <= ?' => $o{date_before} ) : (), defined $o{date_after} ? ( 'rr.released >= ?' => $o{date_after} ) : (), + defined $o{minage} ? ( 'rr.minage IN(!l)' => [ ref $o{minage} ? $o{minage} : [$o{minage}] ] ) : (), defined $o{resolution} ? ( 'rr.resolution IN(!l)' => [ ref $o{resolution} ? $o{resolution} : [$o{resolution}] ] ) : (), defined $o{voiced} ? ( 'rr.voiced IN(!l)' => [ ref $o{voiced} ? $o{voiced} : [$o{voiced}] ] ) : (), defined $o{ani_story} ? ( 'rr.ani_story IN(!l)' => [ ref $o{ani_story} ? $o{ani_story} : [$o{ani_story}] ] ) : (), @@ -47,16 +48,6 @@ sub dbReleaseGet { 'rr.id IN(SELECT irm.rid FROM releases_media irm JOIN releases ir ON ir.latest = irm.rid WHERE irm.medium IN(!l))' => [ ref $o{med} ? $o{med} : [ $o{med} ] ] ) : (), ); - # TODO: don't allow NULL for rr.minage after all, since this could be a lot easier... - if(exists $o{minage}) { - my @m = ref $o{minage} ? @{$o{minage}} : ($o{minage}); - my @w = ( - grep(!defined $_ || $_ == -1, @m) ? 'rr.minage IS NULL' : (), - grep(defined $_ && $_ != -1, @m) ? 'rr.minage IN(!s)' : () - ); - push @where, '('.join(' OR ', @w).')', [ grep defined $_ && $_ != -1, @m ]; - } - if($o{search}) { for (split /[ -,._]/, $o{search}) { s/%//g; diff --git a/lib/VNDB/DB/Tags.pm b/lib/VNDB/DB/Tags.pm index 4a87713b..b3e16960 100644 --- a/lib/VNDB/DB/Tags.pm +++ b/lib/VNDB/DB/Tags.pm @@ -24,7 +24,7 @@ sub dbTagGet { my %where = ( $o{id} ? ( - 't.id = ?' => $o{id} ) : (), + 't.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (), $o{noid} ? ( 't.id <> ?' => $o{noid} ) : (), $o{name} ? ( diff --git a/lib/VNDB/DB/ULists.pm b/lib/VNDB/DB/ULists.pm index e52cbf37..e6b6419a 100644 --- a/lib/VNDB/DB/ULists.pm +++ b/lib/VNDB/DB/ULists.pm @@ -7,56 +7,62 @@ use Exporter 'import'; our @EXPORT = qw| - dbVNListGet dbVNListList dbVNListAdd dbVNListDel + dbRListGet dbVNListGet dbVNListList dbVNListAdd dbVNListDel dbRListAdd dbRListDel dbVoteGet dbVoteStats dbVoteAdd dbVoteDel dbWishListGet dbWishListAdd dbWishListDel |; -# Simpler and more efficient version of dbVNListList below -# %options->{ uid rid } -sub dbVNListGet { +# Options: uid rid +sub dbRListGet { my($self, %o) = @_; my %where = ( 'uid = ?' => $o{uid}, - $o{rid} && !ref $o{rid} ? ( - 'rid = ?' => $o{rid} ) : (), - $o{rid} && ref $o{rid} ? ( - 'rid IN(!l)' => [$o{rid}] ) : (), + $o{rid} ? ('rid IN(!l)' => [ ref $o{rid} ? $o{rid} : [$o{rid}] ]) : (), ); return $self->dbAll(q| - SELECT uid, rid, rstat, vstat + SELECT uid, rid, status FROM rlists !W|, \%where ); } +# Options: uid vid +sub dbVNListGet { + my($self, %o) = @_; -# %options->{ uid char voted page results sort reverse } + my %where = ( + 'uid = ?' => $o{uid}, + $o{vid} ? ('vid IN(!l)' => [ ref $o{vid} ? $o{vid} : [$o{vid}] ]) : (), + ); + + return $self->dbAll(q| + SELECT uid, vid, status + FROM vnlists + !W|, + \%where + ); +} + + +# Options: uid char voted page results sort reverse # sort: title vote -# NOTE: this function is mostly copied from 1.x, may need some rewriting... sub dbVNListList { my($self, %o) = @_; - $o{results} ||= 50; $o{page} ||= 1; - $o{voted} ||= 0; # -1: only non-voted, 0: all, 1: only voted - - # construct the global WHERE clause - my $where = $o{voted} != -1 ? 'vo.vote IS NOT NULL' : ''; - $where .= ($where?' OR ':'').q|v.id = ANY(ARRAY( - SELECT irv.vid - FROM rlists irl - JOIN releases ir ON ir.id = irl.rid - JOIN releases_vn irv ON irv.rid = ir.latest - WHERE uid = ? - ))| if $o{voted} != 1; - $where = '('.$where.') AND LOWER(SUBSTR(vr.title, 1, 1)) = \''.$o{char}.'\'' if $o{char}; - $where = '('.$where.') AND (ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' if defined $o{char} && !$o{char}; - $where = '('.$where.') AND vo.vote IS NULL' if $o{voted} == -1; + + my %where = ( + 'vl.uid = ?' => $o{uid}, + defined($o{voted}) ? ('vo.vote !s NULL' => $o{voted} ? 'IS NOT' : 'IS') : (), + defined($o{status})? ('vl.status = ?' => $o{status}) : (), + $o{char} ? ('LOWER(SUBSTR(vr.title, 1, 1)) = ?' => $o{char} ) : (), + defined $o{char} && !$o{char} ? ( + '(ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' => 1 ) : (), + ); my $order = sprintf { title => 'vr.title %s', @@ -65,14 +71,14 @@ sub dbVNListList { # execute query my($r, $np) = $self->dbPage(\%o, qq| - SELECT vr.vid, vr.title, vr.original, COALESCE(vo.vote, 0) AS vote - FROM vn v + SELECT vr.vid, vr.title, vr.original, vl.status, vl.notes, COALESCE(vo.vote, 0) AS vote + FROM vnlists vl + JOIN vn v ON v.id = vl.vid JOIN vn_rev vr ON vr.id = v.latest - !s JOIN votes vo ON vo.vid = v.id AND vo.uid = ? - WHERE $where + LEFT JOIN votes vo ON vo.vid = vl.vid AND vo.uid = vl.uid + !W ORDER BY !s|, - $o{voted} == 1 ? '' : 'LEFT', $o{uid}, # JOIN if we only want votes, LEFT JOIN if we also want rlist items - $o{voted} != 1 ? $o{uid} : (), $order + \%where, $order ); # fetch releases and link to VNs @@ -83,7 +89,7 @@ sub dbVNListList { } @$r; my $rel = $self->dbAll(q| - SELECT rv.vid, rr.rid, r.latest, rr.title, rr.original, rr.released, rr.type, rl.rstat, rl.vstat + SELECT rv.vid, rr.rid, r.latest, rr.title, rr.original, rr.released, rr.type, rl.status FROM rlists rl JOIN releases r ON rl.rid = r.id JOIN releases_rev rr ON rr.id = r.latest @@ -114,35 +120,57 @@ sub dbVNListList { } -# %options->{ uid rid rstat vstat } +# Arguments: uid vid status notes +# vid can be an arrayref only when the rows are already present, in which case an update is done +# status and notes can be undef when an update is done, in which case these fields aren't updated sub dbVNListAdd { - my($self, %o) = @_; + my($self, $uid, $vid, $stat, $notes) = @_; + $self->dbExec( + 'UPDATE vnlists !H WHERE uid = ? AND vid IN(!l)', + {defined($stat) ? ('status = ?' => $stat ):(), + defined($notes)? ('notes = ?' => $notes):()}, + $uid, ref($vid) ? $vid : [ $vid ] + ) + || + $self->dbExec( + 'INSERT INTO vnlists (uid, vid, status, notes) VALUES(?, ?, ?, ?)', + $uid, $vid, $stat||0, $notes||'' + ); +} - my %s = ( - defined $o{rstat} ? ( 'rstat = ?', $o{rstat} ) : (), - defined $o{vstat} ? ( 'vstat = ?', $o{vstat} ) : (), + +# Arguments: uid, vid +sub dbVNListDel { + my($self, $uid, $vid) = @_; + $self->dbExec( + 'DELETE FROM vnlists WHERE uid = ? AND vid IN(!l)', + $uid, ref($vid) ? $vid : [ $vid ] ); - $o{rstat}||=0; - $o{vstat}||=0; +} + +# Arguments: uid rid status +# rid can be an arrayref only when the rows are already present, in which case an update is done +sub dbRListAdd { + my($self, $uid, $rid, $stat) = @_; $self->dbExec( - 'UPDATE rlists !H WHERE uid = ? AND rid IN(!l)', - \%s, $o{uid}, ref($o{rid}) eq 'ARRAY' ? $o{rid} : [ $o{rid} ] + 'UPDATE rlists SET status = ? WHERE uid = ? AND rid IN(!l)', + $stat, $uid, ref($rid) ? $rid : [ $rid ] ) || $self->dbExec( - 'INSERT INTO rlists (uid, rid, rstat, vstat) VALUES(!l)', - [@o{qw| uid rid rstat vstat |}] + 'INSERT INTO rlists (uid, rid, status) VALUES(?, ?, ?)', + $uid, $rid, $stat ); } # Arguments: uid, rid -sub dbVNListDel { +sub dbRListDel { my($self, $uid, $rid) = @_; $self->dbExec( 'DELETE FROM rlists WHERE uid = ? AND rid IN(!l)', - $uid, ref($rid) eq 'ARRAY' ? $rid : [ $rid ] + $uid, ref($rid) ? $rid : [ $rid ] ); } @@ -160,8 +188,14 @@ sub dbVoteGet { my %where = ( $o{uid} ? ( 'n.uid = ?' => $o{uid} ) : (), $o{vid} ? ( 'n.vid = ?' => $o{vid} ) : (), - $o{hide} ? ( 'u.show_list = TRUE' => 1 ) : (), + $o{hide} ? ( 'NOT EXISTS(SELECT 1 FROM users_prefs WHERE uid = n.uid AND key = \'hide_list\')' => 1 ) : (), $o{hide_ign} ? ( '(NOT u.ign_votes OR u.id = ?)' => $self->authInfo->{id}||0 ) : (), + $o{vn_char} ? ( 'LOWER(SUBSTR(vr.title, 1, 1)) = ?' => $o{vn_char} ) : (), + defined $o{vn_char} && !$o{vn_char} ? ( + '(ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' => 1 ) : (), + $o{user_char} ? ( 'LOWER(SUBSTR(u.username, 1, 1)) = ?' => $o{user_char} ) : (), + defined $o{user_char} && !$o{user_char} ? ( + '(ASCII(u.username) < 97 OR ASCII(u.username) > 122) AND (ASCII(u.username) < 65 OR ASCII(u.username) > 90)' => 1 ) : (), ); my @select = ( @@ -221,14 +255,15 @@ sub dbVoteStats { # Adds a new vote or updates an existing one # Arguments: vid, uid, vote +# vid can be an arrayref only when the rows are already present, in which case an update is done sub dbVoteAdd { my($self, $vid, $uid, $vote) = @_; $self->dbExec(q| UPDATE votes - SET vote = ? - WHERE vid = ? + SET vote = ?, date = NOW() + WHERE vid IN(!l) AND uid = ?|, - $vote, $vid, $uid + $vote, ref($vid) ? $vid : [$vid], $uid ) || $self->dbExec(q| INSERT INTO votes (vid, uid, vote) @@ -239,10 +274,11 @@ sub dbVoteAdd { # Arguments: uid, vid +# vid can be an arrayref sub dbVoteDel { my($self, $uid, $vid) = @_; $self->dbExec('DELETE FROM votes !W', - { 'vid = ?' => $vid, 'uid = ?' => $uid } + { 'vid IN(!l)' => [ref($vid)?$vid:[$vid]], 'uid = ?' => $uid } ); } diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index 7440f495..bd7db201 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -6,14 +6,14 @@ use warnings; use Exporter 'import'; our @EXPORT = qw| - dbUserGet dbUserEdit dbUserAdd dbUserDel + dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserPrefSet dbSessionAdd dbSessionDel dbSessionUpdateLastUsed dbNotifyGet dbNotifyMarkRead dbNotifyRemove |; # %options->{ username passwd mail session uid ip registered search results page what sort reverse } -# what: notifycount stats extended +# what: notifycount stats extended prefs hide_list # sort: username registered votes changes tags sub dbUserGet { my $s = shift; @@ -21,6 +21,7 @@ sub dbUserGet { page => 1, results => 10, what => '', + sort => '', @_ ); @@ -51,12 +52,13 @@ sub dbUserGet { ); my @select = ( - qw|id username c_votes c_changes show_list c_tags|, + qw|id username c_votes c_changes c_tags|, q|extract('epoch' from registered) as registered|, $o{what} =~ /extended/ ? ( - qw|mail rank salt skin customcss show_nsfw ign_votes notify_dbedit notify_announce|, + qw|mail rank salt ign_votes|, q|encode(passwd, 'hex') AS passwd| ) : (), + $o{what} =~ /hide_list/ ? 'up.value AS hide_list' : (), $o{what} =~ /notifycount/ ? '(SELECT COUNT(*) FROM notifications WHERE uid = u.id AND read IS NULL) AS notifycount' : (), $o{what} =~ /stats/ ? ( @@ -72,12 +74,14 @@ sub dbUserGet { my @join = ( $o{session} ? 'JOIN sessions s ON s.uid = u.id' : (), + $o{what} =~ /hide_list/ || $o{sort} eq 'votes' ? + "LEFT JOIN users_prefs up ON up.uid = u.id AND up.key = 'hide_list'" : (), ); my $order = sprintf { username => 'u.username %s', registered => 'u.registered %s', - votes => 'NOT u.show_list, u.c_votes %s', + votes => 'up.value NULLS FIRST, u.c_votes %s', changes => 'u.c_changes %s', tags => 'u.c_tags %s', }->{ $o{sort}||'username' }, $o{reverse} ? 'DESC' : 'ASC'; @@ -90,6 +94,20 @@ sub dbUserGet { ORDER BY !s|, join(', ', @select), join(' ', @join), \%where, $order ); + + if(@$r && $o{what} =~ /prefs/) { + my %r = map { + $r->[$_]{prefs} = {}; + ($r->[$_]{id}, $r->[$_]) + } 0..$#$r; + + $r{$_->{uid}}{prefs}{$_->{key}} = $_->{value} for (@{$s->dbAll(q| + SELECT uid, key, value + FROM users_prefs + WHERE uid IN(!l)|, + [ keys %r ] + )}); + } return wantarray ? ($r, $np) : $r; } @@ -100,7 +118,7 @@ sub dbUserEdit { my %h; defined $o{$_} && ($h{$_.' = ?'} = $o{$_}) - for (qw| username mail rank show_nsfw show_list skin customcss salt ign_votes notify_dbedit notify_announce |); + for (qw| username mail rank salt ign_votes |); $h{'passwd = decode(?, \'hex\')'} = $o{passwd} if defined $o{passwd}; @@ -127,6 +145,15 @@ sub dbUserDel { } +# uid, key, val +sub dbUserPrefSet { + my($s, $uid, $key, $val) = @_; + !$val ? $s->dbExec('DELETE FROM users_prefs WHERE uid = ? AND key = ?', $uid, $key) + : $s->dbExec('UPDATE users_prefs SET value = ? WHERE uid = ? AND key = ?', $val, $uid, $key) + || $s->dbExec('INSERT INTO users_prefs (uid, key, value) VALUES (?, ?, ?)', $uid, $key, $val); +} + + # Adds a session to the database # uid, 40 character session token sub dbSessionAdd { diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm index d25a5796..2a7f2477 100644 --- a/lib/VNDB/DB/VN.pm +++ b/lib/VNDB/DB/VN.pm @@ -10,7 +10,8 @@ use Encode 'decode_utf8'; our @EXPORT = qw|dbVNGet dbVNRevisionInsert dbVNImageId dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|; -# Options: id, rev, char, search, length, lang, olang, plat, tags_include, tags_exclude, hasani, results, page, what, sort, reverse +# Options: id, rev, char, search, length, lang, olang, plat, tag_inc, tag_exc, tagspoil, +# hasani, hasshot, results, page, what, sort, reverse # What: extended anime relations screenshots relgraph rating ranking changes # Sort: id rel pop rating title tagscore rand sub dbVNGet { @@ -19,6 +20,12 @@ sub dbVNGet { $o{page} ||= 1; $o{what} ||= ''; $o{sort} ||= 'title'; + $o{tagspoil} //= 2; + + # user input that is literally added to the query should be checked... + die "Invalid input for tagspoil or tag_inc at dbVNGet()\n" if + grep !defined($_) || $_!~/^\d+$/, $o{tagspoil}, + !$o{tag_inc} ? () : (ref($o{tag_inc}) ? @{$o{tag_inc}} : $o{tag_inc}); my @where = ( $o{id} ? ( @@ -39,19 +46,20 @@ sub dbVNGet { '('.join(' OR ', map "v.c_platforms ILIKE '%%$_%%'", ref $o{plat} ? @{$o{plat}} : $o{plat}).')' => 1 ) : (), defined $o{hasani} ? ( '!sEXISTS(SELECT 1 FROM vn_anime va WHERE va.vid = vr.id)' => [ $o{hasani} ? '' : 'NOT ' ]) : (), - $o{tags_include} && @{$o{tags_include}} ? ( + defined $o{hasshot} ? ( + '!sEXISTS(SELECT 1 FROM vn_screenshots vs WHERE vs.vid = vr.id)' => [ $o{hasshot} ? '' : 'NOT ' ]) : (), + $o{tag_inc} ? ( 'v.id IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l) AND spoiler <= ? GROUP BY vid HAVING COUNT(tag) = ?)', - [ $o{tags_include}[1], $o{tags_include}[0], $#{$o{tags_include}[1]}+1 ] - ) : (), - $o{tags_exclude} && @{$o{tags_exclude}} ? ( - 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ $o{tags_exclude} ] ) : (), + [ ref $o{tag_inc} ? $o{tag_inc} : [$o{tag_inc}], $o{tagspoil}, ref $o{tag_inc} ? $#{$o{tag_inc}}+1 : 1 ]) : (), + $o{tag_exc} ? ( + 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ ref $o{tag_exc} ? $o{tag_exc} : [$o{tag_exc}] ] ) : (), $o{search} ? ( map +('v.c_search like ?', "%$_%"), normalize_query($o{search})) : (), # don't fetch hidden items unless we ask for an ID !$o{id} && !$o{rev} ? ( 'v.hidden = FALSE' => 0 ) : (), # optimize fetching random entries (only when there are no other filters present, otherwise this won't work well) - $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort)$/, keys %o) ? ( + $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort|tagspoil)$/, keys %o) ? ( sprintf 'v.id IN(SELECT floor(random() * last_value)::integer FROM generate_series(1,20), (SELECT last_value FROM vn_id_seq) s1 LIMIT 20)' ) : (), @@ -69,7 +77,7 @@ sub dbVNGet { 'JOIN relgraphs vg ON vg.id = v.rgraph' : (), ); - my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]}; + my $tag_ids = $o{tag_inc} && join ',', ref $o{tag_inc} ? @{$o{tag_inc}} : $o{tag_inc}; my @select = ( # see https://rt.cpan.org/Ticket/Display.html?id=54224 for the cast on c_languages qw|v.id v.locked v.hidden v.c_released v.c_languages::text[] v.c_platforms vr.title vr.original v.rgraph|, 'vr.id AS cid', $o{what} =~ /extended/ ? ( @@ -84,7 +92,7 @@ sub dbVNGet { ) : (), # TODO: optimize this, as it will be very slow when the selected tags match a lot of VNs (>1000) $tag_ids ? - qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvh.vid) AS tagscore| : (), + qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tagspoil} GROUP BY tvh.vid) AS tagscore| : (), ); my $order = sprintf { @@ -216,8 +224,10 @@ sub dbScreenshotGet { # Fetch random VN + screenshots +# if any arguments are given, it will return one random screenshot for each VN sub dbScreenshotRandom { - return shift->dbAll(q| + my($self, @vids) = @_; + return $self->dbAll(q| SELECT s.id AS scr, s.width, s.height, vr.vid, vr.title FROM screenshots s JOIN vn_screenshots vs ON vs.scr = s.id @@ -230,7 +240,22 @@ sub dbScreenshotRandom { LIMIT 20 ) LIMIT 4| - ); + ) if !@vids; + # this query is faster than it looks + return $self->dbAll(join(' UNION ALL ', map + q|SELECT s.id AS scr, s.width, s.height, vr.vid, vr.title, RANDOM() AS position + FROM vn v + JOIN vn_rev vr ON vr.id = v.latest + JOIN vn_screenshots vs ON vs.vid = v.latest + JOIN screenshots s ON s.id = vs.scr + WHERE v.id = ? AND s.id = ( + SELECT vs2.scr + FROM vn_screenshots vs2 + JOIN vn v2 ON v2.latest = vs2.vid + WHERE v2.id = v.id + ORDER BY RANDOM() + LIMIT 1 + )|, @vids).' ORDER BY position', @vids); } diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm index 65b66f9e..17d69cd5 100644 --- a/lib/VNDB/Func.pm +++ b/lib/VNDB/Func.pm @@ -7,7 +7,7 @@ use YAWF ':html'; use Exporter 'import'; use POSIX 'strftime', 'ceil', 'floor'; use VNDBUtil; -our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage fil_parse fil_serialize |); +our @EXPORT = (@VNDBUtil::EXPORT, qw| clearfloat cssicon tagscore mt minage fil_parse fil_serialize |); # three ways to represent the same information @@ -16,21 +16,6 @@ our @fil_escape = split //, $fil_escape; our %fil_escape = map +($fil_escape[$_], sprintf '%02d', $_), 0..$#fil_escape; -# Argument: hashref with rstat and vstat -# Returns: empty string if not in list, otherwise colour-encoded list status -sub liststat { - my $l = shift; - return '' if !$l; - 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 = 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"; -} - - # Clears a float, to make sure boxes always have the correct height sub clearfloat { div class => 'clearfloat', ''; @@ -82,7 +67,7 @@ sub mt { sub minage { my($a, $ex) = @_; - my $str = !defined($a) ? mt '_minage_null' : !$a ? mt '_minage_all' : mt '_minage_age', $a; + my $str = $a == -1 ? mt '_minage_null' : !$a ? mt '_minage_all' : mt '_minage_age', $a; $ex = !defined($a) ? '' : { 0 => 'CERO A', 12 => 'CERO B', @@ -118,7 +103,7 @@ sub fil_serialize { my @v = ref $fil->{$_} ? @{$fil->{$_}} : ($fil->{$_}); s/$e/_$fil_escape{$1}/g for(@v); $_.'-'.join '~', @v - } keys %$fil; + } grep defined($fil->{$_}), keys %$fil; } 1; diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index d9974b00..0b5daa6c 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -48,7 +48,7 @@ sub thread { end; end; - $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 't', 1); + $self->htmlBrowseNavigate("/t$tid/", $page, [ $t->{count}, 25 ], 't', 1); div class => 'mainbox thread'; table; for my $i (0..$#$p) { @@ -83,7 +83,7 @@ sub thread { } end; end; - $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 'b', 1); + $self->htmlBrowseNavigate("/t$tid/", $page, [ $t->{count}, 25 ], 'b', 1); if($t->{locked}) { div class => 'mainbox'; @@ -278,7 +278,7 @@ sub board { return 404 if $f->{_err}; my $obj = !$iid ? undef : - $type eq 'u' ? $self->dbUserGet(uid => $iid)->[0] : + $type eq 'u' ? $self->dbUserGet(uid => $iid, what => 'hide_list')->[0] : $type eq 'p' ? $self->dbProducerGet(id => $iid)->[0] : $self->dbVNGet(id => $iid)->[0]; return 404 if $iid && !$obj; diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index 88e08f68..90a457aa 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -16,6 +16,7 @@ YAWF::register( qr{setlang}, \&setlang, qr{nospam}, \&nospam, qr{we-dont-like-ie}, \&iemessage, + qr{xml/prefs\.xml}, \&prefs, qr{opensearch\.xml}, \&opensearch, # redirects for old URLs @@ -44,7 +45,13 @@ sub homepage { lit mt '_home_intro'; end; - my $scr = $self->dbScreenshotRandom; + # with filters applied it's signifcantly slower, so special-code the situations with and without filters + my @vns; + if($self->authPref('filter_vn')) { + my $r = $self->filFetchDB(vn => undef, undef, {hasshot => 1, results => 4, order => 'rand'}); + @vns = map $_->{id}, @$r; + } + my $scr = $self->dbScreenshotRandom(@vns); p class => 'screenshots'; for (@$scr) { my($w, $h) = imgsize($_->{width}, $_->{height}, @{$self->{scr_size}}); @@ -124,7 +131,7 @@ sub homepage { h1; a href => '/v/rand', mt '_home_randomvn'; end; - my $random = $self->dbVNGet(results => 10, sort => 'rand'); + my $random = $self->filFetchDB(vn => undef, undef, {results => 10, sort => 'rand'}); ul; for (@$random) { li; @@ -139,7 +146,7 @@ sub homepage { h1; a href => strftime('/r?fil=date_after-%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming'; end; - my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms'); + my $upcoming = $self->filFetchDB(release => undef, undef, {results => 10, unreleased => 1, what => 'platforms'}); ul; for (@$upcoming) { li; @@ -159,7 +166,7 @@ sub homepage { h1; a href => strftime('/r?fil=date_before-%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased'; end; - my $justrel = $self->dbReleaseGet(results => 10, sort => 'released', reverse => 1, unreleased => 0, what => 'platforms'); + my $justrel = $self->filFetchDB(release => undef, undef, {results => 10, sort => 'released', reverse => 1, unreleased => 0, what => 'platforms'}); ul; for (@$justrel) { li; @@ -197,7 +204,7 @@ sub history { return 404 if $f->{_err}; # get item object and title - my $obj = $type eq 'u' ? $self->dbUserGet(uid => $id)->[0] : + my $obj = $type eq 'u' ? $self->dbUserGet(uid => $id, what => 'hide_list')->[0] : $type eq 'p' ? $self->dbProducerGet(id => $id)->[0] : $type eq 'r' ? $self->dbReleaseGet(id => $id)->[0] : $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : undef; @@ -340,10 +347,17 @@ sub setlang { return 404 if $lang->{_err}; $lang = $lang->{lang}; + my $browser = VNDB::L10N->get_handle()->language_tag(); + (my $ref = $self->reqHeader('Referer')||'/') =~ s/^\Q$self->{url}//; $self->resRedirect($ref, 'post'); - $self->resHeader('Set-Cookie', "l10n=$lang; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}") - if $lang ne $self->{l10n}->language_tag(); + if($lang ne $self->{l10n}->language_tag()) { + $self->authInfo->{id} + ? $self->authPref(l10n => $lang eq $browser ? undef : $lang) + : $self->resHeader('Set-Cookie', sprintf 'l10n=%s; expires=%s; path=/; domain=%s', + $lang, $lang eq $browser ? 'Sat, 01-Jan-2000 00:00:00 GMT' : 'Sat, 01-Jan-2030 00:00:00 GMT', + $self->{cookie_domain}); + } } @@ -405,6 +419,24 @@ sub iemessage { } +sub prefs { + my $self = shift; + return if !$self->authCheckCode; + return 404 if !$self->authInfo->{id}; + my $f = $self->formValidate( + { name => 'key', enum => [qw|filter_vn filter_release|] }, + { name => 'value', required => 0, maxlength => 2000 }, + ); + return 404 if $f->{_err}; + $self->authPref($f->{key}, $f->{value}); + + # doesn't really matter what we return, as long as it's XML + $self->resHeader('Content-type' => 'text/xml'); + xml; + tag 'done', ''; +} + + sub opensearch { my $self = shift; $self->resHeader('Content-Type' => 'application/opensearchdescription+xml'); diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index 6e11e829..f7a46c2d 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -162,7 +162,7 @@ sub _releases { for my $rel (@{$vn{$v->{vid}}}) { Tr class => 'rel'; td class => 'tc1'; lit $self->{l10n}->datestr($rel->{released}); end; - td class => 'tc2', !defined($rel->{minage}) ? '' : minage $rel->{minage}; + td class => 'tc2', $rel->{minage} < 0 ? '' : minage $rel->{minage}; td class => 'tc3'; for (sort @{$rel->{platforms}}) { next if $_ eq 'oth'; @@ -225,10 +225,11 @@ sub edit { { name => 'l_wp', required => 0, maxlength => 150, default => '' }, { name => 'desc', required => 0, maxlength => 5000, default => '' }, { name => 'prodrelations', required => 0, maxlength => 5000, default => '' }, - { name => 'editsum', maxlength => 5000 }, + { name => 'editsum', required => 0, maxlength => 5000 }, { name => 'ihid', required => 0 }, { name => 'ilock', required => 0 }, ); + push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{desc}); if(!$frm->{_err}) { # parse my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$pid || $2 != $pid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{prodrelations} ]; diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index ce5c62c2..06dcfd0a 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -56,7 +56,7 @@ sub page { [ media => join => ', ', split => sub { map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]} } ], - [ resolution => serialize => sub { $self->{resolutions}[$_[0]][0] } ], + [ resolution => serialize => sub { my $r = $self->{resolutions}[$_[0]][0]; $r =~ /^_/ ? mt($r) : $r } ], [ voiced => serialize => sub { mt '_voiced_'.$_[0] } ], [ ani_story => serialize => sub { mt '_animated_'.$_[0] } ], [ ani_ero => serialize => sub { mt '_animated_'.$_[0] } ], @@ -190,7 +190,7 @@ sub _infotable { end; end; - if(defined $r->{minage}) { + if($r->{minage} >= 0) { Tr ++$i % 2 ? (class => 'odd') : (); td mt '_relinfo_minage'; td minage $r->{minage}; @@ -236,22 +236,18 @@ sub _infotable { } if($self->authInfo->{id}) { - my $rl = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => $r->{id})->[0]; + my $rl = $self->dbRListGet(uid => $self->authInfo->{id}, rid => $r->{id})->[0]; Tr ++$i % 2 ? (class => 'odd') : (); td mt '_relinfo_user'; td; Select id => 'listsel', name => $self->authGetCode("/r$r->{id}/list"); - option mt !$rl ? '_relinfo_user_notlist' : - ('_relinfo_user_inlist', mt('_rlst_rstat_'.$rl->{rstat}), mt('_rlst_vstat_'.$rl->{vstat})); - optgroup label => mt '_relinfo_user_setr'; - option value => "r$_", mt '_rlst_rstat_'.$_ - for (@{$self->{rlst_rstat}}); + option value => -2, + mt !$rl ? '_relinfo_user_notlist' : ('_relinfo_user_inlist', mt('_rlist_status_'.$rl->{status})); + optgroup label => mt '_relinfo_user_setstatus'; + option value => $_, mt '_rlist_status_'.$_ + for (@{$self->{rlist_status}}); end; - optgroup label => mt '_relinfo_user_setv'; - option value => "v$_", mt '_rlst_vstat_'.$_ - for (@{$self->{rlst_vstat}}); - end; - option value => 'del', mt '_relinfo_user_del' if $rl; + option value => -1, mt '_relinfo_user_del' if $rl; end; end; end; @@ -287,9 +283,8 @@ sub edit { my $vn = $rid ? $r->{vn} : [{ vid => $vid, title => $v->{title} }]; my %b4 = !$rid ? () : ( - (map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released + (map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released minage notes platforms patch resolution voiced freeware doujin ani_story ani_ero ihid ilock|), - minage => defined($r->{minage}) ? $r->{minage} : -1, media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}), producers => join('|||', map sprintf('%d,%d,%s', $_->{id}, ($_->{developer}?1:0)+($_->{publisher}?2:0), $_->{name}), @@ -315,7 +310,7 @@ sub edit { { name => 'languages', multi => 1, enum => $self->{languages} }, { name => 'website', required => 0, default => '', maxlength => 250, template => 'url' }, { name => 'released', required => 0, default => 0, template => 'int' }, - { name => 'minage' , required => 0, default => -1, enum => [map !defined($_)?-1:$_, @{$self->{age_ratings}}] }, + { name => 'minage' , required => 0, default => -1, enum => $self->{age_ratings} }, { name => 'notes', required => 0, default => '', maxlength => 10240 }, { name => 'platforms', required => 0, default => '', multi => 1, enum => $self->{platforms} }, { name => 'media', required => 0, default => '' }, @@ -325,11 +320,12 @@ sub edit { { name => 'ani_ero', required => 0, default => 0, enum => $self->{animated} }, { name => 'producers', required => 0, default => '' }, { name => 'vn', maxlength => 5000 }, - { name => 'editsum', maxlength => 5000 }, + { name => 'editsum', required => 0, maxlength => 5000 }, { name => 'ihid', required => 0 }, { name => 'ilock', required => 0 }, ); + push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{notes}); push @{$frm->{_err}}, [ 'released', 'required', 1 ] if !$frm->{released}; my($media, $producers, $new_vn); @@ -356,9 +352,8 @@ sub edit { if(!$frm->{_err}) { my $nrev = $self->dbItemEdit(r => !$copy && $rid ? $r->{cid} : undef, - (map { $_ => $frm->{$_} } qw| type title original gtin catalog languages website released + (map { $_ => $frm->{$_} } qw| type title original gtin catalog languages website released minage notes platforms resolution editsum patch voiced freeware doujin ani_story ani_ero ihid ilock|), - minage => $frm->{minage} < 0 ? undef : $frm->{minage}, vn => $new_vn, producers => $producers, media => $media, @@ -406,7 +401,7 @@ sub _form { [ date => short => 'released', name => mt('_redit_form_released') ], [ static => content => mt('_redit_form_released_note') ], [ select => short => 'minage', name => mt('_redit_form_minage'), - options => [ map [ !defined($_)?-1:$_, minage $_, 1 ], @{$self->{age_ratings}} ] ], + options => [ map [ $_, minage $_, 1 ], @{$self->{age_ratings}} ] ], [ textarea => short => 'notes', name => mt('_redit_form_notes').'<br /><b class="standout">'.mt('_inenglish').'</b>' ], [ static => content => mt('_redit_form_notes_note') ], ], @@ -492,19 +487,16 @@ sub browse { { name => 'fil',required => 0, default => '' }, ); return 404 if $f->{_err}; + $f->{fil} = $self->authPref('filter_release') if !grep $_ eq 'fil', $self->reqParam(); - my $fil = fil_parse $f->{fil}, qw|type patch freeware doujin date_before date_after minage lang olang resolution plat med voiced ani_story ani_ero|; - _fil_compat($self, $fil); - $f->{fil} = fil_serialize($fil); - - my($list, $np) = !$f->{q} && !keys %$fil ? ([], 0) : $self->dbReleaseGet( + my %compat = _fil_compat($self); + my($list, $np) = !$f->{q} && !$f->{fil} && !keys %compat ? ([], 0) : $self->filFetchDB(release => $f->{fil}, \%compat, { sort => $f->{s}, reverse => $f->{o} eq 'd', page => $f->{p}, results => 50, what => 'platforms', $f->{q} ? ( search => $f->{q} ) : (), - %$fil - ); + }); $self->htmlHeader(title => mt('_rbrowse_title')); @@ -519,13 +511,14 @@ sub browse { end; end; + my $uri = sprintf '/r?q=%s;fil=%s', uri_escape($f->{q}), $f->{fil}; $self->htmlBrowse( class => 'relbrowse', items => $list, options => $f, nextpage => $np, - pageurl => "/r?q=$f->{q};fil=$f->{fil};s=$f->{s};o=$f->{o}", - sorturl => "/r?q=$f->{q};fil=$f->{fil}", + pageurl => "$uri;s=$f->{s};o=$f->{o}", + sorturl => $uri, header => [ [ mt('_rbrowse_col_released'), 'released' ], [ mt('_rbrowse_col_minage'), 'minage' ], @@ -538,7 +531,7 @@ sub browse { td class => 'tc1'; lit $self->{l10n}->datestr($l->{released}); end; - td class => 'tc2', !defined($l->{minage}) ? '' : minage $l->{minage}; + td class => 'tc2', $l->{minage} < 0 ? '' : minage $l->{minage}; td class => 'tc3'; $_ ne 'oth' && cssicon $_, mt "_plat_$_" for (@{$l->{platforms}}); cssicon "lang $_", mt "_lang_$_" for (@{$l->{languages}}); @@ -551,7 +544,7 @@ sub browse { end; }, ) if @$list; - if(($f->{q} || keys %$fil) && !@$list) { + if(($f->{q} || $f->{fil}) && !@$list) { div class => 'mainbox'; h1 mt '_rbrowse_noresults_title'; div class => 'notice'; @@ -559,13 +552,14 @@ sub browse { end; end; } - $self->htmlFooter; + $self->htmlFooter(prefs => [qw|filter_release|]); } -# provide compatibility with old filter URLs +# provide compatibility with old URLs sub _fil_compat { - my($self, $fil) = @_; + my $self = shift; + my %c; my $f = $self->formValidate( { name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} }, { name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} }, @@ -575,23 +569,24 @@ sub _fil_compat { { name => 'fw', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'do', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] }, - { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$self->{age_ratings}} ] }, + { name => 'ma_a', required => 0, default => 0, enum => $self->{age_ratings} }, { name => 'mi', required => 0, default => 0, template => 'int' }, { name => 'ma', required => 0, default => 99999999, template => 'int' }, { name => 're', required => 0, multi => 1, default => 0, enum => [ 1..$#{$self->{resolutions}} ] }, ); - return if $f->{_err}; - $fil->{minage} //= [ grep defined($_) && $f->{ma_m} ? $f->{ma_a} >= $_ : defined ($_) && $f->{ma_a} <= $_, @{$self->{age_ratings}} ] if $f->{ma_a} || $f->{ma_m}; - $fil->{date_after} //= $f->{mi} if $f->{mi}; - $fil->{date_before} //= $f->{ma} if $f->{ma} < 99990000; - $fil->{plat} //= $f->{pl} if $f->{pl}[0]; - $fil->{lang} //= $f->{ln} if $f->{ln}[0]; - $fil->{med} //= $f->{me} if $f->{me}[0]; - $fil->{resolution} //= $f->{re} if $f->{re}[0]; - $fil->{type} //= $f->{tp} if $f->{tp}; - $fil->{patch} //= $f->{pa} == 2 ? 0 : 1 if $f->{pa}; - $fil->{freeware} //= $f->{fw} == 2 ? 0 : 1 if $f->{fw}; - $fil->{doujin} //= $f->{do} == 2 ? 0 : 1 if $f->{do}; + return () if $f->{_err}; + $c{minage} = [ grep $_ >= 0 && ($f->{ma_m} ? $f->{ma_a} >= $_ : $f->{ma_a} <= $_), @{$self->{age_ratings}} ] if $f->{ma_a} || $f->{ma_m}; + $c{date_after} = $f->{mi} if $f->{mi}; + $c{date_before} = $f->{ma} if $f->{ma} < 99990000; + $c{plat} = $f->{pl} if $f->{pl}[0]; + $c{lang} = $f->{ln} if $f->{ln}[0]; + $c{med} = $f->{me} if $f->{me}[0]; + $c{resolution} = $f->{re} if $f->{re}[0]; + $c{type} = $f->{tp} if $f->{tp}; + $c{patch} = $f->{pa} == 2 ? 0 : 1 if $f->{pa}; + $c{freeware} = $f->{fw} == 2 ? 0 : 1 if $f->{fw}; + $c{doujin} = $f->{do} == 2 ? 0 : 1 if $f->{do}; + return %c; } diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm index 0567bff6..ad736027 100644 --- a/lib/VNDB/Handler/Tags.pm +++ b/lib/VNDB/Handler/Tags.pm @@ -39,13 +39,15 @@ sub tagpage { my $tagspoil = $self->reqCookie($self->{cookie_prefix}.'tagspoil'); $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 0 if $f->{m} == -1; - my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->dbVNGet( + my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->filFetchDB(vn => undef, undef, { what => 'rating', results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - tags_include => [ $f->{m}, [$tag ]], - ); + tagspoil => $f->{m}, + tag_inc => $tag, + tag_exc => undef, + }); my $title = mt '_tagp_title', $t->{meta}?0:1, $t->{name}; $self->htmlHeader(title => $title, noindex => $t->{state} != 2); @@ -357,12 +359,12 @@ sub taglinks { my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'o', required => 0, default => 'd', enum => ['a', 'd'] }, - { name => 's', required => 0, default => 'date', enum => [qw|date username title tag|] }, + { name => 's', required => 0, default => 'date', enum => [qw|date tag|] }, { name => 'v', required => 0, default => 0, template => 'int' }, { name => 'u', required => 0, default => 0, template => 'int' }, { name => 't', required => 0, default => 0, template => 'int' }, ); - return 404 if $f->{_err}; + return 404 if $f->{_err} || $f->{p} > 100; my($list, $np) = $self->dbTagLinks( what => 'details', @@ -432,11 +434,11 @@ sub taglinks { sorturl => $url->(s=>0,o=>0), header => [ [ mt('_taglink_col_date'), 'date' ], - [ mt('_taglink_col_user'), 'username' ], + [ mt('_taglink_col_user') ], [ mt('_taglink_col_rating') ], [ mt('_taglink_col_tag'), 'tag' ], [ mt('_taglink_col_spoiler') ], - [ mt('_taglink_col_vn'), 'title' ], + [ mt('_taglink_col_vn'), ], ], row => sub { my($s, $n, $l) = @_; @@ -668,19 +670,22 @@ sub fulltree { sub tagxml { my $self = shift; - my $q = $self->formValidate({ name => 'q', maxlength => 500 }); - return 404 if $q->{_err}; - $q = $q->{q}; + my $f = $self->formValidate( + { name => 'q', required => 0, maxlength => 500 }, + { name => 'id', required => 0, multi => 1, template => 'int' }, + ); + return 404 if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]); my($list, $np) = $self->dbTagGet( - $q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q), + !$f->{q} ? () : $f->{q} =~ /^g([1-9]\d*)/ ? (id => $1) : $f->{q} =~ /^name:(.+)$/ ? (name => $1) : (search => $f->{q}), + $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (), results => 15, page => 1, ); $self->resHeader('Content-type' => 'text/xml; charset=UTF-8'); xml; - tag 'tags', more => $np ? 'yes' : 'no', query => $q; + tag 'tags', more => $np ? 'yes' : 'no', $f->{q} ? (query => $f->{q}) : (); for(@$list) { tag 'item', id => $_->{id}, meta => $_->{meta} ? 'yes' : 'no', state => $_->{state}, $_->{name}; } diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm index 6efb0a13..403b391d 100644 --- a/lib/VNDB/Handler/ULists.pm +++ b/lib/VNDB/Handler/ULists.pm @@ -10,8 +10,9 @@ use VNDB::Func; YAWF::register( qr{v([1-9]\d*)/vote}, \&vnvote, qr{v([1-9]\d*)/wish}, \&vnwish, - qr{r([1-9]\d*)/list}, \&rlist, - qr{xml/rlist.xml}, \&rlist, + qr{v([1-9]\d*)/list}, \&vnlist_e, + qr{r([1-9]\d*)/list}, \&rlist_e, + qr{xml/rlist.xml}, \&rlist_e, qr{([uv])([1-9]\d*)/votes}, \&votelist, qr{u([1-9]\d*)/wish}, \&wishlist, qr{u([1-9]\d*)/list}, \&vnlist, @@ -56,7 +57,26 @@ sub vnwish { } -sub rlist { +sub vnlist_e { + my($self, $id) = @_; + + my $uid = $self->authInfo->{id}; + return $self->htmlDenied() if !$uid; + + return if !$self->authCheckCode; + my $f = $self->formValidate( + { name => 'e', enum => [ -1, @{$self->{vnlist_status}} ] } + ); + return 404 if $f->{_err}; + + $self->dbVNListDel($uid, $id) if $f->{e} == -1; + $self->dbVNListAdd($uid, $id, $f->{e}) if $f->{e} != -1; + + $self->resRedirect('/v'.$id, 'temp'); +} + + +sub rlist_e { my($self, $id) = @_; my $rid = $id; @@ -73,27 +93,21 @@ sub rlist { return if !$self->authCheckCode; my $f = $self->formValidate( - { name => 'e', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] }, + { name => 'e', required => 1, enum => [ -1, @{$self->{rlist_status}} ] } ); return 404 if $f->{_err}; - $self->dbVNListDel($uid, $rid) if $f->{e} eq 'del'; - $self->dbVNListAdd( - rid => $rid, - uid => $uid, - $f->{e} =~ /^([rv])(\d+)$/ && $1 eq 'r' ? (rstat => $2) : (vstat => $2) - ) if $f->{e} ne 'del'; + $self->dbRListDel($uid, $rid) if $f->{e} == -1; + $self->dbRListAdd($uid, $rid, $f->{e}) if $f->{e} >= 0; if($id) { (my $ref = $self->reqHeader('Referer')||"/r$id") =~ s/^\Q$self->{url}//; $self->resRedirect($ref, 'temp'); } else { + # doesn't really matter what we return, as long as it's XML $self->resHeader('Content-type' => 'text/xml'); - my $st = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => [$rid])->[0]; xml; - tag 'rlist', uid => $self->authInfo->{id}, rid => $rid; - txt $st ? liststat $st : '--'; - end; + tag 'done', ''; } } @@ -101,17 +115,32 @@ sub rlist { sub votelist { my($self, $type, $id) = @_; - my $obj = $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : $self->dbUserGet(uid => $id)->[0]; + my $obj = $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : $self->dbUserGet(uid => $id, what => 'hide_list')->[0]; return 404 if !$obj->{id}; my $own = $type eq 'u' && $self->authInfo->{id} && $self->authInfo->{id} == $id; - return 404 if $type eq 'u' && !$own && !($obj->{show_list} || $self->authCan('usermod')); + return 404 if $type eq 'u' && !$own && !(!$obj->{hide_list} || $self->authCan('usermod')); my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'o', required => 0, default => 'd', enum => ['a', 'd'] }, { name => 's', required => 0, default => 'date', enum => [qw|date title vote|] }, + { name => 'c', required => 0, default => 'all', enum => [ 'all', 'a'..'z', 0 ] }, ); + return 404 if $f->{_err}; + + if($own && $self->reqMethod eq 'POST') { + return if !$self->authCheckCode; + my $frm = $self->formValidate( + { name => 'vid', required => 1, multi => 1, template => 'int' }, + { name => 'batchedit', required => 1, enum => [ -2, -1, 1..10 ] }, + ); + my @vid = grep $_ > 0, @{$frm->{vid}}; + if(!$frm->{_err} && @vid && $frm->{batchedit} > -2) { + $self->dbVoteDel($id, \@vid) if $frm->{batchedit} == -1; + $self->dbVoteAdd(\@vid, $id, $frm->{batchedit}) if $frm->{batchedit} >= 0; + } + } my($list, $np) = $self->dbVoteGet( $type.'id' => $id, @@ -121,24 +150,35 @@ sub votelist { sort => $f->{s} eq 'title' && $type eq 'v' ? 'username' : $f->{s}, reverse => $f->{o} eq 'd', results => 50, - page => $f->{p} + page => $f->{p}, + $f->{c} ne 'all' ? ($type eq 'u' ? 'vn_char' : 'user_char', $f->{c}) : (), ); - return 404 if !@$list; my $title = mt $type eq 'v' ? '_votelist_title_vn' : '_votelist_title_user', $obj->{title} || $obj->{username}; $self->htmlHeader(noindex => 1, title => $title); $self->htmlMainTabs($type => $obj, 'votes'); div class => 'mainbox'; h1 $title; + p class => 'browseopts'; + for ('all', 'a'..'z', 0) { + a href => "/$type$id/votes?c=$_", $_ eq $f->{c} ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; + } + end; + p mt '_votelist_novotes' if !@$list; end; - $self->htmlBrowse( + if($own) { + my $code = $self->authGetCode("/u$id/votes"); + form action => "/u$id/votes?formcode=$code;c=$f->{c};s=$f->{s};p=$f->{p}", method => 'post'; + } + + @$list && $self->htmlBrowse( class => 'votelist', items => $list, options => $f, nextpage => $np, - pageurl => "/$type$id/votes?o=$f->{o};s=$f->{s}", - sorturl => "/$type$id/votes", + pageurl => "/$type$id/votes?c=$f->{c};o=$f->{o};s=$f->{s}", + sorturl => "/$type$id/votes?c=$f->{c}", header => [ [ mt('_votelist_col_date'), 'date' ], [ mt('_votelist_col_vote'), 'vote' ], @@ -147,15 +187,33 @@ sub votelist { row => sub { my($s, $n, $l) = @_; Tr $n % 2 ? (class => 'odd') : (); - td class => 'tc1', $self->{l10n}->date($l->{date}); + td class => 'tc1'; + input type => 'checkbox', name => 'vid', value => $l->{vid} if $own; + txt ' '.$self->{l10n}->date($l->{date}); + end; td class => 'tc2', $l->{vote}; td class => 'tc3'; a href => $type eq 'v' ? ("/u$l->{uid}", $l->{username}) : ("/v$l->{vid}", shorten $l->{title}, 100); end; end; }, + $own ? (footer => sub { + Tr; + td colspan => 3, class => 'tc1'; + input type => 'checkbox', class => 'checkall', name => 'vid', value => -1; + txt ' '; + Select name => 'batchedit', id => 'batchedit'; + option value => -2, '-- with selected --'; + optgroup label => 'Change vote'; + option value => $_, "$_ (".mt("_vote_$_").')' for (reverse 1..10); + end; + option value => -1, 'revoke'; + end; + end; + end; + }) : (), ); - + end if $own; $self->htmlFooter; } @@ -164,8 +222,8 @@ sub wishlist { my($self, $uid) = @_; my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid; - my $u = $self->dbUserGet(uid => $uid)->[0]; - return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod')); + my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0]; + return 404 if !$u || !$own && !(!$u->{hide_list} || $self->authCan('usermod')); my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, @@ -266,8 +324,8 @@ sub vnlist { my($self, $uid) = @_; my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid; - my $u = $self->dbUserGet(uid => $uid)->[0]; - return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod')); + my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0]; + return 404 if !$u || !$own && !(!$u->{hide_list} || $self->authCan('usermod')); my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, @@ -275,33 +333,40 @@ sub vnlist { { name => 's', required => 0, default => 'title', enum => [ 'title', 'vote' ] }, { name => 'c', required => 0, default => 'all', enum => [ 'all', 'a'..'z', 0 ] }, { name => 'v', required => 0, default => 0, enum => [ -1..1 ] }, + { name => 't', required => 0, default => -1, enum => [ -1, @{$self->{vnlist_status}} ] }, ); return 404 if $f->{_err}; if($own && $self->reqMethod eq 'POST') { return if !$self->authCheckCode; my $frm = $self->formValidate( - { name => 'sel', required => 0, default => 0, multi => 1, template => 'int' }, - { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] }, + { name => 'vid', required => 0, default => 0, multi => 1, template => 'int' }, + { name => 'rid', required => 0, default => 0, multi => 1, template => 'int' }, + { name => 'not', required => 0, default => '', maxlength => 2000 }, + { name => 'vns', required => 1, enum => [ -2, -1, @{$self->{vnlist_status}}, 999 ] }, + { name => 'rel', required => 1, enum => [ -2, -1, @{$self->{rlist_status}} ] }, ); - if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) { - $self->dbVNListDel($uid, $frm->{sel}) if $frm->{batchedit} eq 'del'; - $self->dbVNListAdd( - rid => $frm->{sel}, - uid => $uid, - $frm->{batchedit} =~ /^([rv])(\d+)$/ && $1 eq 'r' ? (rstat => $2) : (vstat => $2) - ) if $frm->{batchedit} ne 'del'; + my @vid = grep $_ > 0, @{$frm->{vid}}; + my @rid = grep $_ > 0, @{$frm->{rid}}; + if(!$frm->{_err} && @vid && $frm->{vns} > -2) { + $self->dbVNListDel($uid, \@vid) if $frm->{vns} == -1; + $self->dbVNListAdd($uid, \@vid, $frm->{vns}) if $frm->{vns} >= 0 && $frm->{vns} < 999; + $self->dbVNListAdd($uid, \@vid, undef, $frm->{not}) if $frm->{vns} == 999; + } + if(!$frm->{_err} && @rid && $frm->{rel} > -2) { + $self->dbRListDel($uid, \@rid) if $frm->{rel} == -1; + $self->dbRListAdd($uid, \@rid, $frm->{rel}) if $frm->{rel} >= 0; } } - my($list, $np) = $self->dbVNListList( uid => $uid, results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - voted => $f->{v}, + voted => $f->{v} == 0 ? undef : $f->{v} < 0 ? 0 : $f->{v}, $f->{c} ne 'all' ? (char => $f->{c}) : (), + $f->{t} >= 0 ? (status => $f->{t}) : (), ); my $title = $own ? mt '_rlist_title_my' : mt '_rlist_title_other', $u->{username}; @@ -315,6 +380,7 @@ sub vnlist { local $_ = "/u$uid/list"; $_ .= '?c='.($n eq 'c' ? $v : $f->{c}); $_ .= ';v='.($n eq 'v' ? $v : $f->{v}); + $_ .= ';t='.($n eq 't' ? $v : $f->{t}); if($n eq 'page') { $_ .= ';o='.($n eq 'o' ? $v : $f->{o}); $_ .= ';s='.($n eq 's' ? $v : $f->{s}); @@ -330,10 +396,14 @@ sub vnlist { } end; p class => 'browseopts'; - a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_all'; + a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_all'; a href => $url->(v => 1), 1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_only'; a href => $url->(v => -1), -1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_none'; end; + p class => 'browseopts'; + a href => $url->(t => -1), -1 == $f->{t} ? (class => 'optselected') : (), mt '_rlist_all'; + a href => $url->(t => $_), $_ == $f->{t} ? (class => 'optselected') : (), mt '_vnlist_status_'.$_ for @{$self->{vnlist_status}}; + end; end; _vnlist_browse($self, $own, $list, $np, $f, $url, $uid); @@ -343,8 +413,11 @@ sub vnlist { sub _vnlist_browse { my($self, $own, $list, $np, $f, $url, $uid) = @_; - form action => $url->().';formcode='.$self->authGetCode("/u$uid/list"), method => 'post' - if $own; + if($own) { + form action => $url->(), method => 'post'; + input type => 'hidden', class => 'hidden', name => 'not', id => 'not', value => ''; + input type => 'hidden', class => 'hidden', name => 'formcode', id => 'formcode', value => $self->authGetCode("/u$uid/list"); + } $self->htmlBrowse( class => 'rlist', @@ -354,66 +427,84 @@ sub _vnlist_browse { sorturl => $url->(), pageurl => $url->('page'), header => [ - [ mt('_rlist_col_title') => 'title', 3 ], - sub { td class => 'tc2', id => 'expandall'; lit '<i>▸</i>'.mt('_rlist_col_releases').'*'; end; }, + [ '' ], + sub { td class => 'tc2', id => 'expandall'; lit '▸'; end; }, + [ mt('_rlist_col_title') => 'title' ], + [ '' ], [ '' ], + [ mt('_rlist_col_status') ], + [ mt('_rlist_col_releases').'*' ], [ mt('_rlist_col_vote') => 'vote' ], ], row => sub { my($s, $n, $i) = @_; Tr $n % 2 == 0 ? (class => 'odd') : (); - td class => 'tc1', colspan => 3; + td class => 'tc1'; input type => 'checkbox', name => 'vid', value => $i->{vid} if $own; end; + if(@{$i->{rels}}) { + td class => 'tc2 collapse_but', id => "vid$i->{vid}"; lit '▸'; end; + } else { + td class => 'tc2', ''; + } + td class => 'tc3_5', colspan => 3; a href => "/v$i->{vid}", title => $i->{original}||$i->{title}, shorten $i->{title}, 70; + b class => 'grayedout', $i->{notes} if $i->{notes}; end; - td class => 'tc2'.(@{$i->{rels}} ? ' collapse_but' : ''), id => 'vid'.$i->{vid}; - lit '<i>▸</i>'; - my $obtained = grep $_->{rstat}==2, @{$i->{rels}}; - my $finished = grep $_->{vstat}==2, @{$i->{rels}}; - my $txt = sprintf '%d/%d/%d', $obtained, $finished, scalar @{$i->{rels}}; - $txt = qq|<b class="done">$txt</b>| if $finished > $obtained || $finished && $finished == $obtained; - $txt = qq|<b class="todo">$txt</b>| if $obtained > $finished; + td class => 'tc6', $i->{status} ? mt '_vnlist_status_'.$i->{status} : ''; + td class => 'tc7'; + my $obtained = grep $_->{status}==2, @{$i->{rels}}; + my $total = scalar @{$i->{rels}}; + my $txt = sprintf '%d/%d', $obtained, $total; + $txt = qq|<b class="done">$txt</b>| if $total && $obtained == $total; + $txt = qq|<b class="todo">$txt</b>| if $obtained < $total; lit $txt; end; - td class => 'tc3', $i->{vote} || '-'; + td class => 'tc8', $i->{vote} || '-'; end; for (@{$i->{rels}}) { - Tr class => "collapse relhid collapse_vid$i->{vid}"; - td class => 'tc1'.($own ? ' own' : ''); - input type => 'checkbox', name => 'sel', value => $_->{rid} - if $own; - lit $self->{l10n}->datestr($_->{released}); - end; + Tr class => "collapse relhid collapse_vid$i->{vid}".($n%2 ? '':' odd'); + td class => 'tc1', ''; td class => 'tc2'; + input type => 'checkbox', name => 'rid', value => $_->{rid} if $own; + end; + td class => 'tc3', $self->{l10n}->datestr($_->{released}); + td class => 'tc4'; cssicon "lang $_", mt "_lang_$_" for @{$_->{languages}}; cssicon "rt$_->{type}", mt "_rtype_$_->{type}"; end; - td class => 'tc3'; + td class => 'tc5'; a href => "/r$_->{rid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 50; end; - td colspan => 2, class => 'tc4'; - lit liststat($_); - end; + td class => 'tc6', $_->{status} ? mt '_rlist_status_'.$_->{status} : ''; + td class => 'tc7_8', colspan => 2, ''; end; } }, $own ? (footer => sub { Tr; - td class => 'tc1', colspan => 3; - Select id => 'batchedit', name => 'batchedit'; - option mt '_rlist_selection'; - optgroup label => mt '_rlist_changerel'; - option value => "r$_", mt "_rlst_rstat_$_" - for (@{$self->{rlst_rstat}}); + td class => 'tc1'; input type => 'checkbox', name => 'vid', value => -1, class => 'checkall'; end; + td class => 'tc2'; input type => 'checkbox', name => 'rid', value => -1, class => 'checkall'; end; + td class => 'tc3_6', colspan => 4; + Select id => 'vns', name => 'vns'; + option value => -2, mt '_rlist_withvn'; + optgroup label => mt '_rlist_changestat'; + option value => $_, mt "_vnlist_status_$_" + for (@{$self->{vnlist_status}}); end; - optgroup label => mt '_rlist_changeplay'; - option value => "v$_", mt "_rlst_vstat_$_" - for (@{$self->{rlst_vstat}}); + option value => 999, mt '_rlist_setnote'; + option value => -1, mt '_rlist_del'; + end; + Select id => 'rel', name => 'rel'; + option value => -2, mt '_rlist_withrel'; + optgroup label => mt '_rlist_changestat'; + option value => $_, mt "_rlist_status_$_" + for (@{$self->{rlist_status}}); end; - option value => 'del', mt '_rlist_del'; + option value => -1, mt '_rlist_del'; end; + input type => 'submit', value => mt '_rlist_update'; end; - td class => 'tc2', colspan => 2, mt '_rlist_releasenote'; + td class => 'tc7_8', colspan => 2, mt '_rlist_releasenote'; end; }) : (), ); diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 3e2a1aef..044c72b2 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -27,7 +27,7 @@ YAWF::register( sub userpage { my($self, $uid) = @_; - my $u = $self->dbUserGet(uid => $uid, what => 'stats')->[0]; + my $u = $self->dbUserGet(uid => $uid, what => 'stats hide_list')->[0]; return 404 if !$u->{id}; my $votes = $u->{c_votes} && $self->dbVoteStats(uid => $uid); @@ -69,7 +69,7 @@ sub userpage { Tr ++$i % 2 ? (class => 'odd') : (); td mt '_userpage_votes'; td; - if(!$u->{show_list}) { + if($u->{hide_list}) { txt mt '_userpage_hidden'; } elsif($votes) { my($total, $count) = (0, 0); @@ -99,7 +99,7 @@ sub userpage { Tr ++$i % 2 ? (class => 'odd') : (); td mt '_userpage_list'; - td !$u->{show_list} ? mt('_userpage_hidden') : + td $u->{hide_list} ? mt('_userpage_hidden') : mt('_userpage_list_item', $u->{releasecount}, $u->{vncount}); end; @@ -116,7 +116,7 @@ sub userpage { end; end; - if($u->{show_list} && $votes) { + if(!$u->{hide_list} && $votes) { div class => 'mainbox'; h1 mt '_userpage_votestats'; $self->htmlVoteStats(u => $u, $votes); @@ -291,7 +291,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, what => 'extended')->[0]; + my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'extended prefs')->[0]; return 404 if !$u->{id}; # check POST data @@ -300,30 +300,27 @@ sub edit { return if !$self->authCheckCode; $frm = $self->formValidate( $self->authCan('usermod') ? ( - { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 }, - { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] }, + { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 }, + { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] }, { name => 'ign_votes', required => 0, default => 0 }, ) : (), - { name => 'mail', template => 'mail' }, - { name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' }, - { name => 'usrpass2', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' }, - { name => 'flags_list', required => 0, default => 0 }, - { name => 'flags_nsfw', required => 0, default => 0 }, - { name => 'skin', enum => [ '', keys %{$self->{skins}} ], required => 0, default => '' }, - { name => 'customcss', required => 0, maxlength => 2000, default => '' }, + { name => 'mail', template => 'mail' }, + { name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' }, + { name => 'usrpass2', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' }, + { name => 'hide_list', required => 0, default => 0, enum => [0,1] }, + { name => 'show_nsfw', required => 0, default => 0, enum => [0,1] }, + { name => 'skin', required => 0, default => '', enum => [ '', keys %{$self->{skins}} ] }, + { name => 'customcss', required => 0, maxlength => 2000, default => '' }, ); push @{$frm->{_err}}, 'passmatch' if ($frm->{usrpass} || $frm->{usrpass2}) && (!$frm->{usrpass} || !$frm->{usrpass2} || $frm->{usrpass} ne $frm->{usrpass2}); if(!$frm->{_err}) { + $self->dbUserPrefSet($uid, $_ => $frm->{$_}) for (qw|skin customcss show_nsfw hide_list |); my %o; $o{username} = $frm->{usrname} if $frm->{usrname}; $o{rank} = $frm->{rank} if $frm->{rank}; $o{mail} = $frm->{mail}; - $o{skin} = $frm->{skin}; - $o{customcss} = $frm->{customcss}; ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; - $o{show_list} = $frm->{flags_list} ? 1 : 0; - $o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0; $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod'); $self->dbUserEdit($uid, %o); $self->dbSessionDel($uid) if $frm->{usrpass}; @@ -334,9 +331,8 @@ sub edit { # fill out default values $frm->{usrname} ||= $u->{username}; - $frm->{$_} ||= $u->{$_} for(qw|rank mail skin customcss|); - $frm->{flags_list} = $u->{show_list} if !defined $frm->{flags_list}; - $frm->{flags_nsfw} = $u->{show_nsfw} if !defined $frm->{flags_nsfw}; + $frm->{$_} ||= $u->{$_} for(qw|rank mail|); + $frm->{$_} //= $u->{prefs}{$_} for(qw|skin customcss show_nsfw hide_list|); $frm->{ign_votes} = $u->{ign_votes} if !defined $frm->{ign_votes}; # create the page @@ -368,8 +364,8 @@ sub edit { [ passwd => short => 'usrpass2', name => mt '_usere_confirm' ], [ part => title => mt '_usere_options' ], - [ check => short => 'flags_list', name => mt '_usere_flist', "/u$uid/list", "/u$uid/wish" ], - [ check => short => 'flags_nsfw', name => mt '_usere_fnsfw' ], + [ check => short => 'hide_list', name => mt '_usere_flist', "/u$uid/list", "/u$uid/votes", "/u$uid/wish" ], + [ check => short => 'show_nsfw', name => mt '_usere_fnsfw' ], [ select => short => 'skin', name => mt('_usere_skin'), width => 300, options => [ map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_}[0].($self->debug?" [$_]":'') ], sort { $self->{skins}{$a}[0] cmp $self->{skins}{$b}[0] } keys %{$self->{skins}} ] ], [ textarea => short => 'customcss', name => mt '_usere_css' ], @@ -382,7 +378,7 @@ sub posts { my($self, $uid) = @_; # fetch user info (cached if uid == loggedin uid) - my $u = $self->authInfo->{id} && $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid)->[0]; + my $u = $self->authInfo->{id} && $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'hide_list')->[0]; return 404 if !$u->{id}; my $f = $self->formValidate( @@ -439,7 +435,7 @@ sub delete { # confirm if(!$act) { my $code = $self->authGetCode("/u$uid/del/o"); - my $u = $self->dbUserGet(uid => $uid)->[0]; + my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0]; return 404 if !$u->{id}; $self->htmlHeader(title => 'Delete user', noindex => 1); $self->htmlMainTabs('u', $u, 'del'); @@ -500,6 +496,7 @@ sub list { my($list, $np) = $self->dbUserGet( sort => $f->{s}, reverse => $f->{o} eq 'd', + what => 'hide_list', $char ne 'all' ? ( firstchar => $char ) : (), results => 50, @@ -527,8 +524,8 @@ sub list { a href => '/u'.$l->{id}, $l->{username}; end; td class => 'tc2', $self->{l10n}->date($l->{registered}); - td class => 'tc3'.(!$l->{show_list} && $self->authCan('usermod') ? ' linethrough' : ''); - lit !$l->{show_list} && !$self->authCan('usermod') ? '-' : !$l->{c_votes} ? 0 : + td class => 'tc3'.($l->{hide_list} && $self->authCan('usermod') ? ' linethrough' : ''); + lit $l->{hide_list} && !$self->authCan('usermod') ? '-' : !$l->{c_votes} ? 0 : qq|<a href="/u$l->{id}/votes">$l->{c_votes}</a>|; end; td class => 'tc4'; @@ -546,9 +543,9 @@ sub list { sub notifies { my($self, $uid) = @_; - return $self->htmlDenied if !$self->authInfo->{id} || $uid != $self->authInfo->{id}; - my $u = $self->dbUserGet(uid => $uid)->[0]; + my $u = $self->authInfo; + return $self->htmlDenied if !$u->{id} || $uid != $u->{id}; my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, @@ -562,15 +559,11 @@ sub notifies { if($self->reqMethod() eq 'POST' && $self->reqParam('set')) { return if !$self->authCheckCode; my $frm = $self->formValidate( - { name => 'notify_dbedit', required => 0 }, - { name => 'notify_announce', required => 0 } + { name => 'notify_nodbedit', required => 0, default => 1, enum => [0,1] }, + { name => 'notify_announce', required => 0, default => 0, enum => [0,1] } ); return 404 if $frm->{_err}; - for ('notify_dbedit', 'notify_announce') { - $frm->{$_} = $frm->{$_} ? 1 : 0; - $self->authInfo->{$_} = $frm->{$_}; - } - $self->dbUserEdit($uid, %$frm); + $self->authPref($_, $frm->{$_}) for ('notify_nodbedit', 'notify_announce'); $saved = 1; # updating notifications @@ -661,9 +654,10 @@ sub notifies { h1 mt '_usern_set_title'; div class => 'notice', mt '_usern_set_saved' if $saved; p; - for('dbedit', 'announce') { - input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => 1, - $self->authInfo->{"notify_$_"} ? (checked => 'checked') : (); + for('nodbedit', 'announce') { + my $def = $_ eq 'nodbedit'? 0 : 1; + input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => $def, + ($self->authPref("notify_$_")||0) == $def ? (checked => 'checked') : (); label for => "notify_$_", ' '.mt("_usern_set_$_"); br; } diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index 8e30da99..8a63b79b 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -25,72 +25,40 @@ sub list { ); return 404 if $f->{_err}; $f->{q} ||= $f->{sq}; - my $fil = fil_parse $f->{fil}, qw|length hasani taginc tagexc tagspoil lang olang plat|; - _fil_compat($self, $fil); - - if($f->{q}) { - return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp') - if $f->{q} =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/; - - # for URL compatibilty with older versions (ugly hack to get English strings) - my @lang; - $f->{q} =~ s/\s*$VNDB::L10N::en::Lexicon{"_lang_$_"}\s*//&&push @lang, $_ for (@{$self->{languages}}); - $fil->{lang} = $fil->{lang} ? [ ref($fil->{lang}) ? @{$fil->{lang}} : $fil->{lang}, @lang ] : \@lang if @lang; - } - $f->{fil} = fil_serialize $fil; - - # TODO: this should be moved to dbVNGet() in order for savable VN filters to be useful - my @ignored; - my $tagfind = sub { - return map { - my $i = $self->dbTagGet(name => $_)->[0]; - push @ignored, [$_, 0] if !$i; - push @ignored, [$_, 1] if $i && $i->{meta}; - $i && !$i->{meta} ? $i->{id} : (); - } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'') - }; - my @ti = $tagfind->(delete $fil->{taginc}); - my @te = $tagfind->(delete $fil->{tagexc}); - - $f->{s} = 'title' if !@ti && $f->{s} eq 'tagscore'; + $f->{fil} = $self->authPref('filter_vn') if !grep $_ eq 'fil', $self->reqParam(); + my %compat = _fil_compat($self); + + return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp') + if $f->{q} && $f->{q} =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/; + + $f->{s} = 'title' if $f->{fil} !~ /tag_inc-/ && $f->{s} eq 'tagscore'; $f->{o} = $f->{s} eq 'tagscore' ? 'd' : 'a' if !$f->{o}; - my($list, $np) = $self->dbVNGet( + my($list, $np) = $self->filFetchDB(vn => $f->{fil}, \%compat, { what => 'rating', $char ne 'all' ? ( char => $char ) : (), $f->{q} ? ( search => $f->{q} ) : (), results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - @ti ? (tags_include => [ delete $fil->{tagspoil}, \@ti ]) : (), - @te ? (tags_exclude => \@te) : (), - %$fil - ); + }); $self->resRedirect('/v'.$list->[0]{id}, 'temp') if $f->{q} && @$list == 1 && $f->{p} == 1; $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q}); + my $quri = uri_escape($f->{q}); form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get'; div class => 'mainbox'; h1 mt '_vnbrowse_title'; $self->htmlSearchBox('v', $f->{q}); p class => 'browseopts'; for ('all', 'a'..'z', 0) { - a href => "/v/$_?q=$f->{q};fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; + a href => "/v/$_?q=$quri;fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; } end; - if(@ignored) { - div class => 'warning'; - h2 mt '_vnbrowse_tagign_title'; - ul; - li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @ignored; - end; - end; - } - a id => 'filselect', href => '#v'; lit '<i>▸</i> '.mt('_rbrowse_filters').'<i></i>'; # TODO: it's not *r*browse end; @@ -98,25 +66,24 @@ sub list { end; end; # /form - $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", scalar @ti); - $self->htmlFooter; + $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$quri;fil=$f->{fil}", $f->{fil} =~ /tag_inc-/); + $self->htmlFooter(prefs => ['filter_vn']); } sub _fil_compat { - my($self, $fil) = @_; + my $self = shift; + my %c; my $f = $self->formValidate( { name => 'ln', required => 0, multi => 1, enum => $self->{languages}, default => '' }, { 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($self->{cookie_prefix}.'tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] }, ); - $fil->{lang} //= $f->{ln} if $f->{ln}[0]; - $fil->{plat} //= $f->{pl} if $f->{pl}[0]; - $fil->{taginc} //= $f->{ti} if $f->{ti}; - $fil->{tagexc} //= $f->{te} if $f->{te}; - $fil->{tagspoil} //= $f->{sp}; + return () if $f->{_err}; + $c{lang} //= $f->{ln} if $f->{ln}[0]; + $c{plat} //= $f->{pl} if $f->{pl}[0]; + $c{tagspoil} //= $f->{sp}; + return %c; } diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm index 8ee104af..b93c544d 100644 --- a/lib/VNDB/Handler/VNEdit.pm +++ b/lib/VNDB/Handler/VNEdit.pm @@ -52,11 +52,13 @@ sub edit { { name => 'img_nsfw', required => 0, default => 0 }, { name => 'vnrelations', required => 0, default => '', maxlength => 5000 }, { name => 'screenshots', required => 0, default => '', maxlength => 1000 }, - { name => 'editsum', maxlength => 5000 }, + { name => 'editsum', required => 0, maxlength => 5000 }, { name => 'ihid', required => 0 }, { name => 'ilock', required => 0 }, ); + push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{desc}); + # handle image upload my $image = _uploadimage($self, $v, $frm); diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 1a3eb514..5e024424 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -16,7 +16,7 @@ YAWF::register( sub rand { my $self = shift; - $self->resRedirect('/v'.$self->dbVNGet(results => 1, sort => 'rand')->[0]{id}, 'temp'); + $self->resRedirect('/v'.$self->filFetchDB(vn => undef, undef, {results => 1, sort => 'rand'})->[0]{id}, 'temp'); } @@ -74,12 +74,12 @@ sub page { } elsif($v->{image} < 0) { p mt '_vnpage_imgproc'; } else { - p $v->{img_nsfw} ? (id => 'nsfw_hid', style => $self->authInfo->{show_nsfw} ? 'display: block' : '') : (); + p $v->{img_nsfw} ? (id => 'nsfw_hid', style => $self->authPref('show_nsfw') ? 'display: block' : '') : (); img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title}; i mt '_vnpage_imgnsfw_foot' if $v->{img_nsfw}; end; if($v->{img_nsfw}) { - p id => 'nsfw_show', $self->authInfo->{show_nsfw} ? (style => 'display: none') : (); + p id => 'nsfw_show', $self->authPref('show_nsfw') ? (style => 'display: none') : (); txt mt('_vnpage_imgnsfw_msg')."\n\n"; a href => '#', mt '_vnpage_imgnsfw_show'; txt "\n\n".mt '_vnpage_imgnsfw_note'; @@ -225,7 +225,7 @@ sub _revision { [ image => htmlize => sub { my $url = sprintf "%s/cv/%02d/%d.jpg", $self->{url_static}, $_[0]%100, $_[0]; if($_[0] > 0) { - return $_[1]->{img_nsfw} && !$self->authInfo->{show_nsfw} ? "<a href=\"$url\">".mt('_vndiff_image_nsfw').'</a>' : "<img src=\"$url\" />"; + return $_[1]->{img_nsfw} && !$self->authPref('show_nsfw') ? "<a href=\"$url\">".mt('_vndiff_image_nsfw').'</a>' : "<img src=\"$url\" />"; } else { return mt $_[0] < 0 ? '_vndiff_image_proc' : '_vndiff_image_none'; } @@ -343,6 +343,7 @@ sub _useroptions { my($self, $i, $v) = @_; my $vote = $self->dbVoteGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; + my $list = $self->dbVNListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; my $wish = $self->dbWishListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; Tr ++$$i % 2 ? (class => 'odd') : (); @@ -358,6 +359,16 @@ sub _useroptions { end; br; } + + Select id => 'listsel', name => $self->authGetCode("/v$v->{id}/list"); + option $list ? mt '_vnpage_uopt_vnlisted', mt '_vnlist_status_'.$list->{status} : mt '_vnpage_uopt_novn'; + optgroup label => $list ? mt '_vnpage_uopt_changevn' : mt '_vnpage_uopt_addvn'; + option value => $_, mt "_vnlist_status_$_" for (@{$self->{rlist_status}}); + end; + option value => -1, mt '_vnpage_uopt_delvn' if $list; + end; + br; + if(!$vote || $wish) { Select id => 'wishsel', name => $self->authGetCode("/v$v->{id}/wish"); option $wish ? mt '_vnpage_uopt_wishlisted', mt '_wish_'.$wish->{wstat} : mt '_vnpage_uopt_nowish'; @@ -385,7 +396,7 @@ sub _releases { } if($self->authInfo->{id}) { - my $l = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => [map $_->{id}, @$r]); + my $l = $self->dbRListGet(uid => $self->authInfo->{id}, rid => [map $_->{id}, @$r]); for my $i (@$l) { [grep $i->{rid} == $_->{id}, @$r]->[0]{ulist} = $i; } @@ -406,7 +417,7 @@ sub _releases { for my $rel (grep grep($_ eq $l, @{$_->{languages}}), @$r) { Tr; td class => 'tc1'; lit $self->{l10n}->datestr($rel->{released}); end; - td class => 'tc2', !defined($rel->{minage}) ? '' : minage $rel->{minage}; + td class => 'tc2', $rel->{minage} < 0 ? '' : minage $rel->{minage}; td class => 'tc3'; for (sort @{$rel->{platforms}}) { next if $_ eq 'oth'; @@ -420,9 +431,8 @@ sub _releases { end; td class => 'tc5'; if($self->authInfo->{id}) { - a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel'; - lit $rel->{ulist} ? liststat $rel->{ulist} : '--'; - end; + a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel', + $rel->{ulist} ? mt '_rlist_status_'.$rel->{ulist}{status} : '--'; } else { txt ' '; } @@ -451,7 +461,7 @@ sub _screenshots { if(grep $_->{nsfw}, @{$v->{screenshots}}) { p class => 'nsfwtoggle'; lit mt '_vnpage_scr_showing', - sprintf('<i id="nsfwshown">%d</i>', $self->authInfo->{show_nsfw} ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}})), + sprintf('<i id="nsfwshown">%d</i>', $self->authPref('show_nsfw') ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}})), scalar @{$v->{screenshots}}; txt " "; a href => '#', id => "nsfwhide", mt '_vnpage_scr_nsfwhide'; @@ -471,7 +481,7 @@ sub _screenshots { for (@scr) { my($w, $h) = imgsize($_->{width}, $_->{height}, @{$self->{scr_size}}); a href => sprintf('%s/sf/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}), - class => sprintf('scrlnk%s%s', $_->{nsfw} ? ' nsfw':'', $_->{nsfw}&&!$self->authInfo->{show_nsfw}?' hidden':''), + class => sprintf('scrlnk%s%s', $_->{nsfw} ? ' nsfw':'', $_->{nsfw}&&!$self->authPref('show_nsfw')?' hidden':''), rel => "iv:$_->{width}x$_->{height}:scr"; img src => sprintf('%s/st/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}), width => $w, height => $h, alt => mt '_vnpage_scr_num', $_->{id}; diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index 24e316ce..9ad76894 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -14,7 +14,7 @@ use YAWF ':html'; use VNDB::Func; -our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass authGetCode authCheckCode |; +our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass authGetCode authCheckCode authPref |; # initializes authentication information and checks the vndb_auth cookie @@ -27,7 +27,7 @@ sub authInit { return _rmcookie($self) if length($cookie) < 41; my $token = substr($cookie, 0, 40); my $uid = substr($cookie, 40); - $self->{_auth} = $uid =~ /^\d+$/ && $self->dbUserGet(uid => $uid, session => $token, what => 'extended notifycount')->[0]; + $self->{_auth} = $uid =~ /^\d+$/ && $self->dbUserGet(uid => $uid, session => $token, what => 'extended notifycount prefs')->[0]; # update the sessions.lastused column if lastused < now()'6 hours' $self->dbSessionUpdateLastUsed($uid, $token) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600; return _rmcookie($self) if !$self->{_auth}; @@ -70,6 +70,10 @@ sub authLogout { $self->resRedirect('/', 'temp'); _rmcookie($self); + + # set l10n cookie if the user has a preferred language set + my $l10n = $self->authPref('l10n'); + $self->resHeader('Set-Cookie', "l10n=$l10n; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}") if $l10n; } @@ -196,5 +200,14 @@ sub _incorrectcode { } +sub authPref { + my($self, $key, $val) = @_; + my $nfo = $self->authInfo; + return '' if !$nfo->{id}; + return $nfo->{prefs}{$key}||'' if @_ == 2; + $nfo->{prefs}{$key} = $val; + $self->dbUserPrefSet($nfo->{id}, $key, $val); +} + 1; diff --git a/lib/VNDB/Util/BrowseHTML.pm b/lib/VNDB/Util/BrowseHTML.pm index 139ff1b0..62e01fd1 100644 --- a/lib/VNDB/Util/BrowseHTML.pm +++ b/lib/VNDB/Util/BrowseHTML.pm @@ -6,6 +6,7 @@ use warnings; use YAWF ':html', 'xml_escape'; use Exporter 'import'; use VNDB::Func; +use POSIX 'ceil'; our @EXPORT = qw| htmlBrowse htmlBrowseNavigate htmlBrowseHist htmlBrowseVN |; @@ -83,23 +84,39 @@ sub htmlBrowse { # creates next/previous buttons (tabs), if needed -# Arguments: page url, current page (1..n), nextpage (0/1), alignment (t/b), noappend (0/1) +# Arguments: page url, current page (1..n), nextpage (0/1 or [$total, $perpage]), alignment (t/b), noappend (0/1) sub htmlBrowseNavigate { my($self, $url, $p, $np, $al, $na) = @_; - return if $p == 1 && !$np; + my($cnt, $pp) = ref($np) ? @$np : ($p+$np, 1); + return if $p == 1 && $cnt <= $pp; $url .= $url =~ /\?/ ? ';p=' : '?p=' unless $na; - ul class => 'maintabs ' . ($al eq 't' ? 'notfirst' : 'bottom'); - if($p > 1) { - li class => 'left'; - a href => $url.($p-1), '<- '.mt '_browse_previous'; - end; - } - if($np) { - li; - a href => $url.($p+1), mt('_browse_next').' ->'; - end; - } + + my $tab = sub { + my($left, $page, $label) = @_; + li $left ? (class => 'left') : (); + a href => $url.$page; lit $label; end; + end; + }; + my $ell = sub { + use utf8; + li class => 'ellipsis'.(shift() ? ' left' : ''); + b '⋯'; + end; + }; + my $nc = 5; # max. number of buttons on each side + + ul class => 'maintabs browsetabs ' . ($al eq 't' ? 'notfirst' : 'bottom'); + $p > 2 and ref $np and $tab->(1, 1, '« '.mt '_browse_first'); + $p > $nc+1 and ref $np and $ell->(1); + $p > $_ and ref $np and $tab->(1, $p-$_, $p-$_) for (reverse 2..($nc>$p-2?$p-2:$nc-1)); + $p > 1 and $tab->(1, $p-1, '‹ '.mt '_browse_previous'); + + my $l = ceil($cnt/$pp)-$p+1; + $l > 2 and $tab->(0, $l+$p-1, mt('_browse_last').' »'); + $l > $nc+1 and $ell->(0); + $l > $_ and $tab->(0, $p+$_, $p+$_) for (reverse 2..($nc>$l-2?$l-2:$nc-1)); + $l > 1 and $tab->(0, $p+1, mt('_browse_next').' ›'); end; } diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm index c7d67647..a412949a 100644 --- a/lib/VNDB/Util/CommonHTML.pm +++ b/lib/VNDB/Util/CommonHTML.pm @@ -45,11 +45,15 @@ sub htmlMainTabs { end; } - if($type eq 'u' && ($obj->{show_list} || $self->authCan('usermod'))) { + if($type eq 'u' && (!($obj->{hide_list} || $obj->{prefs}{hide_list}) || ($self->authInfo->{id} && $self->authInfo->{id} == $obj->{id}) || $self->authCan('usermod'))) { li $sel eq 'wish' ? (class => 'tabselected') : (); a href => "/$id/wish", mt '_mtabs_wishlist'; end; + li $sel eq 'votes' ? (class => 'tabselected') : (); + a href => "/$id/votes", mt '_mtabs_votes'; + end; + li $sel eq 'list' ? (class => 'tabselected') : (); a href => "/$id/list", mt '_mtabs_list'; end; diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm index d619754a..41ee0ccc 100644 --- a/lib/VNDB/Util/FormHTML.pm +++ b/lib/VNDB/Util/FormHTML.pm @@ -27,7 +27,7 @@ sub htmlFormError { ul; for my $e (@{$frm->{_err}}) { if(!ref $e) { - li mt '_formerr_e_'.$e; + li; lit mt '_formerr_e_'.$e; end; next; } my($field, $type, $rule) = @$e; @@ -89,7 +89,7 @@ sub htmlFormPart { end; td class => 'field'; input type => 'checkbox', name => $o{short}, id => $o{short}, - value => $o{value}||'true', $frm->{$o{short}} ? ( checked => 'checked' ) : (); + value => $o{value}||1, ($frm->{$o{short}}||0) eq ($o{value}||1) ? ( checked => 'checked' ) : (); label for => $o{short}; lit $o{name}; end; diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index f1a6dc2d..cc34b874 100644 --- a/lib/VNDB/Util/LayoutHTML.pm +++ b/lib/VNDB/Util/LayoutHTML.pm @@ -12,7 +12,7 @@ our @EXPORT = qw|htmlHeader htmlFooter|; sub htmlHeader { # %options->{ title, noindex, search, feeds } my($self, %o) = @_; - my $skin = $self->reqParam('skin') || $self->authInfo->{skin} || $self->{skin_default}; + my $skin = $self->reqParam('skin') || $self->authPref('skin') || $self->{skin_default}; $skin = $self->{skin_default} if !$self->{skins}{$skin} || !-d "$VNDB::ROOT/static/s/$skin"; # heading @@ -22,8 +22,8 @@ sub htmlHeader { # %options->{ title, noindex, search, feeds } 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($self->authInfo->{customcss}) { - (my $css = $self->authInfo->{customcss}) =~ s/\n/ /g; + if($self->authPref('customcss')) { + (my $css = $self->authPref('customcss')) =~ s/\n/ /g; style type => 'text/css', $css; } Link rel => 'alternate', type => 'application/atom+xml', href => "/feeds/$_.atom", title => $self->{atom_feeds}{$_}[1] @@ -88,6 +88,7 @@ sub _menu { div; a href => "$uid/edit", mt '_menu_myprofile'; br; a href => "$uid/list", mt '_menu_myvnlist'; br; + a href => "$uid/votes",mt '_menu_myvotes'; br; a href => "$uid/wish", mt '_menu_mywishlist'; br; a href => "$uid/notifies", $nc ? (class => 'notifyget') : (), mt('_menu_mynotifications').($nc?" ($nc)":''); br; a href => "$uid/hist", mt '_menu_mychanges'; br; @@ -134,8 +135,8 @@ sub _menu { } -sub htmlFooter { - my $self = shift; +sub htmlFooter { # %options => { prefs => [pref1,..] } + my($self, %o) = @_; div id => 'footer'; my $q = $self->dbRandomQuote; @@ -155,6 +156,17 @@ sub htmlFooter { a href => $self->{source_url}, mt '_footer_source'; end; end; # /div maincontent + + # insert users' preference data when required by JS + if($o{prefs}) { + script type => 'text/javascript'; + txt sprintf "PREF_CODE='%s';", $self->authInfo->{id} ? $self->authGetCode('/xml/prefs.xml') : ''; + txt 'PREFS={'; + # assumes the preference value doesn't contain a ' + txt join ',', map sprintf("'%s':'%s'", $_, $self->authPref($_)), @{$o{prefs}}; + txt '};'; + end; + } script type => 'text/javascript', src => $self->{url_static}.'/f/js/'.$self->{l10n}->language_tag().'.js?'.$self->{version}, ''; end; # /body end; # /html diff --git a/lib/VNDB/Util/Misc.pm b/lib/VNDB/Util/Misc.pm new file mode 100644 index 00000000..71aca7a8 --- /dev/null +++ b/lib/VNDB/Util/Misc.pm @@ -0,0 +1,100 @@ + +package VNDB::Util::Misc; + +use strict; +use warnings; +use Exporter 'import'; +use VNDB::Func; + +our @EXPORT = qw|filFetchDB|; + + +my %filfields = ( + vn => [qw|length hasani tag_inc tag_exc taginc tagexc tagspoil lang olang plat|], + release => [qw|type patch freeware doujin date_before date_after minage lang olang resolution plat med voiced ani_story ani_ero|], +); + + +# Arguments: +# type ('vn' or 'release'), +# filter overwrite (string or undef), +# when defined, these filters will be used instead of the preferences, +# must point to a variable, will be modified in-place with the actually used filters +# options to pass to db*Get() before the filters (hashref or undef) +# these options can be overwritten by the filters or the next option +# options to pass to db*Get() after the filters (hashref or undef) +# these options overwrite all other options (pre-options and filters) + +sub filFetchDB { + my($self, $type, $overwrite, $pre, $post) = @_; + $pre = {} if !$pre; + $post = {} if !$post; + my $dbfunc = $self->can($type eq 'vn' ? 'dbVNGet' : 'dbReleaseGet'); + my $prefname = 'filter_'.$type; + my $pref = $self->authPref($prefname); + + # simply call the DB if we're not applying filters + return $dbfunc->($self, %$pre, %$post) if !$pref && !$overwrite; + + my $filters = fil_parse $overwrite // $pref, @{$filfields{$type}}; + + # compatibility + $self->authPref($prefname => fil_serialize $filters) + if $type eq 'vn' && _fil_vn_compat($self, $filters) && !defined $overwrite; + + # write the definite filter string in $overwrite + $_[2] = fil_serialize({map +( + exists($post->{$_}) ? ($_ => $post->{$_}) : + exists($filters->{$_}) ? ($_ => $filters->{$_}) : + exists($pre->{$_}) ? ($_ => $pre->{$_}) : (), + ), @{$filfields{$type}}}) if defined $overwrite; + + return $dbfunc->($self, %$pre, %$filters, %$post) if defined $overwrite; + + # since incorrect filters can throw a database error, we have to special-case + # filters that originate from a preference setting, so that in case these are + # the cause of an error, they are removed. Not doing this will result in VNDB + # throwing 500's even for non-browse pages. We have to do some low-level + # PostgreSQL stuff with savepoints to ensure that an error won't affect our + # existing transaction. + my $dbh = $self->{_YAWF}{DB}{sql}; + $dbh->pg_savepoint('filter'); + my($r, $np); + my $OK = eval { + ($r, $np) = $dbfunc->($self, %$pre, %$filters, %$post); + 1; + }; + $dbh->pg_rollback_to('filter') if !$OK; + $dbh->pg_release('filter'); + + # error occured, let's try again without filters. if that succeeds we know + # it's the fault of the filter preference, and we should remove it. + if(!$OK) { + ($r, $np) = $dbfunc->($self, %$pre, %$post); + # if we're here, it means the previous function didn't die() (duh!) + $self->authPref($prefname => ''); + warn sprintf "Reset filter preference for userid %d. Old: %s\n", $self->authInfo->{id}||0, $pref; + } + return wantarray ? ($r, $np) : $r; +} + + +sub _fil_vn_compat { + my($self, $fil) = @_; + + # older tag specification (by name rather than ID) + if($fil->{taginc} || $fil->{tagexc}) { + my $tagfind = sub { + return map { + my $i = $self->dbTagGet(name => $_)->[0]; + $i && !$i->{meta} ? $i->{id} : (); + } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'') + }; + $fil->{tag_inc} //= [ $tagfind->(delete $fil->{taginc}) ] if $fil->{taginc}; + $fil->{tag_exc} //= [ $tagfind->(delete $fil->{tagexc}) ] if $fil->{tagexc}; + return 1; + } + + return 0; +} + |