summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile23
-rw-r--r--README.md39
-rw-r--r--data/js/dateselector.js84
-rw-r--r--data/js/dropdown.js91
-rw-r--r--data/js/dropdownsearch.js204
-rw-r--r--data/js/filter.js727
-rw-r--r--data/js/lib.js179
-rw-r--r--data/js/main.js39
-rw-r--r--data/js/misc.js25
-rw-r--r--lib/VNDB/DB/Chars.pm150
-rw-r--r--lib/VNDB/DB/Producers.pm108
-rw-r--r--lib/VNDB/DB/Releases.pm224
-rw-r--r--lib/VNDB/DB/Staff.pm79
-rw-r--r--lib/VNDB/DB/Tags.pm162
-rw-r--r--lib/VNDB/DB/Traits.pm86
-rw-r--r--lib/VNDB/DB/Users.pm49
-rw-r--r--lib/VNDB/DB/VN.pm257
-rw-r--r--lib/VNDB/Func.pm151
-rw-r--r--lib/VNDB/Handler/Chars.pm111
-rw-r--r--lib/VNDB/Handler/Misc.pm53
-rw-r--r--lib/VNDB/Handler/Producers.pm45
-rw-r--r--lib/VNDB/Handler/Releases.pm160
-rw-r--r--lib/VNDB/Handler/Staff.pm116
-rw-r--r--lib/VNDB/Handler/Tags.pm202
-rw-r--r--lib/VNDB/Handler/Traits.pm165
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm143
-rw-r--r--lib/VNDB/Handler/VNPage.pm293
-rw-r--r--lib/VNDB/Util/Auth.pm81
-rw-r--r--lib/VNDB/Util/BrowseHTML.pm190
-rw-r--r--lib/VNDB/Util/CommonHTML.pm34
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm44
-rw-r--r--lib/VNDB/Util/Misc.pm93
-rw-r--r--lib/VNDB/Util/ValidateTemplates.pm16
-rw-r--r--lib/VNWeb/Filters.pm101
-rw-r--r--lib/VNWeb/HTML.pm18
-rwxr-xr-xutil/jsgen.pl62
l---------util/sql1
-rwxr-xr-xutil/vndb.pl29
38 files changed, 48 insertions, 4586 deletions
diff --git a/Makefile b/Makefile
index 816f8d20..dbba4695 100644
--- a/Makefile
+++ b/Makefile
@@ -28,7 +28,6 @@ ALL_KEEP=\
www/robots.txt static/robots.txt
ALL_CLEAN=\
- static/g/vndb.js \
static/g/plain.js \
static/g/elm.js \
data/icons/icons.css \
@@ -36,7 +35,6 @@ ALL_CLEAN=\
$(shell ls css/skins/*.sass | sed -e 's/css\/skins\/\(.\+\)\.sass/static\/g\/\1.css/g')
PROD=\
- static/g/vndb.min.js static/g/vndb.min.js.gz \
static/g/plain.min.js static/g/plain.min.js.gz \
static/g/elm.min.js static/g/elm.min.js.gz \
static/g/icons.opt.png \
@@ -49,6 +47,7 @@ clean:
rm -f ${ALL_CLEAN} ${PROD}
rm -f static/g/icons.png
rm -f static/f/{vndb,elm,plain}{,.min}.js{,.gz} static/f/icons{,.opt}.png static/s/*/style{,.min}.css{,.gz} static/s/*/boxbg.png
+ rm -f static/g/vndb{,.min}.js{,.gz}
rm -rf elm/Gen/
rm -rf elm/elm-stuff/build-artifacts
$(MAKE) -C sql/c clean
@@ -81,8 +80,6 @@ chmod: all
-# v2 & v2-rw
-
data/icons/icons.css: data/icons/*.png data/icons/*/*.png util/spritegen.pl | static/g
util/spritegen.pl
@@ -97,24 +94,12 @@ static/g/%.css: css/skins/%.sass css/v2.css data/icons/icons.css | static/g
-# v2
-
-static/g/vndb.js: data/js/*.js lib/VNDB/Types.pm util/jsgen.pl data/conf.pl | static/g
- util/jsgen.pl
-
-static/g/vndb.min.js: static/g/vndb.js
- uglifyjs $< --compress --mangle --comments '/(@license|@source|SPDX-)/' -o $@
-
-
-
-# v2-rw
-
# Order of JS files matters, so we read an '//order:x' comment from the files and sort by that.
# Files without that comment are assumed to have '//order:4'.
# (This trick will not work if we ever add JS files generated by this Makefile)
JS_FILES=$(shell find elm \! -path 'elm/elm-stuff/*' -name '*.js' -exec sh -c "echo \`grep -Eo '^// *order: *[0-9]+' \"{}\" || echo 4\` \"{}\"" \; | sed -E 's/\/\/ *order: *//' | sort | sed 's/..//')
-static/g/plain.js: ${JS_FILES} | static/f
+static/g/plain.js: ${JS_FILES} | static/g
echo '// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0-only' > $@~
echo '// @source: https://code.blicky.net/yorhel/vndb/src/branch/master/elm' >>$@~
echo '// SPDX-License-Identifier: AGPL-3.0-only' >>$@~
@@ -157,11 +142,11 @@ endef
elm/Gen/.generated: lib/VNWeb/*.pm lib/VNWeb/*/*.pm lib/VNDB/Types.pm lib/VNDB/ExtLinks.pm lib/VNDB/Config.pm data/conf.pl
util/vndb.pl elmgen
-static/g/elm.js: ${ELM_FILES} elm/Gen/.generated | static/f
+static/g/elm.js: ${ELM_FILES} elm/Gen/.generated | static/g
cd elm && ELM_HOME=elm-stuff elm make ${ELM_MODULES} --output ../$@
${fix-elm}
-static/g/elm.min.js: ${ELM_FILES} elm/Gen/.generated | static/f
+static/g/elm.min.js: ${ELM_FILES} elm/Gen/.generated | static/g
cd elm && ELM_HOME=elm-stuff elm make --optimize ${ELM_MODULES} --output ../$@
${fix-elm}
uglifyjs $@ --comments '/(@license|@source|SPDX-)/' --compress \
diff --git a/README.md b/README.md
index 5225ac37..4b73843c 100644
--- a/README.md
+++ b/README.md
@@ -134,45 +134,6 @@ util/multi.pl (application server, optional):
```
-# Rewrites, rewrites, rewrites
-
-The VNDB website is currently (like every project beyond a certain age) in a
-transitional state of rewrites. There are three "versions" and coding styles
-across this repository:
-
-**Version 2**
-
-This is the code that powers the actual website. It lives in `lib/VNDB/` and
-has `util/vndb.pl` as entry point. Front-end assets are in `data/js/`,
-`data/style.css`, `data/icons/`, `static/f/` and `static/s/`.
-
-**Version 2-rw**
-
-This is a (recently started) backend rewrite of version 2. It lives in
-`lib/VNWeb/` with Elm and Javascript code in `elm/`. Individual parts of the
-website are gradually being moved into this new coding style and structure.
-Version 2 and 2-rw run side-by-side in the same process and share a common
-route table and database connection, so the entry point is still
-`util/vndb.pl`. The primary goal of this rewrite is to make use of the clearer
-version 3 structure and to slowly migrate the brittle frontend Javascript parts
-to Elm and JSON APIs.
-
-**Version 3**
-
-There also used to be a "version 3" rewrite with a completely new user
-interface. All of the improvements developed in version 3 are slowly being
-backported and improved upon in version 2-rw and version 3 does not exist
-anymore (though it can still be found in the version history).
-
-**Non-rewrites**
-
-Some parts of this repository are not affected by these rewrites. These include
-the database structure, most of the scripts in `util/`, some common modules
-spread across `lib/` and Multi, which resides in `lib/Multi/`. That's not to
-say these are *final* or *stable*, but they're largely independent from the
-website code.
-
-
# License
GNU AGPL, see COPYING file for details.
diff --git a/data/js/dateselector.js b/data/js/dateselector.js
deleted file mode 100644
index ed96ab8a..00000000
--- a/data/js/dateselector.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Date selector widget for the 'release date'-style dates, with support for
- * TBA and unknown month or day. Usage:
- *
- * <input type="hidden" class="dateinput" .. />
- *
- * Will add a date selector to the HTML at that place, and automatically
- * read/write the value of the hidden field. Alternative usage:
- *
- * var obj = dateLoad(ref, serfunc);
- *
- * If 'ref' is set, it will behave as above with 'ref' being the input object.
- * Otherwise it will return the widget object. The setfunc, if set, will be
- * called whenever the date widget is focussed or its value is changed.
- *
- * The object returned by dateLoad() can be used as follows:
- * obj.date_val: Always contains the currently selected date.
- * obj.dateSet(val): Change the selected date
- */
-function load(obj, serfunc) {
- var i;
- var selops = {style: 'width: 70px', onfocus:serfunc, onchange: serialize, tabIndex: 10};
-
- var year = tag('select', selops,
- tag('option', {value:0}, '-year-'),
- tag('option', {value:9999}, 'TBA')
- );
- for(i=(new Date()).getFullYear()+5; i>=1980; i--)
- year.appendChild(tag('option', {value: i}, i));
-
- var month = tag('select', selops,
- tag('option', {value:99}, '-month-')
- );
- for(i=1; i<=12; i++)
- month.appendChild(tag('option', {value: i}, i));
-
- var day = tag('select', selops,
- tag('option', {value:99}, '-day-')
- );
- for(i=1; i<=31; i++)
- day.appendChild(tag('option', {value: i}, i));
-
- var div = tag('div', {
- date_obj: obj,
- date_serfunc: serfunc,
- date_val: obj ? obj.value : 0
- }, year, month, day);
- div.dateSet = function(v){ set(div, v) };
-
- set(div, div.date_val);
- return obj ? obj.parentNode.insertBefore(div, obj) : div;
-}
-
-function set(div, val) {
- val = +val || 0;
- val = [ Math.floor(val/10000), Math.floor(val/100)%100, val%100 ];
- if(val[1] == 0) val[1] = 99;
- if(val[2] == 0) val[2] = 99;
- var l = byName(div, 'select');
- for(var i=0; i<l.length; i++)
- for(var j=0; j<l[i].options.length; j++)
- l[i].options[j].selected = l[i].options[j].value == val[i];
- serialize(div, true);
-}
-
-function serialize(div, nonotify) {
- div = div.dateSet ? div : this.parentNode;
- var sel = byName(div, 'select');
- var val = [
- sel[0].options[sel[0].selectedIndex].value*1,
- sel[1].options[sel[1].selectedIndex].value*1,
- sel[2].options[sel[2].selectedIndex].value*1
- ];
- div.date_val = val[0] == 0 ? 0 : val[0] == 9999 ? 99999999 : val[0]*10000+val[1]*100+(val[1]==99?99:val[2]);
- if(div.date_obj)
- div.date_obj.value = div.date_val;
- if(!nonotify && div.date_serfunc)
- div.date_serfunc(div);
-}
-
-var l = byClass('input', 'dateinput');
-for(var i=0; i<l.length; i++)
- load(l[i]);
-
-window.dateLoad = load;
diff --git a/data/js/dropdown.js b/data/js/dropdown.js
deleted file mode 100644
index 76be3f35..00000000
--- a/data/js/dropdown.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/* Dropdown widget, used as follows:
- *
- * ddInit(obj, align, func);
- *
- * Show a dropdown box on mouse-over on 'obj'. 'func' should generate and
- * return the contents of the box as a DOM node, or null to not show a dropdown
- * box at all. The 'align' argument indicates where the box should be shown,
- * relative to the obj:
- *
- * left: To the left of obj
- * bottom: To the bottom of obj
- * tagmod: Special alignment for tagmod page
- *
- * Other functions:
- *
- * ddHide(); Hides the box
- * ddRefresh(); Refreshes the box contents
- */
-var box;
-
-function init(obj, align, contents) {
- obj.dd_align = align;
- obj.dd_contents = contents;
- obj.onmouseover = show;
-}
-
-function show() {
- if(!box) {
- box = tag('div', {id:'dd_box', 'class':'hidden'});
- addBody(box);
- }
- box.dd_lnk = this;
- document.onmousemove = mouse;
- document.onscroll = hide;
- refresh();
-}
-
-function hide() {
- if(box) {
- setText(box, '');
- setClass(box, 'hidden', true);
- box.dd_lnk = document.onmousemove = document.onscroll = null;
- }
-}
-
-function mouse(e) {
- e = e || window.event;
- // Don't hide if the cursor is on the link
- for(var lnk = e.target || e.srcElement; lnk; lnk=lnk.parentNode)
- if(lnk == box.dd_lnk)
- return;
-
- // Hide if it's 10px outside of the box
- var mouseX = e.pageX || (e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft);
- var mouseY = e.pageY || (e.clientY + document.body.scrollTop + document.documentElement.scrollTop);
- if(mouseX < box.dd_x-10 || mouseX > box.dd_x+box.offsetWidth+10 || mouseY < box.dd_y-10 || mouseY > box.dd_y+box.offsetHeight+10)
- hide();
-}
-
-function refresh() {
- if(!box || !box.dd_lnk)
- return hide();
- var lnk = box.dd_lnk;
- var content = lnk.dd_contents(lnk, box);
- if(content == null)
- return hide();
- setContent(box, content);
- setClass(box, 'hidden', false);
-
- var o = lnk;
- ddx = ddy = 0;
- do {
- ddx += o.offsetLeft;
- ddy += o.offsetTop;
- } while(o = o.offsetParent);
-
- if(lnk.dd_align == 'left')
- ddx -= box.offsetWidth;
- if(lnk.dd_align == 'tagmod')
- ddx += lnk.offsetWidth-35;
- if(lnk.dd_align == 'bottom')
- ddy += lnk.offsetHeight;
- box.dd_x = ddx;
- box.dd_y = ddy;
- box.style.left = ddx+'px';
- box.style.top = ddy+'px';
-}
-
-window.ddInit = init;
-window.ddHide = hide;
-window.ddRefresh = refresh;
diff --git a/data/js/dropdownsearch.js b/data/js/dropdownsearch.js
deleted file mode 100644
index 428858cd..00000000
--- a/data/js/dropdownsearch.js
+++ /dev/null
@@ -1,204 +0,0 @@
-/* Interactive drop-down search widget. Usage:
- *
- * dsInit(obj, url, trfunc, serfunc, retfunc);
- *
- * obj: An <input type="text"> object.
- *
- * url: The base URL of the XML API, e.g. "/xml/tags.xml?q=", the search query is appended to this URL.
- * The resource at the URL should return an XML document with a
- * <item id="something" ..>..</item>
- * element for each result.
- *
- * trfunc(item, tr): Function that is given an <item> object given by the XML
- * document and an empty <tr> object. The function should format the data of
- * the item to be shown in the tr.
- *
- * serfunc(item, obj): Called whenever a user selects an item from the search
- * results. Should return a string, which will be used as the new value of the
- * input object.
- *
- * retfunc(obj): Called whenever the user selects an item from the search
- * results (after setfunc()) or when enter is pressed (even if nothing is
- * selected).
- *
- * setfunc and retfunc can be null.
- *
- * TODO: Some users of this widget consider serfunc() as their final "apply
- * this selection" function, whereas others use retfunc() for this. Might be
- * worth investigating whether the additional flexibility offered by
- * retfunc() is actually necessary, and remove the callback if not.
- */
-var boxobj;
-
-function box() {
- if(!boxobj) {
- boxobj = tag('div', {id: 'ds_box', 'class':'hidden'}, tag('b', 'Loading...'));
- addBody(boxobj);
- }
- return boxobj;
-}
-
-function init(obj, url, trfunc, serfunc, retfunc) {
- obj.setAttribute('autocomplete', 'off');
- obj.onkeydown = keydown;
- obj.onclick = obj.onchange = obj.oninput = function() { return textchanged(obj); };
- obj.onblur = blur;
- obj.ds_returnFunc = retfunc;
- obj.ds_trFunc = trfunc;
- obj.ds_serFunc = serfunc;
- obj.ds_searchURL = url;
- obj.ds_selectedId = 0;
- obj.ds_dosearch = null;
- obj.ds_lastVal = obj.value;
-}
-
-function blur() {
- setTimeout(function () {
- setClass(box(), 'hidden', true)
- }, 500)
-}
-
-function setselected(obj, id) {
- obj.ds_selectedId = id;
- var l = byName(box(), 'tr');
- for(var i=0; i<l.length; i++)
- setClass(l[i], 'selected', id && l[i].id == 'ds_box_'+id);
-}
-
-function setvalue(obj) {
- if(obj.ds_selectedId != 0)
- obj.value = obj.ds_lastVal = obj.ds_serFunc(byId('ds_box_'+obj.ds_selectedId).ds_itemData, obj);
- if(obj.ds_returnFunc)
- obj.ds_returnFunc(obj);
-
- setClass(box(), 'hidden', true);
- setContent(box(), tag('b', 'Loading...'));
- setselected(obj, 0);
- if(obj.ds_dosearch) {
- clearTimeout(obj.ds_dosearch);
- obj.ds_dosearch = null;
- }
-}
-
-function enter(obj) {
- // Make sure the form doesn't submit when enter is pressed.
- // This solution is a hack, but it's simple and reliable.
- var frm = obj;
- while(frm && frm.nodeName.toLowerCase() != 'form')
- frm = frm.parentNode;
- if(frm) {
- var oldsubmit = frm.onsubmit;
- frm.onsubmit = function() { return false };
- setTimeout(function() { frm.onsubmit = oldsubmit }, 100);
- }
-
- setvalue(obj);
- return false;
-}
-
-function updown(obj, up) {
- var i, sel, l = byName(box(), 'tr');
- if(l.length < 1)
- return true;
-
- if(obj.ds_selectedId == 0)
- sel = up ? l.length-1 : 0;
- else
- for(i=0; i<l.length; i++)
- if(l[i].id == 'ds_box_'+obj.ds_selectedId)
- sel = up ? (i>0 ? i-1 : l.length-1) : (l[i+1] ? i+1 : 0);
-
- setselected(obj, l[sel].id.substr(7));
- return false;
-}
-
-function textchanged(obj) {
- // Ignore this event if the text hasn't actually changed.
- if(obj.ds_lastVal == obj.value)
- return true;
- obj.ds_lastVal = obj.value;
-
- // perform search after a timeout
- if(obj.ds_dosearch)
- clearTimeout(obj.ds_dosearch);
- obj.ds_dosearch = setTimeout(function() {
- search(obj);
- }, 500);
- return true;
-}
-
-function keydown(ev) {
- var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode;
- var obj = this;
-
- if(c == 9) // tab
- return true;
-
- if(c == 13) // enter
- return enter(obj);
-
- if(c == 38 || c == 40) // up / down
- return updown(obj, c == 38);
-
- return textchanged(obj);
-}
-
-function search(obj) {
- var b = box();
- var val = obj.value;
-
- clearTimeout(obj.ds_dosearch);
- obj.ds_dosearch = null;
-
- // hide the ds_box div if the search string is too short
- if(val.length < 2) {
- setClass(b, 'hidden', true);
- setContent(b, tag('b', 'Loading...'));
- setselected(obj, 0);
- return;
- }
-
- // position the div
- var ddx=0;
- var ddy=obj.offsetHeight;
- var o = obj;
- do {
- ddx += o.offsetLeft;
- ddy += o.offsetTop;
- } while(o = o.offsetParent);
-
- b.style.position = 'absolute';
- b.style.left = ddx+'px';
- b.style.top = ddy+'px';
- b.style.width = obj.offsetWidth+'px';
- setClass(b, 'hidden', false);
-
- // perform search
- ajax(obj.ds_searchURL + encodeURIComponent(val), function(hr) { results(hr, obj) });
-}
-
-function results(hr, obj) {
- var lst = hr.responseXML.getElementsByTagName('item');
- var b = box();
- if(lst.length < 1) {
- setContent(b, tag('b', 'No results...'));
- setselected(obj, 0);
- return;
- }
-
- var tb = tag('tbody', null);
- for(var i=0; i<lst.length; i++) {
- var id = lst[i].getAttribute('id');
- var tr = tag('tr', {id: 'ds_box_'+id, ds_itemData: lst[i]} );
-
- tr.onmouseover = function() { setselected(obj, this.id.substr(7)) };
- tr.onmousedown = function() { setselected(obj, this.id.substr(7)); setvalue(obj) };
-
- obj.ds_trFunc(lst[i], tr);
- tb.appendChild(tr);
- }
- setContent(b, tag('table', tb));
- setselected(obj, obj.ds_selectedId != 0 && !byId('ds_box_'+obj.ds_selectedId) ? 0 : obj.ds_selectedId);
-}
-
-window.dsInit = init;
diff --git a/data/js/filter.js b/data/js/filter.js
deleted file mode 100644
index 8c2edb6d..00000000
--- a/data/js/filter.js
+++ /dev/null
@@ -1,727 +0,0 @@
-/* Filter box definition:
- * [ <title>,
- * [ <category_name>,
- * [ <fieldcode>, <fieldname>, <fieldcontents>, <fieldreadfunc>, <fieldwritefunc>, <fieldshowfunc> ], ..
- * ], ..
- * ]
- * Where:
- * <title> human-readable title of the filter box
- * <category_name> human-readable name of the category. ignored if there's only one category
- * <fieldcode> code of this field, refers to the <field> in the filter format. Empty string for just a <tr>
- * <fieldname> human-readanle name of the field. Empty to not display a label. Space for always-enabled items (without checkbox)
- * <fieldcontents> tag() object, or an array of tag() objects
- * <fieldreadfunc> function reference. argument: <fieldcontents>; must return data to be used in the filter format
- * <fieldwritefunc> function reference, argument: <fieldcontents>, data from filter format; must update the contents with the passed data
- * <fieldshowfunc> function reference, argument: <fieldcontents>, called when the field is displayed
- *
- * Filter string format:
- * <field>-<value1>~<value2>.<field2>-<value>.<field3>-<value1>~<value2>
- * Where:
- * <field> = [a-z0-9]+
- * <value> = [a-zA-Z0-9_]+ and any UTF-8 characters not in the ASCII range
- * Escaping of the <value>:
- * "_<two-number-code>"
- * Where <two-number-code> is the decimal index to the following array:
- * _ <space> ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ ` { | } ~
- * For boolean fields, the <value> is either 0 or 1.
- */
-
-var fil_escape = "_ !\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~".split('');
-var fil_objs = [];
-
-function getObj(obj) {
- while(!obj.fil_fields)
- obj = obj.parentNode;
- return obj;
-}
-
-
-function filLoad(lnk, serobj) {
- var type = lnk.href.match(/#r$/) ? 'r' : lnk.href.match(/#c$/) ? 'c' : lnk.href.match(/#s$/) ? 's' : 'v';
- var l = {r: filReleases, c: filChars, s: filStaff, v: filVN}[type]();
-
- var fields = {};
- var cats = [];
- var p = tag('p', {'class':'browseopts'});
- var c = tag('div', null);
- var idx = 0;
- for(var i=1; i<l.length; i++) {
- if(!l[i])
- continue;
-
- // category link
- var a = tag('a', { href: '#', onclick: selectCat, fil_onshow:[] }, l[i][0]);
- cats.push(a);
- p.appendChild(a);
- p.appendChild(tag(' '));
-
- // category contents
- var t = tag('table', {'class':'formtable hidden', fil_a: a}, null);
- a.fil_t = t;
- for(var j=1; j<l[i].length; j++) {
- var fd = l[i][j];
- var lab = typeof fd[1] == 'object' ? fd[1][0] : fd[1];
- var name = 'fil_check_'+type+'_'+fd[0];
- var f = tag('tr', {'class':'newfield', fil_code: fd[0], fil_readfunc: fd[3], fil_writefunc: fd[4]},
- // Checkbox
- fd[0] ? tag('td', {'class':'check'},
- tag('input', {type:'checkbox', id:name, name:name, 'class': 'enabled_check'+(fd[1]==' '?' hidden':''), onclick: selectField }))
- : tag('td', null),
- // Label
- fd[1] ? tag('td', {'class':'label'},
- tag('label', {'for':name}, lab),
- typeof fd[1] == 'object' ? tag('b', fd[1][1]) : null
- ) : null,
- // Contents
- tag('td', {'class':'cont' }, fd[2]));
- if(fd[0])
- fields[fd[0]] = f;
- if(fd[5])
- a.fil_onshow.push([ fd[5], fd[2] ]);
- t.appendChild(f);
- }
- c.appendChild(t);
- idx++;
- }
-
- var savenote = tag('p', {'class':'hidden'}, '')
- var obj = tag('div', {
- 'class': 'fil_div hidden',
- fil_fields: fields,
- fil_cats: cats,
- fil_savenote: savenote,
- fil_serobj: serobj,
- fil_lnk: lnk,
- fil_type: type
- },
- tag('a', {href:'#', onclick:show, 'class':'close'}, 'close'),
- tag('h3', l[0]),
- p,
- tag('b', {'class':'ruler'}, null),
- c,
- tag('b', {'class':'ruler'}, null),
- tag('input', {type:'button', 'class':'submit', value: 'Apply', onclick:function () {
- var f = serobj;
- while(f.nodeName.toLowerCase() != 'form')
- f = f.parentNode;
- f.submit();
- }}),
- tag('input', {type:'button', 'class':'submit', value: 'Reset', onclick:function () { serobj.value = ''; deSerialize(obj) } }),
- byId('pref_code') && lnk.id == 'filselect' ? tag('input', {type:'button', 'class':'submit', value: 'Save as default', onclick:saveDefault }) : null,
- savenote
- );
- lnk.fil_obj = obj;
- lnk.onclick = show;
-
- addBody(obj);
- fil_objs.push(obj);
- deSerialize(obj);
- selectCat(obj.fil_cats[0]);
-}
-
-
-function saveDefault() {
- var but = this;
- var obj = getObj(this);
- var note = obj.fil_savenote;
- setText(note, 'Loading...');
- but.enabled = false;
- setClass(note, 'hidden', false);
- var type = obj.fil_type == 'r' ? 'release' : 'vn';
- ajax('/xml/prefs.xml?formcode='+byId('pref_code').title+';key=filter_'+type+';value='+obj.fil_serobj.value, function (hr) {
- setText(note, 'Your saved filters will be applied automatically to several other parts of the site as well, such as the homepage.'+
- ' To change these filters, come back to this page and use the "Save as default" button again.'+
- ' To remove your saved filters, hit "Reset" and then save.');
- but.enable = true;
- });
-}
-
-
-function selectCat(n) {
- var lnk = n.fil_onshow ? n : this;
- var obj = getObj(lnk);
- setClass(obj.fil_savenote, 'hidden', true);
- for(var i=0; i<obj.fil_cats.length; i++) {
- var n = obj.fil_cats[i];
- setClass(n, 'optselected', n == lnk);
- setClass(n.fil_t, 'hidden', n != lnk);
- }
- for(var i=0; i<lnk.fil_onshow.length; i++)
- lnk.fil_onshow[i][0](lnk.fil_onshow[i][1]);
- return false
-}
-
-
-function selectField(f) {
- if(!f.parentNode)
- f = this;
- setClass(getObj(f).fil_savenote, 'hidden', true);
-
- // update checkbox and label
- var o = f;
- while(o.nodeName.toLowerCase() != 'tr')
- o = o.parentNode;
- var c = byClass(o, 'enabled_check')[0];
- if(c != f)
- c.checked = true;
- if(hasClass(c, 'hidden')) // When there's no label (e.g. tagspoil selector)
- c.checked = true;
- var l = byName(o, 'label')[0];
- if(l)
- setClass(l, 'active', c.checked);
-
- // update category link
- while(o.nodeName.toLowerCase() != 'table')
- o = o.parentNode;
- var l = byName(o, 'tr');
- var n=0;
- for(var i=0; i<l.length; i++) {
- var ch = byClass(l[i], 'enabled_check')[0];
- if(ch && !hasClass(ch, 'hidden') && ch.checked)
- n++;
- }
- setClass(o.fil_a, 'active', n>0);
-
- // serialize
- serialize(getObj(o));
- return true;
-}
-
-
-function escapeVal(val) {
- var r = [];
- for(var h=0; h<val.length; h++) {
- var vs = (''+val[h]).split('');
- r[h] = '';
-
- // this isn't a very fast escaping method, blame JavaScript for inflexible search/replace support
- for(var i=0; i<vs.length; i++) {
- for(var j=0; j<fil_escape.length; j++)
- if(vs[i] == fil_escape[j])
- break;
- r[h] += j == fil_escape.length ? vs[i] : '_'+(j<10?'0'+j:j);
- }
- }
-
- return r[0] == '' ? '' : r.join('~');
-}
-
-
-function serialize(obj) {
- if(!obj.fil_fields)
- obj = getObj(this);
- var num = 0;
- var values = {};
-
- for(var f in obj.fil_fields) {
- var fo = obj.fil_fields[f];
- var ch = byClass(fo, 'enabled_check')[0];
- if(!ch || !ch.checked)
- continue;
- if(!hasClass(ch, 'hidden'))
- num++;
-
- var v = escapeVal(fo.fil_readfunc(byClass(fo, 'cont')[0].childNodes[0]));
- if(v != '')
- values[fo.fil_code] = v;
- }
-
- var l = [];
- for(var f in values)
- l.push(f+'-'+values[f]);
-
- obj.fil_serobj.value = l.join('.');
- setText(byName(obj.fil_lnk, 'i')[1], num > 0 ? ' ('+num+')' : '');
-}
-
-
-function deSerialize(obj) {
- var d = obj.fil_serobj.value;
- var fs = d.split('.');
-
- var f = {};
- for(var i=0; i<fs.length; i++) {
- var v = fs[i].split('-');
- if(obj.fil_fields[v[0]])
- f[v[0]] = v[1];
- }
-
- for(var fn in obj.fil_fields)
- if(!f[fn])
- f[fn] = '';
- for(var fn in f) {
- var c = byClass(obj.fil_fields[fn], 'enabled_check')[0];
- if(!c)
- continue;
- c.checked = f[fn] != '';
-
- var v = f[fn].split('~');
- for(var i=0; i<v.length; i++)
- v[i] = v[i].replace(/_([0-9]{2})/g, function (a, e) { return fil_escape[Math.floor(e)] });
-
- obj.fil_fields[fn].fil_writefunc(byClass(obj.fil_fields[fn], 'cont')[0].childNodes[0], v);
- // not very efficient: selectField() does a lot of things that can be
- // batched after all fields have been updated, and in some cases the
- // writefunc() triggers the same selectField() as well
- selectField(c);
- }
-}
-
-
-function show() {
- var obj = this.fil_obj || getObj(this);
-
- // Hide other filter objects
- for(var i=0; i<fil_objs.length; i++)
- if(fil_objs[i] != obj) {
- setClass(fil_objs[i], 'hidden', true);
- setText(byName(fil_objs[i].fil_lnk, 'i')[0], collapsed_icon);
- }
-
- var hid = !hasClass(obj, 'hidden');
- setClass(obj, 'hidden', hid);
- setText(byName(obj.fil_lnk, 'i')[0], hid ? collapsed_icon : expanded_icon);
- setClass(obj.fil_savenote, 'hidden', true);
-
- var o = obj.fil_lnk;
- ddx = ddy = 0;
- do {
- ddx += o.offsetLeft;
- ddy += o.offsetTop;
- } while(o = o.offsetParent);
- ddy += obj.fil_lnk.offsetHeight+2;
- ddx += (obj.fil_lnk.offsetWidth-obj.offsetWidth)/2;
- obj.style.left = ddx+'px';
- obj.style.top = ddy+'px';
-
- return false;
-}
-
-
-var curSlider = null;
-function filFSlider(c, n, min, max, def, unit, ser, deser) {
- // min/max/def/fil_val are in serialized form (i.e. a "value")
- if(!ser) ser = function(v) { return v }; // integer -> value
- if(!deser) deser = function(v) { return parseInt(v) }; // value -> integer
-
- min = deser(min);
- max = deser(max);
- var bw = 200; var pw = 1; // slidebar width and pointer width
- var s = tag('p', {fil_val:def, 'class':'slider'});
- var b = tag('div', {style:'width:'+(bw-2)+'px;', s:s});
- var p = tag('div', {style:'width:'+pw+'px;', s:s});
- var v = tag('span', def+' '+unit);
- s.appendChild(b);
- b.appendChild(p);
- s.appendChild(v);
-
- var set = function (e, v) {
- var w = bw-pw-6;
- var s,x;
-
- if(v) {
- s = e;
- x = deser(v[0] == '' ? def : v[0]);
- x = (x-min)*w/(max-min);
- } else {
- s = curSlider;
- if(!e) e = window.event;
- x = (!e) ? (deser(def)-min)*w/(max-min)
- : (e.pageX || e.clientX + document.body.scrollLeft - document.body.clientLeft)-5;
- var o = s.childNodes[0];
- while(o.offsetParent) {
- x -= o.offsetLeft;
- o = o.offsetParent;
- }
- }
-
- if(x<0) x = 0; if(x>w) x = w;
- s.fil_val = ser(min + Math.floor(x*(max-min)/w));
- s.childNodes[1].innerHTML = s.fil_val+' '+unit;
- s.childNodes[0].childNodes[0].style.left = x+'px';
- return false;
- }
-
- b.onmousedown = p.onmousedown = function (e) {
- curSlider = this.s;
- if(!curSlider.oldmousemove) curSlider.oldmousemove = document.onmousemove;
- if(!curSlider.oldmouseup) curSlider.oldmouseup = document.onmouseup;
- document.onmouseup = function () {
- document.onmousemove = curSlider.oldmousemove;
- curSlider.oldmousemove = null;
- document.onmouseup = curSlider.oldmouseup;
- curSlider.oldmouseup = null;
- selectField(curSlider);
- return false;
- }
- document.onmousemove = set;
- return set(e);
- }
-
- return [c, n, s, function (c) { return [ c.fil_val ]; }, set ];
-}
-
-function filFSelect(c, n, lines, opts) {
- var s = tag('select', {onfocus: selectField, onchange: serialize, multiple: lines > 1, size: lines});
- for(var i=0; i<opts.length; i++) {
- if(typeof opts[i][1] != 'object')
- s.appendChild(tag('option', {name: opts[i][0]}, opts[i][1]));
- else {
- var g = tag('optgroup', {label: opts[i][0]});
- for(var j=1; j<opts[i].length; j++)
- g.appendChild(tag('option', {name: opts[i][j][0]}, opts[i][j][1]));
- s.appendChild(g);
- }
- }
- return [ c, lines > 1 ? [ n, 'Boolean or, selecting more gives more results' ] : n, s,
- function (c) {
- var l = [];
- for(var i=0; i<c.options.length; i++)
- if(c.options[i].selected)
- l.push(c.options[i].name);
- return l;
- },
- function (c, f) {
- for(var i=0; i<c.options.length; i++) {
- for(var j=0; j<f.length; j++)
- if(c.options[i].name+'' == f[j]+'') // beware of JS logic: 0 == '', but '0' != ''
- break;
- c.options[i].selected = j != f.length;
- }
- }
- ];
-}
-
-function filFInput(c, n) {
- return [ c, n,
- tag('input', {type: 'text', 'class': 'text', onfocus: selectField, onchange: serialize}),
- function (c) { return [c.value] },
- function (c, f) { c.value = f }
- ]
-}
-
-function filFEngine(c, n) {
- var input = tag('input', {type: 'text', 'class': 'text', onfocus: selectField, onchange: serialize});
- dsInit(input, '/xml/engines.xml?q=',
- function(item, tr) { tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40))); },
- function(item, obj) { return item.firstChild.nodeValue; },
- function(o) { selectField(o) }
- );
- return [ c, n, input,
- function (c) { return [c.value] },
- function (c, f) { c.value = f }
- ]
-}
-
-
-function filFOptions(c, n, opts) {
- var p = tag('p', {'class':'opts', fil_val:opts[0][0]});
- var sel = function (e) {
- var o = typeof e == 'string' ? e : this.fil_n;
- var l = byName(p, 'a');
- for(var i=0; i<l.length; i++)
- setClass(l[i], 'tsel', l[i].fil_n+'' == o+'');
- p.fil_val = o;
- if(typeof e != 'string')
- selectField(p);
- return false
- };
- for(var i=0; i<opts.length; i++) {
- p.appendChild(tag('a', {href:'#', fil_n: opts[i][0], onclick:sel}, opts[i][1]));
- if(i<opts.length-1)
- p.appendChild(tag('b', '|'));
- }
- return [ c, n, p,
- function (c) { return [ c.fil_val ] },
- function (c, v) { sel(v[0]) }
- ];
-}
-
-
-// fieldcode -> See <fieldcode> in filter definitions
-// fieldname -> See <fieldname> in filter definitions
-// src -> The API URL where to get items, must work with dropdownsearch.js and support appending ';q=', ';id=' and ';r=' to it.
-// fmtlist -> Called with item id + XML data, should return an inline element to inject into the list view.
-function filFDList(fieldcode, fieldname, src, fmtds, fmtlist) {
- var visible = false;
- var addel = function(ul, id, data) {
- ul.appendChild(
- tag('li', { fil_id: id },
- data ? fmtlist(id, data) : null,
- ' (', tag('a', {href:'#',
- onclick:function () {
- // a -> li -> ul -> div
- var ul = this.parentNode.parentNode;
- ul.removeChild(this.parentNode);
- selectField(ul.parentNode);
- return false
- }
- }, 'remove'), ')'
- ));
- }
- var fetch = function(c) {
- var v = c.fil_val;
- var ul = byName(c, 'ul')[0];
- var txt = byName(c, 'input')[0];
- if(v == null)
- return;
- if(!v[0]) {
- setText(ul, '');
- txt.disabled = false;
- txt.value = '';
- return;
- }
- if(!visible)
- setText(ul, '');
- var q = [];
- for(var i=0; i<v.length; i++) {
- q.push('id='+v[i]);
- if(!visible)
- addel(ul, v[i]);
- }
- txt.value = 'Loading...';
- txt.disabled = true;
- if(visible)
- ajax(src+';r=50;'+q.join(';'), function (hr) {
- var items = hr.responseXML.getElementsByTagName('item');
- setText(ul, '');
- for(var i=0; i<items.length; i++)
- addel(ul, items[i].getAttribute('id'), items[i]);
- txt.value = '';
- txt.disabled = false;
- c.fil_val = null;
- }, 1);
- };
- var input = tag('input', {type:'text', 'class':'text', style:'width:300px', onfocus:selectField});
- var list = tag('ul', null);
- dsInit(input, src+';q=', fmtds,
- function(item, obj) {
- if(byName(obj.parentNode, 'li').length >= 50)
- alert('Too many items selected');
- else {
- obj.parentNode.fil_val = null;
- addel(byName(obj.parentNode, 'ul')[0], item.getAttribute('id'), item);
- selectField(obj);
- }
- return '';
- },
- function(o) { selectField(o) }
- );
-
- return [
- fieldcode, fieldname, tag('div', list, input),
- function(c) {
- var v = []; var l = byName(c, 'li');
- for(var i=0; i<l.length; i++)
- v.push(l[i].fil_id);
- return v;
- },
- function(c,v) { c.fil_val = v; fetch(c) },
- function(c) { visible = true; fetch(c); }
- ];
-}
-
-function filFTagInput(code, name) {
- return filFDList(code, name, '/xml/tags.xml?searchable=1',
- function(item, tr) {
- tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40)));
- },
- function(id, item) {
- return tag('span', tag('a', {href:'/g'+id}, item.firstChild.nodeValue||'g'+id))
- }
- )
-}
-
-function filFTraitInput(code, name) {
- return filFDList(code, name, '/xml/traits.xml?searchable=1',
- function(item, tr) {
- var g = item.getAttribute('groupname');
- tr.appendChild(tag('td',
- g ? tag('b', {'class':'grayedout'}, g+' / ') : null,
- shorten(item.firstChild.nodeValue, 40)
- ));
- },
- function(id, item) {
- var g = item.getAttribute('groupname');
- return tag('span',
- g ? tag('b', {'class':'grayedout'}, g+' / ') : null,
- tag('a', {href:'/i'+id}, item.firstChild.nodeValue||'i'+id)
- )
- }
- )
-}
-
-function filFProducerInput(code, name) {
- return filFDList(code, name, '/xml/producers.xml?',
- function(item, tr) {
- tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40)));
- },
- function(id, item) {
- return tag('span', tag('a', {href:'/p'+id}, item.firstChild.nodeValue||'p'+id))
- }
- )
-}
-
-function filFStaffInput(code, name) {
- return filFDList(code, name, '/xml/staff.xml?staffid=1',
- function(item, tr) {
- tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40)));
- },
- function(id, item) {
- return tag('span', tag('a', {href:'/s'+id}, item.firstChild.nodeValue||'s'+id))
- }
- )
-}
-
-
-function filChars() {
- var ontraitpage = location.pathname.indexOf('/i') == 0;
-
- var cup_ser = function(v) { return VARS.cup_size[parseInt(v)] };
- var cup_deser = function(v) {
- for(var i=0; i<VARS.cup_size.length; i++)
- if(VARS.cup_size[i] == v)
- return i;
- return 0;
- };
-
- return [
- 'Character filters',
- [ 'General',
- filFSelect('gender', 'Gender', 4, VARS.genders),
- filFSelect('bloodt', 'Blood type', 5, VARS.blood_types),
- '',
- filFSlider('bust_min', 'Bust min', 20, 120, 40, 'cm'),
- filFSlider('bust_max', 'Bust max', 20, 120, 100, 'cm'),
- filFSlider('waist_min', 'Waist min', 20, 120, 40, 'cm'),
- filFSlider('waist_max', 'Waist max', 20, 120, 100, 'cm'),
- filFSlider('hip_min', 'Hips min', 20, 120, 40, 'cm'),
- filFSlider('hip_max', 'Hips max', 20, 120, 100, 'cm'),
- '',
- filFSlider('height_min', 'Height min', 0, 300, 60, 'cm'),
- filFSlider('height_max', 'Height max', 0, 300, 240, 'cm'),
- filFSlider('weight_min', 'Weight min', 0, 400, 80, 'kg'),
- filFSlider('weight_max', 'Weight max', 0, 400, 320, 'kg'),
- filFSlider('cup_min', 'Cup size min', 'AAA', 'Z', 'AAA', '', cup_ser, cup_deser),
- filFSlider('cup_max', 'Cup size max', 'AAA', 'Z', 'E', '', cup_ser, cup_deser)
- ],
- ontraitpage ? [ 'Traits',
- [ '', ' ', tag('Additional trait filters are not available on this page. Use the character browser instead (available from the main menu -> characters).') ],
- ] : [ 'Traits',
- [ '', ' ', tag('Boolean and, selecting more gives less results') ],
- filFTraitInput('trait_inc', 'Traits to include'),
- filFTraitInput('trait_exc', 'Traits to exclude'),
- filFOptions('tagspoil', ' ', [[0, 'Hide spoilers'],[1, 'Show minor spoilers'],[2, 'Spoil me!']]),
- ],
- [ 'Roles', filFSelect('role', 'Roles', 4, VARS.char_roles) ],
- [ 'Seiyuu',
- [ '', ' ', tag('Boolean or, selecting more gives more results') ],
- filFStaffInput('va_inc', 'Seiyuu to include'),
- filFStaffInput('va_exc', 'Seiyuu to exclude')
- ],
- ];
-}
-
-function filReleases() {
- var plat = VARS.platforms;
- plat.splice(0, 0, [ 'unk', 'Unknown' ]);
- var med = VARS.media;
- med.splice(0, 0, [ 'unk', 'Unknown' ]);
- return [
- 'Release filters',
- [ 'General',
- filFOptions('type', 'Release type', VARS.release_types),
- filFOptions('patch', 'Patch status', [ [1, 'Patch'], [0, 'Standalone'] ]),
- filFOptions('freeware', 'Freeware', [ [1, 'Only freeware'], [0, 'Only non-free releases'] ]),
- filFOptions('doujin', 'Doujin', [ [1, 'Only doujin releases'], [0, 'Only commercial releases'] ]),
- filFOptions('uncensored','Censoring', [ [1, 'Only uncensored releases'], [0, 'Censored or non-erotic releases'] ]),
- [ 'date_after', 'Released after', dateLoad(null, selectField), function (c) { return [c.date_val] }, function(o,v) { o.dateSet(v) } ],
- [ 'date_before', 'Released before', dateLoad(null, selectField), function (c) { return [c.date_val] }, function(o,v) { o.dateSet(v) } ],
- filFOptions('released', 'Release date', [ [1, 'Past (already released)'], [0, 'Future (to be released)'] ])
- ],
- [ 'Age rating', filFSelect('minage', 'Age rating', 15, VARS.age_ratings) ],
- [ 'Language', filFSelect('lang', 'Language', 20, VARS.languages) ],
- byId('rfilselect') ? null :
- [ 'Original language', filFSelect('olang', 'Original language', 20, VARS.languages) ],
- [ 'Screen resolution', filFSelect('resolution', 'Screen resolution', 15, VARS.resolutions) ],
- [ 'Platform', filFSelect('plat', 'Platform', 20, plat) ],
- [ 'Producer',
- [ '', ' ', tag('Boolean or, selecting more gives more results') ],
- filFProducerInput('prod_inc', 'Producers to include'),
- filFProducerInput('prod_exc', 'Producers to exclude')
- ],
- [ 'Misc',
- filFSelect('med', 'Medium', 10, med),
- filFSelect('voiced', 'Voiced', 5, VARS.voiced),
- filFSelect('ani_story', 'Story animation', 5, VARS.animated),
- filFSelect('ani_ero', 'Ero animation', 5, VARS.animated),
- filFEngine('engine', 'Engine')
- ]
- ];
-}
-
-function filVN() {
- var ontagpage = location.pathname.indexOf('/v/') < 0;
-
- return [
- 'Visual Novel Filters',
- [ 'General',
- filFSelect( 'length', 'Length', 6, VARS.vn_lengths),
- filFOptions('hasani', 'Anime', [[1, 'Has anime'], [0, 'Does not have anime']]),
- filFOptions('hasshot','Screenshots', [[1, 'Has screenshot'],[0, 'Does not have a screenshot']]),
- [ 'date_after', 'Released after', dateLoad(null, selectField), function (c) { return [c.date_val] }, function(o,v) { o.dateSet(v) } ],
- [ 'date_before', 'Released before', dateLoad(null, selectField), function (c) { return [c.date_val] }, function(o,v) { o.dateSet(v) } ],
- filFOptions('released', 'Release date', [ [1, 'Past (already released)'], [0, 'Future (to be released)'] ])
- ],
- ontagpage ? [ 'Tags',
- [ '', ' ', tag('Additional tag filters are not available on this page. Use the visual novel browser instead (available from the main menu -> visual novels).') ],
- ] : [ 'Tags',
- [ '', ' ', tag('Boolean and, selecting more gives less results') ],
- [ '', ' ', byId('pref_code') ? tag('These filters are ignored on tag pages (when set as default).') : null ],
- filFTagInput('tag_inc', 'Tags to include'),
- filFTagInput('tag_exc', 'Tags to exclude'),
- filFOptions('tagspoil', ' ', [[0, 'Hide spoilers'],[1, 'Show minor spoilers'],[2, 'Spoil me!']])
- ],
- [ 'Language', filFSelect('lang', 'Language', 20, VARS.languages) ],
- [ 'Original language', filFSelect('olang','Original language', 20, VARS.languages) ],
- [ 'Platform', filFSelect('plat', 'Platform', 20, VARS.platforms) ],
- [ 'Staff',
- [ '', ' ', tag('Boolean or, selecting more gives more results') ],
- filFStaffInput('staff_inc', 'Staff to include'),
- filFStaffInput('staff_exc', 'Staff to exclude')
- ],
- !byId('pref_code') ? null : [
- 'My lists',
- filFOptions('ul_notblack', 'Blacklist', [[1, 'Exclude VNs on my blacklist']]),
- filFOptions('ul_onwish', 'Wishlist', [[0, 'Not on my wishlist'],[1, 'On my wishlist']]),
- filFOptions('ul_voted', 'Voted', [[0, 'Not voted on'], [1, 'Voted on' ]]),
- filFOptions('ul_onlist', 'VN list', [[0, 'Not on my VN list'],[1, 'On my VN list']])
- ],
- ];
-}
-
-function filStaff() {
- var gend = VARS.genders.slice(0, 3);
-
- // Insert seiyuu into the list of roles, before the "staff" role.
- var roles = VARS.credit_type;
- roles.splice(-1, 0, ['seiyuu', 'Voice actor']);
-
- return [
- 'Staff filters',
- [ 'General',
- filFOptions('truename', 'Names', [[1, 'Primary names only'],[0, 'Include aliases']]),
- filFSelect('role', 'Roles', roles.length, roles),
- '',
- filFSelect('gender', 'Gender', gend.length, gend),
- ],
- [ 'Language', filFSelect('lang', 'Language', 20, VARS.languages) ],
- ];
-}
-
-if(byId('filselect'))
- filLoad(byId('filselect'), byId('fil'));
-if(byId('rfilselect'))
- filLoad(byId('rfilselect'), byId('rfil'));
-if(byId('cfilselect'))
- filLoad(byId('cfilselect'), byId('cfil'));
diff --git a/data/js/lib.js b/data/js/lib.js
deleted file mode 100644
index a4921d3e..00000000
--- a/data/js/lib.js
+++ /dev/null
@@ -1,179 +0,0 @@
-window.expanded_icon = '▾',
-window.collapsed_icon = '▸';
-
-
-var ajax_req;
-window.ajax = function(url, func, async, body) {
- if(!async && ajax_req)
- ajax_req.abort();
- var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
- if(!async)
- ajax_req = req;
- req.onreadystatechange = function() {
- if(!req || req.readyState != 4 || !req.responseText)
- return;
- if(req.status != 200)
- return alert('Whoops, error! :(');
- func(req);
- };
- if(!body)
- url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1);
- req.open(body ? 'POST' : 'GET', url, true);
- req.send(body);
- return req;
-};
-
-
-window.setCookie = function(n,v) {
- var date = new Date();
- date.setTime(date.getTime()+(365*24*60*60*1000));
- document.cookie = VARS.cookie_prefix+n+'='+v+'; expires='+date.toGMTString()+'; path=/';
-};
-
-window.getCookie = function(n) {
- var l = document.cookie.split(';');
- n = VARS.cookie_prefix+n;
- for(var i=0; i<l.length; i++) {
- var c = l[i];
- while(c.charAt(0) == ' ')
- c = c.substring(1,c.length);
- if(c.indexOf(n+'=') == 0)
- return c.substring(n.length+1,c.length);
- }
- return null;
-};
-
-
-window.byId = function(n) {
- return document.getElementById(n)
-};
-
-window.byName = function(){
- var d = arguments.length > 1 ? arguments[0] : document;
- var n = arguments.length > 1 ? arguments[1] : arguments[0];
- return d.getElementsByTagName(n);
-};
-
-window.byClass = function() { // [class], [parent, class], [tagname, class], [parent, tagname, class]
- var par = typeof arguments[0] == 'object' ? arguments[0] : document;
- var t = arguments.length == 2 && typeof arguments[0] == 'string' ? arguments[0] : arguments.length == 3 ? arguments[1] : '*';
- var c = arguments[arguments.length-1];
- var l = byName(par, t);
- var ret = [];
- for(var i=0; i<l.length; i++)
- if(hasClass(l[i], c))
- ret[ret.length] = l[i];
- return ret;
-};
-
-
-/* wrapper around DOM element creation
- * tag('string') -> createTextNode
- * tag('tagname', tag(), 'string', ..) -> createElement(), appendChild(), ..
- * tag('tagname', { class: 'meh', title: 'Title' }) -> createElement(), setAttribute()..
- * tag('tagname', { <attributes> }, <elements>) -> create, setattr, append */
-window.tag = function() {
- if(arguments.length == 1)
- return typeof arguments[0] != 'object' ? document.createTextNode(arguments[0]) : arguments[0];
- var el = typeof document.createElementNS != 'undefined'
- ? document.createElementNS('http://www.w3.org/1999/xhtml', arguments[0])
- : document.createElement(arguments[0]);
- for(var i=1; i<arguments.length; i++) {
- if(arguments[i] == null)
- continue;
- if(typeof arguments[i] == 'object' && !arguments[i].appendChild) {
- for(attr in arguments[i]) {
- if(attr == 'style' || attr.match(/^data-/))
- el.setAttribute(attr, arguments[i][attr]);
- else
- el[ attr == 'class' ? 'className' : attr == 'for' ? 'htmlFor' : attr ] = arguments[i][attr];
- }
- } else
- el.appendChild(tag(arguments[i]));
- }
- return el;
-};
-
-
-window.addBody = function(el) {
- if(document.body.appendChild)
- document.body.appendChild(el);
- else if(document.documentElement.appendChild)
- document.documentElement.appendChild(el);
- else if(document.appendChild)
- document.appendChild(el);
-};
-
-window.setContent = function() {
- setText(arguments[0], '');
- for(var i=1; i<arguments.length; i++)
- if(arguments[i] != null)
- arguments[0].appendChild(tag(arguments[i]));
-};
-
-window.getText = function(obj) {
- return obj.textContent || obj.innerText || '';
-};
-
-window.setText = function(obj, txt) {
- if(obj.textContent != null)
- obj.textContent = txt;
- else
- obj.innerText = txt;
-};
-
-
-window.listClass = function(obj) {
- var n = obj.className;
- if(!n)
- return [];
- return n.split(/ /);
-};
-
-window.hasClass = function(obj, c) {
- var l = listClass(obj);
- for(var i=0; i<l.length; i++)
- if(l[i] == c)
- return true;
- return false;
-};
-
-window.setClass = function(obj, c, set) {
- var l = listClass(obj);
- var n = [];
- if(set) {
- n = l;
- if(!hasClass(obj, c))
- n[n.length] = c;
- } else {
- for(var i=0; i<l.length; i++)
- if(l[i] != c)
- n[n.length] = l[i];
- }
- obj.className = n.join(' ');
-};
-
-window.onSubmit = function(form, handler) {
- var prev_handler = form.onsubmit;
- form.onsubmit = function(e) {
- if(prev_handler)
- if(!prev_handler(e))
- return false;
- return handler(e);
- }
-};
-
-
-window.shorten = function(v, l) {
- return v.length > l ? v.substr(0, l-3)+'...' : v;
-};
-
-
-window.fmtspoil = function(s) {
- return ['neutral', 'no spoiler', 'minor spoiler', 'major spoiler'][s+1];
-}
-
-
-window.jsonParse = function(s) {
- return s ? JSON.parse(s) : '';
-};
diff --git a/data/js/main.js b/data/js/main.js
deleted file mode 100644
index 8f3f6ca2..00000000
--- a/data/js/main.js
+++ /dev/null
@@ -1,39 +0,0 @@
-// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0-only
-// @source: https://code.blicky.net/yorhel/vndb/src/branch/master/data/js
-// SPDX-License-Identifier: AGPL-3.0-only
-
-
-/* This is the main Javascript file. This file is processed by util/jsgen.pl to
- * generate the final JS file(s) used by the site. */
-
-// Variables from jsgen.pl
-VARS = /*VARS*/;
-
-// Relic of the past
-VARS.resolutions = [
- ["unknown","Unknown / console / handheld"],
- ["nonstandard","Non-standard"],
- ["4:3",["640x480","640x480"],["800x600","800x600"],["1024x768","1024x768"],["1280x960","1280x960"],["1600x1200","1600x1200"]],
- ["widescreen",["640x400","640x400"],["960x600","960x600"],["960x640","960x640"],["1024x576","1024x576"],["1024x600","1024x600"],["1024x640","1024x640"],["1280x720","1280x720"],["1280x800","1280x800"],["1366x768","1366x768"],["1600x900","1600x900"],["1920x1080","1920x1080"]]
-];
-
-/* The include directives below automatically wrap the file contents inside an
- * anonymous function, so each file has its own local namespace. Included files
- * can't access variables or functions from other files, unless these variables
- * are explicitely shared in DOM objects or (more commonly) the global 'window'
- * object.
- */
-
-// Reusable library functions
-//include lib.js
-
-// Reusable widgets
-//include dropdown.js
-//include dateselector.js
-//include dropdownsearch.js
-
-// Page/functionality-specific widgets
-//include filter.js
-//include misc.js
-
-// @license-end
diff --git a/data/js/misc.js b/data/js/misc.js
deleted file mode 100644
index 9658bccf..00000000
--- a/data/js/misc.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// search tabs
-(function(){
- function click() {
- var str = byId('q').value;
- if(str.length > 1) {
- this.href = this.href.split('?')[0];
- if(this.href.indexOf('/g') >= 0 || this.href.indexOf('/i') >= 0)
- this.href += '/list';
- this.href += '?q=' + encodeURIComponent(str);
- }
- return true;
- };
- if(byId('searchtabs')) {
- var l = byName(byId('searchtabs'), 'a');
- for(var i=0; i<l.length; i++)
- l[i].onclick = click;
- }
-})();
-
-
-// spam protection on all forms
-setTimeout(function() {
- for(var i=1; i<document.forms.length; i++)
- document.forms[i].action = document.forms[i].action.replace(/\/nospam\?/,'');
-}, 500);
diff --git a/lib/VNDB/DB/Chars.pm b/lib/VNDB/DB/Chars.pm
deleted file mode 100644
index 0b159452..00000000
--- a/lib/VNDB/DB/Chars.pm
+++ /dev/null
@@ -1,150 +0,0 @@
-
-package VNDB::DB::Chars;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|dbCharFilters dbCharGet|;
-
-
-# Character filters shared by dbCharGet and dbVNGet
-sub dbCharFilters {
- my($self, %o) = @_;
- return (
- defined $o{gender} ? ( 'c.gender IN(!l)' => [ ref $o{gender} ? $o{gender} : [$o{gender}] ]) : (),
- defined $o{bloodt} ? ( 'c.bloodt IN(!l)' => [ ref $o{bloodt} ? $o{bloodt} : [$o{bloodt}] ]) : (),
- defined $o{bust_min} ? ( 'c.s_bust >= ?' => $o{bust_min} ) : (),
- defined $o{bust_max} ? ( 'c.s_bust <= ? AND c.s_bust > 0' => $o{bust_max} ) : (),
- defined $o{waist_min} ? ( 'c.s_waist >= ?' => $o{waist_min} ) : (),
- defined $o{waist_max} ? ( 'c.s_waist <= ? AND c.s_waist > 0' => $o{waist_max} ) : (),
- defined $o{hip_min} ? ( 'c.s_hip >= ?' => $o{hip_min} ) : (),
- defined $o{hip_max} ? ( 'c.s_hip <= ? AND c.s_hip > 0' => $o{hip_max} ) : (),
- defined $o{height_min} ? ( 'c.height >= ?' => $o{height_min} ) : (),
- defined $o{height_max} ? ( 'c.height <= ? AND c.height > 0' => $o{height_max} ) : (),
- defined $o{weight_min} ? ( 'c.weight >= ?' => $o{weight_min} ) : (),
- defined $o{weight_max} ? ( 'c.weight <= ?' => $o{weight_max} ) : (),
- defined $o{cup_min} ? ( 'c.cup_size >= ?' => $o{cup_min} ) : (),
- defined $o{cup_max} ? ( 'c.cup_size <= ?' => $o{cup_max} ) : (),
- $o{role} ? (
- 'EXISTS(SELECT 1 FROM chars_vns cvi WHERE cvi.id = c.id AND cvi.role IN(!l))',
- [ ref $o{role} ? $o{role} : [$o{role}] ] ) : (),
- $o{trait_inc} ? (
- 'c.id IN(SELECT cid FROM traits_chars WHERE tid IN(!l) AND spoil <= ? GROUP BY cid HAVING COUNT(tid) = ?)',
- [ ref $o{trait_inc} ? $o{trait_inc} : [$o{trait_inc}], $o{tagspoil}, ref $o{trait_inc} ? $#{$o{trait_inc}}+1 : 1 ]) : (),
- $o{trait_exc} ? (
- 'c.id NOT IN(SELECT cid FROM traits_chars WHERE tid IN(!l))' => [ ref $o{trait_exc} ? $o{trait_exc} : [$o{trait_exc}] ] ) : (),
- $o{va_inc} ? ( 'c.id IN(SELECT ivs.cid FROM vn_seiyuu ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN(!l))' => [ ref $o{va_inc} ? $o{va_inc} : [$o{va_inc}] ] ) : (),
- $o{va_exc} ? ( 'c.id NOT IN(SELECT ivs.cid FROM vn_seiyuu ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN(!l))' => [ ref $o{va_exc} ? $o{va_exc} : [$o{va_exc}] ] ) : (),
- )
-}
-
-
-# options: id instance tagspoil trait_inc trait_exc char what results page gender bloodt
-# bust_min bust_max waist_min waist_max hip_min hip_max height_min height_max weight_min weight_max role
-# what: extended traits vns changes
-sub dbCharGet {
- my $self = shift;
- my %o = (
- page => 1,
- results => 10,
- what => '',
- tagspoil => 0,
- @_
- );
-
- $o{search} =~ s/%//g if $o{search};
-
- my %where = (
- !$o{id} ? ( 'c.hidden = FALSE' => 1 ) : (),
- $o{id} ? ( 'c.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (),
- $o{notid} ? ( 'c.id <> ?' => $o{notid} ) : (),
- $o{instance} ? ( 'c.main = ?' => $o{instance} ) : (),
- $o{vid} ? ( 'c.id IN(SELECT id FROM chars_vns WHERE vid = ?)' => $o{vid} ) : (),
- $o{search} ? (
- "(c.name ILIKE ? OR translate(c.original,' ','') ILIKE translate(?,' ','') OR c.alias ILIKE ?)", [ map '%'.$o{search}.'%', 1..3 ] ) : (),
- $o{char} ? (
- 'LOWER(SUBSTR(c.name, 1, 1)) = ?' => $o{char} ) : (),
- defined $o{char} && !$o{char} ? (
- '(ASCII(c.name) < 97 OR ASCII(c.name) > 122) AND (ASCII(c.name) < 65 OR ASCII(c.name) > 90)' => 1 ) : (),
- $self->dbCharFilters(%o),
- );
-
- my @select = (qw|c.id c.name c.original c.gender|);
- push @select, qw|c.hidden c.locked c.alias c.desc c.b_month c.b_day c.s_bust c.s_waist c.s_hip c.height c.weight c.bloodt c.cup_size c.age c.main c.main_spoil|,
- 'coalesce(vndbid_num(c.image),0) AS image' if $o{what} =~ /extended/;
-
- my($r, $np) = $self->dbPage(\%o, q|
- SELECT !s
- FROM chars c
- !W
- ORDER BY c.name|,
- join(', ', @select), \%where
- );
-
- return _enrich($self, $r, $np, 0, $o{what}, $o{vid});
-}
-
-
-sub _enrich {
- my($self, $r, $np, $rev, $what, $vid) = @_;
-
- if(@$r && $what =~ /vns|traits/) {
- my($col, $hist, $colname) = $rev ? ('cid', '_hist', 'chid') : ('id', '', 'id');
- my %r = map {
- $_->{traits} = [];
- $_->{vns} = [];
- ($_->{$col}, $_)
- } @$r;
-
- if($what =~ /traits/) {
- push @{$r{ delete $_->{xid} }{traits}}, $_ for (@{$self->dbAll(qq|
- SELECT ct.$colname AS xid, ct.tid, ct.spoil, t.name, t.sexual, t."group", tg.name AS groupname
- FROM chars_traits$hist ct
- JOIN traits t ON t.id = ct.tid
- JOIN traits tg ON tg.id = t."group"
- WHERE ct.$colname IN(!l)
- ORDER BY tg."order", t.name|, [ keys %r ]
- )});
- }
-
- if($what =~ /vns(?:\((\d+)\))?/) {
- push @{$r{ delete $_->{xid} }{vns}}, $_ for (@{$self->dbAll("
- SELECT cv.$colname AS xid, cv.vid, cv.rid, cv.spoil, cv.role, v.title AS vntitle, r.title AS rtitle
- FROM chars_vns$hist cv
- JOIN vn v ON cv.vid = v.id
- LEFT JOIN releases r ON cv.rid = r.id
- !W
- ORDER BY v.c_released",
- { "cv.$colname IN(!l)" => [[keys %r]], $1 ? ('cv.vid = ?', $1) : () }
- )});
- }
- }
-
- # Depends on the VN revision rather than char revision
- if(@$r && $what =~ /seiyuu/) {
- my %r = map {
- $_->{seiyuu} = [];
- ($_->{id}, $_)
- } @$r;
-
- push @{$r{ delete $_->{cid} }{seiyuu}}, $_ for (@{$self->dbAll(q|
- SELECT vs.cid, s.id AS sid, sa.name, sa.original, vs.note, v.id AS vid, v.title AS vntitle
- FROM vn_seiyuu vs
- JOIN staff_alias sa ON sa.aid = vs.aid
- JOIN staff s ON s.id = sa.id
- JOIN vn v ON v.id = vs.id
- !W
- ORDER BY v.c_released, sa.name|, {
- 's.hidden = FALSE' => 1,
- 'vs.cid IN(!l)' => [[ keys %r ]],
- $vid ? ('v.id = ?' => $vid) : (),
- }
- )});
- }
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-1;
diff --git a/lib/VNDB/DB/Producers.pm b/lib/VNDB/DB/Producers.pm
deleted file mode 100644
index c9d4f95f..00000000
--- a/lib/VNDB/DB/Producers.pm
+++ /dev/null
@@ -1,108 +0,0 @@
-
-package VNDB::DB::Producers;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|dbProducerGet dbProducerGetRev|;
-
-
-# options: results, page, id, search, char, sort, inc_hidden
-# what: extended relations
-sub dbProducerGet {
- my $self = shift;
- my %o = (
- results => 10,
- page => 1,
- what => '',
- @_
- );
-
- $o{search} =~ s/%//g if $o{search};
-
- my %where = (
- !$o{id} && !$o{inc_hidden} ? (
- 'p.hidden = FALSE' => 1 ) : (),
- $o{id} ? (
- 'p.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (),
- $o{search} ? (
- '(p.name ILIKE ? OR p.original ILIKE ? OR p.alias ILIKE ?)', [ map '%'.$o{search}.'%', 1..3 ] ) : (),
- $o{char} ? (
- 'LOWER(SUBSTR(p.name, 1, 1)) = ?' => $o{char} ) : (),
- defined $o{char} && !$o{char} ? (
- '(ASCII(p.name) < 97 OR ASCII(p.name) > 122) AND (ASCII(p.name) < 65 OR ASCII(p.name) > 90)' => 1 ) : (),
- );
-
- my $select = 'p.id, p.type, p.name, p.original, p.lang';
- $select .= ', p.desc, p.alias, p.website, p.l_wp, p.l_wikidata, p.hidden, p.locked' if $o{what} =~ /extended/;
-
- my($order, @order) = ('p.name');
- if($o{sort} && $o{sort} eq 'search') {
- $order = 'least(substr_score(p.name, ?), substr_score(p.original, ?)), p.name';
- @order = ($o{search}) x 2;
- }
-
- my($r, $np) = $self->dbPage(\%o, qq|
- SELECT !s
- FROM producers p
- !W
- ORDER BY $order|,
- $select, \%where, @order
- );
-
- return _enrich($self, $r, $np, 0, $o{what});
-}
-
-
-# options: id, rev, what
-# what: extended relations
-sub dbProducerGetRev {
- my $self = shift;
- my %o = (what => '', @_);
-
- $o{rev} ||= $self->dbRow('SELECT MAX(rev) AS rev FROM changes WHERE type = \'p\' AND itemid = ?', $o{id})->{rev};
-
- my $select = 'c.itemid AS id, p.type, p.name, p.original, p.lang';
- $select .= ', extract(\'epoch\' from c.added) as added, c.comments, c.rev, c.ihid, c.ilock, '.VNWeb::DB::sql_user();
- $select .= ', c.id AS cid, NOT EXISTS(SELECT 1 FROM changes c2 WHERE c2.type = c.type AND c2.itemid = c.itemid AND c2.rev = c.rev+1) AS lastrev';
- $select .= ', p.desc, p.alias, p.website, p.l_wp, p.l_wikidata, po.hidden, po.locked' if $o{what} =~ /extended/;
-
- my $r = $self->dbAll(q|
- SELECT !s
- FROM changes c
- JOIN producers po ON po.id = c.itemid
- JOIN producers_hist p ON p.chid = c.id
- JOIN users u ON u.id = c.requester
- WHERE c.type = 'p' AND c.itemid = ? AND c.rev = ?|,
- $select, $o{id}, $o{rev}
- );
-
- return _enrich($self, $r, 0, 1, $o{what});
-}
-
-
-sub _enrich {
- my($self, $r, $np, $rev, $what) = @_;
-
- if(@$r && $what =~ /relations/) {
- my($col, $hist, $colname) = $rev ? ('cid', '_hist', 'chid') : ('id', '', 'id');
- my %r = map {
- $r->[$_]{relations} = [];
- ($r->[$_]{$col}, $_)
- } 0..$#$r;
-
- push @{$r->[$r{$_->{xid}}]{relations}}, $_ for(@{$self->dbAll(qq|
- SELECT rel.$colname AS xid, rel.pid AS id, rel.relation, p.name, p.original
- FROM producers_relations$hist rel
- JOIN producers p ON rel.pid = p.id
- WHERE rel.$colname IN(!l)|,
- [ keys %r ]
- )});
- }
-
- return wantarray ? ($r, $np) : $r;
-}
-
-1;
-
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
deleted file mode 100644
index 2bfe8032..00000000
--- a/lib/VNDB/DB/Releases.pm
+++ /dev/null
@@ -1,224 +0,0 @@
-
-package VNDB::DB::Releases;
-
-use strict;
-use warnings;
-use POSIX 'strftime';
-use Exporter 'import';
-use VNDB::Func 'gtintype';
-
-our @EXPORT = qw|dbReleaseFilters dbReleaseGet dbReleaseGetRev dbReleaseEngines|;
-
-
-# Release filters shared by dbReleaseGet and dbVNGet
-sub dbReleaseFilters {
- my($self, %o) = @_;
- $o{plat} = [ $o{plat} ] if $o{plat} && !ref $o{plat};
- $o{med} = [ $o{med} ] if $o{med} && !ref $o{med};
- return (
- defined $o{patch} ? ( 'r.patch = ?' => $o{patch} == 1 ? 1 : 0) : (),
- defined $o{freeware} ? ( 'r.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (),
- defined $o{uncensored} ? ( 'r.uncensored = ?' => $o{uncensored} == 1 ? 1 : 0) : (),
- defined $o{type} ? ( 'r.type = ?' => $o{type} ) : (),
- defined $o{date_before} ? ( 'r.released <= ?' => $o{date_before} ) : (),
- defined $o{date_after} ? ( 'r.released >= ?' => $o{date_after} ) : (),
- defined $o{minage} ? ( 'r.minage IN(!l)' => [ ref $o{minage} ? $o{minage} : [$o{minage}] ] ) : (),
- defined $o{doujin} ? ( 'NOT r.patch AND r.doujin = ?' => $o{doujin} == 1 ? 1 : 0) : (),
- defined $o{resolution} ? ( 'NOT r.patch AND ARRAY[r.reso_x, r.reso_y] IN(!l)' =>
- [[ map $_ eq 'unknown' ? '{0,0}' : $_ eq 'nonstandard' ? '{0,1}' : '{'.(s/x/,/r).'}',
- ref $o{resolution} ? $o{resolution}->@* : $o{resolution} ]] ) : (),
- defined $o{voiced} ? ( 'NOT r.patch AND r.voiced IN(!l)' => [ ref $o{voiced} ? $o{voiced} : [$o{voiced}] ] ) : (),
- defined $o{ani_story} ? ( 'NOT r.patch AND r.ani_story IN(!l)' => [ ref $o{ani_story} ? $o{ani_story} : [$o{ani_story}] ] ) : (),
- defined $o{ani_ero} ? ( 'NOT r.patch AND r.ani_ero IN(!l)' => [ ref $o{ani_ero} ? $o{ani_ero} : [$o{ani_ero}] ] ) : (),
- defined $o{engine} ? ( 'r.engine = ?' => $o{engine} ) : (),
- defined $o{released} ? ( 'r.released !s ?' => [ $o{released} ? '<=' : '>', strftime('%Y%m%d', gmtime) ] ) : (),
- $o{lang} ? (
- 'r.id IN(SELECT irl.id FROM releases_lang irl WHERE irl.lang IN(!l))' => [ ref $o{lang} ? $o{lang} : [ $o{lang} ] ] ) : (),
- $o{olang} ? (
- 'r.id IN(SELECT irv.id FROM releases_vn irv JOIN vn v ON irv.vid = v.id WHERE v.c_olang && ARRAY[!l]::language[])' => [ ref $o{olang} ? $o{olang} : [ $o{olang} ] ] ) : (),
- $o{plat} ? ('('.join(' OR ',
- grep(/^unk$/, @{$o{plat}}) ? 'NOT EXISTS(SELECT 1 FROM releases_platforms irp WHERE irp.id = r.id)' : (),
- grep(!/^unk$/, @{$o{plat}}) ? 'r.id IN(SELECT irp.id FROM releases_platforms irp WHERE irp.platform IN(!l))' : (),
- ).')', [ [ grep !/^unk$/, @{$o{plat}} ] ]) : (),
- $o{med} ? ('('.join(' OR ',
- grep(/^unk$/, @{$o{med}}) ? 'NOT EXISTS(SELECT 1 FROM releases_media irm WHERE irm.id = r.id)' : (),
- grep(!/^unk$/, @{$o{med}}) ? 'r.id IN(SELECT irm.id FROM releases_media irm WHERE irm.medium IN(!l))' : ()
- ).')', [ [ grep(!/^unk$/, @{$o{med}}) ] ]) : (),
- $o{prod_inc} ? ('r.id IN(SELECT irp.id FROM releases_producers irp WHERE irp.pid IN(!l))' => [ ref $o{prod_inc} ? $o{prod_inc} : [$o{prod_inc}] ]) : (),
- $o{prod_exc} ? ('r.id NOT IN(SELECT irp.id FROM releases_producers irp WHERE irp.pid IN(!l))' => [ ref $o{prod_exc} ? $o{prod_exc} : [$o{prod_exc}] ]) : (),
- );
-}
-
-
-# Options: id vid pid released page results what med sort reverse date_before date_after
-# plat prod_inc prod_exc lang olang type minage search resolution freeware doujin voiced uncensored ani_story ani_ero hidden_only
-# What: extended vn producers platforms media
-# Sort: title released minage
-sub dbReleaseGet {
- my($self, %o) = @_;
- $o{results} ||= 50;
- $o{page} ||= 1;
- $o{what} ||= '';
-
- my @where = (
- !$o{id} && !$o{hidden_only} ? ( 'r.hidden = FALSE' => 0 ) : (),
- $o{hidden_only} ? ('r.hidden = TRUE' => 1) : (),
- $o{id} ? ( 'r.id = ?' => $o{id} ) : (),
- $o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (),
- $o{vid} ? ( 'r.id IN(SELECT id FROM releases_vn WHERE vid IN(!l))' => [ ref $o{vid} ? $o{vid} : [$o{vid}] ] ) : (),
- $self->dbReleaseFilters(%o),
- );
-
- if($o{search}) {
- for (split /[ -,._]/, $o{search}) {
- s/%//g;
- if(/^\d+$/ && gtintype($_)) {
- push @where, 'r.gtin = ?', $_;
- } elsif(length($_) > 0) {
- $_ = "%$_%";
- push @where, '(r.title ILIKE ? OR r.original ILIKE ? OR r.catalog = ?)',
- [ $_, $_, $_ ];
- }
- }
- }
-
- my @join = (
- $o{pid} ? 'JOIN releases_producers rp ON rp.id = r.id' : (),
- );
-
- my @select = (
- qw|r.id r.title r.original r.website r.released r.minage r.type r.patch|,
- $o{what} =~ /extended/ ? qw|
- r.notes r.catalog r.gtin r.reso_x r.reso_y r.voiced r.freeware r.doujin r.uncensored r.ani_story r.ani_ero r.engine r.hidden r.locked
- | : (),
- $o{pid} ? ('rp.developer', 'rp.publisher') : (),
- $o{what} =~ /links/ ? qw|
- r.gtin r.l_steam r.l_gog r.l_gyutto r.l_digiket r.l_melon r.l_getchu r.l_getchudl r.l_dmm r.l_itch r.l_jastusa r.l_egs r.l_erotrail r.l_mg r.l_denpa r.l_jlist r.l_dlsite r.l_dlsiteen r.l_melonjp r.l_toranoana r.l_gamejolt r.l_nutaku
- | : ()
- );
-
- my $order = sprintf {
- title => 'r.title %s, r.released %1$s',
- type => 'r.patch %s, r.type %1$s, r.released %1$s, r.title %1$s',
- publication => 'r.doujin %s, r.freeware %1$s, r.patch %1$s, r.released %1$s, r.title %1$s',
- resolution => 'r.reso_x %s, r.reso_y %1$s, r.patch %2$s, r.released %1$s, r.title %1$s',
- voiced => 'r.voiced %s, r.patch %2$s, r.released %1$s, r.title %1$s',
- ani_ero => 'r.ani_story %s, r.ani_ero %1$s, r.patch %2$s, r.released %1$s, r.title %1$s',
- released => 'r.released %s, r.id %1$s',
- minage => 'r.minage %s, r.released %1$s, r.title %1$s',
- notes => 'r.notes %s, r.released %1$s, r.title %1$s',
- }->{ $o{sort}||'released' }, $o{reverse} ? 'DESC' : 'ASC', !$o{reverse} ? 'DESC' : 'ASC';
-
- my($r, $np) = $self->dbPage(\%o, q|
- SELECT !s
- FROM releases r
- !s
- !W
- ORDER BY !s|,
- join(', ', @select), join(' ', @join), \@where, $order
- );
-
- return _enrich($self, $r, $np, 0, $o{what});
-}
-
-
-# options: id, rev, what
-# what: extended vn producers platforms media
-sub dbReleaseGetRev {
- my $self = shift;
- my %o = (what => '', @_);
-
- $o{rev} ||= $self->dbRow('SELECT MAX(rev) AS rev FROM changes WHERE type = \'r\' AND itemid = ?', $o{id})->{rev};
-
- my $select = 'c.itemid AS id, r.title, r.original, r.website, r.released, r.minage, r.type, r.patch';
- $select .= ', r.notes, r.catalog, r.gtin, r.reso_x, r.reso_y, r.voiced, r.freeware, r.doujin, r.uncensored, r.ani_story, r.ani_ero, r.engine, ro.hidden, ro.locked' if $o{what} =~ /extended/;
- $select .= ', extract(\'epoch\' from c.added) as added, c.comments, c.rev, c.ihid, c.ilock, '.VNWeb::DB::sql_user();
- $select .= ', c.id AS cid, NOT EXISTS(SELECT 1 FROM changes c2 WHERE c2.type = c.type AND c2.itemid = c.itemid AND c2.rev = c.rev+1) AS lastrev';
- $select .= ', r.gtin, r.l_steam, r.l_gog, r.l_gyutto, r.l_digiket, r.l_melon, r.l_getchu, r.l_getchudl, r.l_dmm, r.l_itch, r.l_jastusa, r.l_egs, r.l_erotrail, r.l_mg, r.l_denpa, r.l_jlist, r.l_dlsite, r.l_dlsiteen, r.l_melonjp, r.l_toranoana, r.l_gamejolt, r.l_nutaku' if $o{what} =~ /links/;
-
- my $r = $self->dbAll(q|
- SELECT !s
- FROM changes c
- JOIN releases ro ON ro.id = c.itemid
- JOIN releases_hist r ON r.chid = c.id
- JOIN users u ON u.id = c.requester
- WHERE c.type = 'r' AND c.itemid = ? AND c.rev = ?|,
- $select, $o{id}, $o{rev}
- );
-
- return _enrich($self, $r, 0, 1, $o{what});
-}
-
-
-sub _enrich {
- my($self, $r, $np, $rev, $what) = @_;
-
- if(@$r) {
- my($col, $hist, $colname) = $rev ? ('cid', '_hist', 'chid') : ('id', '', 'id');
- my %r = map {
- $r->[$_]{producers} = [];
- $r->[$_]{platforms} = [];
- $r->[$_]{media} = [];
- $r->[$_]{vn} = [];
- $r->[$_]{languages} = [];
- ($r->[$_]{$col}, $_)
- } 0..$#$r;
-
- push(@{$r->[$r{$_->{xid}}]{languages}}, $_->{lang}) for (@{$self->dbAll("
- SELECT $colname AS xid, lang
- FROM releases_lang$hist
- WHERE $colname IN(!l)",
- [ keys %r ]
- )});
-
- if($what =~ /vn/) {
- push(@{$r->[$r{$_->{xid}}]{vn}}, $_) for (@{$self->dbAll("
- SELECT rv.$colname AS xid, v.id AS vid, v.title, v.original
- FROM releases_vn$hist rv
- JOIN vn v ON v.id = rv.vid
- WHERE rv.$colname IN(!l)
- ORDER BY v.title",
- [ keys %r ]
- )});
- }
-
- if($what =~ /producers/) {
- push(@{$r->[$r{$_->{xid}}]{producers}}, $_) for (@{$self->dbAll("
- SELECT rp.$colname AS xid, rp.developer, rp.publisher, p.id, p.name, p.original, p.type
- FROM releases_producers$hist rp
- JOIN producers p ON rp.pid = p.id
- WHERE rp.$colname IN(!l)
- ORDER BY p.name",
- [ keys %r ]
- )});
- }
-
- if($what =~ /platforms/) {
- push(@{$r->[$r{$_->{xid}}]{platforms}}, $_->{platform}) for (@{$self->dbAll("
- SELECT $colname AS xid, platform
- FROM releases_platforms$hist
- WHERE $colname IN(!l)",
- [ keys %r ]
- )});
- }
-
- if($what =~ /media/) {
- push(@{$r->[$r{$_->{xid}}]{media}}, $_) for (@{$self->dbAll("
- SELECT $colname AS xid, medium, qty
- FROM releases_media$hist
- WHERE $colname IN(!l)",
- [ keys %r ]
- )});
- }
- }
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-sub dbReleaseEngines {
- shift->dbAll(q{SELECT engine, count(*) as cnt FROM releases WHERE engine <> '' GROUP BY engine ORDER BY COUNT(*) desc, engine});
-}
-
-1;
-
diff --git a/lib/VNDB/DB/Staff.pm b/lib/VNDB/DB/Staff.pm
deleted file mode 100644
index 5a393dbb..00000000
--- a/lib/VNDB/DB/Staff.pm
+++ /dev/null
@@ -1,79 +0,0 @@
-
-package VNDB::DB::Staff;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|dbStaffGet |;
-
-# options: results, page, id, aid, search, exact, truename, role, gender
-sub dbStaffGet {
- my $self = shift;
- my %o = (
- results => 10,
- page => 1,
- what => '',
- @_
- );
- my(@roles, $seiyuu);
- if(defined $o{role}) {
- if(ref $o{role}) {
- $seiyuu = grep /^seiyuu$/, @{$o{role}};
- @roles = grep !/^seiyuu$/, @{$o{role}};
- } else {
- $seiyuu = $o{role} eq 'seiyuu';
- @roles = $o{role} unless $seiyuu;
- }
- }
-
- $o{search} =~ s/%//g if $o{search};
-
- my %where = (
- !$o{id} ? ( 's.hidden = FALSE' => 1 ) : (),
- $o{id} ? ( ref $o{id} ? ('s.id IN(!l)' => [$o{id}]) : ('s.id = ?' => $o{id}) ) : (),
- $o{aid} ? ( ref $o{aid} ? ('sa.aid IN(!l)' => [$o{aid}]) : ('sa.aid = ?' => $o{aid}) ) : (),
- $o{id} || $o{truename} ? ( 's.aid = sa.aid' => 1 ) : (),
- defined $o{gender} ? ( 's.gender IN(!l)' => [ ref $o{gender} ? $o{gender} : [$o{gender}] ]) : (),
- defined $o{lang} ? ( 's.lang IN(!l)' => [ ref $o{lang} ? $o{lang} : [$o{lang}] ]) : (),
- defined $o{role} ? (
- '('.join(' OR ',
- @roles ? ( 'EXISTS(SELECT 1 FROM vn_staff vs JOIN vn v ON v.id = vs.id WHERE vs.aid = sa.aid AND vs.role IN(!l) AND NOT v.hidden)' ) : (),
- $seiyuu ? ( 'EXISTS(SELECT 1 FROM vn_seiyuu vsy JOIN vn v ON v.id = vsy.id WHERE vsy.aid = sa.aid AND NOT v.hidden)' ) : ()
- ).')' => ( @roles ? [ \@roles ] : 1 ),
- ) : (),
- $o{exact} ? ( '(lower(sa.name) = lower(?) OR lower(sa.original) = lower(?))' => [ ($o{exact}) x 2 ] ) : (),
- $o{search} ?
- $o{search} =~ /[\x{3000}-\x{9fff}\x{ff00}-\x{ff9f}]/ ?
- # match against 'original' column only if search string contains any
- # japanese character.
- # note: more precise regex would be /[\p{Hiragana}\p{Katakana}\p{Han}]/
- ( q|(sa.original LIKE ? OR translate(sa.original,' ','') LIKE ?)| => [ '%'.$o{search}.'%', ($o{search} =~ s/\s+//gr).'%' ] ) :
- ( '(sa.name ILIKE ? OR sa.original ILIKE ?)' => [ map '%'.$o{search}.'%', 1..2 ] ) : (),
- $o{char} ? ( 'LOWER(SUBSTR(sa.name, 1, 1)) = ?' => $o{char} ) : (),
- defined $o{char} && !$o{char} ?
- ( '(ASCII(sa.name) < 97 OR ASCII(sa.name) > 122) AND (ASCII(sa.name) < 65 OR ASCII(sa.name) > 90)' => 1 ) : (),
- );
-
- my $select = 's.id, sa.aid, sa.name, sa.original, s.gender, s.lang';
-
- my($order, @order) = ('sa.name');
- if($o{sort} && $o{sort} eq 'search') {
- $order = 'least(substr_score(sa.name, ?), substr_score(sa.original, ?)), sa.name';
- @order = ($o{search}) x 2;
- }
-
- my($r, $np) = $self->dbPage(\%o, qq|
- SELECT !s
- FROM staff s
- JOIN staff_alias sa ON sa.id = s.id
- !W
- ORDER BY $order|,
- $select, \%where, @order
- );
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-1;
diff --git a/lib/VNDB/DB/Tags.pm b/lib/VNDB/DB/Tags.pm
deleted file mode 100644
index 1104bad8..00000000
--- a/lib/VNDB/DB/Tags.pm
+++ /dev/null
@@ -1,162 +0,0 @@
-
-package VNDB::DB::Tags;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|dbTagGet dbTTTree dbTagStats|;
-
-
-# %options->{ id noid name search state searchable applicable page results what sort reverse }
-# what: parents childs(n) aliases addedby
-# sort: id name added items search
-sub dbTagGet {
- my $self = shift;
- my %o = (
- page => 1,
- results => 10,
- what => '',
- @_
- );
-
- $o{search} =~ s/%//g if $o{search};
-
- my %where = (
- $o{id} ? (
- 't.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (),
- $o{noid} ? (
- 't.id <> ?' => $o{noid} ) : (),
- $o{name} ? (
- 't.id = (SELECT id FROM tags LEFT JOIN tags_aliases ON id = tag WHERE lower(name) = ? OR lower(alias) = ? LIMIT 1)' => [ lc $o{name}, lc $o{name} ]) : (),
- defined $o{state} && $o{state} != -1 ? (
- 't.state = ?' => $o{state} ) : (),
- !defined $o{state} && !$o{id} && !$o{name} ? (
- 't.state <> 1' => 1 ) : (),
- $o{search} ? (
- 't.id IN (SELECT id FROM tags LEFT JOIN tags_aliases ON id = tag WHERE name ILIKE ? OR alias ILIKE ?)' => [ "%$o{search}%", "%$o{search}%" ] ) : (),
- defined $o{searchable} ? ('t.searchable = ?' => $o{searchable}?1:0 ) : (),
- defined $o{applicable} ? ('t.applicable = ?' => $o{applicable}?1:0 ) : (),
- );
- my @select = (
- qw|t.id t.searchable t.applicable t.name t.description t.state t.cat t.c_items t.defaultspoil|,
- q|extract('epoch' from t.added) as added|,
- $o{what} =~ /addedby/ ? (VNWeb::DB::sql_user()) : (),
- );
- my @join = $o{what} =~ /addedby/ ? 'JOIN users u ON u.id = t.addedby' : ();
-
- my $order = sprintf {
- id => 't.id %s',
- name => 't.name %s',
- added => 't.added %s',
- items => 't.c_items %s',
- search=> 'substr_score(t.name, ?) ASC, t.name %s', # Assigning a matching score for aliases is also possible, but more involved
- }->{ $o{sort}||'id' }, $o{reverse} ? 'DESC' : 'ASC';
- my @order = $o{sort} && $o{sort} eq 'search' ? ($o{search}) : ();
-
-
- my($r, $np) = $self->dbPage(\%o, qq|
- SELECT !s
- FROM tags t
- !s
- !W
- ORDER BY $order|,
- join(', ', @select), join(' ', @join), \%where, @order
- );
-
- if(@$r && $o{what} =~ /aliases/) {
- my %r = map {
- $_->{aliases} = [];
- ($_->{id}, $_->{aliases})
- } @$r;
-
- push @{$r{$_->{tag}}}, $_->{alias} for (@{$self->dbAll(q|
- SELECT tag, alias FROM tags_aliases WHERE tag IN(!l)|, [ keys %r ]
- )});
- }
-
- if($o{what} =~ /parents\((\d+)\)/) {
- $_->{parents} = $self->dbTTTree(tag => $_->{id}, $1, 1) for(@$r);
- }
-
- if($o{what} =~ /childs\((\d+)\)/) {
- $_->{childs} = $self->dbTTTree(tag => $_->{id}, $1) for(@$r);
- }
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-# Walks the tag/trait tree
-# type = tag | trait
-# id = tag to start with, or 0 to start with top-level tags
-# lvl = max. recursion level
-# back = false for parent->child, true for child->parent
-# Returns: [ { id, name, c_items, sub => [ { id, name, c_items, sub => [..] }, .. ] }, .. ]
-sub dbTTTree {
- my($self, $type, $id, $lvl, $back) = @_;
- $lvl ||= 15;
- my $xtra = $type eq 'trait' ? ', "order"' : '';
- my $xtra2 = $type eq 'trait' ? ', t."order"' : '';
- my $r = $self->dbAll(qq|
- WITH RECURSIVE thetree(lvl, id, parent, name, c_items) AS (
- SELECT ?::integer, id, 0, name, c_items$xtra
- FROM ${type}s
- !W
- UNION ALL
- SELECT tt.lvl-1, t.id, tt.id, t.name, t.c_items$xtra2
- FROM thetree tt
- JOIN ${type}s_parents tp ON !s
- JOIN ${type}s t ON !s
- WHERE tt.lvl > 0
- AND t.state = 2
- ) SELECT DISTINCT id, parent, name, c_items$xtra FROM thetree ORDER BY name|, $lvl,
- $id ? {'id = ?' => $id} : {"NOT EXISTS(SELECT 1 FROM ${type}s_parents WHERE $type = id)" => 1, 'state = 2' => 1},
- !$back ? ('tp.parent = tt.id', "t.id = tp.$type") : ("tp.$type = tt.id", 't.id = tp.parent')
- );
-
- my %pars; # parent-id -> [ child-object, .. ]
- push @{$pars{$_->{parent}}}, $_ for(@$r);
- $_->{'sub'} = $pars{$_->{id}} || [] for(@$r);
- my @r = grep !delete($_->{parent}), @$r;
- return $id ? $r[0]{'sub'} : \@r;
-}
-
-
-# Fetch all tags related to a VN
-# Argument: %options->{ vid minrating state results what page sort reverse }
-# sort: name, rating
-sub dbTagStats {
- my($self, %o) = @_;
- $o{results} ||= 10;
- $o{page} ||= 1;
-
- my $rating = 'avg(CASE WHEN tv.ignore THEN NULL ELSE tv.vote END)';
- my $order = sprintf {
- name => 't.name %s',
- rating => "$rating %s",
- }->{ $o{sort}||'name' }, $o{reverse} ? 'DESC' : 'ASC';
-
- my %where = (
- 'tv.vid = ?' => $o{vid},
- defined $o{state} ? ('t.state = ?', $o{state}) : (),
- );
-
- my($r, $np) = $self->dbPage(\%o, qq|
- SELECT t.id, t.name, t.cat, count(*) as cnt, $rating as rating,
- COALESCE(avg(CASE WHEN tv.ignore THEN NULL ELSE tv.spoiler END), t.defaultspoil) as spoiler,
- bool_or(tv.ignore) AS overruled
- FROM tags t
- JOIN tags_vn tv ON tv.tag = t.id
- !W
- GROUP BY t.id, t.name, t.cat
- !s
- ORDER BY !s|,
- \%where, defined $o{minrating} ? "HAVING $rating > $o{minrating}" : '', $order
- );
-
- return wantarray ? ($r, $np) : $r;
-}
-
-1;
-
diff --git a/lib/VNDB/DB/Traits.pm b/lib/VNDB/DB/Traits.pm
deleted file mode 100644
index ac0e81b4..00000000
--- a/lib/VNDB/DB/Traits.pm
+++ /dev/null
@@ -1,86 +0,0 @@
-
-package VNDB::DB::Traits;
-
-# This module is for a large part a copy of VNDB::DB::Tags. I could have chosen
-# to modify that module to work for both traits and tags but that would have
-# complicated the code, so I chose to maintain two versions with similar
-# functionality instead.
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|dbTraitGet|;
-
-
-# Options: id noid search name state searchable applicable what results page sort reverse
-# what: parents childs(n) addedby
-# sort: id name name added items search
-sub dbTraitGet {
- my $self = shift;
- my %o = (
- page => 1,
- results => 10,
- what => '',
- @_,
- );
-
- $o{search} =~ s/%//g if $o{search};
-
- my %where = (
- $o{id} ? ( 't.id IN(!l)' => [ ref($o{id}) ? $o{id} : [$o{id}] ]) : (),
- $o{group} ? ( 't.group = ?' => $o{group} ) : (),
- $o{noid} ? ( 't.id <> ?' => $o{noid} ) : (),
- defined $o{state} && $o{state} != -1 ? (
- 't.state = ?' => $o{state} ) : (),
- !defined $o{state} && !$o{id} && !$o{name} ? (
- 't.state = 2' => 1 ) : (),
- $o{search} ? (
- '(t.name ILIKE ? OR t.alias ILIKE ?)' => [ "%$o{search}%", "%$o{search}%" ] ) : (),
- $o{name} ? ( # TODO: This is terribly ugly, use an aliases table.
- q{(LOWER(t.name) = LOWER(?) OR t.alias ~ ('(!sin)^'||?||'$'))} => [ $o{name}, '?', quotemeta $o{name} ] ) : (),
- defined $o{applicable} ? ('t.applicable = ?' => $o{applicable}?1:0 ) : (),
- defined $o{searchable} ? ('t.searchable = ?' => $o{searchable}?1:0 ) : (),
- );
-
- my @select = (
- qw|t.id t.searchable t.applicable t.name t.description t.state t.alias t."group" t."order" t.sexual t.c_items t.defaultspoil|,
- 'tg.name AS groupname', 'tg."order" AS grouporder', q|extract('epoch' from t.added) as added|,
- $o{what} =~ /addedby/ ? (VNWeb::DB::sql_user()) : (),
- );
- my @join = $o{what} =~ /addedby/ ? 'JOIN users u ON u.id = t.addedby' : ();
- push @join, 'LEFT JOIN traits tg ON tg.id = t."group"';
-
- my $order = sprintf {
- id => 't.id %s',
- name => 't.name %s',
- group => 'tg."order" %s, t.name %1$s',
- added => 't.added %s',
- items => 't.c_items %s',
- search=> 'substr_score(t.name, ?) ASC, t.name %s', # Can't score aliases at the moment
- }->{ $o{sort}||'id' }, $o{reverse} ? 'DESC' : 'ASC';
- my @order = $o{sort} && $o{sort} eq 'search' ? ($o{search}) : ();
-
- my($r, $np) = $self->dbPage(\%o, qq|
- SELECT !s
- FROM traits t
- !s
- !W
- ORDER BY $order|,
- join(', ', @select), join(' ', @join), \%where, @order,
- );
-
- if($o{what} =~ /parents\((\d+)\)/) {
- $_->{parents} = $self->dbTTTree(trait => $_->{id}, $1, 1) for(@$r);
- }
-
- if($o{what} =~ /childs\((\d+)\)/) {
- $_->{childs} = $self->dbTTTree(trait => $_->{id}, $1) for(@$r);
- }
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-1;
-
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
deleted file mode 100644
index 85654180..00000000
--- a/lib/VNDB/DB/Users.pm
+++ /dev/null
@@ -1,49 +0,0 @@
-
-package VNDB::DB::Users;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw|
- dbUserGet
-|;
-
-
-# %options->{ uid results page what }
-# sort: username registered votes changes tags
-sub dbUserGet {
- my $s = shift;
- my %o = (
- page => 1,
- results => 10,
- what => '',
- @_
- );
-
- my %where = (
- $o{uid} && !ref($o{uid}) ? (
- 'id = ?' => $o{uid} ) : (),
- $o{uid} && ref($o{uid}) ? (
- 'id IN(!l)' => [ $o{uid} ]) : (),
- );
-
- my @select = (
- qw|id username c_votes c_changes c_tags|,
- VNWeb::DB::sql_user(), # XXX: This duplicates id and username, but updating all the code isn't going to be easy
- q|extract('epoch' from registered) as registered|,
- );
-
- my($r, $np) = $s->dbPage(\%o, q|
- SELECT !s
- FROM users u
- !W
- ORDER BY id DESC|,
- join(', ', @select), \%where
- );
-
- return wantarray ? ($r, $np) : $r;
-}
-
-1;
-
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
deleted file mode 100644
index 668b7fec..00000000
--- a/lib/VNDB/DB/VN.pm
+++ /dev/null
@@ -1,257 +0,0 @@
-
-package VNDB::DB::VN;
-
-use strict;
-use warnings;
-use v5.10;
-use TUWF 'sqlprint';
-use POSIX 'strftime';
-use Exporter 'import';
-use VNDB::Func 'normalize_query', 'gtintype';
-
-our @EXPORT = qw|dbVNGet dbVNGetRev|;
-
-
-# Options: id, char, search, gtin, length, lang, olang, plat, tag_inc, tag_exc, tagspoil,
-# hasani, hasshot, ul_notblack, ul_onwish, results, page, what, sort,
-# reverse, inc_hidden, date_before, date_after, released, release, character
-# What: extended anime staff seiyuu relations rating ranking vnlist
-# Note: vnlist is ignored (no db search) unless a user is logged in
-# Sort: id rel pop rating title tagscore rand
-sub dbVNGet {
- my($self, %o) = @_;
- $o{results} ||= 10;
- $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 $uid = $self->authInfo->{id};
-
- $o{gtin} = delete $o{search} if $o{search} && $o{search} =~ /^\d+$/ && gtintype(local $_ = $o{search});
-
- my @where = (
- $o{id} ? (
- 'v.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (),
- $o{char} ? (
- 'LOWER(SUBSTR(v.title, 1, 1)) = ?' => $o{char} ) : (),
- defined $o{char} && !$o{char} ? (
- '(ASCII(v.title) < 97 OR ASCII(v.title) > 122) AND (ASCII(v.title) < 65 OR ASCII(v.title) > 90)' => 1 ) : (),
- defined $o{length} ? (
- 'v.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} ? (
- 'v.c_platforms && ARRAY[!l]::platform[]' => [ ref $o{plat} ? $o{plat} : [$o{plat}] ]) : (),
- defined $o{hasani} ? (
- '!sEXISTS(SELECT 1 FROM vn_anime va WHERE va.id = v.id)' => [ $o{hasani} ? '' : 'NOT ' ]) : (),
- defined $o{hasshot} ? (
- '!sEXISTS(SELECT 1 FROM vn_screenshots vs WHERE vs.id = v.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) = ?)',
- [ 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})) : (),
- $o{gtin} ? (
- 'v.id IN(SELECT irv.vid FROM releases_vn irv JOIN releases ir ON ir.id = irv.id WHERE ir.gtin = ?)' => $o{gtin}) : (),
- $o{staff_inc} ? ( 'v.id IN(SELECT ivs.id FROM vn_staff ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN(!l))' => [ ref $o{staff_inc} ? $o{staff_inc} : [$o{staff_inc}] ] ) : (),
- $o{staff_exc} ? ( 'v.id NOT IN(SELECT ivs.id FROM vn_staff ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN(!l))' => [ ref $o{staff_exc} ? $o{staff_exc} : [$o{staff_exc}] ] ) : (),
- $uid && $o{ul_notblack} ? (
- 'v.id NOT IN(SELECT vid FROM ulist_vns_labels WHERE uid = ? AND lbl = 6)' => $uid ) : (),
- $uid && defined $o{ul_onwish} ? (
- 'v.id !s IN(SELECT vid FROM ulist_vns_labels WHERE uid = ? AND lbl = 5)' => [ $o{ul_onwish} ? '' : 'NOT', $uid ] ) : (),
- $uid && defined $o{ul_voted} ? (
- 'v.id !s IN(SELECT vid FROM ulist_vns_labels WHERE uid = ? AND lbl = 7)' => [ $o{ul_voted} ? '' : 'NOT', $uid ] ) : (),
- $uid && defined $o{ul_onlist} ? (
- 'v.id !s IN(SELECT vid FROM ulist_vns WHERE uid = ?)' => [ $o{ul_onlist} ? '' : 'NOT', $uid ] ) : (),
- !$o{id} && !$o{inc_hidden} ? (
- '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|tagspoil)$/, keys %o) ? (
- 'v.id IN(SELECT floor(random() * last_value)::integer FROM generate_series(1,20), (SELECT MAX(id) AS last_value FROM vn) s1 LIMIT 20)' ) : (),
- defined $o{date_before} ? ( 'v.c_released <= ?' => $o{date_before} ) : (),
- defined $o{date_after} ? ( 'v.c_released >= ?' => $o{date_after} ) : (),
- defined $o{released} ? ( 'v.c_released !s ?' => [ $o{released} ? '<=' : '>', strftime('%Y%m%d', gmtime) ] ) : (),
- );
-
- if($o{release}) {
- my($q, @p) = sqlprint
- 'v.id IN(SELECT rv.vid FROM releases r JOIN releases_vn rv ON rv.id = r.id !W)',
- [ 'NOT r.hidden' => 1, $self->dbReleaseFilters(%{$o{release}}), ];
- push @where, $q, \@p;
- }
- if($o{character}) {
- my($q, @p) = sqlprint
- 'v.id IN(SELECT cv.vid FROM chars c JOIN chars_vns cv ON cv.id = c.id !W)',
- [ 'NOT c.hidden' => 1, $self->dbCharFilters(%{$o{character}}) ];
- push @where, $q, \@p;
- }
-
- my @join = (
- $uid && $o{what} =~ /vnlist/ ? ("LEFT JOIN (
- SELECT irv.vid, COUNT(*) AS userlist_all,
- SUM(CASE WHEN irl.status = 2 THEN 1 ELSE 0 END) AS userlist_obtained
- FROM rlists irl
- JOIN releases_vn irv ON irv.id = irl.rid
- WHERE irl.uid = $uid
- GROUP BY irv.vid
- ) AS vnlist ON vnlist.vid = v.id") : (),
- );
-
- 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 and c_platforms
- qw|v.id v.locked v.hidden v.c_released v.c_languages::text[] v.c_olang::text[] v.c_platforms::text[] v.title v.original|,
- $o{what} =~ /extended/ ? (
- qw|v.alias v.length v.desc v.l_wp v.l_encubed v.l_renai v.l_wikidata|, 'coalesce(vndbid_num(v.image),0) as image' ) : (),
- $o{what} =~ /rating/ ? (qw|v.c_popularity v.c_rating v.c_votecount|) : (),
- $o{what} =~ /ranking/ ? (
- '(SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_popularity > COALESCE(v.c_popularity, 0.0)) AS p_ranking',
- '(SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_rating > COALESCE(v.c_rating, 0.0)) AS r_ranking',
- ) : (),
- $uid && $o{what} =~ /vnlist/ ? (qw|vnlist.userlist_all vnlist.userlist_obtained|) : (),
- # 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{tagspoil} GROUP BY tvh.vid) AS tagscore| : (),
- );
-
- no if $] >= 5.022, warnings => 'redundant';
- my $order = sprintf {
- id => 'v.id %s',
- rel => 'v.c_released %s, v.title ASC',
- pop => 'v.c_popularity %s NULLS LAST',
- rating => 'v.c_rating %s NULLS LAST',
- title => 'v.title %s',
- tagscore => 'tagscore %s, v.title ASC',
- rand => 'RANDOM()',
- }->{$o{sort}}, $o{reverse} ? 'DESC' : 'ASC';
-
- my($r, $np) = $self->dbPage(\%o, q|
- SELECT !s
- FROM vn v
- !s
- !W
- ORDER BY !s|,
- join(', ', @select), join(' ', @join), \@where, $order,
- );
-
- return _enrich($self, $r, $np, 0, $o{what});
-}
-
-
-sub dbVNGetRev {
- my $self = shift;
- my %o = (what => '', @_);
-
- $o{rev} ||= $self->dbRow('SELECT MAX(rev) AS rev FROM changes WHERE type = \'v\' AND itemid = ?', $o{id})->{rev};
-
- # XXX: Too much duplication with code in dbVNGet() here. Can we combine some code here?
- my $uid = $self->authInfo->{id};
-
- my $select = 'c.itemid AS id, vo.c_released, vo.c_languages::text[], vo.c_olang::text[], vo.c_platforms::text[], v.title, v.original';
- $select .= ', extract(\'epoch\' from c.added) as added, c.comments, c.rev, c.ihid, c.ilock, '.VNWeb::DB::sql_user();
- $select .= ', c.id AS cid, NOT EXISTS(SELECT 1 FROM changes c2 WHERE c2.type = c.type AND c2.itemid = c.itemid AND c2.rev = c.rev+1) AS lastrev';
- $select .= ', v.alias, coalesce(vndbid_num(v.image), 0) as image, v.length, v.desc, v.l_wp, v.l_encubed, v.l_renai, v.l_wikidata, vo.hidden, vo.locked' if $o{what} =~ /extended/;
- $select .= ', vo.c_popularity, vo.c_rating, vo.c_votecount' if $o{what} =~ /rating/;
- $select .= ', (SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_popularity > COALESCE(vo.c_popularity, 0.0)) AS p_ranking'
- .', (SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_rating > COALESCE(vo.c_rating, 0.0)) AS r_ranking' if $o{what} =~ /ranking/;
-
- my $r = $self->dbAll(q|
- SELECT !s
- FROM changes c
- JOIN vn vo ON vo.id = c.itemid
- JOIN vn_hist v ON v.chid = c.id
- JOIN users u ON u.id = c.requester
- WHERE c.type = 'v' AND c.itemid = ? AND c.rev = ?|,
- $select, $o{id}, $o{rev}
- );
-
- return _enrich($self, $r, 0, 1, $o{what});
-}
-
-
-sub _enrich {
- my($self, $r, $np, $rev, $what) = @_;
-
- if(@$r && $what =~ /anime|relations|staff|seiyuu/) {
- my($col, $hist, $colname) = $rev ? ('cid', '_hist', 'chid') : ('id', '', 'id');
- my %r = map {
- $r->[$_]{anime} = [];
- $r->[$_]{credits} = [];
- $r->[$_]{seiyuu} = [];
- $r->[$_]{relations} = [];
- ($r->[$_]{$col}, $_)
- } 0..$#$r;
-
- if($what =~ /staff/) {
- push(@{$r->[$r{ delete $_->{xid} }]{credits}}, $_) for (@{$self->dbAll("
- SELECT vs.$colname AS xid, s.id, vs.aid, sa.name, sa.original, s.gender, s.lang, vs.role, vs.note
- FROM vn_staff$hist vs
- JOIN staff_alias sa ON vs.aid = sa.aid
- JOIN staff s ON s.id = sa.id
- WHERE vs.$colname IN(!l)
- ORDER BY vs.role ASC, sa.name ASC",
- [ keys %r ]
- )});
- }
-
- if($what =~ /seiyuu/) {
- # The seiyuu query needs the VN id to get the VN<->Char spoiler level.
- # Obtaining this ID is different when using the hist table.
- my($vid, $join) = $rev ? ('h.itemid', 'JOIN changes h ON h.id = vs.chid') : ('vs.id', '');
- push(@{$r->[$r{ delete $_->{xid} }]{seiyuu}}, $_) for (@{$self->dbAll("
- SELECT vs.$colname AS xid, s.id, vs.aid, sa.name, sa.original, s.gender, s.lang, c.id AS cid, c.name AS cname, vs.note,
- (SELECT MAX(spoil) FROM chars_vns cv WHERE cv.vid = $vid AND cv.id = c.id) AS spoil
- FROM vn_seiyuu$hist vs
- JOIN staff_alias sa ON vs.aid = sa.aid
- JOIN staff s ON s.id = sa.id
- JOIN chars c ON c.id = vs.cid
- $join
- WHERE vs.$colname IN(!l)
- ORDER BY c.name",
- [ keys %r ]
- )});
- }
-
- if($what =~ /anime/) {
- push(@{$r->[$r{ delete $_->{xid} }]{anime}}, $_) for (@{$self->dbAll("
- SELECT va.$colname AS xid, a.id, a.year, a.ann_id, a.nfo_id, a.type, a.title_romaji, a.title_kanji, extract('epoch' from a.lastfetch) AS lastfetch
- FROM vn_anime$hist va
- JOIN anime a ON va.aid = a.id
- WHERE va.$colname IN(!l)",
- [ keys %r ]
- )});
- }
-
- if($what =~ /relations/) {
- push(@{$r->[$r{ delete $_->{xid} }]{relations}}, $_) for(@{$self->dbAll("
- SELECT rel.$colname AS xid, rel.vid AS id, rel.relation, rel.official, v.title, v.original
- FROM vn_relations$hist rel
- JOIN vn v ON rel.vid = v.id
- WHERE rel.$colname IN(!l)",
- [ keys %r ]
- )});
- }
- }
-
- VNWeb::DB::enrich_flatten(vnlist_labels => id => vid => sub { VNWeb::DB::sql('
- SELECT uvl.vid, ul.label
- FROM ulist_vns_labels uvl
- JOIN ulist_labels ul ON ul.uid = uvl.uid AND ul.id = uvl.lbl
- WHERE uvl.uid =', \$self->authInfo->{id}, 'AND uvl.vid IN', $_[0], '
- ORDER BY CASE WHEN ul.id < 10 THEN ul.id ELSE 10 END, ul.label'
- )}, $r) if $what =~ /vnlist/ && $self->authInfo->{id};
-
- return wantarray ? ($r, $np) : $r;
-}
-
-
-1;
diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm
index 1ce6b47b..a1ce84e5 100644
--- a/lib/VNDB/Func.pm
+++ b/lib/VNDB/Func.pm
@@ -3,7 +3,7 @@ package VNDB::Func;
use strict;
use warnings;
-use TUWF ':html', 'uri_escape';
+use TUWF 'uri_escape';
use Exporter 'import';
use POSIX 'strftime';
use VNDBUtil;
@@ -11,8 +11,8 @@ use VNDB::Config;
use VNDB::Types;
use VNDB::BBCode;
our @EXPORT = (@VNDBUtil::EXPORT, 'bb_format', qw|
- clearfloat cssicon minage fil_parse fil_serialize parenttags childtags
- fmtvote fmtmedia fmtvnlen fmtage fmtdatestr fmtdate fmtrating fmtspoil
+ minage
+ fmtvote fmtmedia fmtage fmtdate fmtrating fmtspoil
imgpath imgurl
lang_attr
query_encode
@@ -20,26 +20,6 @@ our @EXPORT = (@VNDBUtil::EXPORT, 'bb_format', qw|
|);
-# 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;
-
-
-# Clears a float, to make sure boxes always have the correct height
-sub clearfloat {
- div class => 'clearfloat', '';
-}
-
-
-# Draws a CSS icon, arguments: class, title
-sub cssicon {
- abbr class => "icons $_[0]", title => $_[1];
- lit '&#xa0;';
- end;
-}
-
-
sub minage {
my($a, $ex) = @_;
$a = $AGE_RATING{$a};
@@ -47,103 +27,6 @@ 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_~\x81-\x{ffffff}]+)$/ || !$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
- } grep defined($fil->{$_}), sort keys %$fil;
-}
-
-
-# generates a parent tags/traits listing
-sub parenttags {
- my($t, $index, $type) = @_;
- p;
- my @p = _parenttags(@{$t->{parents}});
- for my $p (@p ? @p : []) {
- a href => "/$type", $index;
- for (reverse @$p) {
- txt ' > ';
- a href => "/$type$_->{id}", $_->{name};
- }
- txt " > $t->{name}";
- br;
- }
- end 'p';
-}
-
-# arg: tag/trait hashref
-# returns: [ [ tag1, tag2, tag3 ], [ tag1, tag2, tag5 ] ]
-sub _parenttags {
- my @r;
- for my $t (@_) {
- for (@{$t->{'sub'}}) {
- push @r, [ $t, @$_ ] for _parenttags($_);
- }
- push @r, [$t] if !@{$t->{'sub'}};
- }
- return @r;
-}
-
-
-# a child tags/traits box
-sub childtags {
- my($self, $title, $type, $t, $order) = @_;
-
- div class => 'mainbox';
- h1 $title;
- ul class => 'tagtree';
- for my $p (sort { !$order ? @{$b->{'sub'}} <=> @{$a->{'sub'}} : $a->{$order} <=> $b->{$order} } @{$t->{childs}}) {
- li;
- a href => "/$type$p->{id}", $p->{name};
- b class => 'grayedout', " ($p->{c_items})" if $p->{c_items};
- end, next if !@{$p->{'sub'}};
- ul;
- for (0..$#{$p->{'sub'}}) {
- last if $_ >= 5 && @{$p->{'sub'}} > 6;
- li;
- txt '> ';
- a href => "/$type$p->{sub}[$_]{id}", $p->{'sub'}[$_]{name};
- b class => 'grayedout', " ($p->{sub}[$_]{c_items})" if $p->{'sub'}[$_]{c_items};
- end;
- }
- if(@{$p->{'sub'}} > 6) {
- my $c = @{$p->{'sub'}}-5;
- li;
- txt '> ';
- a href => "/$type$p->{id}", style => 'font-style: italic',
- sprintf '%d more %s%s', $c, $type eq 'g' ? 'tag' : 'trait', $c==1 ? '' : 's';
- end;
- }
- end;
- end 'li';
- }
- end 'ul';
- clearfloat;
- br;
- end 'div';
-}
-
-
sub _path {
my($t, $id) = $_[1] =~ /([a-z]+)([0-9]+)/;
$t = 'st' if $t eq 'sf' && $_[2];
@@ -171,13 +54,6 @@ sub fmtmedia {
$med->{ $med->{qty} && $qty > 1 ? 'plural' : 'txt' };
}
-# Formats a VN length (xtra = time indication)
-sub fmtvnlen {
- my($len, $xtra) = @_;
- $len = $VN_LENGTH{$len};
- $len->{txt}.($xtra && $len->{time} ? " ($len->{time})" : '');
-}
-
# Formats a UNIX timestamp as a '<number> <unit> ago' string
sub fmtage {
my $a = time-shift;
@@ -193,26 +69,6 @@ sub fmtage {
sprintf '%d %s ago', $t, $t == 1 ? $single : $plural;
}
-# argument: database release date format (yyyymmdd)
-# y = 0000 -> unknown
-# y = 9999 -> TBA
-# m = 99 -> month+day unknown
-# d = 99 -> day unknown
-# return value: (unknown|TBA|yyyy|yyyy-mm|yyyy-mm-dd)
-# if date > now: <b class="future">str</b>
-sub fmtdatestr {
- my $date = sprintf '%08d', shift||0;
- my $future = $date > strftime '%Y%m%d', gmtime;
- my($y, $m, $d) = ($1, $2, $3) if $date =~ /^([0-9]{4})([0-9]{2})([0-9]{2})$/;
-
- my $str = $y == 0 ? 'unknown' : $y == 9999 ? 'TBA' :
- $m == 99 ? sprintf('%04d', $y) :
- $d == 99 ? sprintf('%04d-%02d', $y, $m) :
- sprintf('%04d-%02d-%02d', $y, $m, $d);
-
- return $str if !$future;
- return qq|<b class="future">$str</b>|;
-}
# argument: unix timestamp and optional format (compact/full)
sub fmtdate {
@@ -299,4 +155,3 @@ sub md2html {
}
1;
-
diff --git a/lib/VNDB/Handler/Chars.pm b/lib/VNDB/Handler/Chars.pm
deleted file mode 100644
index f75e1b46..00000000
--- a/lib/VNDB/Handler/Chars.pm
+++ /dev/null
@@ -1,111 +0,0 @@
-
-package VNDB::Handler::Chars;
-
-use strict;
-use warnings;
-use TUWF ':html', 'uri_escape';
-use Exporter 'import';
-use VNDB::Func;
-use VNDB::Types;
-
-our @EXPORT = ('charBrowseTable');
-
-TUWF::register(
- qr{old/c/([a-z0]|all)} => \&list,
-);
-
-
-sub list {
- my($self, $fch) = @_;
-
- my $f = $self->formValidate(
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'q', required => 0, default => '' },
- { get => 'fil', required => 0, default => '' },
- );
- return $self->resNotFound if $f->{_err};
-
- my($list, $np) = $self->filFetchDB(char => $f->{fil}, {
- tagspoil => $self->authPref('spoilers')||0,
- }, {
- $fch ne 'all' ? ( char => $fch ) : (),
- $f->{q} ? ( search => $f->{q} ) : (),
- results => 50,
- page => $f->{p},
- what => 'vns',
- });
-
- $self->htmlHeader(title => 'Browse characters');
-
- my $quri = uri_escape($f->{q});
- form action => '/old/c/all', 'accept-charset' => 'UTF-8', method => 'get';
- div class => 'mainbox';
- h1 'Browse characters';
- $self->htmlSearchBox('c', $f->{q});
- p class => 'browseopts';
- for ('all', 'a'..'z', 0) {
- a href => "/old/c/$_?q=$quri;fil=$f->{fil}", $_ eq $fch ? (class => 'optselected') : (), $_ eq 'all' ? 'ALL' : $_ ? uc $_ : '#';
- }
- end;
-
- p class => 'filselect';
- a id => 'filselect', href => '#c';
- lit '<i>&#9656;</i> Filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
- end;
- end 'form';
-
- if(!@$list) {
- div class => 'mainbox';
- h1 'No results';
- p 'No characters found that matched your criteria.';
- end;
- }
-
- @$list && $self->charBrowseTable($list, $np, $f, "/old/c/$fch?q=$quri;fil=$f->{fil}");
-
- $self->htmlFooter;
-}
-
-
-# Also used on Handler::Traits
-sub charBrowseTable {
- my($self, $list, $np, $f, $uri) = @_;
-
- $self->htmlBrowse(
- class => 'charb',
- items => $list,
- options => $f,
- nextpage => $np,
- pageurl => $uri,
- sorturl => $uri,
- header => [ [ '' ], [ '' ] ],
- row => sub {
- my($s, $n, $l) = @_;
- Tr;
- td class => 'tc1';
- cssicon "gen $l->{gender}", $GENDER{$l->{gender}} if $l->{gender} ne 'unknown';
- end;
- td class => 'tc2';
- a href => "/c$l->{id}", title => $l->{original}||$l->{name}, shorten $l->{name}, 50;
- b class => 'grayedout';
- my $i = 1;
- my %vns;
- for (@{$l->{vns}}) {
- next if $_->{spoil} || $vns{$_->{vid}}++;
- last if $i++ > 4;
- txt ', ' if $i > 2;
- a href => "/v$_->{vid}/chars", title => $_->{vntitle}, shorten $_->{vntitle}, 30;
- }
- end;
- end;
- end;
- }
- )
-}
-
-
-1;
-
diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm
deleted file mode 100644
index cca10ed5..00000000
--- a/lib/VNDB/Handler/Misc.pm
+++ /dev/null
@@ -1,53 +0,0 @@
-
-package VNDB::Handler::Misc;
-
-
-use strict;
-use warnings;
-use TUWF ':html', ':xml', 'uri_escape';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{nospam}, \&nospam,
- qr{xml/prefs\.xml}, \&prefs,
-);
-
-
-sub nospam {
- my $self = shift;
- $self->htmlHeader(title => 'Could not send form', noindex => 1);
-
- div class => 'mainbox';
- h1 'Could not send form';
- div class => 'warning';
- h2 'Error';
- p 'The form could not be sent, please make sure you have Javascript enabled in your browser.';
- end;
- end;
-
- $self->htmlFooter;
-}
-
-
-sub prefs {
- my $self = shift;
- return if !$self->authCheckCode;
- return $self->resNotFound if !$self->authInfo->{id};
- my $f = $self->formValidate(
- { get => 'key', enum => [qw|filter_vn filter_release|] },
- { get => 'value', required => 0, maxlength => 2000 },
- );
- return $self->resNotFound 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', '';
-}
-
-
-1;
-
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
deleted file mode 100644
index 44201e79..00000000
--- a/lib/VNDB/Handler/Producers.pm
+++ /dev/null
@@ -1,45 +0,0 @@
-
-package VNDB::Handler::Producers;
-
-use strict;
-use warnings;
-use TUWF ':html', ':xml';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{xml/producers\.xml} => \&pxml,
-);
-
-
-# peforms a (simple) search and returns the results in XML format
-sub pxml {
- my $self = shift;
-
- my $f = $self->formValidate(
- { get => 'q', required => 0, maxlength => 500 },
- { get => 'id', required => 0, multi => 1, template => 'id' },
- { get => 'r', required => 0, template => 'uint', min => 1, max => 50, default => 10 },
- );
- return $self->resNotFound if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]);
-
- my($list, $np) = $self->dbProducerGet(
- !$f->{q} ? () : $f->{q} =~ /^p([1-9]\d*)/ ? (id => $1) : (search => $f->{q}, sort => 'search'),
- $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (),
- results => $f->{r},
- page => 1,
- );
-
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
- xml;
- tag 'producers', more => $np ? 'yes' : 'no', query => $f->{q}||'';
- for(@$list) {
- tag 'item', id => $_->{id}, $_->{name};
- }
- end;
-}
-
-
-1;
-
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
deleted file mode 100644
index 1fdbb0d6..00000000
--- a/lib/VNDB/Handler/Releases.pm
+++ /dev/null
@@ -1,160 +0,0 @@
-
-package VNDB::Handler::Releases;
-
-use strict;
-use warnings;
-use TUWF ':html', ':xml', 'uri_escape';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{old/r} => \&browse,
- qr{xml/engines.xml} => \&enginexml,
-);
-
-
-sub browse {
- my $self = shift;
-
- my $f = $self->formValidate(
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'o', required => 0, default => 'a', enum => ['a', 'd'] },
- { get => 'q', required => 0, default => '', maxlength => 500 },
- { get => 's', required => 0, default => 'title', enum => [qw|released minage title|] },
- { get => 'fil',required => 0 },
- );
- return $self->resNotFound if $f->{_err};
- $f->{fil} //= $self->authPref('filter_release');
-
- 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} ) : (),
- });
-
- $self->htmlHeader(title => 'Browse releases');
-
- form method => 'get', action => '/old/r', 'accept-charset' => 'UTF-8';
- div class => 'mainbox';
- h1 'Browse releases';
- $self->htmlSearchBox('r', $f->{q});
- p class => 'filselect';
- a id => 'filselect', href => '#r';
- lit '<i>&#9656;</i> Filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
- end;
- end 'form';
-
- my $uri = sprintf '/old/r?q=%s;fil=%s', uri_escape($f->{q}), $f->{fil};
- $self->htmlBrowse(
- class => 'relbrowse',
- items => $list,
- options => $f,
- nextpage => $np,
- pageurl => "$uri;s=$f->{s};o=$f->{o}",
- sorturl => $uri,
- header => [
- [ 'Released', 'released' ],
- [ 'Rating', 'minage' ],
- [ '', '' ],
- [ 'Title', 'title' ],
- ],
- row => sub {
- my($s, $n, $l) = @_;
- Tr;
- td class => 'tc1';
- lit fmtdatestr $l->{released};
- end;
- td class => 'tc2', $l->{minage} < 0 ? '' : minage $l->{minage};
- td class => 'tc3';
- $_ ne 'oth' && cssicon $_, $PLATFORM{$_} for (@{$l->{platforms}});
- cssicon "lang $_", $LANGUAGE{$_} for (@{$l->{languages}});
- cssicon "rt$l->{type}", $l->{type};
- end;
- td class => 'tc4';
- a href => "/r$l->{id}", title => $l->{original}||$l->{title}, shorten $l->{title}, 90;
- b class => 'grayedout', ' (patch)' if $l->{patch};
- end;
- end 'tr';
- },
- ) if @$list;
- if(($f->{q} || $f->{fil}) && !@$list) {
- div class => 'mainbox';
- h1 'No results found';
- div class => 'notice';
- p;
- txt 'Sorry, couldn\'t find anything that comes through your filters. You might want to disable a few filters to get more results.';
- br; br;
- txt 'Also, keep in mind that we don\'t have all information about all releases.'
- .' So e.g. filtering on screen resolution will exclude all releases of which we don\'t know it\'s resolution,'
- .' even though it might in fact be in the resolution you\'re looking for.';
- end
- end;
- end;
- }
- $self->htmlFooter(pref_code => 1);
-}
-
-
-# provide compatibility with old URLs
-sub _fil_compat {
- my $self = shift;
- my %c;
- my $f = $self->formValidate(
- { get => 'ln', required => 0, multi => 1, default => '', enum => [ keys %LANGUAGE ] },
- { get => 'pl', required => 0, multi => 1, default => '', enum => [ keys %PLATFORM ] },
- { get => 'me', required => 0, multi => 1, default => '', enum => [ keys %MEDIUM ] },
- { get => 'tp', required => 0, default => '', enum => [ '', keys %RELEASE_TYPE ] },
- { get => 'pa', required => 0, default => 0, enum => [ 0..2 ] },
- { get => 'fw', required => 0, default => 0, enum => [ 0..2 ] },
- { get => 'do', required => 0, default => 0, enum => [ 0..2 ] },
- { get => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] },
- { get => 'ma_a', required => 0, default => 0, enum => [ keys %AGE_RATING ] },
- { get => 'mi', required => 0, default => 0, template => 'uint' },
- { get => 'ma', required => 0, default => 99999999, template => 'uint' },
- );
- return () if $f->{_err};
- $c{minage} = [ grep $_ >= 0 && ($f->{ma_m} ? $f->{ma_a} >= $_ : $f->{ma_a} <= $_), keys %AGE_RATING ] 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{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;
-}
-
-
-sub enginexml {
- my $self = shift;
-
- # The list of engines happens to be small enough for this to make sense, and
- # fetching all unique engines from the releases table also happens to be fast
- # enough right now, but this may need a separate cache or index in the future.
- my $lst = $self->dbReleaseEngines();
-
- my $f = $self->formValidate(
- { get => 'q', required => 1, maxlength => 500 },
- );
- return $self->resNotFound if $f->{_err};
-
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
- xml;
- tag 'engines';
- for(grep $lst->[$_]{engine} =~ /\Q$f->{q}\E/i, 0..$#$lst) {
- tag 'item', count => $lst->[$_]{cnt}, id => $_+1, $lst->[$_]{engine};
- }
- end;
-}
-
-1;
-
diff --git a/lib/VNDB/Handler/Staff.pm b/lib/VNDB/Handler/Staff.pm
deleted file mode 100644
index 6a291f09..00000000
--- a/lib/VNDB/Handler/Staff.pm
+++ /dev/null
@@ -1,116 +0,0 @@
-
-package VNDB::Handler::Staff;
-
-use strict;
-use warnings;
-use TUWF qw(:html :xml uri_escape);
-use VNDB::Func;
-use VNDB::Types;
-use List::Util qw(first);
-
-TUWF::register(
- qr{old/s/([a-z0]|all)} => \&list,
- qr{xml/staff\.xml} => \&staffxml,
-);
-
-
-sub list {
- my ($self, $char) = @_;
-
- my $f = $self->formValidate(
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'q', required => 0, default => '' },
- { get => 'fil', required => 0, default => '' },
- );
- return $self->resNotFound if $f->{_err};
-
- my ($list, $np) = $self->filFetchDB(staff => $f->{fil}, {}, {
- $char ne 'all' ? ( char => $char ) : (),
- $f->{q} ? ($f->{q} =~ /^=(.+)$/ ? (exact => $1) : (search => $f->{q})) : (),
- results => 150,
- page => $f->{p}
- });
-
- return $self->resRedirect('/s'.$list->[0]{id}, 'temp')
- if $f->{q} && @$list && (!first { $_->{id} != $list->[0]{id} } @$list) && $f->{p} == 1 && !$f->{fil};
- # redirect to the staff page if all results refer to the same entry
-
- my $quri = join(';', $f->{q} ? 'q='.uri_escape($f->{q}) : (), $f->{fil} ? "fil=$f->{fil}" : ());
- $quri = '?'.$quri if $quri;
- my $pageurl = "/old/s/$char$quri";
-
- $self->htmlHeader(title => 'Browse staff');
-
- form action => '/old/s/all', 'accept-charset' => 'UTF-8', method => 'get';
- div class => 'mainbox';
- h1 'Browse staff';
- $self->htmlSearchBox('s', $f->{q});
- p class => 'browseopts';
- for ('all', 'a'..'z', 0) {
- a href => "/old/s/$_$quri", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? 'ALL' : $_ ? uc $_ : '#';
- }
- end;
-
- p class => 'filselect';
- a id => 'filselect', href => '#s';
- lit '<i>&#9656;</i> Filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
- end;
- end 'form';
-
- $self->htmlBrowseNavigate($pageurl, $f->{p}, $np, 't');
- div class => 'mainbox staffbrowse';
- h1 $f->{q} ? 'Search results' : 'Staff list';
- if(!@$list) {
- p 'No results found';
- } else {
- # spread the results over 3 equivalent-sized lists
- my $perlist = @$list/3 < 1 ? 1 : @$list/3;
- for my $c (0..(@$list < 3 ? $#$list : 2)) {
- ul;
- for ($perlist*$c..($perlist*($c+1))-1) {
- li;
- cssicon 'lang '.$list->[$_]{lang}, $LANGUAGE{$list->[$_]{lang}};
- a href => "/s$list->[$_]{id}",
- title => $list->[$_]{original}, $list->[$_]{name};
- end;
- }
- end;
- }
- }
- clearfloat;
- end 'div';
- $self->htmlBrowseNavigate($pageurl, $f->{p}, $np, 'b');
- $self->htmlFooter;
-}
-
-
-sub staffxml {
- my $self = shift;
-
- my $f = $self->formValidate(
- { get => 'q', required => 0, maxlength => 500 },
- { get => 'id', required => 0, multi => 1, template => 'id' },
- { get => 'staffid', required => 0, default => 0 }, # The returned id = staff id when set, otherwise it's the alias id
- { get => 'r', required => 0, template => 'uint', min => 1, max => 50, default => 10 },
- );
- return $self->resNotFound if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]);
-
- my($list, $np) = $self->dbStaffGet(
- !$f->{q} ? () : $f->{q} =~ /^s([1-9]\d*)/ ? (id => $1) : $f->{q} =~ /^=(.+)/ ? (exact => $1) : (search => $f->{q}, sort => 'search'),
- $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (),
- results => $f->{r}, page => 1,
- );
-
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
- xml;
- tag 'staff', more => $np ? 'yes' : 'no';
- for(@$list) {
- tag 'item', sid => $_->{id}, id => $f->{staffid} ? $_->{id} : $_->{aid}, orig => $_->{original}, $_->{name};
- }
- end;
-}
-
-1;
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
deleted file mode 100644
index d4807055..00000000
--- a/lib/VNDB/Handler/Tags.pm
+++ /dev/null
@@ -1,202 +0,0 @@
-
-package VNDB::Handler::Tags;
-
-
-use strict;
-use warnings;
-use TUWF ':html', ':xml', 'xml_escape';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{old/g([1-9]\d*)}, \&tagpage,
- qr{g/debug}, \&fulltree,
- qr{xml/tags\.xml}, \&tagxml,
-);
-
-
-sub tagpage {
- my($self, $tag) = @_;
-
- my $t = $self->dbTagGet(id => $tag, what => 'parents(0) childs(2) aliases')->[0];
- return $self->resNotFound if !$t;
-
- my $f = $self->formValidate(
- { get => 's', required => 0, default => 'tagscore', enum => [ qw|title rel pop tagscore rating| ] },
- { get => 'o', required => 0, default => 'd', enum => [ 'a','d' ] },
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'm', required => 0, default => $self->authPref('spoilers') || 0, enum => [qw|0 1 2|] },
- { get => 'fil', required => 0 },
- );
- return $self->resNotFound if $f->{_err};
- $f->{fil} //= $self->authPref('filter_vn');
-
- my($list, $np) = !$t->{searchable} || $t->{state} != 2 ? ([],0) : $self->filFetchDB(vn => $f->{fil}, undef, {
- what => 'rating',
- results => 50,
- page => $f->{p},
- sort => $f->{s}, reverse => $f->{o} eq 'd',
- tagspoil => $f->{m},
- tag_inc => $tag,
- tag_exc => undef,
- });
-
- my $title = "Tag: $t->{name}";
- $self->htmlHeader(title => $title, noindex => $t->{state} != 2);
- $self->htmlMainTabs('g', $t);
-
- if($t->{state} != 2) {
- div class => 'mainbox';
- h1 $title;
- if($t->{state} == 1) {
- div class => 'warning';
- h2 'Tag deleted';
- p;
- txt 'This tag has been removed from the database, and cannot be used or re-added.';
- br;
- txt 'File a request on the ';
- a href => '/t/db', 'discussion board';
- txt ' if you disagree with this.';
- end;
- end;
- } else {
- div class => 'notice';
- h2 'Waiting for approval';
- p 'This tag is waiting for a moderator to approve it. You can still use it to tag VNs as you would with a normal tag.';
- end;
- }
- end 'div';
- }
-
- div class => 'mainbox';
- a class => 'addnew', href => "/g$tag/add", 'Create child tag' if $self->authCan('tag') && $t->{state} != 1;
- h1 $title;
-
- parenttags($t, 'Tags', 'g');
-
- if($t->{description}) {
- p class => 'description';
- lit bb_format $t->{description};
- end;
- }
- if(!$t->{applicable} || !$t->{searchable}) {
- p class => 'center';
- b 'Properties';
- br;
- txt 'Not searchable.' if !$t->{searchable};
- br;
- txt 'Can not be directly applied to visual novels.' if !$t->{applicable};
- end;
- }
- p class => 'center';
- b 'Category';
- br;
- txt $TAG_CATEGORY{$t->{cat}};
- end;
- if(@{$t->{aliases}}) {
- p class => 'center';
- b 'Aliases';
- br;
- lit xml_escape($_).'<br />' for (@{$t->{aliases}});
- end;
- }
- end 'div';
-
- childtags($self, 'Child tags', 'g', $t) if @{$t->{childs}};
-
- if($t->{searchable} && $t->{state} == 2) {
- form action => "/old/g$t->{id}", 'accept-charset' => 'UTF-8', method => 'get';
- div class => 'mainbox';
- a class => 'addnew', href => "/g/links?t=$tag", 'Recently tagged';
- h1 'Visual novels';
-
- p class => 'browseopts';
- a href => "/old/g$t->{id}?fil=$f->{fil};s=$f->{s};o=$f->{o};m=0", $f->{m} == 0 ? (class => 'optselected') : (), 'Hide spoilers';
- a href => "/old/g$t->{id}?fil=$f->{fil};s=$f->{s};o=$f->{o};m=1", $f->{m} == 1 ? (class => 'optselected') : (), 'Show minor spoilers';
- a href => "/old/g$t->{id}?fil=$f->{fil};s=$f->{s};o=$f->{o};m=2", $f->{m} == 2 ? (class => 'optselected') : (), 'Spoil me!';
- end;
-
- p class => 'filselect';
- a id => 'filselect', href => '#v';
- lit '<i>&#9656;</i> Filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
- input type => 'hidden', class => 'hidden', name => 'm', id => 'm', value => $f->{m};
-
- if(!@$list) {
- p; br; br; txt 'This tag has not been linked to any visual novels yet, or they were hidden because of your spoiler settings or default filters.'; end;
- }
- if(@{$t->{childs}}) {
- p; br; txt 'The list below also includes all visual novels linked to child tags.'; end;
- }
- end 'div';
- end 'form';
- $self->htmlBrowseVN($list, $f, $np, "/old/g$t->{id}?fil=$f->{fil};m=$f->{m}", 1) if @$list;
- }
-
- $self->htmlFooter(pref_code => 1);
-}
-
-
-# non-translatable debug page
-sub fulltree {
- my $self = shift;
- return $self->htmlDenied if !$self->authCan('tagmod');
-
- my $e;
- $e = sub {
- my $lst = shift;
- ul style => 'list-style-type: none; margin-left: 15px';
- for (@$lst) {
- li;
- txt '> ';
- a href => "/g$_->{id}", $_->{name};
- b class => 'grayedout', " ($_->{c_items})" if $_->{c_items};
- end;
- $e->($_->{sub}) if $_->{sub};
- }
- end;
- };
-
- my $tags = $self->dbTTTree(tag => 0, 25);
- $self->htmlHeader(title => '[DEBUG] Tag tree', noindex => 1);
- div class => 'mainbox';
- h1 '[DEBUG] Tag tree';
- $e->($tags);
- end;
- $self->htmlFooter;
-}
-
-
-sub tagxml {
- my $self = shift;
-
- my $f = $self->formValidate(
- { get => 'q', required => 0, maxlength => 500 },
- { get => 'id', required => 0, multi => 1, template => 'id' },
- { get => 'searchable', required => 0, default => 0 },
- { get => 'r', required => 0, template => 'uint', min => 1, max => 50, default => 15 },
- );
- return $self->resNotFound if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]);
-
- my($list, $np) = $self->dbTagGet(
- !$f->{q} ? () : $f->{q} =~ /^g([1-9]\d*)/ ? (id => $1) : $f->{q} =~ /^=(.+)$/ ? (name => $1) : (search => $f->{q}, sort => 'search'),
- $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (),
- results => $f->{r},
- page => 1,
- $f->{searchable} ? (state => 2, searchable => 1) : (),
- );
-
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
- xml;
- tag 'tags', more => $np ? 'yes' : 'no', $f->{q} ? (query => $f->{q}) : ();
- for(@$list) {
- tag 'item', id => $_->{id}, searchable => $_->{searchable} ? 'yes' : 'no', applicable => $_->{applicable} ? 'yes' : 'no', state => $_->{state}, $_->{name};
- }
- end;
-}
-
-
-1;
diff --git a/lib/VNDB/Handler/Traits.pm b/lib/VNDB/Handler/Traits.pm
deleted file mode 100644
index 9dc08b9f..00000000
--- a/lib/VNDB/Handler/Traits.pm
+++ /dev/null
@@ -1,165 +0,0 @@
-
-package VNDB::Handler::Traits;
-
-use strict;
-use warnings;
-use TUWF ':html', ':xml', 'html_escape', 'xml_escape';
-use VNDB::Func;
-
-
-TUWF::register(
- qr{old/i([1-9]\d*)}, \&traitpage,
- qr{xml/traits\.xml}, \&traitxml,
-);
-
-
-sub traitpage {
- my($self, $trait) = @_;
-
- my $t = $self->dbTraitGet(id => $trait, what => 'parents(0) childs(2)')->[0];
- return $self->resNotFound if !$t;
-
- my $f = $self->formValidate(
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'm', required => 0, default => $self->authPref('spoilers')||0, enum => [qw|0 1 2|] },
- { get => 'fil', required => 0, default => '' },
- );
- return $self->resNotFound if $f->{_err};
-
- my $title = "Trait: $t->{name}";
- $self->htmlHeader(title => $title, noindex => $t->{state} != 2);
- $self->htmlMainTabs('i', $t);
-
- if($t->{state} != 2) {
- div class => 'mainbox';
- h1 $title;
- if($t->{state} == 1) {
- div class => 'warning';
- h2 'Trait deleted';
- p;
- txt 'This trait has been removed from the database, and cannot be used or re-added. File a request on the ';
- a href => '/t/db', 'discussion board';
- txt ' if you disagree with this.';
- end;
- end;
- } else {
- div class => 'notice';
- h2 'Waiting for approval';
- p 'This trait is waiting for a moderator to approve it.';
- end;
- }
- end 'div';
- }
-
- div class => 'mainbox';
- a class => 'addnew', href => "/i$trait/add", 'Create child trait' if $self->authCan('edit') && $t->{state} != 1;
- h1 $title;
-
- parenttags($t, 'Traits', 'i');
-
- if($t->{description}) {
- p class => 'description';
- lit bb_format $t->{description};
- end;
- }
- if(!$t->{applicable} || !$t->{searchable}) {
- p class => 'center';
- b 'Properties';
- br;
- txt 'Not searchable.' if !$t->{searchable};
- br;
- txt 'Can not be directly applied to characters.' if !$t->{applicable};
- end;
- }
- if($t->{sexual}) {
- p class => 'center';
- b 'Sexual content';
- end;
- }
- if($t->{alias}) {
- p class => 'center';
- b 'Aliases';
- br;
- lit html_escape($t->{alias});
- end;
- }
- end 'div';
-
- childtags($self, 'Child traits', 'i', $t) if @{$t->{childs}};
-
- if($t->{searchable} && $t->{state} == 2) {
- my($chars, $np) = $self->filFetchDB(char => $f->{fil}, {}, {
- trait_inc => $trait,
- tagspoil => $f->{m},
- results => 50,
- page => $f->{p},
- what => 'vns',
- });
-
- form action => "/i$t->{id}", 'accept-charset' => 'UTF-8', method => 'get';
- div class => 'mainbox';
- h1 'Characters';
-
- p class => 'browseopts';
- a href => "/i$trait?fil=$f->{fil};m=0", $f->{m} == 0 ? (class => 'optselected') : (), 'Hide spoilers';
- a href => "/i$trait?fil=$f->{fil};m=1", $f->{m} == 1 ? (class => 'optselected') : (), 'Show minor spoilers';
- a href => "/i$trait?fil=$f->{fil};m=2", $f->{m} == 2 ? (class => 'optselected') : (), 'Spoil me!';
- end;
-
- p class => 'filselect';
- a id => 'filselect', href => '#c';
- lit '<i>&#9656;</i> Filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
- input type => 'hidden', class => 'hidden', name => 'm', id => 'm', value => $f->{m};
-
- if(!@$chars) {
- p; br; br; txt 'This trait has not been linked to any characters yet, or they were hidden because of your spoiler settings.'; end;
- }
- if(@{$t->{childs}}) {
- p; br; txt 'The list below also includes all characters linked to child traits.'; end;
- }
- end 'div';
- end 'form';
- @$chars && $self->charBrowseTable($chars, $np, $f, "/i$trait?m=$f->{m};fil=$f->{fil}");
- }
-
- $self->htmlFooter;
-}
-
-
-sub traitxml {
- my $self = shift;
-
- my $f = $self->formValidate(
- { get => 'q', required => 0, maxlength => 500 },
- { get => 'id', required => 0, multi => 1, template => 'id' },
- { get => 'r', required => 0, default => 15, template => 'uint', min => 1, max => 200 },
- { get => 'searchable', required => 0, default => 0 },
- );
- return $self->resNotFound if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]);
-
- my($list, $np) = $self->dbTraitGet(
- results => $f->{r},
- page => 1,
- sort => 'group',
- state => 2,
- $f->{searchable} ? (searchable => 1) : (),
- !$f->{q} ? () : $f->{q} =~ /^i([1-9]\d*)/ ? (id => $1) : (search => $f->{q}, sort => 'search'),
- $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (),
- );
-
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
- xml;
- tag 'traits', more => $np ? 'yes' : 'no';
- for(@$list) {
- tag 'item', id => $_->{id}, searchable => $_->{searchable} ? 'yes' : 'no', applicable => $_->{applicable} ? 'yes' : 'no', group => $_->{group}||'',
- groupname => $_->{groupname}||'', state => $_->{state}, defaultspoil => $_->{defaultspoil}, $_->{name};
- }
- end;
-}
-
-
-1;
-
diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm
deleted file mode 100644
index 090f58ad..00000000
--- a/lib/VNDB/Handler/VNBrowse.pm
+++ /dev/null
@@ -1,143 +0,0 @@
-
-package VNDB::Handler::VNBrowse;
-
-use strict;
-use warnings;
-use TUWF ':html', 'uri_escape';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{old/v/([a-z0]|all)} => \&list,
-);
-
-
-sub list {
- my($self, $char) = @_;
-
- my $f = $self->formValidate(
- { get => 's', required => 0, default => 'tagscore', enum => [ qw|title rel pop tagscore rating| ] },
- { get => 'o', required => 0, enum => [ 'a','d' ] },
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'q', required => 0, default => '' },
- { get => 'sq', required => 0, default => '' },
- { get => 'fil',required => 0 },
- { get => 'rfil', required => 0, default => '' },
- { get => 'cfil', required => 0, default => '' },
- { get => 'vnlist', required => 0, default => 2, enum => [ '0', '1' ] }, # 2: use pref
- );
- return $self->resNotFound if $f->{_err};
- $f->{q} ||= $f->{sq};
- $f->{fil} //= $self->authPref('filter_vn');
- my %compat = _fil_compat($self);
- my $uid = $self->authInfo->{id};
-
- my $read_write_pref = sub {
- my($type, $pref_name) = @_;
-
- return 0 if !$uid; # no data to display anyway
- return $self->authPref($pref_name)?1:0 if $f->{$type} == 2;
-
- $self->authPref($pref_name => $f->{$type}?1:0) if ($self->authPref($pref_name)?1:0) != $f->{$type};
- return $f->{$type};
- };
-
- $f->{vnlist} = $read_write_pref->('vnlist', 'vn_list_own');
-
- return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp')
- if $f->{q} && $f->{q} =~ /^([gvrptudcis])([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 $rfil = fil_parse $f->{rfil}, @{$VNDB::Util::Misc::filfields{release}};
- $self->filCompat(release => $rfil);
- $f->{rfil} = fil_serialize $rfil, @{$VNDB::Util::Misc::filfields{release}};
-
- my $cfil = fil_parse $f->{cfil}, @{$VNDB::Util::Misc::filfields{char}};
- $cfil->{tagspoil} //= $self->authPref('spoilers')||0 if keys %$cfil;
-
- my($list, $np) = $self->filFetchDB(vn => $f->{fil}, {
- %compat,
- tagspoil => $self->authPref('spoilers')||0,
- }, {
- what => ' rating'.($f->{vnlist} ? ' vnlist' : ''),
- $char ne 'all' ? ( char => $char ) : (),
- $f->{q} ? ( search => $f->{q} ) : (),
- keys %$rfil ? ( release => $rfil ) : (),
- keys %$cfil ? ( character => $cfil ) : (),
- results => 50,
- page => $f->{p},
- sort => $f->{s}, reverse => $f->{o} eq 'd',
- });
-
- $self->resRedirect('/v'.$list->[0]{id}, 'temp')
- if $f->{q} && @$list == 1 && $f->{p} == 1;
-
- $self->htmlHeader(title => 'Browse visual novels', search => $f->{q});
-
- my $quri = uri_escape($f->{q});
- form action => '/old/v/all', 'accept-charset' => 'UTF-8', method => 'get';
-
- # url generator
- my $url = sub {
- my($char, $toggle) = @_;
-
- return "/old/v/$char?q=$quri;fil=$f->{fil};rfil=$f->{rfil};cfil=$f->{cfil};s=$f->{s};o=$f->{o}" .
- ($toggle ? ";$toggle=".($f->{$toggle}?0:1) : '');
- };
-
- div class => 'mainbox';
- h1 'Browse visual novels';
- $self->htmlSearchBox('v', $f->{q});
- p class => 'browseopts';
- for ('all', 'a'..'z', 0) {
- a href => $url->($_), $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? 'ALL' : $_ ? uc $_ : '#';
- }
- end;
- if($uid) {
- p class => 'browseopts';
- a href => $url->($char, 'vnlist'), $f->{vnlist} ? (class => 'optselected') : (), 'User VN list';
- end 'p';
- }
-
- p class => 'filselect';
- a id => 'filselect', href => '#v';
- lit '<i>&#9656;</i> Visual Novel Filters<i></i>';
- end;
- a id => 'rfilselect', href => '#r';
- lit '<i>&#9656;</i> Release filters<i></i>';
- end;
- a id => 'cfilselect', href => '#c';
- lit '<i>&#9656;</i> Character filters<i></i>';
- end;
- end;
- input type => 'hidden', class => 'hidden', name => $_, id => $_, value => $f->{$_}
- for (qw{fil rfil cfil s o});
- end;
- end 'form';
-
- $self->htmlBrowseVN($list, $f, $np, "/old/v/$char?q=$quri;fil=$f->{fil};rfil=$f->{rfil};cfil=$f->{cfil}", $f->{fil} =~ /tag_inc-/);
- $self->htmlFooter(pref_code => 1);
-}
-
-
-sub _fil_compat {
- my $self = shift;
- my %c;
- my $f = $self->formValidate(
- { get => 'ln', required => 0, multi => 1, enum => [ keys %LANGUAGE ], default => '' },
- { get => 'pl', required => 0, multi => 1, enum => [ keys %PLATFORM ], default => '' },
- { get => 'sp', required => 0, default => ($self->reqCookie('tagspoil')||'') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] },
- );
- 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;
-}
-
-
-1;
-
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
deleted file mode 100644
index 555903db..00000000
--- a/lib/VNDB/Handler/VNPage.pm
+++ /dev/null
@@ -1,293 +0,0 @@
-
-package VNDB::Handler::VNPage;
-
-use strict;
-use warnings;
-use TUWF ':html';
-use VNDB::Func;
-use VNDB::Types;
-
-
-TUWF::register(
- qr{old/v([1-9]\d*)/releases} => \&releases,
-);
-
-
-# Description of each column, field:
-# id: Identifier used in URLs
-# sort_field: Name of the field when sorting
-# what: Required dbReleaseGet 'what' flag
-# column_string: String to use as column header
-# column_width: Maximum width (in pixels) of the column in 'restricted width' mode
-# button_string: String to use for the hide/unhide button
-# na_for_patch: When the field is N/A for patch releases
-# default: Set when it's visible by default
-# has_data: Subroutine called with a release object, should return true if the release has data for the column
-# draw: Subroutine called with a release object, should draw its column contents
-my @rel_cols = (
- { # Title
- id => 'tit',
- sort_field => 'title',
- column_string => 'Title',
- draw => sub { a href => "/r$_[0]{id}", shorten $_[0]{title}, 60 },
- }, { # Type
- id => 'typ',
- sort_field => 'type',
- button_string => 'Type',
- default => 1,
- draw => sub { cssicon "rt$_[0]{type}", $_[0]{type}; txt '(patch)' if $_[0]{patch} },
- }, { # Languages
- id => 'lan',
- button_string => 'Language',
- default => 1,
- has_data => sub { !!@{$_[0]{languages}} },
- draw => sub {
- for(@{$_[0]{languages}}) {
- cssicon "lang $_", $LANGUAGE{$_};
- br if $_ ne $_[0]{languages}[$#{$_[0]{languages}}];
- }
- },
- }, { # Publication
- id => 'pub',
- sort_field => 'publication',
- column_string => 'Publication',
- column_width => 70,
- button_string => 'Publication',
- default => 1,
- what => 'extended',
- draw => sub { txt join ', ', $_[0]{freeware} ? 'Freeware' : 'Non-free', $_[0]{patch} ? () : ($_[0]{doujin} ? 'doujin' : 'commercial') },
- }, { # Platforms
- id => 'pla',
- button_string => 'Platforms',
- default => 1,
- what => 'platforms',
- has_data => sub { !!@{$_[0]{platforms}} },
- draw => sub {
- for(@{$_[0]{platforms}}) {
- cssicon $_, $PLATFORM{$_};
- br if $_ ne $_[0]{platforms}[$#{$_[0]{platforms}}];
- }
- txt 'Unknown' if !@{$_[0]{platforms}};
- },
- }, { # Media
- id => 'med',
- column_string => 'Media',
- button_string => 'Media',
- what => 'media',
- has_data => sub { !!@{$_[0]{media}} },
- draw => sub {
- for(@{$_[0]{media}}) {
- txt fmtmedia($_->{medium}, $_->{qty});
- br if $_ ne $_[0]{media}[$#{$_[0]{media}}];
- }
- txt 'Unknown' if !@{$_[0]{media}};
- },
- }, { # Resolution
- id => 'res',
- sort_field => 'resolution',
- column_string => 'Resolution',
- button_string => 'Resolution',
- na_for_patch => 1,
- default => 1,
- what => 'extended',
- has_data => sub { !!$_[0]{reso_y} },
- draw => sub { txt resolution($_[0]) || 'Unknown' },
- }, { # Voiced
- id => 'voi',
- sort_field => 'voiced',
- column_string => 'Voiced',
- column_width => 70,
- button_string => 'Voiced',
- na_for_patch => 1,
- default => 1,
- what => 'extended',
- has_data => sub { !!$_[0]{voiced} },
- draw => sub { txt $VOICED{$_[0]{voiced}}{txt} },
- }, { # Animation
- id => 'ani',
- sort_field => 'ani_ero',
- column_string => 'Animation',
- column_width => 110,
- button_string => 'Animation',
- na_for_patch => '1',
- what => 'extended',
- has_data => sub { !!($_[0]{ani_story} || $_[0]{ani_ero}) },
- draw => sub {
- txt join ', ',
- $_[0]{ani_story} ? "Story: $ANIMATED{$_[0]{ani_story}}{txt}" :(),
- $_[0]{ani_ero} ? "Ero scenes: $ANIMATED{$_[0]{ani_ero}}{txt}":();
- txt 'Unknown' if !$_[0]{ani_story} && !$_[0]{ani_ero};
- },
- }, { # Released
- id => 'rel',
- sort_field => 'released',
- column_string => 'Released',
- button_string => 'Released',
- default => 1,
- draw => sub { lit fmtdatestr $_[0]{released} },
- }, { # Age rating
- id => 'min',
- sort_field => 'minage',
- button_string => 'Age rating',
- default => 1,
- has_data => sub { $_[0]{minage} != -1 },
- draw => sub { txt minage $_[0]{minage} },
- }, { # Notes
- id => 'not',
- sort_field => 'notes',
- column_string => 'Notes',
- column_width => 400,
- button_string => 'Notes',
- default => 1,
- what => 'extended',
- has_data => sub { !!$_[0]{notes} },
- draw => sub { lit bb_format $_[0]{notes} },
- }
-);
-
-
-sub releases {
- my($self, $vid) = @_;
-
- my $v = $self->dbVNGet(id => $vid)->[0];
- return $self->resNotFound if !$v->{id};
-
- my $title = "Releases for $v->{title}";
- $self->htmlHeader(title => $title);
- $self->htmlMainTabs('v', $v, 'releases');
-
- my $f = $self->formValidate(
- map({ get => $_->{id}, required => 0, default => $_->{default}||0, enum => [0,1] }, grep $_->{button_string}, @rel_cols),
- { get => 'cw', required => 0, default => 0, enum => [0,1] },
- { get => 'o', required => 0, default => 0, enum => [0,1] },
- { get => 's', required => 0, default => 'released', enum => [ map $_->{sort_field}, grep $_->{sort_field}, @rel_cols ]},
- { get => 'os', required => 0, default => 'all', enum => [ 'all', keys %PLATFORM ] },
- { get => 'lang', required => 0, default => 'all', enum => [ 'all', keys %LANGUAGE ] },
- );
- return $self->resNotFound if $f->{_err};
-
- # Get the release info
- my %what = map +($_->{what}, 1), grep $_->{what} && $f->{$_->{id}}, @rel_cols;
- my $r = $self->dbReleaseGet(vid => $vid, what => join(' ', keys %what), sort => $f->{s}, reverse => $f->{o}, results => 200);
-
- # url generator
- my $url = sub {
- my %u = (%$f, @_);
- return "/v$vid/releases?".join(';', map "$_=$u{$_}", sort keys %u);
- };
-
- div class => 'mainbox releases_compare';
- h1 $title;
-
- if(!@$r) {
- td 'We don\'t have any information about releases of this visual novel yet...';
- } else {
- _releases_buttons($self, $f, $url, $r);
- }
- end 'div';
-
- _releases_table($self, $f, $url, $r) if @$r;
- $self->htmlFooter;
-}
-
-
-sub _releases_buttons {
- my($self, $f, $url, $r) = @_;
-
- # Column visibility
- p class => 'browseopts';
- a href => $url->($_->{id}, $f->{$_->{id}} ? 0 : 1), $f->{$_->{id}} ? (class => 'optselected') : (), $_->{button_string}
- for (grep $_->{button_string}, @rel_cols);
- end;
-
- # Misc options
- my $all_selected = !grep $_->{button_string} && !$f->{$_->{id}}, @rel_cols;
- my $all_unselected = !grep $_->{button_string} && $f->{$_->{id}}, @rel_cols;
- my $all_url = sub { $url->(map +($_->{id},$_[0]), grep $_->{button_string}, @rel_cols); };
- p class => 'browseopts';
- a href => $all_url->(1), $all_selected ? (class => 'optselected') : (), 'All on';
- a href => $all_url->(0), $all_unselected ? (class => 'optselected') : (), 'All off';
- a href => $url->('cw', $f->{cw} ? 0 : 1), $f->{cw} ? (class => 'optselected') : (), 'Restrict column width';
- end;
-
- # Platform/language filters
- my $plat_lang_draw = sub {
- my($row, $option, $txt, $csscat) = @_;
- my %opts = map +($_,1), map @{$_->{$row}}, @$r;
- return if !keys %opts;
- p class => 'browseopts';
- for('all', sort keys %opts) {
- a href => $url->($option, $_), $_ eq $f->{$option} ? (class => 'optselected') : ();
- $_ eq 'all' ? txt 'All' : cssicon "$csscat $_", $txt->{$_};
- end 'a';
- }
- end 'p';
- };
- $plat_lang_draw->('platforms', 'os', \%PLATFORM, '') if $f->{pla};
- $plat_lang_draw->('languages', 'lang',\%LANGUAGE, 'lang') if $f->{lan};
-}
-
-
-sub _releases_table {
- my($self, $f, $url, $r) = @_;
-
- # Apply language and platform filters
- my @r = grep +
- ($f->{os} eq 'all' || ($_->{platforms} && grep $_ eq $f->{os}, @{$_->{platforms}})) &&
- ($f->{lang} eq 'all' || ($_->{languages} && grep $_ eq $f->{lang}, @{$_->{languages}})), @$r;
-
- # Figure out which columns to display
- my @col;
- for my $c (@rel_cols) {
- next if $c->{button_string} && !$f->{$c->{id}}; # Hidden by settings
- push @col, $c if !@r || !$c->{has_data} || grep $c->{has_data}->($_), @r; # Must have relevant data
- }
-
- div class => 'mainbox releases_compare';
- table;
-
- thead;
- Tr;
- for my $c (@col) {
- td class => 'key';
- txt $c->{column_string} if $c->{column_string};
- for($c->{sort_field} ? (0,1) : ()) {
- my $active = $f->{s} eq $c->{sort_field} && !$f->{o} == !$_;
- a href => $url->(o => $_, s => $c->{sort_field}) if !$active;
- lit $_ ? "\x{25BE}" : "\x{25B4}";
- end 'a' if !$active;
- }
- end 'td';
- }
- end 'tr';
- end 'thead';
-
- for my $r (@r) {
- Tr;
- # Combine "N/A for patches" columns
- my $cspan = 1;
- for my $c (0..$#col) {
- if($r->{patch} && $col[$c]{na_for_patch} && $c < $#col && $col[$c+1]{na_for_patch}) {
- $cspan++;
- next;
- }
- td $cspan > 1 ? (colspan => $cspan) : (),
- $col[$c]{column_width} && $f->{cw} ? (style => "max-width: $col[$c]{column_width}px") : ();
- if($r->{patch} && $col[$c]{na_for_patch}) {
- txt 'NA for patches';
- } else {
- $col[$c]{draw}->($r);
- }
- end;
- $cspan = 1;
- }
- end;
- }
- end 'table';
- end 'div';
-}
-
-
-
-1;
-
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
deleted file mode 100644
index f3094ff0..00000000
--- a/lib/VNDB/Util/Auth.pm
+++ /dev/null
@@ -1,81 +0,0 @@
-# Compatibility shim around VNWeb::Auth, new code should use that instead.
-package VNDB::Util::Auth;
-
-
-use strict;
-use warnings;
-use Exporter 'import';
-use TUWF ':html';
-use VNWeb::Auth;
-
-
-our @EXPORT = qw|
- authInfo authCan authGetCode authCheckCode authPref
-|;
-
-
-sub authInfo {
- # Used to return a lot more, but only the id is still used now.
- # (code using other fields has been migrated)
- +{ id => auth->uid }
-}
-
-
-# returns whether the currently loggedin or anonymous user can perform
-# a certain action.
-sub authCan {
- my(undef, $act) = @_;
- auth && auth->{user}{"perm_$act"}
-}
-
-
-# Generate a code to be used later on to validate that the form was indeed
-# submitted from our site and by the same user/visitor. Not limited to
-# logged-in users.
-# Arguments:
-# form-id (ignored nowadyas)
-# time (also ignored)
-sub authGetCode {
- auth->csrftoken;
-}
-
-
-# Validates the correctness of the returned code, creates an error page and
-# returns false if it's invalid, returns true otherwise. Codes are valid for at
-# least two and at most three hours.
-# Arguments:
-# [ form-id, [ code ] ]
-# If the code is not given, uses the 'formcode' form parameter instead. If
-# form-id is not given, the path of the current requests is used.
-sub authCheckCode {
- my $self = shift;
- my $id = shift;
- my $code = shift || $self->reqParam('formcode');
- return _incorrectcode($self) if !auth->csrfcheck($code);
- 1;
-}
-
-
-sub _incorrectcode {
- my $self = shift;
- $self->resInit;
- $self->htmlHeader(title => 'Validation code expired', noindex => 1);
-
- div class => 'mainbox';
- h1 'Validation code expired';
- div class => 'warning';
- p 'Please hit the back-button of your browser, refresh the page and try again.';
- end;
- end;
-
- $self->htmlFooter;
- return 0;
-}
-
-
-sub authPref {
- my(undef, $key, $val) = @_;
- @_ == 2 ? auth->pref($key)||'' : auth->prefSet($key, $val);
-}
-
-1;
diff --git a/lib/VNDB/Util/BrowseHTML.pm b/lib/VNDB/Util/BrowseHTML.pm
deleted file mode 100644
index 3eb460a6..00000000
--- a/lib/VNDB/Util/BrowseHTML.pm
+++ /dev/null
@@ -1,190 +0,0 @@
-
-package VNDB::Util::BrowseHTML;
-
-use strict;
-use warnings;
-use TUWF ':html', 'xml_escape';
-use Exporter 'import';
-use VNDB::Func;
-use VNDB::Types;
-use POSIX 'ceil';
-
-
-our @EXPORT = qw| htmlBrowse htmlBrowseNavigate htmlBrowseVN |;
-
-
-# generates a browse box, arguments:
-# items => arrayref with the list items
-# options => hashref containing at least the keys s (sort key), o (order) and p (page)
-# nextpage => whether there's a next page or not
-# sorturl => base URL to append the sort options to (if there are any sortable columns)
-# pageurl => base URL to append the page option to
-# class => classname of the mainbox
-# header =>
-# can be either an arrayref or subroutine reference,
-# in the case of a subroutine, it will be called when the header should be written,
-# in the case of an arrayref, the array should contain the header items. Each item
-# can again be either an arrayref or subroutine ref. The arrayref would consist of
-# two elements: the name of the header, and the name of the sorting column if it can
-# be sorted
-# row => subroutine ref, which is called for each item in $list, arguments will be
-# $self, $item_number (starting from 0), $item_value
-# footer => subroutine ref, called after all rows have been processed
-sub htmlBrowse {
- my($self, %opt) = @_;
-
- $opt{sorturl} .= $opt{sorturl} =~ /\?/ ? ';' : '?' if $opt{sorturl};
-
- # top navigation
- $self->htmlBrowseNavigate($opt{pageurl}, $opt{options}{p}, $opt{nextpage}, 't') if $opt{pageurl};
-
- div class => 'mainbox browse'.($opt{class} ? ' '.$opt{class} : '');
- table class => 'stripe';
-
- # header
- thead;
- Tr;
- if(ref $opt{header} eq 'CODE') {
- $opt{header}->($self);
- } else {
- for(0..$#{$opt{header}}) {
- if(ref $opt{header}[$_] eq 'CODE') {
- $opt{header}[$_]->($self, $_+1);
- } else {
- td class => $opt{header}[$_][3]||'tc'.($_+1), $opt{header}[$_][2] ? (colspan => $opt{header}[$_][2]) : ();
- lit $opt{header}[$_][0];
- if($opt{header}[$_][1]) {
- lit ' ';
- $opt{options}{s} eq $opt{header}[$_][1] && $opt{options}{o} eq 'a' ? lit "\x{25B4}" : a href => "$opt{sorturl}o=a;s=$opt{header}[$_][1]", "\x{25B4}";
- $opt{options}{s} eq $opt{header}[$_][1] && $opt{options}{o} eq 'd' ? lit "\x{25BE}" : a href => "$opt{sorturl}o=d;s=$opt{header}[$_][1]", "\x{25BE}";
- }
- end;
- }
- }
- }
- end;
- end 'thead';
-
- # footer
- if($opt{footer}) {
- tfoot;
- $opt{footer}->($self);
- end;
- }
-
- # rows
- $opt{row}->($self, $_+1, $opt{items}[$_])
- for 0..$#{$opt{items}};
-
- end 'table';
- end 'div';
-
- # bottom navigation
- $self->htmlBrowseNavigate($opt{pageurl}, $opt{options}{p}, $opt{nextpage}, 'b') if $opt{pageurl};
-}
-
-
-# creates next/previous buttons (tabs), if needed
-# 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) = @_;
- my($cnt, $pp) = ref($np) ? @$np : ($p+$np, 1);
- return if $p == 1 && $cnt <= $pp;
-
- $url .= $url =~ /\?/ ? ';p=' : '?p=' unless $na;
-
- my $tab = sub {
- my($page, $label) = @_;
- li;
- a href => $url.$page; lit $label; end;
- end;
- };
- my $ell = sub {
- use utf8;
- li class => 'ellipsis';
- b '⋯';
- end;
- };
- my $nc = 5; # max. number of buttons on each side
-
- div class => 'maintabs browsetabs '.($al eq 't' ? '' : 'bottom');
- ul;
- $p > 2 and ref $np and $tab->(1, '&laquo; first');
- $p > $nc+1 and ref $np and $ell->();
- $p > $_ and ref $np and $tab->($p-$_, $p-$_) for (reverse 2..($nc>$p-2?$p-2:$nc-1));
- $p > 1 and $tab->($p-1, '&lsaquo; previous');
- end;
-
- ul;
- my $l = ceil($cnt/$pp)-$p+1;
- $l > 1 and $tab->($p+1, 'next &rsaquo;');
- $l > $_ and $tab->($p+$_, $p+$_) for (2..($nc>$l-2?$l-2:$nc-1));
- $l > $nc+1 and $ell->();
- $l > 2 and $tab->($l+$p-1, 'last &raquo;');
- end;
- end 'div';
-}
-
-
-sub htmlBrowseVN {
- my($self, $list, $f, $np, $url, $tagscore) = @_;
- $self->htmlBrowse(
- class => 'vnbrowse',
- items => $list,
- options => $f,
- nextpage => $np,
- pageurl => "$url;o=$f->{o};s=$f->{s}",
- sorturl => $url,
- header => [
- $tagscore ? [ 'Score', 'tagscore', undef, 'tc_s' ] : (),
- [ 'Title', 'title', undef, $tagscore ? 'tc_t' : 'tc1' ],
- $f->{vnlist} ? [ '', 0, undef, 'tc7' ] : (),
- $f->{wish} ? [ '', 0, undef, 'tc8' ] : (),
- [ '', 0, undef, 'tc2' ],
- [ '', 0, undef, 'tc3' ],
- [ 'Released', 'rel', undef, 'tc4' ],
- [ 'Popularity', 'pop', undef, 'tc5' ],
- [ 'Rating', 'rating', undef, 'tc6' ],
- ],
- row => sub {
- my($s, $n, $l) = @_;
- Tr;
- if($tagscore) {
- td class => 'tc_s';
- VNWeb::TT::Lib::tagscore_($l->{tagscore});
- end;
- }
- td class => $tagscore ? 'tc_t' : 'tc1';
- a href => '/v'.$l->{id}, title => $l->{original}||$l->{title}, shorten $l->{title}, 100;
- end;
- if($f->{vnlist}) {
- td class => 'tc7';
- lit sprintf '<b class="%s">%d/%d</b>', $l->{userlist_obtained} == $l->{userlist_all} ? 'done' : 'todo', $l->{userlist_obtained}, $l->{userlist_all} if $l->{userlist_all};
- abbr title => join(', ', $l->{vnlist_labels}->@*), scalar $l->{vnlist_labels}->@* if $l->{vnlist_labels} && $l->{vnlist_labels}->@*;
- abbr title => 'No labels', ' ' if $l->{vnlist_labels} && !$l->{vnlist_labels}->@*;
- end 'td';
- }
- td class => 'tc2';
- $_ ne 'oth' && cssicon $_, $PLATFORM{$_}
- for (sort @{$l->{c_platforms}});
- end;
- td class => 'tc3';
- cssicon "lang $_", $LANGUAGE{$_}
- for (reverse sort @{$l->{c_languages}});
- end;
- td class => 'tc4';
- lit fmtdatestr $l->{c_released};
- end;
- td class => 'tc5', sprintf '%.2f', ($l->{c_popularity}||0)*100;
- td class => 'tc6';
- txt sprintf '%.2f', ($l->{c_rating}||0)/10;
- b class => 'grayedout', sprintf ' (%d)', $l->{c_votecount};
- end;
- end 'tr';
- },
- );
-}
-
-
-1;
-
diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm
deleted file mode 100644
index 85722f1b..00000000
--- a/lib/VNDB/Util/CommonHTML.pm
+++ /dev/null
@@ -1,34 +0,0 @@
-
-package VNDB::Util::CommonHTML;
-
-use strict;
-use warnings;
-use Exporter 'import';
-use VNDB::Func;
-
-our @EXPORT = qw|
- htmlMainTabs htmlDenied htmlSearchBox
-|;
-
-
-# generates the "main tabs". These are the commonly used tabs for
-# 'objects', i.e. VN/producer/release entries and users
-# Arguments: u/v/r/p/g/i/c/d, object, currently selected item (empty=main)
-sub htmlMainTabs {
- my($self, $type, $obj, $sel) = @_;
- $obj->{entry_hidden} = $obj->{hidden};
- $obj->{entry_locked} = $obj->{locked};
- VNWeb::HTML::_maintabs_({ type => $type, dbobj => $obj, tab => $sel||''});
-}
-
-
-# generates a full error page, including header and footer
-sub htmlDenied { shift->resDenied }
-
-
-sub htmlSearchBox {
- shift; VNWeb::HTML::searchbox_(@_);
-}
-
-
-1;
diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm
deleted file mode 100644
index a9e0c05f..00000000
--- a/lib/VNDB/Util/LayoutHTML.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-
-package VNDB::Util::LayoutHTML;
-
-use strict;
-use warnings;
-use TUWF ':html';
-use VNDB::Config;
-use VNWeb::HTML;
-use Exporter 'import';
-
-our @EXPORT = qw|htmlHeader htmlFooter|;
-
-sub htmlHeader { # %options->{ title, noindex, search, feeds, metadata }
- my($self, %o) = @_;
- %VNWeb::HTML::pagevars = ();
-
- $o{og} = $o{metadata} ? +{ map +(s/og://r, $o{metadata}{$_}), keys $o{metadata}->%* } : undef;
- $o{index} = !$o{noindex};
-
- html lang => 'en';
- head sub { VNWeb::HTML::_head_(\%o) };
- body;
- div id => 'bgright', ' ';
- div id => 'header', sub { h1 sub { a href => '/', 'the visual novel database' } };
- div id => 'menulist', sub { VNWeb::HTML::_menu_(\%o) };
- div id => 'maincontent';
-}
-
-
-sub htmlFooter { # %options => { pref_code => 1 }
- my($self, %o) = @_;
- div id => 'footer', sub { VNWeb::HTML::_footer_ };
- end 'div'; # maincontent
-
- # Abuse an empty noscript tag for the formcode to update a preference setting, if the page requires one.
- noscript id => 'pref_code', title => $self->authGetCode('/xml/prefs.xml'), ''
- if $o{pref_code} && $self->authInfo->{id};
- script type => 'text/javascript', src => config->{url_static}.'/g/vndb.js?'.config->{version}, '';
- VNWeb::HTML::_scripts_({});
- end 'body';
- end 'html';
-}
-
-1;
diff --git a/lib/VNDB/Util/Misc.pm b/lib/VNDB/Util/Misc.pm
deleted file mode 100644
index 6342c0c5..00000000
--- a/lib/VNDB/Util/Misc.pm
+++ /dev/null
@@ -1,93 +0,0 @@
-
-package VNDB::Util::Misc;
-
-use strict;
-use warnings;
-use Exporter 'import';
-use TUWF ':html';
-use VNDB::Func;
-use VNDB::Types;
-
-our @EXPORT = qw|filFetchDB filCompat|;
-
-
-our %filfields = (
- vn => [qw|date_before date_after released length hasani hasshot tag_inc tag_exc taginc tagexc tagspoil lang olang plat staff_inc staff_exc ul_notblack ul_onwish ul_voted ul_onlist|],
- release => [qw|type patch freeware doujin uncensored date_before date_after released minage lang olang resolution plat prod_inc prod_exc med voiced ani_story ani_ero engine|],
- char => [qw|gender bloodt bust_min bust_max waist_min waist_max hip_min hip_max height_min height_max va_inc va_exc weight_min weight_max cup_min cup_max trait_inc trait_exc tagspoil role|],
- staff => [qw|gender role truename lang|],
-);
-
-
-# Arguments:
-# type ('vn', 'release' or 'char'),
-# 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' : $type eq 'release' ? 'dbReleaseGet' : $type eq 'char' ? 'dbCharGet' : 'dbStaffGet');
- my $prefname = 'filter_'.$type;
- my $pref = $self->authPref($prefname);
-
- my $filters = fil_parse $overwrite // $pref, @{$filfields{$type}};
-
- VNWeb::Filters::debug_validate($type, $filters);
-
- # compatibility
- my $compat = $self->filCompat($type, $filters);
- $self->authPref($prefname => fil_serialize $filters) if $compat && !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 or !keys %$filters;;
-
- # 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->dbh;
- $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;
-}
-
-
-# Compatibility with old filters. Modifies the filter in-place and returns the number of changes made.
-sub filCompat {
- my($self, $type, $fil) = @_;
- $type eq 'vn' ? VNWeb::Filters::filter_vn_compat($fil) : 0
-}
-
-
-1;
-
diff --git a/lib/VNDB/Util/ValidateTemplates.pm b/lib/VNDB/Util/ValidateTemplates.pm
deleted file mode 100644
index e28abcb2..00000000
--- a/lib/VNDB/Util/ValidateTemplates.pm
+++ /dev/null
@@ -1,16 +0,0 @@
-# This module implements various templates for formValidate()
-
-package VNDB::Util::ValidateTemplates;
-
-use strict;
-use warnings;
-
-
-TUWF::set(
- validate_templates => {
- id => { template => 'uint', max => 1<<40 },
- page => { template => 'uint', max => 1000 },
- }
-);
-
-1;
diff --git a/lib/VNWeb/Filters.pm b/lib/VNWeb/Filters.pm
index 73f37e99..429a10f5 100644
--- a/lib/VNWeb/Filters.pm
+++ b/lib/VNWeb/Filters.pm
@@ -1,13 +1,13 @@
package VNWeb::Filters;
-# This module implements validating and querying the old search filters. These
-# filters are replaced with the new AdvSearch framework and this code only
-# exists to convert old URLs.
+# This module implements validating old search filters and converting them to
+# the new AdvSearch system. It only exists for compatibility with old URLs.
-use VNWeb::Prelude;
+use TUWF;
+use VNWeb::Validation;
use Exporter 'import';
-our @EXPORT = qw/filter_parse filter_vn_query filter_release_query filter_vn_adv filter_release_adv filter_char_adv filter_staff_adv/;
+our @EXPORT = qw/filter_parse filter_vn_adv filter_release_adv filter_char_adv filter_staff_adv/;
my $VN = form_compile any => {
@@ -87,17 +87,6 @@ my $STAFF = form_compile any => {
};
-sub debug_validate {
- my($type, $data) = @_;
- my $s = {vn => $VN, release => $RELEASE, char => $CHAR, staff => $STAFF}->{$type};
- my $v = $s->validate($data);
- if(!$v) {
- warn sprintf "Filter validation failed!\nData: %s\nError: %s", JSON::XS->new->canonical->pretty->encode($data), JSON::XS->new->canonical->pretty->encode($v->err);
- } else {
- #warn sprintf "Filter validated: %sSerialized: %s", JSON::XS->new->canonical->pretty->encode($v->data), VNDB::Func::fil_serialize($v->data);
- }
-}
-
# Compatibility with old VN filters. Modifies the filter in-place and returns the number of changes made.
sub filter_vn_compat {
@@ -131,12 +120,29 @@ sub filter_release_compat {
}
+
+my @fil_escape = split //, '_ !"#$%&\'()*+,-./:;<=>?@[\]^`{}~';
+
+sub _fil_parse {
+ my $str = shift;
+ my %r;
+ for (split /\./, $str) {
+ next if !/^([a-z0-9_]+)-([a-zA-Z0-9_~\x81-\x{ffffff}]+)$/;
+ 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;
+}
+
+
# Throws error on failure.
sub filter_parse {
my($type, $str) = @_;
return {} if !$str;
my $s = {v => $VN, r => $RELEASE, c => $CHAR, s => $STAFF}->{$type};
- my $data = ref $str ? $str : $str =~ /^{/ ? JSON::XS->new->decode($str) : VNDB::Func::fil_parse $str, keys $s->{known_keys}->%*;
+ my $data = ref $str ? $str : $str =~ /^{/ ? JSON::XS->new->decode($str) : _fil_parse $str;
die "Invalid filter data: $str\n" if !$data;
my $f = $s->validate($data)->data;
filter_vn_compat $f if $type eq 'v';
@@ -145,67 +151,6 @@ sub filter_parse {
}
-# Returns an SQL expression for use in a WHERE clause. Assumption: 'v' is an alias to the vn table being queried.
-sub filter_vn_query {
- my($fil) = @_;
- sql_and
- defined $fil->{date_before} ? sql 'v.c_released <=', \$fil->{date_before} : (),
- defined $fil->{date_after} ? sql 'v.c_released >=', \$fil->{date_after} : (),
- defined $fil->{released} ? sql 'v.c_released', $fil->{released} ? '<=' : '>', \strftime('%Y%m%d', gmtime) : (),
- defined $fil->{length} ? sql 'v.length IN', $fil->{length} : (),
- defined $fil->{hasani} ? sql($fil->{hasani} ?'':'NOT', 'EXISTS(SELECT 1 FROM vn_anime iva WHERE iva.id = v.id)') : (),
- defined $fil->{hasshot} ? sql($fil->{hasshot}?'':'NOT', 'EXISTS(SELECT 1 FROM vn_screenshots ivs WHERE ivs.id = v.id)') : (),
- defined $fil->{tag_inc} ? sql
- 'v.id IN(SELECT vid FROM tags_vn_inherit WHERE tag IN', $fil->{tag_inc}, 'AND spoiler <=', \$fil->{tagspoil}, 'GROUP BY vid HAVING COUNT(tag) =', scalar $fil->{tag_inc}->@*, ')' : (),
- defined $fil->{tag_exc} ? sql 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN', $fil->{tag_exc}, ')' : (),
- defined $fil->{lang} ? sql 'v.c_languages && ARRAY', $fil->{lang}, '::language[]' : (),
- defined $fil->{olang} ? sql 'v.c_olang && ARRAY', $fil->{olang}, '::language[]' : (),
- defined $fil->{plat} ? sql 'v.c_platforms && ARRAY', $fil->{plat}, '::platform[]' : (),
- defined $fil->{staff_inc} ? sql 'v.id IN(SELECT ivs.id FROM vn_staff ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN', $fil->{staff_inc}, ')' : (),
- defined $fil->{staff_exc} ? sql 'v.id NOT IN(SELECT ivs.id FROM vn_staff ivs JOIN staff_alias isa ON isa.aid = ivs.aid WHERE isa.id IN', $fil->{staff_exc}, ')' : (),
- auth ? (
- # TODO: onwish, voted and onlist should respect the label filters in users.ulist_*
- defined $fil->{ul_notblack} ? sql 'v.id NOT IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \auth->uid, 'AND lbl =', \6, ')' : (),
- defined $fil->{ul_onwish} ? sql 'v.id', $fil->{ul_onwish}?'':'NOT', 'IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \auth->uid, 'AND lbl =', \5, ')' : (),
- defined $fil->{ul_voted} ? sql 'v.id', $fil->{ul_voted} ?'':'NOT', 'IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \auth->uid, 'AND lbl =', \7, ')' : (),
- defined $fil->{ul_onlist} ? sql 'v.id', $fil->{ul_onlist}?'':'NOT', 'IN(SELECT vid FROM ulist_vns WHERE uid =', \auth->uid, ')' : (),
- ) : (),
-}
-
-
-# Assumption: 'r' is an alias to the release table being queried.
-sub filter_release_query {
- my($fil) = @_;
- sql_and
- defined $fil->{type} ? sql 'r.type =', \$fil->{type} : (),
- defined $fil->{patch} ? sql($fil->{patch} ?'':'NOT', 'r.patch' ) : (),
- defined $fil->{freeware} ? sql($fil->{freeware} ?'':'NOT', 'r.freeware' ) : (),
- defined $fil->{doujin} ? sql($fil->{doujin} ?'':'NOT', 'r.doujin AND NOT r.patch') : (),
- defined $fil->{uncensored} ? sql($fil->{uncensored}?'':'NOT', 'r.uncensored') : (),
- defined $fil->{date_before} ? sql 'r.released <=', \$fil->{date_before} : (),
- defined $fil->{date_after} ? sql 'r.released >=', \$fil->{date_after} : (),
- defined $fil->{released} ? sql 'r.released', $fil->{released} ? '<=' : '>', \strftime('%Y%m%d', gmtime) : (),
- defined $fil->{minage} ? sql 'r.minage IN', $fil->{minage} : (),
- defined $fil->{lang} ? sql 'r.id IN(SELECT irl.id FROM releases_lang irl WHERE irl.lang IN', $fil->{lang}, ')' : (),
- defined $fil->{olang} ? sql 'r.id IN(SELECT irv.id FROM releases_vn irv JOIN vn iv ON irv.vid = iv.id WHERE iv.c_olang && ARRAY', $fil->{olang}, '::language[])' : (),
- defined $fil->{resolution} ? sql 'NOT r.patch AND ARRAY[r.reso_x,r.reso_y] IN', [ map $_ eq 'unknown' ? '{0,0}' : $_ eq 'nonstandard' ? '{0,1}' : '{'.(s/x/,/r).'}', $fil->{resolution}->@* ] : (),
- defined $fil->{plat} ? sql_or(
- grep( /^unk$/, $fil->{plat}->@*) ? sql 'NOT EXISTS(SELECT 1 FROM releases_platforms irp WHERE irp.id = r.id)' : (),
- grep(!/^unk$/, $fil->{plat}->@*) ? sql 'r.id IN(SELECT irp.id FROM releases_platforms irp WHERE irp.platform IN', [grep !/^unk$/, $fil->{plat}->@*], ')' : (),
- ) : (),
- defined $fil->{prod_inc} ? sql 'r.id IN(SELECT irp.id FROM releases_producers irp WHERE irp.pid IN', $fil->{prod_inc}, ')' : (),
- defined $fil->{prod_exc} ? sql 'r.id NOT IN(SELECT irp.id FROM releases_producers irp WHERE irp.pid IN', $fil->{prod_exc}, ')' : (),
- defined $fil->{med} ? sql_or(
- grep( /^unk$/, $fil->{med}->@*) ? sql 'NOT EXISTS(SELECT 1 FROM releases_media irm WHERE irm.id = r.id)' : (),
- grep(!/^unk$/, $fil->{med}->@*) ? sql 'r.id IN(SELECT irm.id FROM releases_media irm WHERE irm.medium IN', [grep !/^unk$/, $fil->{med}->@*], ')' : (),
- ) : (),
- defined $fil->{voiced} ? sql 'NOT r.patch AND r.voiced IN', $fil->{voiced} : (),
- defined $fil->{ani_story} ? sql 'NOT r.patch AND r.ani_story IN', $fil->{ani_story} : (),
- defined $fil->{ani_ero} ? sql 'NOT r.patch AND r.ani_ero IN', $fil->{ani_ero} : (),
- defined $fil->{engine} ? sql 'r.engine =', \$fil->{engine} : (),
-}
-
-
sub filter_vn_adv {
my($fil) = @_;
[ 'and',
diff --git a/lib/VNWeb/HTML.pm b/lib/VNWeb/HTML.pm
index a52aacce..7d8751c3 100644
--- a/lib/VNWeb/HTML.pm
+++ b/lib/VNWeb/HTML.pm
@@ -473,17 +473,6 @@ sub _hidden_msg_ {
}
-sub _scripts_ {
- my($o) = @_;
- script_ type => 'application/json', id => 'pagevars', sub {
- # Escaping rules for a JSON <script> context are kinda weird, but more efficient than regular xml_escape().
- lit_(JSON::XS->new->canonical->encode(tuwf->req->{pagevars}) =~ s{</}{<\\/}rg =~ s/<!--/<\\u0021--/rg);
- } if keys tuwf->req->{pagevars}->%*;
- script_ type => 'application/javascript', src => config->{url_static}.'/g/elm.js?'.config->{version}, '' if tuwf->req->{pagevars}{elm};
- script_ type => 'application/javascript', src => config->{url_static}.'/g/plain.js?'.config->{version}, '' if tuwf->req->{js} || tuwf->req->{pagevars}{elm};
-}
-
-
# Options:
# title => $title
# index => 1/0, default 0
@@ -515,7 +504,12 @@ sub framework_ {
$cont->() unless $o{hiddenmsg} && _hidden_msg_ \%o;
div_ id => 'footer', \&_footer_;
};
- _scripts_ \%o;
+ script_ type => 'application/json', id => 'pagevars', sub {
+ # Escaping rules for a JSON <script> context are kinda weird, but more efficient than regular xml_escape().
+ lit_(JSON::XS->new->canonical->encode(tuwf->req->{pagevars}) =~ s{</}{<\\/}rg =~ s/<!--/<\\u0021--/rg);
+ } if keys tuwf->req->{pagevars}->%*;
+ script_ type => 'application/javascript', src => config->{url_static}.'/g/elm.js?'.config->{version}, '' if tuwf->req->{pagevars}{elm};
+ script_ type => 'application/javascript', src => config->{url_static}.'/g/plain.js?'.config->{version}, '' if tuwf->req->{js} || tuwf->req->{pagevars}{elm};
}
}
}
diff --git a/util/jsgen.pl b/util/jsgen.pl
deleted file mode 100755
index a4023ff8..00000000
--- a/util/jsgen.pl
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-use Encode 'encode_utf8';
-use Cwd 'abs_path';
-use JSON::XS;
-
-my $ROOT;
-BEGIN { ($ROOT = abs_path $0) =~ s{/util/jsgen\.pl$}{}; }
-
-use lib "$ROOT/lib";
-use VNDB::Config;
-use VNDB::Types;
-
-
-sub vars {
- my %vars = (
- rlist_status => [ map [ $_, $RLIST_STATUS{$_} ], keys %RLIST_STATUS ],
- cookie_prefix => config->{tuwf}{cookie_prefix},
- age_ratings => [ map [ $_, $AGE_RATING{$_}{txt}], keys %AGE_RATING ],
- languages => [ map [ $_, $LANGUAGE{$_} ], sort { $LANGUAGE{$a} cmp $LANGUAGE{$b} } keys %LANGUAGE ],
- platforms => [ map [ $_, $PLATFORM{$_} ], keys %PLATFORM ],
- char_roles => [ map [ $_, $CHAR_ROLE{$_}{txt} ], keys %CHAR_ROLE ],
- media => [ map [ $_, $MEDIUM{$_}{txt}, $MEDIUM{$_}{qty} ], keys %MEDIUM ],
- release_types => [ map [ $_, $RELEASE_TYPE{$_} ], keys %RELEASE_TYPE ],
- animated => [ map [ $_, $ANIMATED{$_}{txt} ], keys %ANIMATED ],
- voiced => [ map [ $_, $VOICED{$_}{txt} ], keys %VOICED ],
- vn_lengths => [ map [ $_, $VN_LENGTH{$_}{txt} ], keys %VN_LENGTH ],
- blood_types => [ map [ $_, $BLOOD_TYPE{$_} ], keys %BLOOD_TYPE ],
- genders => [ map [ $_, $GENDER{$_} ], keys %GENDER ],
- credit_type => [ map [ $_, $CREDIT_TYPE{$_} ], keys %CREDIT_TYPE ],
- cup_size => [ grep $_, keys %CUP_SIZE ],
- );
- JSON::XS->new->encode(\%vars);
-}
-
-
-# Reads main.js and any included files.
-sub readjs {
- my $f = shift || 'main.js';
- open my $JS, '<:utf8', "$ROOT/data/js/$f" or die $!;
- local $/ = undef;
- local $_ = <$JS>;
- close $JS;
- s{^//include (.+)$}{'(function(){'.readjs($1).'})();'}meg;
- $_;
-}
-
-
-sub save {
- my($f, $body) = @_;
- open my $F, '>', "$f~" or die $!;
- print $F encode_utf8($body);
- close $F;
- rename "$f~", $f or die $!;
-}
-
-
-my $js = readjs;
-$js =~ s{/\*VARS\*/}{vars()}eg;
-save "$ROOT/static/g/vndb.js", $js;
diff --git a/util/sql b/util/sql
deleted file mode 120000
index 44657b95..00000000
--- a/util/sql
+++ /dev/null
@@ -1 +0,0 @@
-../sql \ No newline at end of file
diff --git a/util/vndb.pl b/util/vndb.pl
index 29ef026b..f7c94044 100755
--- a/util/vndb.pl
+++ b/util/vndb.pl
@@ -91,27 +91,22 @@ sub TUWF::Object::resDenied {
# Intercept TUWF::any() and TUWF::register() to figure out which module is processing the request.
if(config->{trace_log}) {
- my sub wrap {
- my $f = shift;
- sub {
- my $i = 0;
- my $loc = ['',0];
- while(my($pack, undef, $line) = caller($i++)) {
- if($pack !~ '^(?:main|TUWF|VNWeb::Elm)') {
- $loc = [$pack,$line];
- last;
- }
+ no warnings 'redefine';
+ my $f = \&TUWF::any;
+ *TUWF::any = sub {
+ my($meth, $path, $sub) = @_;
+ my $i = 0;
+ my $loc = ['',0];
+ while(my($pack, undef, $line) = caller($i++)) {
+ if($pack !~ '^(?:main|TUWF|VNWeb::Elm)') {
+ $loc = [$pack,$line];
+ last;
}
- my sub subwrap { my $sub = shift; sub { tuwf->req->{trace_loc} = $loc; $sub->(@_) } }
- $f->(map ref($_) eq 'CODE' ? subwrap($_) : $_, @_)
}
- }
- no warnings 'redefine';
- my $x = \&TUWF::register; *TUWF::register = wrap($x);
- my $y = \&TUWF::any; *TUWF::any = wrap($y);
+ $f->($meth, $path, sub { tuwf->req->{trace_loc} = $loc; $sub->(@_) });
+ };
}
-TUWF::load_recursive('VNDB::Util', 'VNDB::DB', 'VNDB::Handler');
TUWF::set import_modules => 0;
TUWF::load_recursive('VNWeb');