diff options
author | Yorhel <git@yorhel.nl> | 2010-11-28 10:45:43 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2010-11-28 10:45:43 +0100 |
commit | e27071f4110c6a93ef140b2d3dde331194917616 (patch) | |
tree | 269a8479ebe7a1b56480fae45b97ff5f30b728ad /lib | |
parent | afa8f6cb619fd59d164d15d12249560706878639 (diff) | |
parent | 5bfd8f4559f2ce54332d28ac767619c020b151dc (diff) |
Merge branch 'beta'
Conflicts:
lib/VNDB/Handler/Discussions.pm
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Multi/Feed.pm | 170 | ||||
-rw-r--r-- | lib/VNDB/DB/Releases.pm | 63 | ||||
-rw-r--r-- | lib/VNDB/DB/Users.pm | 16 | ||||
-rw-r--r-- | lib/VNDB/DB/VN.pm | 16 | ||||
-rw-r--r-- | lib/VNDB/Func.pm | 34 | ||||
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Handler/Misc.pm | 29 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 8 | ||||
-rw-r--r-- | lib/VNDB/Handler/Releases.pm | 195 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNBrowse.pm | 114 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Plugin/TransAdmin.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Util/CommonHTML.pm | 9 | ||||
-rw-r--r-- | lib/VNDB/Util/LayoutHTML.pm | 6 | ||||
-rw-r--r-- | lib/VNDBUtil.pm | 127 |
15 files changed, 453 insertions, 344 deletions
diff --git a/lib/Multi/Feed.pm b/lib/Multi/Feed.pm new file mode 100644 index 00000000..c97d87d8 --- /dev/null +++ b/lib/Multi/Feed.pm @@ -0,0 +1,170 @@ + +# +# Multi::Feed - Generates and updates Atom feeds +# + +package Multi::Feed; + +use strict; +use warnings; +use POE; +use XML::Writer; +use POSIX 'strftime'; +use Time::HiRes 'time'; +use VNDBUtil 'bb2html'; + + +sub spawn { + my $p = shift; + POE::Session->create( + package_states => [ + $p => [qw| _start shutdown generate write_atom log_stats |], + ], + heap => { + regenerate_interval => 900, # 15 min. + stats_interval => 86400, # daily + debug => 0, + @_, + stats => {}, # key = feed, value = [ count, total, max ] + }, + ); +} + + +sub _start { + $_[KERNEL]->alias_set('feed'); + $_[KERNEL]->yield('generate'); + $_[KERNEL]->alarm(log_stats => int((time+3)/$_[HEAP]{stats_interval}+1)*$_[HEAP]{stats_interval}); + $_[KERNEL]->sig(shutdown => 'shutdown'); +} + + +sub shutdown { + $_[KERNEL]->delay('generate'); + $_[KERNEL]->delay('log_stats'); + $_[KERNEL]->alias_remove('feed'); +} + + +sub generate { + $_[KERNEL]->alarm(generate => int((time+3)/$_[HEAP]{regenerate_interval}+1)*$_[HEAP]{regenerate_interval}); + + # announcements + $_[KERNEL]->post(pg => query => q{ + SELECT '/t'||t.id AS id, t.title, extract('epoch' from tp.date) AS published, + extract('epoch' from tp.edited) AS updated, u.username, u.id AS uid, tp.msg AS summary + FROM threads t + JOIN threads_posts tp ON tp.tid = t.id AND tp.num = 1 + JOIN threads_boards tb ON tb.tid = t.id AND tb.type = 'an' + JOIN users u ON u.id = tp.uid + WHERE NOT t.hidden + ORDER BY t.id DESC + LIMIT ?}, [ $VNDB::S{atom_feeds}{announcements}[0] ], 'write_atom', 'announcements' + ); + + # changes + $_[KERNEL]->post(pg => query => q{ + SELECT '/'||c.type||COALESCE(vr.vid, rr.rid, pr.pid)||'.'||c.rev AS id, + COALESCE(vr.title, rr.title, pr.name) AS title, extract('epoch' from c.added) AS updated, + u.username, u.id AS uid, c.comments AS summary + FROM changes c + 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.requester <> 1 + ORDER BY c.id DESC + LIMIT ?}, [ $VNDB::S{atom_feeds}{changes}[0] ], 'write_atom', 'changes' + ); + + # posts (this query isn't all that fast) + $_[KERNEL]->post(pg => query => q{ + SELECT '/t'||t.id||'.'||tp.num AS id, t.title||' (#'||tp.num||')' AS title, extract('epoch' from tp.date) AS published, + extract('epoch' from tp.edited) AS updated, u.username, u.id AS uid, tp.msg AS summary + FROM threads_posts tp + JOIN threads t ON t.id = tp.tid + JOIN users u ON u.id = tp.uid + WHERE NOT tp.hidden AND NOT t.hidden + ORDER BY tp.date DESC + LIMIT ?}, [ $VNDB::S{atom_feeds}{posts}[0] ], 'write_atom', 'posts' + ); +} + + +sub write_atom { # num, res, feed, time + my $r = $_[ARG1]; + my $feed = $_[ARG2]; + + my $start = time; + + my $updated = 0; + for(@$r) { + $updated = $_->{published} if $_->{published} && $_->{published} > $updated; + $updated = $_->{updated} if $_->{updated} && $_->{updated} > $updated; + } + + my $data; + my $x = XML::Writer->new(OUTPUT => \$data, DATA_MODE => 1, DATA_INDENT => 2); + $x->xmlDecl('UTF-8'); + $x->startTag(feed => xmlns => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en', 'xml:base' => $VNDB::S{url}.'/'); + $x->dataElement(title => $VNDB::S{atom_feeds}{$feed}[1]); + $x->dataElement(updated => datetime($updated)); + $x->dataElement(id => $VNDB::S{url}.$VNDB::S{atom_feeds}{$feed}[2]); + $x->emptyTag(link => rel => 'self', type => 'application/atom+xml', href => "$VNDB::S{url}/feeds/$feed.atom"); + $x->emptyTag(link => rel => 'alternate', type => 'text/html', href => $VNDB::S{atom_feeds}{$feed}[2]); + + for(@$r) { + $x->startTag('entry'); + $x->dataElement(id => $VNDB::S{url}.$_->{id}); + $x->dataElement(title => $_->{title}); + $x->dataElement(updated => $_->{updated}?datetime($_->{updated}):datetime($_->{published})); + $x->dataElement(published => datetime($_->{published})) if $_->{published}; + if($_->{username}) { + $x->startTag('author'); + $x->dataElement(name => $_->{username}); + $x->dataElement(uri => '/u'.$_->{uid}) if $_->{uid}; + $x->endTag('author'); + } + $x->emptyTag(link => rel => 'alternate', type => 'text/html', href => $_->{id}); + $x->dataElement(summary => bb2html($_->{summary}, 200), type => 'html') if $_->{summary}; + $x->endTag('entry'); + } + + $x->endTag('feed'); + + open my $f, '>:utf8', "$VNDB::ROOT/www/feeds/$feed.atom" || die $!; + print $f $data; + close $f; + + $_[HEAP]{debug} && $_[KERNEL]->call(core => log => 'Wrote %s.atom (%d entries, sql:%4dms, perl:%4dms)', + $feed, scalar(@$r), $_[ARG3]*1000, (time-$start)*1000); + + $_[HEAP]{stats}{$feed} = [ 0, 0, 0 ] if !$_[HEAP]{stats}{$feed}; + my $time = ((time-$start)+$_[ARG3])*1000; + $_[HEAP]{stats}{$feed}[0]++; + $_[HEAP]{stats}{$feed}[1] += $time; + $_[HEAP]{stats}{$feed}[2] = $time if $_[HEAP]{stats}{$feed}[2] < $time; +} + + +sub log_stats { + $_[KERNEL]->alarm(log_stats => int((time+3)/$_[HEAP]{stats_interval}+1)*$_[HEAP]{stats_interval}); + + for (keys %{$_[HEAP]{stats}}) { + my $v = $_[HEAP]{stats}{$_}; + next if !$v->[0]; + $_[KERNEL]->call(core => log => 'Stats summary for %s.atom: total:%5dms, avg:%4dms, max:%4dms, size: %.1fkB', + $_, $v->[1], $v->[1]/$v->[0], $v->[2], (-s "$VNDB::ROOT/www/feeds/$_.atom")/1024); + } + $_[HEAP]{stats} = {}; +} + + +# non-POE helper function +sub datetime { + strftime('%Y-%m-%dT%H:%M:%SZ', gmtime shift); +} + + +1; + diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm index 01ca6b44..b0fb9a89 100644 --- a/lib/VNDB/DB/Releases.pm +++ b/lib/VNDB/DB/Releases.pm @@ -10,8 +10,8 @@ use VNDB::Func 'gtintype'; our @EXPORT = qw|dbReleaseGet dbReleaseRevisionInsert|; -# Options: id vid pid rev unreleased page results what date media sort reverse -# platforms languages type minage search resolutions freeware doujin +# Options: id vid pid rev unreleased page results what med sort reverse date_before date_after +# plat lang olang type minage search resolution freeware doujin voiced ani_story ani_ero # What: extended changes vn producers platforms media # Sort: title released minage sub dbReleaseGet { @@ -21,33 +21,42 @@ sub dbReleaseGet { $o{what} ||= ''; my @where = ( - !$o{id} && !$o{rev} ? ( 'r.hidden = FALSE' => 0 ) : (), - $o{id} ? ( 'r.id = ?' => $o{id} ) : (), - $o{rev} ? ( 'c.rev = ?' => $o{rev} ) : (), - $o{vid} ? ( 'rv.vid = ?' => $o{vid} ) : (), - $o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (), - $o{patch} ? ( 'rr.patch = ?' => $o{patch} == 1 ? 1 : 0) : (), - $o{freeware} ? ( 'rr.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (), - $o{doujin} ? ( 'rr.doujin = ?' => $o{doujin} == 1 ? 1 : 0) : (), - defined $o{unreleased} ? ( - q|rr.released !s ?| => [ $o{unreleased} ? '>' : '<=', strftime('%Y%m%d', gmtime) ] ) : (), - $o{date} ? ( - '(rr.released >= ? AND rr.released <= ?)' => [ $o{date}[0], $o{date}[1] ] ) : (), - $o{languages} ? ( - 'rr.id IN(SELECT irl.rid FROM releases_lang irl JOIN releases ir ON ir.latest = irl.rid WHERE irl.lang IN(!l))', => [ $o{languages} ] ) : (), - $o{platforms} ? ( - #'EXISTS(SELECT 1 FROM releases_platforms rp WHERE rp.rid = rr.id AND rp.platform IN(!l))' => [ $o{platforms} ] ) : (), - 'rr.id IN(SELECT irp.rid FROM releases_platforms irp JOIN releases ir ON ir.latest = irp.rid WHERE irp.platform IN(!l))' => [ $o{platforms} ] ) : (), - defined $o{type} ? ( - 'rr.type = ?' => $o{type} ) : (), - $o{minage} ? ( - 'rr.minage !s ?' => [ $o{minage}[0] ? '<=' : '>=', $o{minage}[1] ] ) : (), - $o{media} ? ( - 'rr.id IN(SELECT irm.rid FROM releases_media irm JOIN releases ir ON ir.latest = irm.rid WHERE irm.medium IN(!l))' => [ $o{media} ] ) : (), - $o{resolutions} ? ( - 'rr.resolution IN(!l)' => [ $o{resolutions} ] ) : (), + !$o{id} && !$o{rev} ? ( 'r.hidden = FALSE' => 0 ) : (), + $o{id} ? ( 'r.id = ?' => $o{id} ) : (), + $o{rev} ? ( 'c.rev = ?' => $o{rev} ) : (), + $o{vid} ? ( 'rv.vid = ?' => $o{vid} ) : (), + $o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (), + defined $o{patch} ? ( 'rr.patch = ?' => $o{patch} == 1 ? 1 : 0) : (), + defined $o{freeware} ? ( 'rr.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (), + defined $o{doujin} ? ( 'rr.doujin = ?' => $o{doujin} == 1 ? 1 : 0) : (), + 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{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}] ] ) : (), + defined $o{ani_ero} ? ( 'rr.ani_ero IN(!l)' => [ ref $o{ani_ero} ? $o{ani_ero} : [$o{ani_ero}] ] ) : (), + defined $o{unreleased} ? ( 'rr.released !s ?' => [ $o{unreleased} ? '>' : '<=', strftime('%Y%m%d', gmtime) ] ) : (), + $o{lang} ? ( + 'rr.id IN(SELECT irl.rid FROM releases_lang irl JOIN releases ir ON ir.latest = irl.rid WHERE irl.lang IN(!l))' => [ ref $o{lang} ? $o{lang} : [ $o{lang} ] ] ) : (), + $o{olang} ? ( + 'rr.id IN(SELECT irv.rid FROM releases_vn irv JOIN releases ir ON ir.latest = irv.rid JOIN vn v ON irv.vid = v.id WHERE v.c_olang && ARRAY[!l]::language[])' => [ ref $o{olang} ? $o{olang} : [ $o{olang} ] ] ) : (), + $o{plat} ? ( + 'rr.id IN(SELECT irp.rid FROM releases_platforms irp JOIN releases ir ON ir.latest = irp.rid WHERE irp.platform IN(!l))' => [ ref $o{plat} ? $o{plat} : [ $o{plat} ] ] ) : (), + $o{med} ? ( + '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/Users.pm b/lib/VNDB/DB/Users.pm index b9d66200..7440f495 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -123,21 +123,7 @@ sub dbUserAdd { # uid sub dbUserDel { - my($s, $id) = @_; - # TODO: delete/update all those referenced rows using PgSQL reference actions - $s->dbExec($_, $id) for ( - q|DELETE FROM rlists WHERE uid = ?|, - q|DELETE FROM wlists WHERE uid = ?|, - q|DELETE FROM votes WHERE uid = ?|, - q|DELETE FROM tags_vn WHERE uid = ?|, - q|DELETE FROM sessions WHERE uid = ?|, - q|DELETE FROM notifications WHERE uid = ?|, - q|UPDATE notifications SET c_byuser = 0 WHERE c_byuser = ?|, - q|UPDATE changes SET requester = 0 WHERE requester = ?|, - q|UPDATE tags SET addedby = 0 WHERE addedby = ?|, - q|UPDATE threads_posts SET uid = 0 WHERE uid = ?|, - q|DELETE FROM users WHERE id = ?| - ); + $_[0]->dbExec(q|DELETE FROM users WHERE id = ?|, $_[1]); } diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm index 6d99ba8a..d25a5796 100644 --- a/lib/VNDB/DB/VN.pm +++ b/lib/VNDB/DB/VN.pm @@ -10,7 +10,7 @@ use Encode 'decode_utf8'; our @EXPORT = qw|dbVNGet dbVNRevisionInsert dbVNImageId dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|; -# Options: id, rev, char, search, lang, platform, tags_include, tags_exclude, results, page, what, sort, reverse +# Options: id, rev, char, search, length, lang, olang, plat, tags_include, tags_exclude, hasani, results, page, what, sort, reverse # What: extended anime relations screenshots relgraph rating ranking changes # Sort: id rel pop rating title tagscore rand sub dbVNGet { @@ -29,10 +29,16 @@ sub dbVNGet { '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 ) : (), - $o{lang} && @{$o{lang}} ? ( - 'v.c_languages && ARRAY[!l]::language[]' => [ $o{lang} ]) : (), - $o{platform} && @{$o{platform}} ? ( - '('.join(' OR ', map "v.c_platforms ILIKE '%%$_%%'", @{$o{platform}}).')' => 1 ) : (), + defined $o{length} ? ( + 'vr.length IN(!l)' => [ ref $o{length} ? $o{length} : [$o{length}] ]) : (), + $o{lang} ? ( + 'v.c_languages && ARRAY[!l]::language[]' => [ ref $o{lang} ? $o{lang} : [$o{lang}] ]) : (), + $o{olang} ? ( + 'v.c_olang && ARRAY[!l]::language[]' => [ ref $o{olang} ? $o{olang} : [$o{olang}] ]) : (), + $o{plat} ? ( + '('.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}} ? ( '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 ] diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm index f3f9d009..65b66f9e 100644 --- a/lib/VNDB/Func.pm +++ b/lib/VNDB/Func.pm @@ -7,7 +7,13 @@ use YAWF ':html'; use Exporter 'import'; use POSIX 'strftime', 'ceil', 'floor'; use VNDBUtil; -our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage |); +our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage fil_parse fil_serialize |); + + +# three ways to represent the same information +our $fil_escape = '_ !"#$%&\'()*+,-./:;<=>?@[\]^`{}~'; +our @fil_escape = split //, $fil_escape; +our %fil_escape = map +($fil_escape[$_], sprintf '%02d', $_), 0..$#fil_escape; # Argument: hashref with rstat and vstat @@ -89,5 +95,31 @@ sub minage { } +# arguments: $filter_string, @allowed_keys +sub fil_parse { + my $str = shift; + my %keys = map +($_,1), @_; + my %r; + for (split /\./, $str) { + next if !/^([a-z0-9_]+)-([a-zA-Z0-9_~]+)$/ || !$keys{$1}; + my($f, $v) = ($1, $2); + my @v = split /~/, $v; + s/_([0-9]{2})/$1 > $#fil_escape ? '' : $fil_escape[$1]/eg for(@v); + $r{$f} = @v > 1 ? \@v : $v[0] + } + return \%r; +} + + +sub fil_serialize { + my $fil = shift; + my $e = qr/([\Q$fil_escape\E])/; + return join '.', map { + my @v = ref $fil->{$_} ? @{$fil->{$_}} : ($fil->{$_}); + s/$e/_$fil_escape{$1}/g for(@v); + $_.'-'.join '~', @v + } keys %$fil; +} + 1; diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index 8f7ae76c..4cd9f04d 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -294,7 +294,7 @@ sub board { sort => $type eq 'an' ? 'id' : 'lastpost', reverse => 1, ); - $self->htmlHeader(title => $title, noindex => 1); + $self->htmlHeader(title => $title, noindex => 1, feeds => [ $type eq 'an' ? 'announcements' : 'posts' ]); $self->htmlMainTabs($type, $obj, 'disc') if $iid; div class => 'mainbox'; @@ -330,7 +330,7 @@ sub board { sub index { my $self = shift; - $self->htmlHeader(title => mt('_disindex_title'), noindex => 1); + $self->htmlHeader(title => mt('_disindex_title'), noindex => 1, feeds => [ 'posts', 'announcements' ]); div class => 'mainbox'; h1 mt '_disindex_title'; p class => 'browseopts'; diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index 87839730..b26868d5 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -15,7 +15,7 @@ YAWF::register( qr{d([1-9]\d*)}, \&docpage, qr{setlang}, \&setlang, qr{nospam}, \&nospam, - qr{we-dont-like-ie6}, \&ie6message, + qr{we-dont-like-ie}, \&iemessage, qr{opensearch\.xml}, \&opensearch, # redirects for old URLs @@ -37,7 +37,7 @@ YAWF::register( sub homepage { my $self = shift; - $self->htmlHeader(title => mt '_site_title'); + $self->htmlHeader(title => mt('_site_title'), feeds => [ keys %{$self->{atom_feeds}} ]); div class => 'mainbox'; h1 mt '_site_title'; @@ -63,7 +63,8 @@ sub homepage { # Recent changes td; h1; - a href => '/hist', mt '_home_recentchanges'; + a href => '/hist', mt '_home_recentchanges'; txt ' '; + a href => '/feeds/changes.atom'; cssicon 'feed', mt '_atom_feed'; end; end; my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1); ul; @@ -82,7 +83,8 @@ sub homepage { td; my $an = $self->dbThreadGet(type => 'an', sort => 'id', reverse => 1, results => 2); h1; - a href => '/t/an', mt '_home_announcements'; + a href => '/t/an', mt '_home_announcements'; txt ' '; + a href => '/feeds/announcements.atom'; cssicon 'feed', mt '_atom_feed'; end; end; for (@$an) { my $post = $self->dbPostGet(tid => $_->{id}, num => 1)->[0]; @@ -98,7 +100,8 @@ sub homepage { # Recent posts td; h1; - a href => '/t', mt '_home_recentposts'; + a href => '/t', mt '_home_recentposts'; txt ' '; + a href => '/feeds/posts.atom'; cssicon 'feed', mt '_atom_feed'; end; end; my $posts = $self->dbThreadGet(what => 'lastpost boardtitles', results => 10, sort => 'lastpost', reverse => 1, notusers => 1); ul; @@ -135,7 +138,7 @@ sub homepage { # Upcoming releases td; h1; - a href => strftime('/r?mi=%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming'; + 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'); ul; @@ -155,7 +158,7 @@ sub homepage { # Just released td; h1; - a href => strftime('/r?ma=%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased'; + 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'); ul; @@ -216,7 +219,7 @@ sub history { releases => $f->{r}, ); - $self->htmlHeader(title => $title, noindex => 1); + $self->htmlHeader(title => $title, noindex => 1, feeds => [ 'changes' ]); $self->htmlMainTabs($type, $obj, 'hist') if $type; # url generator @@ -361,12 +364,12 @@ sub nospam { } -sub ie6message { +sub iemessage { my $self = shift; if($self->reqParam('i-still-want-access')) { (my $ref = $self->reqHeader('Referer') || '/') =~ s/^\Q$self->{url}//; - $ref = '/' if $ref eq '/we-dont-like-ie6'; + $ref = '/' if $ref eq '/we-dont-like-ie'; $self->resRedirect($ref, 'temp'); $self->resHeader('Set-Cookie', "ie-sucks=1; path=/; domain=$self->{cookie_domain}"); return; @@ -386,16 +389,16 @@ sub ie6message { div; h1 'Oops, we were too lazy to support your browser!'; p; - lit qq|We decided to stop supporting Internet Explorer 6, as it's a royal pain in | + lit qq|We decided to stop supporting Internet Explorer 6 and 7, as it's a royal pain in | .qq|the ass to make our site look good in a browser that doesn't want to cooperate with us.<br />| .qq|You can try one of the following free alternatives: | .qq|<a href="http://www.mozilla.com/firefox/">Firefox</a>, | .qq|<a href="http://www.opera.com/">Opera</a>, | .qq|<a href="http://www.apple.com/safari/">Safari</a>, or | .qq|<a href="http://www.google.com/chrome">Chrome</a>.<br /><br />| - .qq|If you're really stubborn about using Internet Explorer, upgrading to version 7 will also work.<br /><br />| + .qq|If you're really stubborn about using Internet Explorer, upgrading to version 8 will also work.<br /><br />| .qq|...and if you're mad, you can also choose to ignore this warning and | - .qq|<a href="/we-dont-like-ie6?i-still-want-access=1">open the site anyway</a>.|; + .qq|<a href="/we-dont-like-ie?i-still-want-access=1">open the site anyway</a>.|; end; end; end; diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index 2931e25f..6e11e829 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -60,13 +60,13 @@ sub page { [ type => serialize => sub { mt "_ptype_$_[0]" } ], [ name => diff => 1 ], [ original => diff => 1 ], - [ alias => diff => 1 ], + [ alias => diff => qr/[ ,\n\.]/ ], [ lang => serialize => sub { "$_[0] (".mt("_lang_$_[0]").')' } ], [ website => diff => 1 ], [ l_wp => htmlize => sub { $_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : mt '_revision_nolink' }], - [ desc => diff => 1 ], + [ desc => diff => qr/[ ,\n\.]/ ], [ 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 @@ -153,6 +153,10 @@ sub _releases { td colspan => 6; i; lit $self->{l10n}->datestr($vn{$v->{vid}}[0]{released}); end; a href => "/v$v->{vid}", title => $v->{original}, $v->{title}; + span '('.join(', ', + (grep($_->{developer}, @{$vn{$v->{vid}}}) ? mt '_prodpage_dev' : ()), + (grep($_->{publisher}, @{$vn{$v->{vid}}}) ? mt '_prodpage_pub' : ()) + ).')'; end; end; for my $rel (@{$vn{$v->{vid}}}) { diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index f9c600bd..e2a18e1a 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -51,7 +51,7 @@ sub page { [ 'website' ], [ released => htmlize => sub { $self->{l10n}->datestr($_[0]) } ], [ minage => serialize => \&minage ], - [ notes => diff => 1 ], + [ notes => diff => qr/[ ,\n\.]/ ], [ platforms => join => ', ', split => sub { map mt("_plat_$_"), @{$_[0]} } ], [ media => join => ', ', split => sub { map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]} @@ -485,60 +485,46 @@ sub browse { my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, - { name => 's', required => 0, default => 'title', enum => [qw|released minage title|] }, { name => 'o', required => 0, default => 'a', enum => ['a', 'd'] }, { name => 'q', required => 0, default => '', maxlength => 500 }, - { 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 => '', 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 ] }, - { name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] }, - { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$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}} ] }, + { name => 's', required => 0, default => 'title', enum => [qw|released minage title|] }, + { name => 'fil',required => 0, default => '' }, ); return 404 if $f->{_err}; - my @filters = ( - $f->{mi} > 0 || $f->{ma} < 99990000 ? (date => [ $f->{mi}, $f->{ma} ]) : (), - $f->{q} ? (search => $f->{q}) : (), - $f->{pl}[0] ? (platforms => $f->{pl}) : (), - $f->{ln}[0] ? (languages => $f->{ln}) : (), - $f->{me}[0] ? (media => $f->{me}) : (), - $f->{re}[0] ? (resolutions => $f->{re} ) : (), - $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}) : (), - $f->{do} ? (doujin => $f->{do}) : (), - ); - my($list, $np) = !@filters ? ([], 0) : $self->dbReleaseGet( + 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( sort => $f->{s}, reverse => $f->{o} eq 'd', page => $f->{p}, results => 50, what => 'platforms', - @filters, + $f->{q} ? ( search => $f->{q} ) : (), + %$fil ); - my $url = "/r?tp=$f->{tp};pa=$f->{pa};ma_m=$f->{ma_m};ma_a=$f->{ma_a};q=$f->{q};mi=$f->{mi};ma=$f->{ma};do=$f->{do};fw=$f->{fw}"; - $_&&($url .= ";ln=$_") for @{$f->{ln}}; - $_&&($url .= ";pl=$_") for @{$f->{pl}}; - $_&&($url .= ";re=$_") for @{$f->{re}}; - $_&&($url .= ";me=$_") for @{$f->{me}}; - $self->htmlHeader(title => mt('_rbrowse_title')); - _filters($self, $f, !@filters || !@$list); + + form method => 'get', action => '/r', 'accept-charset' => 'UTF-8'; + div class => 'mainbox'; + h1 mt '_rbrowse_title'; + $self->htmlSearchBox('r', $f->{q}); + a id => 'filselect', href => '#r'; + lit '<i>▸</i> '.mt('_rbrowse_filters').'<i></i>'; + end; + input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil}; + end; + end; + $self->htmlBrowse( class => 'relbrowse', items => $list, options => $f, nextpage => $np, - pageurl => "$url;s=$f->{s};o=$f->{o}", - sorturl => $url, + pageurl => "/r?q=$f->{q};fil=$f->{fil};s=$f->{s};o=$f->{o}", + sorturl => "/r?q=$f->{q};fil=$f->{fil}", header => [ [ mt('_rbrowse_col_released'), 'released' ], [ mt('_rbrowse_col_minage'), 'minage' ], @@ -564,7 +550,7 @@ sub browse { end; }, ) if @$list; - if(@filters && !@$list) { + if(($f->{q} || keys %$fil) && !@$list) { div class => 'mainbox'; h1 mt '_rbrowse_noresults_title'; div class => 'notice'; @@ -576,108 +562,35 @@ sub browse { } -sub _filters { - my($self, $f, $shown) = @_; - - form method => 'get', action => '/r', 'accept-charset' => 'UTF-8'; - div class => 'mainbox'; - h1 mt '_rbrowse_title'; - - $self->htmlSearchBox('r', $f->{q}); - - a id => 'advselect', href => '#'; - lit '<i>'.($shown?'▾':'▸').'</i> '.mt('_rbrowse_filters'); - end; - div id => 'advoptions', !$shown ? (class => 'hidden') : (); - - h2 mt '_rbrowse_filters'; - table class => 'formtable', style => 'margin-left: 0'; - Tr class => 'newfield'; - td class => 'label'; label for => 'ma_m', mt '_rbrowse_minage'; end; - td class => 'field'; - Select id => 'ma_m', name => 'ma_m', style => 'width: 160px'; - option value => 0, $f->{ma_m} == 0 ? ('selected' => 'selected') : (), mt '_rbrowse_ge'; - option value => 1, $f->{ma_m} == 1 ? ('selected' => 'selected') : (), mt '_rbrowse_le'; - end; - Select id => 'ma_a', name => 'ma_a', style => 'width: 80px; text-align: center'; - defined($_) && option value => $_, $f->{ma_a} == $_ ? ('selected' => 'selected') : (), minage $_ - for (@{$self->{age_ratings}}); - end; - end; - td rowspan => 5, style => 'padding-left: 40px'; - label for => 're', mt '_rbrowse_resolution'; br; - Select id => 're', name => 're', multiple => 'multiple', size => 8; - my $l=''; - for my $i (1..$#{$self->{resolutions}}) { - if($l ne $self->{resolutions}[$i][1]) { - end if $l; - $l = $self->{resolutions}[$i][1]; - optgroup label => $l; - } - option value => $i, scalar grep($i==$_, @{$f->{re}}) ? (selected => 'selected') : (), $self->{resolutions}[$i][0]; - } - end if $l; - end; - end; - end; - $self->htmlFormPart($f, [ select => short => 'tp', name => mt('_rbrowse_type'), - 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'), - options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_freewareonly'], [2, mt '_rbrowse_freewarenone']]]); - $self->htmlFormPart($f, [ select => short => 'do', name => mt('_rbrowse_doujin'), - options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_doujinonly'], [2, mt '_rbrowse_doujinnone']]]); - $self->htmlFormPart($f, [ date => short => 'mi', name => mt '_rbrowse_dateafter' ]); - $self->htmlFormPart($f, [ date => short => 'ma', name => mt '_rbrowse_datebefore' ]); - end; - - h2; - txt mt '_rbrowse_languages'; - b ' ('.mt('_rbrowse_boolor').')'; - end; - 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"; - cssicon "lang $i", mt "_lang_$i"; - txt mt "_lang_$i"; - end; - end; - } - - h2; - txt mt '_rbrowse_platforms'; - b ' ('.mt('_rbrowse_boolor').')'; - end; - for my $i (sort @{$self->{platforms}}) { - span; - input type => 'checkbox', name => 'pl', value => $i, id => "plat_$i", grep($_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : (); - label for => "plat_$i"; - cssicon $i, mt "_plat_$i"; - txt mt "_plat_$i"; - end; - end; - } - - h2; - txt mt '_rbrowse_media'; - b ' ('.mt('_rbrowse_boolor').')'; - end; - 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", mt "_med_$i", 1; - end; - } - - div style => 'text-align: center; clear: left;'; - input type => 'submit', value => mt('_rbrowse_apply'), class => 'submit'; - input type => 'reset', value => mt('_rbrowse_clear'), class => 'submit', onclick => 'location.href="/r"'; - end; - end; - end; - end; +# provide compatibility with old filter URLs +sub _fil_compat { + my($self, $fil) = @_; + my $f = $self->formValidate( + { 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 => '', 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 ] }, + { name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] }, + { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$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} >= $_ : $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}; } diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index f3193b57..8e30da99 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -21,14 +21,12 @@ sub list { { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'q', required => 0, default => '' }, { name => 'sq', required => 0, default => '' }, - { 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] }, + { name => 'fil',required => 0, default => '' }, ); 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') @@ -37,9 +35,11 @@ sub list { # 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}}); - $f->{ln} = $f->{ln}[0] ? [ @{$f->{ln}}, @lang ] : \@lang; + $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 { @@ -47,10 +47,10 @@ sub list { push @ignored, [$_, 0] if !$i; push @ignored, [$_, 1] if $i && $i->{meta}; $i && !$i->{meta} ? $i->{id} : (); - } grep $_, split /\s*,\s*/, $_[0]; + } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'') }; - my @ti = $tagfind->($f->{ti}); - my @te = $tagfind->($f->{te}); + my @ti = $tagfind->(delete $fil->{taginc}); + my @te = $tagfind->(delete $fil->{tagexc}); $f->{s} = 'title' if !@ti && $f->{s} eq 'tagscore'; $f->{o} = $f->{s} eq 'tagscore' ? 'd' : 'a' if !$f->{o}; @@ -62,28 +62,15 @@ sub list { results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - $f->{pl}[0] ? ( platform => $f->{pl} ) : (), - $f->{ln}[0] ? ( lang => $f->{ln} ) : (), - @ti ? (tags_include => [ $f->{sp}, \@ti ]) : (), + @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}); - _filters($self, $f, $char, \@ignored); - - my $url = "/v/$char?q=$f->{q};ti=$f->{ti};te=$f->{te}"; - $_ and $url .= ";pl=$_" for @{$f->{pl}}; - $_ and $url .= ";ln=$_" for @{$f->{ln}}; - $self->htmlBrowseVN($list, $f, $np, $url, scalar @ti); - $self->htmlFooter; -} - - -sub _filters { - my($self, $f, $char, $ign) = @_; form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get'; div class => 'mainbox'; @@ -91,72 +78,45 @@ sub _filters { $self->htmlSearchBox('v', $f->{q}); p class => 'browseopts'; for ('all', 'a'..'z', 0) { - a href => "/v/$_", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; + a href => "/v/$_?q=$f->{q};fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#'; } end; - if(@$ign) { + if(@ignored) { div class => 'warning'; h2 mt '_vnbrowse_tagign_title'; ul; - li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @$ign; + li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @ignored; end; end; } - a id => 'advselect', href => '#'; - lit '<i>▸</i> '.mt('_vnbrowse_advsearch'); + a id => 'filselect', href => '#v'; + lit '<i>▸</i> '.mt('_rbrowse_filters').'<i></i>'; # TODO: it's not *r*browse end; - div id => 'advoptions', class => 'hidden vnoptions'; - - h2; - txt mt '_vnbrowse_tags'; - b ' ('.mt('_vnbrowse_booland').')'; - end; - table class => 'formtable', style => 'margin-left: 0'; - $self->htmlFormPart($f, [ input => short => 'ti', name => mt('_vnbrowse_taginc'), width => 350 ]); - $self->htmlFormPart($f, [ radio => short => 'sp', name => '', options => [map [$_, mt '_vnbrowse_spoil'.$_], 0..2]]); - $self->htmlFormPart($f, [ input => short => 'te', name => mt('_vnbrowse_tagexc'), width => 350 ]); - end; - - h2; - txt mt '_vnbrowse_lang'; - b ' ('.mt('_vnbrowse_boolor').')'; - end; - for my $i (@{$self->{languages}}) { - span; - input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", - (scalar grep $_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : (); - label for => "lang_$i"; - cssicon "lang $i", mt "_lang_$i"; - txt mt "_lang_$i"; - end; - end; - } + input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil}; + end; + end; # /form - h2; - txt mt '_vnbrowse_plat'; - b ' ('.mt('_vnbrowse_boolor').')'; - end; - for my $i (sort @{$self->{platforms}}) { - next if $i eq 'oth'; - span; - input type => 'checkbox', id => "plat_$i", name => 'pl', value => $i, - (scalar grep $_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : (); - label for => "plat_$i"; - cssicon $i, mt "_plat_$i"; - txt mt "_plat_$i"; - end; - end; - } + $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", scalar @ti); + $self->htmlFooter; +} - div style => 'text-align: center; clear: left;'; - input type => 'submit', value => mt('_vnbrowse_apply'), class => 'submit'; - input type => 'reset', value => mt('_vnbrowse_clear'), class => 'submit', onclick => 'location.href="/v/all"'; - end; - end; - end; - end; + +sub _fil_compat { + my($self, $fil) = @_; + 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}; } diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 4a4b3f4f..1a3eb514 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -191,8 +191,8 @@ sub _revision { $self->htmlRevision('v', $prev, $v, [ title => diff => 1 ], [ original => diff => 1 ], - [ alias => diff => 1 ], - [ desc => diff => 1 ], + [ alias => diff => qr/[ ,\n\.]/ ], + [ desc => diff => qr/[ ,\n\.]/ ], [ length => serialize => sub { mt '_vnlength_'.$_[0] } ], [ l_wp => htmlize => sub { $_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : mt '_revision_nolink' diff --git a/lib/VNDB/Plugin/TransAdmin.pm b/lib/VNDB/Plugin/TransAdmin.pm index 4859ea67..6d4de67e 100644 --- a/lib/VNDB/Plugin/TransAdmin.pm +++ b/lib/VNDB/Plugin/TransAdmin.pm @@ -121,7 +121,7 @@ sub _savelang { # 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(); + system "make -sC $VNDB::ROOT js" if $self->{regen_static}; } } diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm index a862911d..280ceddb 100644 --- a/lib/VNDB/Util/CommonHTML.pm +++ b/lib/VNDB/Util/CommonHTML.pm @@ -153,7 +153,7 @@ sub htmlHiddenMessage { # Where @fields is a list of fields as arrayrefs with: # [ shortname, displayname, %options ], # Where %options: -# diff => 1/0, whether do show a diff on this field +# diff => 1/0/regex, whether to show a diff on this field, and what to split it with (1 = character-level diff) # serialize => coderef, should convert the field into a readable string, no HTML allowed # htmlize => same as serialize, but HTML is allowed and this can't be diff'ed # split => coderef, should return an array of HTML strings that can be diff'ed. (implies diff => 1) @@ -226,7 +226,7 @@ sub revdiff { my($i, $type, $old, $new, $short, %o) = @_; $o{serialize} ||= $o{htmlize}; - $o{diff}++ if $o{split}; + $o{diff} = 1 if $o{split}; $o{join} ||= ''; my $ser1 = $o{serialize} ? $o{serialize}->($old->{$short}, $old) : $old->{$short}; @@ -234,8 +234,9 @@ sub revdiff { return if $ser1 eq $ser2; if($o{diff} && $ser1 && $ser2) { - my @ser1 = $o{split} ? $o{split}->($ser1) : map xml_escape($_), split //, $ser1; - my @ser2 = $o{split} ? $o{split}->($ser2) : map xml_escape($_), split //, $ser2; + my $sep = ref $o{diff} ? qr/($o{diff})/ : qr//; + my @ser1 = $o{split} ? $o{split}->($ser1) : map xml_escape($_), split $sep, $ser1; + my @ser2 = $o{split} ? $o{split}->($ser2) : map xml_escape($_), split $sep, $ser2; return if $o{split} && $#ser1 == $#ser2 && !grep $ser1[$_] ne $ser2[$_], 0..$#ser1; $ser1 = $ser2 = ''; diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index 93a35e0e..b5dc3e9c 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, noindex, search } +sub htmlHeader { # %options->{ title, noindex, search, feeds } 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"; @@ -26,6 +26,8 @@ sub htmlHeader { # %options->{ title, noindex, search } (my $css = $self->authInfo->{customcss}) =~ s/\n/ /g; style type => 'text/css', $css; } + Link rel => 'alternate', type => 'application/atom+xml', href => "/feeds/$_.atom", title => $self->{atom_feeds}{$_}[1] + for ($o{feeds} ? @{$o{feeds}} : ()); meta name => 'robots', content => 'noindex, follow', undef if $o{noindex}; end; body; @@ -153,7 +155,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}, ''; + 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/VNDBUtil.pm b/lib/VNDBUtil.pm index f23811a9..b0213d07 100644 --- a/lib/VNDBUtil.pm +++ b/lib/VNDBUtil.pm @@ -29,95 +29,118 @@ sub bb2html { my $raw = shift; my $maxlength = shift; $raw =~ s/\r//g; - $raw =~ s/\n{5,}/\n\n/g; return '' if !$raw && $raw ne "0"; - my($result, $length, $rmnewline, @open) = ('', 0, 0, 'first'); + my($result, $last, $length, $rmnewline, @open) = ('', 0, 0, 0, 'first'); + # escapes, returns string, and takes care of $length and $maxlength; also + # takes care to remove newlines and double spaces when necessary my $e = sub { local $_ = shift; + s/^\n// if $rmnewline && $rmnewline--; + s/\n{5,}/\n\n/g if $open[$#open] ne 'code'; + s/ +/ /g if $open[$#open] ne 'code'; + $length += length $_; + if($maxlength && $length > $maxlength) { + $_ = substr($_, 0, $maxlength-$length); + s/[ \.,:;]+[^ \.,:;]*$//; # cleanly cut off on word boundary + } s/&/&/g; s/>/>/g; s/</</g; s/\n/<br \/>/g if !$maxlength; - s/\n/ /g if $maxlength; + s/\n/ /g if $maxlength; return $_; }; - for (split /(\s|\n|\[[^\]]+\])/, $raw) { - next if !defined $_; - next if $_ eq ''; + while($raw =~ m{( + ([tdvpr][1-9][0-9]*\.[1-9][0-9]*) | # 2. exid + ([tdvprug][1-9][0-9]*) | # 3. id + (\[[^\s\]]+\]) | # 4. tag + ((?:https?|ftp)://[^><"\n\s\]\[]+[\d\w=/-]) # 5. url + )}xg) { + my($match, $exid, $id, $tag, $url) = ($1, $2, $3, $4, $5); - # (note to self: stop using unreadable hacks like these!) - $rmnewline-- && $_ eq "\n" && next if $rmnewline; + # add string before the match + $result .= $e->(substr $raw, $last, (pos($raw)-length($match))-$last); + last if $maxlength && $length > $maxlength; + $last = pos $raw; - my $lit = $_; if($open[$#open] ne 'raw' && $open[$#open] ne 'code') { - if (lc$_ eq '[raw]') { push @open, 'raw'; next } - elsif (lc$_ eq '[spoiler]') { push @open, 'spoiler'; $result .= '<b class="spoiler">'; next } - elsif (lc$_ eq '[quote]') { - push @open, 'quote'; - $result .= '<div class="quote">' if !$maxlength; - $rmnewline = 1; - next - } elsif (lc$_ eq '[code]') { - push @open, 'code'; - $result .= '<pre>' if !$maxlength; - $rmnewline = 1; - next - } elsif (lc$_ eq '[/spoiler]') { - if($open[$#open] eq 'spoiler') { + # handle tags + if($tag) { + $tag = lc $tag; + if($tag eq '[raw]') { + push @open, 'raw'; + next; + } elsif($tag eq '[spoiler]') { + push @open, 'spoiler'; + $result .= '<b class="spoiler">'; + next; + } elsif($tag eq '[quote]') { + push @open, 'quote'; + $result .= '<div class="quote">' if !$maxlength; + $rmnewline = 1; + next; + } elsif($tag eq '[code]') { + push @open, 'code'; + $result .= '<pre>' if !$maxlength; + $rmnewline = 1; + next; + } elsif($tag eq '[/spoiler]' && $open[$#open] eq 'spoiler') { $result .= '</b>'; pop @open; - } - next; - } elsif (lc$_ eq '[/quote]') { - if($open[$#open] eq 'quote') { + next; + } elsif($tag eq '[/quote]' && $open[$#open] eq 'quote') { $result .= '</div>' if !$maxlength; $rmnewline = 1; - pop @open; - } - next; - } elsif(lc$_ eq '[/url]') { - if($open[$#open] eq 'url') { + next; + } elsif($tag eq '[/url]' && $open[$#open] eq 'url') { $result .= '</a>'; pop @open; + next; + } elsif($tag =~ s{\[url=((https?://|/)[^\]>]+)\]}{<a href="$1" rel="nofollow">}i) { + $result .= $tag; + push @open, 'url'; + next; } - next; - } elsif(s{\[url=((https?://|/)[^\]>]+)\]}{<a href="$1" rel="nofollow">}i) { - $result .= $_; - push @open, 'url'; - next; - } elsif(!grep(/url/, @open) && - s{(.*)(http|https)://(.+[\d\w=/-])(.*)} - {$e->($1).qq|<a href="$2://|.$e->($3, 1).'" rel="nofollow">'.$e->('link').'</a>'.$e->($4)}e) { + } + # handle URLs + if($url && !grep(/url/, @open)) { $length += 4; last if $maxlength && $length > $maxlength; - $result .= $_; + $result .= sprintf '<a href="%s" rel="nofollow">link</a>', $url; next; - } elsif(!grep(/url/, @open) && ( - s{^(.*[^\w]|)([tdvpr][1-9][0-9]*)\.([1-9][0-9]*)([^\w].*|)$}{$e->($1).qq|<a href="/$2.$3">$2.$3</a>|.$e->($4)}e || - s{^(.*[^\w]|)([tdvprug][1-9][0-9]*)([^\w].*|)$}{$e->($1).qq|<a href="/$2">$2</a>|.$e->($3)}e)) { - $length += length $lit; + } + # id + if(($id || $exid) && substr($raw, $last-1-length($match), 1) !~ /[\w]/ && substr($raw, $last, 1) !~ /[\w]/) { + $length += length $match; last if $maxlength && $length > $maxlength; - $result .= $_; - next; + $result .= sprintf '<a href="/%s">%1$s</a>', $match; + next } - } elsif($open[$#open] eq 'raw' && lc$_ eq '[/raw]') { + } + + if($tag && $open[$#open] eq 'raw' && lc$tag eq '[/raw]') { pop @open; next; - } elsif($open[$#open] eq 'code' && lc$_ eq '[/code]') { + } + + if($tag && $open[$#open] eq 'code' && lc$tag eq '[/code]') { $result .= '</pre>' if !$maxlength; pop @open; next; } - # normal text processing - $length += length $_; + # We'll only get here when the bbcode input isn't correct or something else + # didn't work out. In that case, just output whatever we've matched. + $result .= $e->($match); last if $maxlength && $length > $maxlength; - $result .= $e->($_); } + # the last unmatched part, just escape and output + $result .= $e->(substr $raw, $last); + # close open tags while((local $_ = pop @open) ne 'first') { $result .= $_ eq 'url' ? '</a>' : $_ eq 'spoiler' ? '</b>' : ''; |