Initial commit.

This commit is contained in:
2026-03-10 21:25:26 +08:00
commit 78739bf725
3089 changed files with 472990 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,943 @@
-- 檔案名稱: emily.sql
-- 程式說明: 吳芳美網站資料庫定義檔
-- 程式作者: 依瑪貓 imacat <imacat@mail.imacat.idv.tw>
-- 初稿日期: 2004-10-16
-- 版權字樣: 版權所有 (c) 2004-2020 依瑪貓
SET NAMES 'utf8';
START TRANSACTION;
--
-- Table structure for table "mtime"
--
CREATE TABLE mtime (
tabname varchar(16) NOT NULL PRIMARY KEY,
mtime timestamp NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE, DELETE ON mtime TO nobody;
--
-- Function definition for function "eschtml"
--
-- integer eschtml(source text)
CREATE FUNCTION eschtml(source text) RETURNS text AS '
DECLARE
result text;
BEGIN
result := source;
result := replace(result, ''&'', ''&amp;'');
result := replace(result, ''<'', ''&lt;'');
result := replace(result, ''>'', ''&gt;'');
result := replace(result, ''"'', ''&quot;'');
return result;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION eschtml(source text) TO nobody;
--
-- Table structure for table "country"
--
CREATE TABLE country (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
id char(2) NOT NULL UNIQUE CHECK (position(' ' in id) = 0),
name_en varchar(64) NOT NULL CHECK (name_en != ''),
name_zhtw varchar(32) CHECK (name_zhtw IS NULL OR name_zhtw != ''),
special boolean NOT NULL DEFAULT FALSE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL
);
GRANT SELECT, INSERT, UPDATE, DELETE ON country TO nobody;
--
-- Table structure for table "users"
--
CREATE TABLE users (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
id varchar(32) NOT NULL UNIQUE CHECK (char_length(id) >= 3),
passwd char(32) NOT NULL,
name varchar(32) NOT NULL CHECK (name != ''),
disabled boolean NOT NULL DEFAULT FALSE,
deleted boolean NOT NULL DEFAULT FALSE,
lang varchar(5) CHECK (lang IS NULL OR lang != ''),
visits smallint NOT NULL DEFAULT 0 CHECK (visits >= 0),
visited timestamp,
ip inet,
host varchar(128),
ct char(2) REFERENCES country (id) ON UPDATE CASCADE DEFERRABLE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON users TO nobody;
CREATE VIEW users_list AS
SELECT
users.sn AS sn,
users.id AS id,
users.name AS name,
CASE WHEN users.disabled THEN '停用'
ELSE ''
END AS disabled,
CASE WHEN users.deleted THEN '已刪'
ELSE ''
END AS deleted,
CASE WHEN users.lang IS NULL THEN '(無)'
ELSE CASE users.lang
WHEN 'en' THEN '英文'
WHEN 'zh-tw' THEN '繁體中文'
WHEN 'zh-cn' THEN '簡體中文'
WHEN 'ja' THEN '日文'
WHEN 'de' THEN '德文'
WHEN 'es' THEN '西班牙文'
ELSE users.lang
END
END AS lang,
users.visits AS visits,
CASE WHEN users.visited IS NULL THEN '(無)'
ELSE to_char(users.visited, 'YYYY-MM-DD HH:MI:SS')
END AS visited,
CASE WHEN users.ip IS NULL THEN '(無)'
ELSE host(users.ip)
END AS ip,
CASE WHEN users.host IS NULL THEN '(無)'
ELSE users.host
END AS host,
CASE WHEN users.ct IS NULL THEN '(無)'
ELSE ct.name_zhtw
END AS ct,
to_char(users.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(users.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM users
LEFT JOIN country AS ct ON users.ct = ct.id
LEFT JOIN users AS createdby ON users.createdby = createdby.sn
LEFT JOIN users AS updatedby ON users.updatedby = updatedby.sn
ORDER BY id;
GRANT SELECT ON users_list TO nobody;
-- INSERT INTO users (sn, id, passwd, name, disabled, deleted, lang, visits, visited, ip, host, created, createdby, updated, updatedby) VALUES (923153018, 'imacat', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', '依瑪貓', FALSE, FALSE, NULL, 0, NULL, NULL, NULL, now(), 923153018, now(), 923153018);
-- INSERT INTO users (sn, id, passwd, name, disabled, deleted, lang, visits, visited, ip, host, created, createdby, updated, updatedby) VALUES (460376330, 'mandy', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', '小招', FALSE, FALSE, NULL, 0, NULL, NULL, NULL, now(), 923153018, now(), 923153018);
-- INSERT INTO users (sn, id, passwd, name, disabled, deleted, lang, visits, visited, ip, host, created, createdby, updated, updatedby) VALUES (723676436, 'guestbook', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', '留言本', FALSE, FALSE, NULL, 0, NULL, NULL, NULL, now(), 923153018, now(), 923153018);
--
-- Fixing the country table
--
ALTER TABLE country ADD FOREIGN KEY (createdby) REFERENCES users ON UPDATE CASCADE DEFERRABLE;
ALTER TABLE country ADD FOREIGN KEY (updatedby) REFERENCES users ON UPDATE CASCADE DEFERRABLE;
CREATE VIEW country_list AS
SELECT
country.sn AS sn,
country.id AS id,
COALESCE(country.name_zhtw, country.name_en) AS title,
CASE WHEN country.special THEN '特殊'
ELSE ''
END AS special,
to_char(country.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(country.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM country
LEFT JOIN users AS createdby ON country.createdby = createdby.sn
LEFT JOIN users AS updatedby ON country.updatedby = updatedby.sn
ORDER BY id;
GRANT SELECT ON country_list TO nobody;
--
-- Table structure for table "groups"
--
CREATE TABLE groups (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
id varchar(16) NOT NULL UNIQUE CHECK (char_length(id) >= 3),
dsc varchar(64) NOT NULL CHECK (dsc != ''),
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON groups TO nobody;
CREATE VIEW groups_list AS
SELECT
groups.sn AS sn,
groups.id AS id,
groups.dsc AS dsc,
to_char(groups.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(groups.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM groups
LEFT JOIN users AS createdby ON groups.createdby = createdby.sn
LEFT JOIN users AS updatedby ON groups.updatedby = updatedby.sn
ORDER BY id;
GRANT SELECT ON groups_list TO nobody;
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (553229108, 'root', '總管理員', now(), 923153018, now(), 923153018);
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (802339805, 'guests', '暱名訪客', now(), 923153018, now(), 923153018);
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (958210993, 'users', '已登入使用者', now(), 923153018, now(), 923153018);
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (329685674, 'admin', '所有網站管理員', now(), 923153018, now(), 923153018);
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (157696540, 'acctman', '帳號管理員', now(), 923153018, now(), 923153018);
-- INSERT INTO groups (sn, id, dsc, created, createdby, updated, updatedby) VALUES (390105230, 'editor', '網站編輯', now(), 923153018, now(), 923153018);
--
-- Table structure for table "usermem"
--
CREATE TABLE usermem (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
grp int NOT NULL REFERENCES groups ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE,
member int NOT NULL REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
UNIQUE (grp, member)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON usermem TO nobody;
CREATE VIEW usermem_list AS
SELECT
usermem.sn AS sn,
groups.id || ' (' || groups.dsc || ')' AS grp,
members.id || ' (' || members.name || ')' AS member,
to_char(usermem.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(usermem.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM usermem
LEFT JOIN groups ON usermem.grp = groups.sn
LEFT JOIN users AS members ON usermem.member = members.sn
LEFT JOIN users AS createdby ON usermem.createdby = createdby.sn
LEFT JOIN users AS updatedby ON usermem.updatedby = updatedby.sn
ORDER BY grp, member;
GRANT SELECT ON usermem_list TO nobody;
-- INSERT INTO usermem (sn, grp, member, created, createdby, updated, updatedby) VALUES (593684712, 553229108, 923153018, now(), 923153018, now(), 923153018);
--
-- Table structure for table "groupmem"
--
CREATE TABLE groupmem (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
grp int NOT NULL REFERENCES groups ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE,
member int NOT NULL REFERENCES groups ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE CHECK (member != grp),
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
UNIQUE (grp, member)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON groupmem TO nobody;
CREATE VIEW groupmem_list AS
SELECT
groupmem.sn AS sn,
groups.id || ' (' || groups.dsc || ')' AS grp,
members.id || ' (' || members.dsc || ')' AS member,
to_char(groupmem.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(groupmem.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM groupmem
LEFT JOIN groups ON groupmem.grp = groups.sn
LEFT JOIN groups AS members ON groupmem.member = members.sn
LEFT JOIN users AS createdby ON groupmem.createdby = createdby.sn
LEFT JOIN users AS updatedby ON groupmem.updatedby = updatedby.sn
ORDER BY grp, member;
GRANT SELECT ON groupmem_list TO nobody;
-- INSERT INTO groupmem (sn, grp, member, created, createdby, updated, updatedby) VALUES (569742102, 329685674, 157696540, now(), 923153018, now(), 923153018);
-- INSERT INTO groupmem (sn, grp, member, created, createdby, updated, updatedby) VALUES (859385977, 329685674, 390105230, now(), 923153018, now(), 923153018);
--
-- Table structure for table "scptpriv"
--
CREATE TABLE scptpriv (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
script varchar(64) NOT NULL CHECK (script != ''),
grp int NOT NULL REFERENCES groups ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
UNIQUE (script, grp)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON scptpriv TO nobody;
CREATE VIEW scptpriv_list AS
SELECT
scptpriv.sn AS sn,
scptpriv.script AS script,
groups.dsc AS grp,
to_char(scptpriv.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(scptpriv.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM scptpriv
LEFT JOIN groups ON scptpriv.grp = groups.sn
LEFT JOIN users AS createdby ON scptpriv.createdby = createdby.sn
LEFT JOIN users AS updatedby ON scptpriv.updatedby = updatedby.sn
ORDER BY script, grp;
GRANT SELECT ON scptpriv_list TO nobody;
--
-- Table structure for table "userpref"
--
CREATE TABLE userpref (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
usr int REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE,
domain varchar(64) CHECK (domain IS NULL OR domain != ''),
name varchar(16) NOT NULL CHECK (name != ''),
value varchar(255) NOT NULL,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
UNIQUE (usr, domain, name)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON userpref TO nobody;
CREATE VIEW userpref_list AS
SELECT
userpref.sn AS sn,
CASE WHEN userpref.usr IS NOT NULL THEN users.name
ELSE '所有人'
END AS usr,
CASE WHEN userpref.domain IS NOT NULL THEN userpref.domain
ELSE '所有地方'
END AS domain,
userpref.name AS name,
userpref.value AS value,
to_char(userpref.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(userpref.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM userpref LEFT JOIN users ON userpref.usr = users.sn
LEFT JOIN users AS createdby ON userpref.createdby = createdby.sn
LEFT JOIN users AS updatedby ON userpref.updatedby = updatedby.sn
ORDER BY domain, usr, name;
GRANT SELECT ON userpref_list TO nobody;
--
-- Function definitions for table "guestbook"
--
-- integer guestbook_oldlen(date timestamp, ip inet, hostname text, name text, identity text, location text, email text, url text, message text, updated timestamp, updatedby_arg integer)
CREATE FUNCTION guestbook_oldlen(date timestamp, ip inet, hostname text, name text, identity text, location text, email text, url text, message text, updated timestamp, updatedby_arg integer) RETURNS integer AS '
DECLARE
row record;
len integer;
BEGIN
-- <record></record>: 19, sn: 30 + 1, date: 54 + 1
len := 105;
len := len + octet_length(host(ip)) + 22;
IF hostname IS NOT NULL THEN
len := len + octet_length(hostname) + 24;
END IF;
IF name IS NOT NULL THEN
len := len + octet_length(eschtml(name)) + 24;
END IF;
IF identity IS NOT NULL THEN
len := len + octet_length(eschtml(identity)) + 28;
END IF;
IF location IS NOT NULL THEN
len := len + octet_length(eschtml(location)) + 28;
END IF;
IF email IS NOT NULL THEN
len := len + octet_length(eschtml(email)) + 25;
END IF;
IF url IS NOT NULL THEN
len := len + octet_length(eschtml(url)) + 23;
END IF;
IF message IS NOT NULL THEN
len := len + octet_length(eschtml(message)) + 27;
END IF;
IF updated != date THEN
len := len + 58;
IF updatedby_arg = 923153018 THEN
len := len + 35;
ELSIF updatedby_arg = 460376330 THEN
len := len + 34;
ELSE
SELECT INTO row name FROM users WHERE sn=updatedby_arg;
len := len + octet_length(row.name) + 29;
END IF;
END IF;
RETURN len;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION guestbook_oldlen(date timestamp, ip inet, hostname text, name text, identity text, location text, email text, url text, message text, updated timestamp, updatedby_arg integer) TO nobody;
-- integer guestbook_oldlen(sn_arg integer)
CREATE FUNCTION guestbook_oldlen(sn_arg integer) RETURNS integer AS '
DECLARE
row record;
BEGIN
IF sn_arg IS NULL THEN
RETURN NULL;
END IF;
SELECT INTO row * FROM guestbook WHERE sn=sn_arg;
IF NOT FOUND THEN
RETURN NULL;
END IF;
RETURN guestbook_oldlen(row.created, row.ip, row.host, row.name, row.identity, row.location, row.email, row.url, row.message, row.updated, row.updatedby);
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION guestbook_oldlen(sn_arg integer) TO nobody;
--
-- Table structure for table "guestbook"
--
CREATE TABLE guestbook (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
name varchar(32) CHECK (name IS NULL OR name != ''),
identity varchar(64) CHECK (identity IS NULL OR identity != ''),
location varchar(64) CHECK (location IS NULL OR location != ''),
email varchar(64) CHECK (email IS NULL OR email != ''),
url varchar(128) CHECK (url IS NULL OR (url != '' AND url != 'http://')),
message text NOT NULL CHECK (message != ''),
hid boolean NOT NULL DEFAULT FALSE,
ip inet NOT NULL,
host varchar(255) CHECK (host IS NULL OR host != ''),
ct char(2) NOT NULL REFERENCES country (id) ON UPDATE CASCADE DEFERRABLE,
pageno int NOT NULL,
oldpageno int,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON guestbook TO nobody;
CREATE VIEW guestbook_list AS
SELECT
'/cgi-bin/guestbook.cgi?pageno=' || guestbook.pageno
|| '#msg' || guestbook.sn
AS _viewurl,
guestbook.sn AS sn,
to_char(guestbook.created, 'YYYY-MM-DD') AS date,
COALESCE(guestbook.name, '(未設定)') AS name,
COALESCE(guestbook.identity, '(未設定)') AS identity,
COALESCE(guestbook.location, '(未設定)') AS location,
COALESCE(guestbook.email, '(未設定)') AS email,
COALESCE(guestbook.url, '(未設定)') AS url,
guestbook.message AS message,
CASE WHEN guestbook.hid THEN '隱藏'
ELSE '秀出'
END AS hid,
host(guestbook.ip) AS ip,
COALESCE(guestbook.host, '(不可考)') AS host,
COALESCE(country.name_zhtw, country.name_en) AS ct,
guestbook.pageno AS pageno,
guestbook.oldpageno AS oldpageno,
to_char(guestbook.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(guestbook.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM guestbook
LEFT JOIN country ON guestbook.ct = country.id
LEFT JOIN users AS createdby ON guestbook.createdby = createdby.sn
LEFT JOIN users AS updatedby ON guestbook.updatedby = updatedby.sn
ORDER BY guestbook.created DESC;
GRANT SELECT ON guestbook_list TO nobody;
CREATE VIEW guestbook_public AS
SELECT
sn AS sn,
extract(epoch FROM created) AS date,
name AS name,
identity AS identity,
location AS location,
email AS email,
url AS url,
message AS message,
pageno AS pageno,
oldpageno AS oldpageno
FROM guestbook
WHERE NOT hid
ORDER BY guestbook.created DESC;
GRANT SELECT ON guestbook_public TO nobody;
--
-- Table structure for table "pages"
--
CREATE TABLE pages (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
path varchar(64) NOT NULL UNIQUE CHECK (path != ''),
ord smallint NOT NULL DEFAULT 5000 CHECK (ord >= 0 AND ord < 10000),
title varchar(128) NOT NULL CHECK (title != ''),
body text NOT NULL CHECK (body != ''),
kw varchar(128) NOT NULL CHECK (kw != ''),
html boolean NOT NULL DEFAULT FALSE,
hid boolean NOT NULL DEFAULT FALSE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON pages TO nobody;
CREATE VIEW pages_list AS
SELECT
pages.path AS _viewurl,
pages.sn AS sn,
pages.path AS path,
pages.ord AS ord,
pages.title AS title,
pages.body AS body,
pages.kw AS kw,
CASE WHEN pages.html THEN 'HTML'
ELSE '純文字'
END AS html,
CASE WHEN pages.hid THEN '隱藏'
ELSE '秀出'
END AS hid,
to_char(pages.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(pages.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM pages
LEFT JOIN users AS createdby ON pages.createdby = createdby.sn
LEFT JOIN users AS updatedby ON pages.updatedby = updatedby.sn
ORDER BY path;
GRANT SELECT ON pages_list TO nobody;
--
-- Function definitions for table "links"
--
-- boolean linkcat_id_unique(id_arg text, sn_arg integer, parent_arg integer);
CREATE FUNCTION linkcat_id_unique(id_arg text, sn_arg integer, parent_arg integer) RETURNS boolean AS '
BEGIN
IF parent_arg IS NULL THEN
-- TODO: 2019/3/9 Removed schema "public"? Added or it will not work on restore. Not knowing why.
PERFORM * FROM public.linkcat
WHERE id=id_arg AND sn!=sn_arg AND parent IS NULL;
RETURN NOT FOUND;
ELSE
-- TODO: 2019/3/9 Removed schema "public"? Added or it will not work on restore. Not knowing why.
PERFORM * FROM public.linkcat
WHERE id=id_arg AND sn!=sn_arg AND parent=parent_arg;
RETURN NOT FOUND;
END IF;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_id_unique(id_arg text, sn_arg integer, parent_arg integer) TO nobody;
-- numeric linkcat_fullord(parent_arg integer, ord_arg integer);
CREATE FUNCTION linkcat_fullord(parent_arg integer, ord_arg integer) RETURNS numeric AS '
DECLARE
row record;
sn_loop integer;
ord_loop numeric;
BEGIN
sn_loop := parent_arg;
ord_loop := ord_arg;
WHILE sn_loop IS NOT NULL LOOP
SELECT INTO row parent, ord FROM linkcat WHERE sn=sn_loop;
IF NOT FOUND THEN
RETURN NULL;
END IF;
sn_loop := row.parent;
ord_loop := row.ord + ord_loop / 100;
END LOOP;
RETURN ord_loop;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_fullord(parent_arg integer, ord_arg integer) TO nobody;
-- numeric linkcat_fullord(sn_arg integer);
CREATE FUNCTION linkcat_fullord(sn_arg integer) RETURNS numeric AS '
BEGIN
RETURN linkcat_fullord(sn_arg, 0);
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_fullord(sn_arg integer) TO nobody;
-- boolean linkcat_isshown(sn_arg integer, hid_arg boolean, parent_arg integer);
CREATE FUNCTION linkcat_isshown(sn_arg integer, hid_arg boolean, parent_arg integer) RETURNS boolean AS '
DECLARE
row record;
sn_loop integer;
BEGIN
IF hid_arg THEN
RETURN FALSE;
END IF;
-- Check if we are hidden by our ancestors
sn_loop := parent_arg;
WHILE sn_loop IS NOT NULL LOOP
SELECT INTO row parent, hid FROM linkcat WHERE sn=sn_loop;
IF row.hid THEN
RETURN FALSE;
END IF;
sn_loop = row.parent;
END LOOP;
-- Check if we have childs
PERFORM links.sn FROM links
INNER JOIN linkcatz ON linkcatz.link=links.sn
WHERE NOT links.hid AND linkcatz.cat=sn_arg;
IF FOUND THEN
RETURN TRUE;
END IF;
-- Check if we have shown child categories
PERFORM sn FROM linkcat WHERE parent=sn_arg AND linkcat_isshown(sn, hid, parent);
IF FOUND THEN
RETURN TRUE;
END IF;
RETURN FALSE;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_isshown(sn_arg integer, hid_arg boolean, parent_arg integer) TO nobody;
-- boolean linkcat_isshown(sn_arg integer);
CREATE FUNCTION linkcat_isshown(sn_arg integer) RETURNS boolean AS '
DECLARE
row record;
BEGIN
SELECT INTO row parent, hid FROM linkcat WHERE sn=sn_arg;
IF NOT FOUND THEN
RETURN NULL;
END IF;
RETURN linkcat_isshown(sn_arg, row.hid, row.parent);
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_isshown(sn_arg integer) TO nobody;
-- boolean linkcat_isshown_preview(sn_arg integer, hid_arg boolean, parent_arg integer);
CREATE FUNCTION linkcat_isshown_preview(sn_arg integer, hid_arg boolean, parent_arg integer) RETURNS boolean AS '
DECLARE
row record;
sn_loop integer;
BEGIN
IF hid_arg THEN
RETURN FALSE;
END IF;
-- Check if we are hidden by our ancestors
sn_loop := parent_arg;
WHILE sn_loop IS NOT NULL LOOP
SELECT INTO row parent, hid FROM linkcat WHERE sn=sn_loop;
IF row.hid THEN
RETURN FALSE;
END IF;
sn_loop = row.parent;
END LOOP;
RETURN TRUE;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_isshown_preview(sn_arg integer, hid_arg boolean, parent_arg integer) TO nobody;
-- text linkcat_path(sn_arg integer, id_arg text, parent_arg integer);
CREATE FUNCTION linkcat_path(sn_arg integer, id_arg text, parent_arg integer) RETURNS text AS '
DECLARE
row record;
sn_loop integer;
path text;
BEGIN
PERFORM sn FROM linkcat
WHERE parent=sn_arg
AND linkcat_isshown(sn, hid, parent);
IF FOUND THEN
path := ''/'' || id_arg || ''/'';
ELSE
path := ''/'' || id_arg || ''.html'';
END IF;
sn_loop := parent_arg;
WHILE sn_loop IS NOT NULL LOOP
SELECT INTO row parent, id FROM linkcat WHERE sn=sn_loop;
path := ''/'' || row.id || path;
sn_loop := row.parent;
END LOOP;
RETURN path;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_path(sn_arg integer, id_arg text, parent_arg integer) TO nobody;
-- text linkcat_path(sn_arg integer);
CREATE FUNCTION linkcat_path(sn_arg integer) RETURNS text AS '
DECLARE
row record;
BEGIN
IF sn_arg IS NULL THEN
RETURN NULL;
END IF;
SELECT INTO row parent, id FROM linkcat WHERE sn=sn_arg;
IF NOT FOUND THEN
RETURN NULL;
END IF;
RETURN linkcat_path(sn_arg, row.id, row.parent);
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_path(sn_arg integer) TO nobody;
-- boolean linkcat_ischild(parent_arg integer, child_arg integer);
CREATE FUNCTION linkcat_ischild(parent_arg integer, child_arg integer) RETURNS boolean AS '
DECLARE
row record;
sn_loop integer;
BEGIN
sn_loop := child_arg;
WHILE sn_loop IS NOT NULL LOOP
SELECT INTO row parent FROM linkcat WHERE sn=sn_loop;
IF NOT FOUND THEN
RETURN FALSE;
END IF;
IF row.parent = parent_arg THEN
RETURN TRUE;
END IF;
sn_loop := row.parent;
END LOOP;
RETURN FALSE;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_ischild(parent_arg integer, child_arg integer) TO nobody;
-- text linkcat_fulltitle(sn_arg integer);
CREATE FUNCTION linkcat_fulltitle(sn_arg integer) RETURNS text AS '
DECLARE
row record;
sn_loop integer;
title_full text;
BEGIN
IF sn_arg IS NULL THEN
RETURN NULL;
END IF;
SELECT INTO row * FROM linkcat WHERE sn=sn_arg;
IF NOT FOUND THEN
RETURN NULL;
END IF;
title_full := row.title;
WHILE row.parent IS NOT NULL LOOP
sn_loop := row.parent;
SELECT INTO row * FROM linkcat WHERE sn=sn_loop;
title_full := row.title || '' / '' || title_full;
END LOOP;
RETURN title_full;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_fulltitle(sn_arg integer) TO nobody;
-- text linkcat_fulltitle(parent_arg integer, title_arg text);
CREATE FUNCTION linkcat_fulltitle(parent_arg integer, title_arg text) RETURNS text AS '
BEGIN
IF parent_arg IS NULL THEN
RETURN title_arg;
END IF;
RETURN linkcat_fulltitle(parent_arg) || '' / '' || title_arg;
END
' LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION linkcat_fulltitle(parent_arg integer, title_arg text) TO nobody;
--
-- Table structure for table "linkcat"
--
CREATE TABLE linkcat (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
parent int REFERENCES linkcat ON UPDATE CASCADE DEFERRABLE CHECK (parent IS NULL OR (parent != sn AND NOT linkcat_ischild(sn, parent))),
id varchar(8) NOT NULL CHECK (char_length(id) >= 2 AND linkcat_id_unique(id, sn, parent)),
ord smallint NOT NULL DEFAULT 50 CHECK (ord >= 0 AND ord < 100),
title varchar(128) NOT NULL CHECK (title != ''),
kw varchar(128) NOT NULL CHECK (kw != ''),
hid boolean NOT NULL DEFAULT FALSE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON linkcat TO nobody;
CREATE VIEW linkcat_list AS
SELECT
'/links' || linkcat_path(linkcat.sn, linkcat.id, linkcat.parent) AS _viewurl,
linkcat.sn AS sn,
linkcat.id AS id,
linkcat.ord AS ord,
linkcat_fulltitle(linkcat.parent, linkcat.title) AS title,
linkcat.kw AS kw,
CASE WHEN linkcat.hid THEN '隱藏'
ELSE '秀出'
END AS hid,
to_char(linkcat.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(linkcat.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM linkcat
LEFT JOIN users AS createdby ON linkcat.createdby = createdby.sn
LEFT JOIN users AS updatedby ON linkcat.updatedby = updatedby.sn
ORDER BY linkcat_fullord(linkcat.parent, linkcat.ord), id;
GRANT SELECT ON linkcat_list TO nobody;
--
-- Table structure for table "links"
--
CREATE TABLE links (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
title varchar(96) NOT NULL CHECK (title != ''),
title_2ln varchar(96) CHECK (title_2ln IS NULL OR title_2ln != ''),
url varchar(128) NOT NULL UNIQUE CHECK (url != '' AND url != 'http://'),
email varchar(64) CHECK (email IS NULL OR email != ''),
icon varchar(128) CHECK (icon IS NULL OR (icon != '' AND icon != 'http://')),
addr varchar(128) CHECK (addr IS NULL OR addr != ''),
tel varchar(48) CHECK (tel IS NULL OR tel != ''),
fax varchar(32) CHECK (fax IS NULL OR fax != ''),
dsc varchar(256) NOT NULL CHECK (dsc != ''),
hid boolean NOT NULL DEFAULT FALSE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE
);
GRANT SELECT, INSERT, UPDATE, DELETE ON links TO nobody;
CREATE VIEW links_list AS
SELECT
links.url AS _viewurl,
links.sn AS sn,
links.title AS title,
links.title_2ln AS title_2ln,
links.url AS url,
links.url AS _urlcheck,
links.icon AS _imgsrc,
links.email AS email,
links.addr AS addr,
links.tel AS tel,
links.fax AS fax,
links.dsc AS dsc,
CASE WHEN links.hid THEN '隱藏'
ELSE '秀出'
END AS hid,
to_char(links.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(links.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM links
LEFT JOIN users AS createdby ON links.createdby = createdby.sn
LEFT JOIN users AS updatedby ON links.updatedby = updatedby.sn
ORDER BY links.title;
GRANT SELECT ON links_list TO nobody;
--
-- Table structure for table "linkcatz"
--
CREATE TABLE linkcatz (
sn int NOT NULL PRIMARY KEY CHECK (sn >= 100000000 AND sn <= 999999999),
cat int NOT NULL REFERENCES linkcat ON UPDATE CASCADE DEFERRABLE,
link int NOT NULL REFERENCES links ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
created timestamp NOT NULL DEFAULT now(),
createdby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
updated timestamp NOT NULL DEFAULT now(),
updatedby int NOT NULL REFERENCES users ON UPDATE CASCADE DEFERRABLE,
UNIQUE (cat, link)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON linkcatz TO nobody;
CREATE VIEW linkcatz_list AS
SELECT
linkcatz.sn AS sn,
linkcat_fulltitle(linkcat.parent, linkcat.title) AS cat,
links.title AS link,
to_char(linkcatz.created, 'YYYY-MM-DD HH:MI:SS') AS created,
createdby.name AS createdby,
to_char(linkcatz.updated, 'YYYY-MM-DD HH:MI:SS') AS updated,
updatedby.name AS updatedby
FROM linkcatz
LEFT JOIN linkcat ON linkcatz.cat = linkcat.sn
LEFT JOIN links ON linkcatz.link = links.sn
LEFT JOIN users AS createdby ON linkcatz.createdby = createdby.sn
LEFT JOIN users AS updatedby ON linkcatz.updatedby = updatedby.sn
ORDER BY linkcat_fullord(linkcat.parent, linkcat.ord), linkcat.id, link;
GRANT SELECT ON linkcatz_list TO nobody;
--
-- VIEW: Search
--
CREATE VIEW search_list AS
(SELECT
'pages' AS section,
pages.path AS path,
pages.title AS title,
null AS author,
to_char(pages.updated, 'YYYY-MM-DD') AS date,
pages.body AS body,
pages.kw AS kw,
pages.html AS html
FROM pages
WHERE NOT pages.hid
AND pages.path NOT LIKE '/errors/%')
UNION
(SELECT
'links' AS section,
links.url AS path,
links.title AS title,
null AS author,
to_char(links.updated, 'YYYY-MM-DD') AS date,
links.dsc AS body,
CASE WHEN links.title_2ln IS NULL THEN '' ELSE links.title_2ln END
|| '
' || CASE WHEN links.url IS NULL THEN '' ELSE links.url END
|| '
' || CASE WHEN links.email IS NULL THEN '' ELSE links.email END
|| '
' || CASE WHEN links.addr IS NULL THEN '' ELSE links.addr END
|| '
' || CASE WHEN links.tel IS NULL THEN '' ELSE links.tel END
|| '
' || CASE WHEN links.fax IS NULL THEN '' ELSE links.fax END
AS kw,
FALSE AS html
FROM links
WHERE NOT links.hid)
UNION
(SELECT
'guestbook' AS section,
'/cgi-bin/guestbook.cgi?pageno=' || guestbook.pageno
|| '#msg' || guestbook.sn
AS path,
null AS title,
guestbook.name AS author,
to_char(guestbook.created, 'YYYY-MM-DD') AS date,
guestbook.message AS body,
CASE WHEN guestbook.identity IS NULL THEN '' ELSE guestbook.identity END
|| '
' || CASE WHEN guestbook.location IS NULL THEN '' ELSE guestbook.location END
|| '
' || CASE WHEN guestbook.email IS NULL THEN '' ELSE guestbook.email END
|| '
' || CASE WHEN guestbook.url IS NULL THEN '' ELSE guestbook.url END
AS kw,
FALSE AS html
FROM guestbook
WHERE NOT guestbook.hid)
ORDER BY date DESC, title;
GRANT SELECT ON search_list TO nobody;
COMMIT;

View File

@@ -0,0 +1,55 @@
# Emily Wu's Website
# emily.pm: Emily Wu's Website
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-06
package Selima::emily;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
@EXPORT = qw();
# Import our site-specific subroutines
use Selima::emily::Config;
push @EXPORT, @Selima::emily::Config::EXPORT;
use Selima::emily::HTML;
push @EXPORT, @Selima::emily::HTML::EXPORT;
use Selima::emily::Rebuild;
push @EXPORT, @Selima::emily::Rebuild::EXPORT;
# Import our site-specific classess
use Selima::emily::Checker::Guestbook;
use Selima::emily::Checker::Guestbook::Public;
use Selima::emily::Form::Guestbook;
use Selima::emily::Form::Guestbook::Public;
use Selima::emily::L10N;
use Selima::emily::List::Guestbook;
use Selima::emily::List::Guestbook::Public;
use Selima::emily::List::Search;
use Selima::emily::List::Funds;
use Selima::emily::Processor::Guestbook::Public;
# Import our common modules
use Selima;
push @EXPORT, @Selima::EXPORT;
@EXPORT_OK = @EXPORT;
return 1;

View File

@@ -0,0 +1,48 @@
# Emily Wu's Website
# Guestbook.pm: The administrative guestbook form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::Checker::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker::Guestbook);
use Selima::ShortCut;
# _check_identity: Check the identity
sub _check_identity : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("identity");
return $error if defined $error;
# Regularize it
$self->_trim("identity");
# Check the length
return {"msg"=>N_("This occupation is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"identity"}]}
if length $form->param("identity") > ${$self->{"maxlens"}}{"identity"};
# OK
return;
}
return 1;

View File

@@ -0,0 +1,57 @@
# Emily Wu's Website
# Public.pm: The guestbook form checker.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::Checker::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Checker::Guestbook::Public);
use Selima::DataVars qw($DBH);
use Selima::HTTP;
use Selima::Logging;
use Selima::ShortCut;
# _check_name: Check the name
sub _check_name : method {
# Run the parent checker
return $_[0]->SUPER::_check_name_req;
}
# _check_identity: Check the identity
sub _check_identity : method {
local ($_, %_);
my ($self, $form, $error);
$self = $_[0];
$form = $self->{"form"};
# Check if it exists
$error = $self->_missing("identity");
return $error if defined $error;
# Regularize it
$self->_trim("identity");
# Check the length
return {"msg"=>N_("Your occupation is too long. (Max. length [#,_1])"),
"margs"=>[${$self->{"maxlens"}}{"identity"}]}
if length $form->param("identity") > ${$self->{"maxlens"}}{"identity"};
# OK
return;
}
return 1;

View File

@@ -0,0 +1,83 @@
# Emily Wu's Website
# Config.pm: The web site configuration.
# Copyright (c) 2003-2020 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-06
package Selima::emily::Config;
use 5.008;
use utf8;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(siteconf page_replacements);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub siteconf();
sub page_replacements();
}
# Get into the public variable space and initialize them
use lib $ENV{"DOCUMENT_ROOT"} . qw(/../../lib/perl5);
use Selima::CopyYear;
use Selima::DataVars qw(:all);
use Selima::ShortCut;
# siteconf: Subroutine to initialize site configuration
sub siteconf() {
local ($_, %_);
# The package name and the package title
$PACKAGE = "emily";
$SITENAME_ABBR = "Emily";
# The author and the copyright
$AUTHOR = "依瑪貓";
$COPYRIGHT = "&copy; <!--selima:copyyear--> 依瑪貓。依瑪貓保有所有權利。";
# Document root, the library and the l10n directories
$DOC_ROOT = $ENV{"DOCUMENT_ROOT"};
$SITE_LIBDIR = $DOC_ROOT . "/magicat/lib/perl5";
$LOCALEDIR = $DOC_ROOT . "/magicat/locale";
# Tables to lock when rebuilding pages
@REBUILD_TABLES = qw(linkcat links linkcatz);
# The languages
$DEFAULT_LANG = "zh-tw";
@ALL_LINGUAS = qw(zh-tw);
return;
}
# page_replacements: Dynamic page elements to be replaced,
# but not part of the content. Used by xfupdate_template().
sub page_replacements() {
return {
"copyyear" => {
"pattern" => "2000(?:-\\d{4})?",
"content" => copyyear(2000),
},
"generator" => {
"pattern" => "Selima \\d+\\.\\d+",
"content" => "Selima $Selima::VERSION",
},
};
}
no utf8;
return 1;

View File

@@ -0,0 +1,40 @@
# Emily Wu's Website
# Guestbook.pm: The administrative guestbook form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::Form::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form::Guestbook);
use Selima::MarkAbbr;
use Selima::ShortCut;
# _html_col_identity: The identity
sub _html_col_identity : method {
$_[0]->_html_coltmpl_text("identity", h_abbr(__("Occupation:")));
}
# _html_col_url: The website URL
sub _html_col_url : method {
$_[0]->_html_coltmpl_url("url", h_abbr(__("Website URL.:")));
}
return 1;

View File

@@ -0,0 +1,78 @@
# Emily Wu's Website
# Public.pm: The guestbook form.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::Form::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::Form::Guestbook::Public);
use Selima::HTTP;
use Selima::MarkAbbr;
use Selima::ShortCut;
# new: Initialize the HTML form table displayer
sub new : method {
local ($_, %_);
my ($class, $status, $args, $self);
($class, $status, $args) = @_;
$args = {} if !defined $args;
# $args must be a hash reference
http_500 "type of argument 2 must be a hash reference"
if ref($args) ne "HASH";
$$args{"prefmsg"} = [] if !exists $$args{"prefmsg"};
push @{$$args{"prefmsg"}}, __("General commercial advertisements are not welcomed. They may be deleted without notice. HTML is not supported.");
$self = $class->SUPER::new($status, $args);
return $self;
}
# _html_col_email: The e-mail
sub _html_col_email : method {
$_[0]->_html_coltmpl_text("email", h_abbr(__("E-mail")));
}
# _html_col_identity: The identity
sub _html_col_identity : method {
$_[0]->_html_coltmpl_text("identity", h_abbr(__("Occupation")));
}
# _html_col_location: The location
sub _html_col_location : method {
$_[0]->_html_coltmpl_text("location", h_abbr(__("Location")));
}
# _html_col_message: The message
sub _html_col_message : method {
$_[0]->_html_coltmpl_textarea("message", h_abbr(__("Message")),
h_abbr(__("Fill in your message here.")));
}
# _html_col_name: The name
sub _html_col_name : method {
$_[0]->_html_coltmpl_text("name", h_abbr(__("Signature")));
}
# _html_col_url: The website URL
sub _html_col_url : method {
$_[0]->_html_coltmpl_url("url", h_abbr(__("Website URL.")));
}
return 1;

View File

@@ -0,0 +1,690 @@
# Emily Wu's Website
# HTML.pm: The HTML web page parts.
# Copyright (c) 2003-2020 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-06
package Selima::emily::HTML;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw();
push @EXPORT, qw(html_header html_title html_message);
push @EXPORT, qw(html_errmsg html_body html_links html_links_index html_footer);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub html_header($;$);
sub html_title($;$);
sub html_message($);
sub html_errmsg($);
sub html_nav(;$);
sub html_login(;$);
sub html_nav_admin(;$);
sub html_nav_page(;$);
sub html_body($;$);
sub html_links($;$);
sub html_links_index(\@;$);
sub html_footer(;$);
sub merged_tree($$;$);
}
use Cwd qw(realpath);
use File::Basename qw(dirname);
use File::Spec::Functions qw();
use IO::NestedCapture qw(CAPTURE_STDOUT);
use Selima::A2HTML;
use Selima::AddGet;
use Selima::AltLang;
use Selima::DataVars qw(:author :env :input :list :lninfo :requri :siteconf);
use Selima::ErrMsg;
use Selima::HTTPS;
use Selima::Links;
use Selima::LnInfo;
use Selima::LogIn;
use Selima::MarkAbbr;
use Selima::MungAddr;
use Selima::PageFunc;
use Selima::Preview;
use Selima::ScptPriv;
use Selima::ShortCut;
use Selima::Unicode;
use Selima::XFileIO;
use vars qw(@ADMIN_SCRIPTS %HEADER %FOOTER);
@ADMIN_SCRIPTS = (
{ "title" => N_("Manage Content"),
"sub" => [
{ "title" => N_("Guestbook"),
"path" => "/magicat/cgi-bin/guestbook.cgi" },
{ "title" => N_("Pages"),
"path" => "/magicat/cgi-bin/pages.cgi" },
{ "title" => N_("Links"),
"path" => "/magicat/cgi-bin/links.cgi" },
{ "title" => N_("Link Categories"),
"path" => "/magicat/cgi-bin/linkcat.cgi" },
{ "title" => N_("Link Categorization"),
"path" => "/magicat/cgi-bin/linkcatz.cgi" },
],
},
{ "title" => N_("Manage Accounts"),
"sub" => [
{ "title" => N_("Users"),
"path" => "/magicat/cgi-bin/users.cgi" },
{ "title" => N_("Groups"),
"path" => "/magicat/cgi-bin/groups.cgi" },
{ "title" => N_("User Membership"),
"path" => "/magicat/cgi-bin/usermem.cgi" },
{ "title" => N_("Group Membership"),
"path" => "/magicat/cgi-bin/groupmem.cgi" },
{ "title" => N_("User Preferences"),
"path" => "/magicat/cgi-bin/userpref.cgi" },
{ "title" => N_("Script Privileges"),
"path" => "/magicat/cgi-bin/scptpriv.cgi" },
],
},
{ "title" => N_("Miscellaneous"),
"sub" => [
# { "title" => N_("Funds"),
# "path" => "/magicat/cgi-bin/funds.cgi" },
{ "title" => N_("Activity Log"),
"path" => "/magicat/cgi-bin/actlog.cgi" },
{ "title" => N_("Rebuild Pages"),
"path" => "/magicat/cgi-bin/rebuild.cgi" },
{ "title" => N_("Analog"),
"path" => "/magicat/analog/" },
{ "title" => N_("Test Script"),
"path" => "/magicat/cgi-bin/test.cgi" },
],
},
);
# html_header: Display the page header
sub html_header($;$) {
local ($_, %_);
my ($title, $args, $guide);
my ($langname, $langfile);
my ($author, $copyright, $keywords, $copypage);
my ($stylesheets, $javascripts, $favicon, $class, $onload);
my ($titlelang, $skiptobody);
($title, $args) = @_;
# Obtain the page parameters
$args = page_param $args;
# Set the language
$langname = h(ln $$args{"lang"}, LN_NAME);
$langfile = ln($$args{"lang"}, LN_FILENAME);
# Misc
# The copyright message should be already HTML-escaped,
# for the copyright sign "&copy;".
$author = exists $$args{"author"}? h($$args{"author"}):
defined $AUTHOR? h($AUTHOR): undef;
$copyright = exists $$args{"copyright"}? $$args{"copyright"}:
defined $COPYRIGHT? $COPYRIGHT: undef;
$keywords = exists $$args{"keywords"}? h($$args{"keywords"}): undef;
$copypage = exists $$args{"copypage"}?
h($$args{"copypage"}): h("/copying.html");
# Style sheets
$stylesheets = [];
push @$stylesheets, "/stylesheets/common.css";
push @$stylesheets, @{$$args{"stylesheets"}}
if exists $$args{"stylesheets"};
# JavaScripts
$javascripts = [];
if (exists $$args{"javascripts"}) {
push @$javascripts, "/scripts/common.js";
push @$javascripts, "/scripts/lang.$langfile.js";
push @$javascripts, @{$$args{"javascripts"}};
}
# Favorite icon
$favicon = exists $$args{"favicon"}?
h($$args{"favicon"}): h("/favicon.ico");
# The class of body
$class = exists $$args{"class"}?
" class=\"" . h($$args{"class"}) . "\"": "";
# The onload JavaScript event handler
$onload = exists $$args{"onload"}?
" onload=\"" . h($$args{"onload"}) . "\"": "";
# The accessibility guide
$skiptobody = h(__("Skip to the page content area."));
$guide = h(__("Page Content Area"));
print << "EOT";
<?xml version="1.0" encoding="<!--selima:charset-->" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="$langname">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=<!--selima:charset-->" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
EOT
# Author, copyright and keywords
print "<meta name=\"author\" content=\"$author\" />\n"
if defined $author;
print "<meta name=\"copyright\" content=\"$copyright\" />\n"
if defined $copyright;
print "<meta name=\"keywords\" content=\"$keywords\" />\n"
if defined $keywords;
print "<meta name=\"generator\" content=\"<!--selima:generator-->\" />\n"
if $$args{"static"};
# The home page
print "<link rel=\"start\" type=\"application/xhtml+xml\" href=\"/\" />\n";
# The copyright page
print "<link rel=\"copyright\" type=\"application/xhtml+xml\""
. " href=\"$copypage\" />\n"
if defined $copypage;
# The author contact information
print "<link rel=\"author\" href=\"mailto:emily6wu\@ms27.hinet.net\" />\n";
# Revelent pages
print "<link rel=\"up\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"up"}) . "\" />\n"
if exists $$args{"up"};
print "<link rel=\"first\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"first"}) . "\" />\n"
if exists $$args{"first"};
print "<link rel=\"prev\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"prev"}) . "\" />\n"
if exists $$args{"prev"};
print "<link rel=\"next\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"next"}) . "\" />\n"
if exists $$args{"next"};
print "<link rel=\"last\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"last"}) . "\" />\n"
if exists $$args{"last"};
print "<link rel=\"contents\" type=\"application/xhtml+xml\""
. " href=\"" . h($$args{"toc"}) . "\" />\n"
if exists $$args{"toc"};
# Style sheets
print "<link rel=\"stylesheet\" type=\"text/css\""
. " href=\"" . h($_) . "\" />\n"
foreach @$stylesheets;
# JavaScripts
print "<script type=\"text/javascript\" src=\""
. h($_) . "\"></script>\n"
foreach @$javascripts;
# Favorite Icon
print "<link rel=\"shortcut icon\" type=\"image/x-icon\""
. " href=\"$favicon\" />\n";
# The title
$titlelang = $$args{"title_lang"} eq $$args{"lang"}? "":
" xml:lang=\"" . h(ln $$args{"title_lang"}, LN_NAME) . "\"";
print "<title" . $titlelang . ">" . h($title) . "</title>\n";
print << "EOT";
</head>
<body$class$onload>
<div id="topofpage" class="skiptobody">
<a accesskey="2" href="#body">$skiptobody</a>
</div>
EOT
# Show the navigation area
html_nav $args;
# Embrace the content
print << "EOT";
<div id="body" class="body" title="$guide">
<div class="accessguide"><a accesskey="C"
href="#body" title="$guide">:::</a></div>
EOT
# Display the title
html_title $title, $args unless $$args{"no_auto_title"};
return;
}
# html_title: Print an HTML title
sub html_title($;$) {
local ($_, %_);
my ($title, $args, $h);
($title, $args) = @_;
$h = << "EOT";
<h1>%s</h1>
EOT
printf $h, h_abbr($title);
return;
}
# html_message: Print an HTML message
sub html_message($) {
local ($_, %_);
$_ = $_[0];
return if !defined $_ || $_ eq "";
$_ = h_abbr($_);
print << "EOT";
<p class="message">$_</p>
EOT
return;
}
# html_errmsg: Print an HTML error message, a wrapper to html_message()
sub html_errmsg($) {
local ($_, %_);
$_ = $_[0];
return if !defined $_;
html_message(err2msg $_);
return;
}
# html_nav: Print the HTML navigation bar
sub html_nav(;$) {
local ($_, %_);
my ($args, $lang, $guide, $FD, @sections);
$args = $_[0];
# Obtain the page parameters
$args = page_param $args;
$lang = $$args{"lang"};
# The accessibility guide
$guide = h(__("Navigation Links Area"));
@sections = qw();
# Print the primary navigation bar
$HEADER{"file"} = sprintf("%s/magicat/include/header.html", $DOC_ROOT)
if !exists $HEADER{"file"};
undef $_;
if ( !exists $HEADER{"content"}
|| !exists $HEADER{"date"}
|| $HEADER{"date"} < ($_ = (stat $HEADER{"file"})[9])) {
$_ = (stat $HEADER{"file"})[9] if !defined $_;
$HEADER{"date"} = $_;
$HEADER{"content"} = hcref_decode ln($lang, LN_CHARSET), xfread $HEADER{"file"};
}
push @sections, $HEADER{"content"};
# Print the section-specific navigation links
push @sections, $$args{"header_html_nav"}
if exists $$args{"header_html_nav"};
# Print the log-in information
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
html_login $args;
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$_ = join "", <$FD>;
push @sections, $_ if $_ ne "";
# Print the section-specific navigation bar
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
if ($$args{"admin"}) {
html_nav_admin $args;
} else {
html_nav_page $args;
}
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$_ = join "", <$FD>;
push @sections, $_ if $_ ne "";
# Embrace the navigation links
print << "EOT";
<div id="nav" class="nav" title="$guide">
<div class="accessguide"><a accesskey="L"
href="#nav" title="$guide">:::</a></div>
EOT
# Print each navigation sections
print join "<hr />\n\n", @sections;
# Embrace the navigation links
print << "EOT";
</div>
<hr />
EOT
return;
}
# html_login: Print the HTML log-in information
sub html_login(;$) {
local ($_, %_);
my ($args, $msg, $modify, $submit);
$args = $_[0];
# Skip if not logged-in
return if !defined get_login_sn;
# Obtain the page parameters
$args = page_param $args;
# No log-in bar for static pages
return if $$args{"static"};
# The message
$modify = "/magicat/cgi-bin/users.cgi?form=cur&sn=" . get_login_sn;
$msg = sprintf __("Welcome, %s. (<span><a href=\"%s\">Modify</a></span>)"),
h(get_login_name), h($modify);
$submit = h(__("Log out"));
print << "EOT";
<form class="login" action="/magicat/cgi-bin/logout.cgi" method="post">
<div class="navibar">
$msg <input
type="submit" name="confirm" value="$submit" />
</div>
</form>
EOT
return;
}
# html_nav_admin: Print the HTML administrative navigation bar
sub html_nav_admin(;$) {
local ($_, %_);
my ($args, $cgidir, $path, $title);
$args = $_[0];
# Obtain the page parameters
$args = page_param $args;
# Find the current CGI directory
$cgidir = "cgi-bin";
$cgidir = $1 if $REQUEST_PATH =~ /\/(cgi-[a-z0-9]+)\/[a-z0-9]+\.cgi$/;
# Output them
foreach my $cat (@ADMIN_SCRIPTS) {
@_ = qw();
foreach (@{$$cat{"sub"}}) {
next unless is_script_permitted $$_{"path"};
($path, $title) = ($$_{"path"}, $$_{"title"});
# Fix the path to use the same cgi-* directory alias
$path =~ s/\/cgi-[a-z0-9]+\/([a-z0-9]+\.cgi)$/\/$cgidir\/$1/;
# Fix the path of the HTTPS scripts to use HTTPS
$path = "https://" . https_host . "/$PACKAGE$path"
if exists $$_{"https"} && $$_{"https"} && !is_https;
push @_, sprintf(" <span><a href=\"%s\">%s</a></span>",
h($path), h_abbr(__($title)));
}
next if @_ == 0;
$title = $$cat{"title"};
$_ = sprintf(__("%s:"), h_abbr(__($title)));
print "<div class=\"navibar\">\n"
. $_ . "\n" . join(" |\n", @_) . "\n"
. "</div>\n"
if @_ > 0;
}
return;
}
# html_nav_page: Print the HTML page navigation bar
sub html_nav_page(;$) {
local ($_, %_);
my ($args, $tree);
$args = $_[0];
# Obtain the page parameters
$args = page_param $args;
# Obtain the page tree
$tree = merged_tree $$args{"path"}, $$args{"lang"}, $$args{"preview"};
# Bounce for nothing
return if !defined $tree
|| !exists $$tree{"pages"}
|| !defined $$tree{"pages"}
|| @{$$tree{"pages"}} <= 1;
# Output them
print << "EOT";
<div class="navibar">
EOT
@_ = qw();
foreach (@{$$tree{"pages"}}) {
push @_, " <span><a href=\"" . h($$_{"path"}) . "\">"
. h($$_{"title"}) . "</a></span>";
}
print join(" |\n", @_) . "\n";
print << "EOT";
</div>
EOT
return;
}
# html_body: Print the HTML body
sub html_body($;$) {
local ($_, %_);
my ($page, $args);
($page, $args) = @_;
# Obtain page parameters
$args = page_param $args;
# Output the picture
# To be done
# Output the content
print "" . (!exists $$page{"html"} || !$$page{"html"}?
a2html($$page{"body"}): $$page{"body"}) . "\n\n";
return;
}
# html_links: Print the HTML links list
sub html_links($;$) {
local ($_, %_);
my ($page, $args);
($page, $args) = @_;
# Obtain page parameters
$args = page_param $args;
# Output the breadcrumb trai
@_ = qw();
push @_, "<a href=\"/links/\">" . h(__("Related Links")) . "</a>";
foreach my $parent (@{$$page{"parents"}}) {
push @_, "<a href=\"" . h($$parent{"path"}) . "\">"
. h($$parent{"title"}) . "</a>";
}
push @_, h($$page{"title"});
print "<div class=\"breadcrumb\">\n"
. join(" /\n", @_) . "\n</div>\n\n";
# Output the subcategories
if (@{$$page{"scats"}} > 0) {
print "<h2>" . h(__("Subcategories:")) . "</h2>\n\n<ol>\n";
foreach my $cat (@{$$page{"scats"}}) {
$_ = h($$cat{"title"});
$_ .= " <span class=\"note\">("
. h($$cat{"links"}) . ")</span>"
if $$cat{"links"} > 0;
print "<li><a href=\"" . h($$cat{"path"}) . "\">"
. "$_</a></li>\n";
}
print "</ol>\n\n";
}
# Output the links
if (@{$$page{"links"}} > 0) {
my $emailalt;
$emailalt = h(__("E-mail"));
print "<ol class=\"linkslist\">\n";
foreach my $link (@{$$page{"links"}}) {
my ($url, $title, $ctitle, $dsc);
$url = h($$link{"url"});
$title = h($$link{"title"});
print "<li>\n";
print "<form action=\"/cgi-bin/mailto.cgi\" method=\"post\">\n<div>\n"
if defined $$link{"email"};
# Output the link icon
print "<a href=\"$url\"><img class=\"linkicon\"\n"
. " src=\"" . h($$link{"icon"}) . "\"\n"
. " alt=\"$title\" /></a><br />\n"
if defined $$link{"icon"};
# Output the site title
$ctitle = is_usascii_printable($$link{"title"})?
"<span xml:lang=\"en\">$title</span>": $title;
if (defined $$link{"title_2ln"}) {
$_ = h($$link{"title_2ln"});
$_ = "<span xml:lang=\"en\">$_</span>"
if is_usascii_printable($$link{"title_2ln"});
$ctitle .= " $_";
}
print "<cite>$ctitle</cite><br />\n";
# Output the URL
print __("URL.:") . " <a href=\"$url\">$url</a><br />\n";
# Output other information
if (defined $$link{"email"}) {
print __("E-mail:") . " ";
print "<input type=\"hidden\" name=\"email\" value=\""
. h(mung_address_at($$link{"email"})) . "\" />\n";
print "<input type=\"image\" src=\"/images/email\" alt=\"$emailalt\" />\n";
print mung_email_span(h($$link{"email"})) . "<br />\n";
}
print __("Address:") . " " . h($$link{"addr"}) . "<br />\n"
if defined $$link{"addr"};
print __("Tel.:") . " " . h($$link{"tel"}) . "<br />\n"
if defined $$link{"tel"};
print __("Fax.:") . " " . h($$link{"fax"}) . "<br />\n"
if defined $$link{"fax"};
# Output the description
$dsc = $$link{"dsc"};
print h($dsc) . "<br />\n";
print "</div>\n</form>\n" if defined $$link{"email"};
print "</li>\n\n";
}
print "</ol>\n\n";
}
return;
}
# html_links_index: Print the HTML link categories index
sub html_links_index(\@;$) {
local ($_, %_);
my ($cats, $args);
($cats, $args) = @_;
# Obtain page parameters
$args = page_param $args;
# Bounce for nothing
if (@$cats == 0) {
print "<p>" . h(__("The database is empty.")) . "</p>\n\n";
return;
}
# Output the root categories
print << "EOT";
<div class="body">
<ul class="toc">
EOT
foreach my $cat (@$cats) {
$_ = h($$cat{"title"});
$_ .= " <span class=\"note\">("
. h($$cat{"links"}) . ")</span>"
if $$cat{"links"} > 0;
print "<li><a href=\"" . h($$cat{"path"}) . "\">"
. "$_</a></li>\n";
}
print << "EOT";
</ul>
</div>
EOT
return;
}
# html_footer: Print the HTML footer
sub html_footer(;$) {
local ($_, %_);
my ($args, $lang);
$args = $_[0];
# Obtain the page parameters
$args = page_param $args;
$lang = $$args{"lang"};
# Embrace the content
print << "EOT";
</div>
EOT
# Print the section-specific navigation bar
print "<hr />\n" . $$args{"footer_html_nav"} . "\n\n"
if exists $$args{"footer_html_nav"};
# Print the common footer
$FOOTER{"file"} = sprintf("%s/magicat/include/footer.html", $DOC_ROOT)
if !exists $FOOTER{"file"};
undef $_;
if ( !exists $FOOTER{"content"}
|| !exists $FOOTER{"date"}
|| $FOOTER{"date"} < ($_ = (stat $FOOTER{"file"})[9])) {
$_ = (stat $FOOTER{"file"})[9] if !defined $_;
$FOOTER{"date"} = $_;
$FOOTER{"content"} = hcref_decode ln($lang, LN_CHARSET), xfread $FOOTER{"file"};
}
$_ = $FOOTER{"content"};
$FOOTER{"perl"} = {} if !exists $FOOTER{"perl"};
if ($$args{"static"}) {
s/\n+<!--selima:perl-->\n+/\n\n/;
} elsif ($IS_MODPERL) {
if (!exists ${$FOOTER{"perl"}}{"modperl"}) {
${$FOOTER{"perl"}}{"modperl"} = << "EOT";
<div class="modperl">
<a href="http://perl.apache.org/"><img
src="/images/modperl" alt="%s" /></a>
<p>%s</p>
</div>
EOT
${$FOOTER{"perl"}}{"modperl"} = sprintf(${$FOOTER{"perl"}}{"modperl"},
h(__("mod_perl -- Speed, Power, Scalability")),
__("This script is written in <a href=\"http://www.perl.com/\"><acronym title=\"Practical Extraction and Reporting Language\">Perl</acronym></a> and optimized for <a href=\"http://perl.apache.org/\">mod_perl</a>."));
${$FOOTER{"perl"}}{"modperl"} =~ s/(<a href=".+?")(>)/$1 hreflang="en"$2/g
if $lang ne "en";
}
s/<!--selima:perl-->\n/${$FOOTER{"perl"}}{"modperl"}/;
} else {
if (!exists ${$FOOTER{"perl"}}{"cgi"}) {
${$FOOTER{"perl"}}{"cgi"} = << "EOT";
<div>
<p>%s</p>
</div>
EOT
${$FOOTER{"perl"}}{"cgi"} = sprintf(${$FOOTER{"perl"}}{"cgi"},
__("This script is written in <a href=\"http://www.perl.com/\"><acronym title=\"Practical Extraction and Reporting Language\">Perl</acronym></a>."));
${$FOOTER{"perl"}}{"cgi"} =~ s/(<a href=".+?")(>)/$1 hreflang="en"$2/g
if $lang ne "en";
}
s/<!--selima:perl-->\n/${$FOOTER{"perl"}}{"cgi"}/;
}
print $_;
# Show the HTML preview mark
html_preview_mark $args;
print "\n</body>\n</html>\n";
return;
}
# merged_tree: Get the page tree in a directory
sub merged_tree($$;$) {
local ($_, %_);
my ($path, $lang, $preview);
($path, $lang, $preview) = @_;
# Return special areas
if ($path =~ /^\/links\//) {
return link_tree($path, $lang, $preview);
# Non-pages (scripts... etc)
} else {
return {};
}
}
return 1;

View File

@@ -0,0 +1,38 @@
# Emily Wu's Website
# L10N.pm: The localization class.
# Copyright (c) 2003-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2003-04-26
package Selima::emily::L10N;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
return 1;
# The Chinese (Taiwan) localized messages.
package Selima::emily::L10N::zh_tw;
use 5.008;
use strict;
use warnings;
use base qw(Locale::Maketext::Gettext);
sub numerate : method { $_[2] }
return 1;

View File

@@ -0,0 +1,269 @@
# Emily Wu's Website
# Funds.pm: The fund performance list.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-12-30
package Selima::emily::List::Funds;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::CommText;
use Selima::DataVars qw(:input :requri);
use Selima::MarkAbbr;
use Selima::ShortCut;
use Selima::Format;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "funds" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = __("Browse Mutual Fund Performances");
# The default sort order
$self->{"DEFAULT_SORTBY"} = "majcat,mincat,title";
# Known columns that should not be displayed (has a special purpose)
push @{$self->{"COLS_NO_DISPLAY"}}, qw(_m1rank _m3rank _m6rank
_y1rank _y2rank _y3rank _y5rank _y10rank _ytrank);
# No selection
$self->{"noselect"} = 1;
# Column labels
$self->col_labels(
"title" => __("Name"),
"m1ret" => __("1m return"),
"m1rank" => __("1m ranking"),
"m3ret" => __("3m return"),
"m3rank" => __("3m ranking"),
"m6ret" => __("6m return"),
"m6rank" => __("6m ranking"),
"y1ret" => __("1y return"),
"y1rank" => __("1y ranking"),
"y2ret" => __("2y return"),
"y2rank" => __("2y ranking"),
"y3ret" => __("3y return"),
"y3rank" => __("3y ranking"),
"y5ret" => __("5y return"),
"y5rank" => __("5y ranking"),
"y10ret" => __("10y return"),
"y10rank" => __("10y ranking"),
"ytret" => __("This year return"),
"ytrank" => __("This year ranking"),
"beginret" => __("Total return"),
"begindate" => __("Begin from"),
"bestm3" => __("Best 3m return"),
"worstm3" => __("Worst 3m return"),
"sd12" => __("Standard deviation (12m)"),
"sd24" => __("Standard deviation (24m)"),
"beta12" => __("Beta (12m)"),
"beta24" => __("Beta (24m)"),
"sharpe12" => __("Sharpe (12m)"),
"sharpe24" => __("Sharpe (24m)"),
"jensen12" => __("Jensen (12m)"),
"jensen24" => __("Jensen (24m)"),
"treynor12" => __("Treynor (12m)"),
"treynor24" => __("Treynor (24m)"),
"infrma12" => __("Information Ratio (major categories) (12m)"),
"infrma24" => __("Information Ratio (major categories) (24m)"),
"infrmi12" => __("Information Ratio (minor categories) (12m)"),
"infrmi24" => __("Information Ratio (minor categories) (24m)"),
"turnmt" => __("This month turnover"),
"turny1" => __("12m turnover"),
"duration" => __("Duration"),
"rating" => __("Rating"),
"newman" => __("Manager less than 1y?"),
);
# The pre-defined filter
$self->{"pre_filter"} = [
["y5ret IS NOT NULL AND y5ret > 150 AND m1rank < 1.0/4 AND m3rank < 1.0/4 AND m6rank < 1.0/4 AND y1rank < 1.0/4 AND y2rank < 1.0/4 AND y3rank < 1.0/4 AND y5rank < 1.0/4"],
["y2ret IS NOT NULL AND y1rank < 1.0/4 AND y2rank < 1.0/4 AND m3rank < 1.0/3 AND m6rank < 1.0/3", __("4433 Principle")],
];
return $self;
}
# pre_filter: Set the pre-defined filter
sub pre_filter : method {
local ($_, %_);
my $self;
$self = $_[0];
if (!defined $GET->param("filter") || $GET->param("filter") eq "none") {
return undef;
} elsif ($GET->param("filter") eq "free") {
return $GET->param("filtertext") eq ""? undef:
$GET->param("filtertext");
} elsif ($GET->param("filter") =~ /^\d+$/) {
return $GET->param("filter") <= @{$self->{"pre_filter"}}?
${${$self->{"pre_filter"}}[$GET->param("filter") - 1]}[0]: undef;
}
return undef;
}
# sql_filter: Get the SQL WHERE phase
# A filter to update the *rank to _*rank
sub sql_filter : method {
local ($_, %_);
my $self;
$self = $_[0];
$_ = $self->SUPER::sql_filter;
s/(?<!_)([my][\dt]+rank)/_$1/g;
return $_;
}
# sql_orderby: Get the SQL ORDER BY phase
# A filter to update the *rank to _*rank
sub sql_orderby : method {
local ($_, %_);
my $self;
$self = $_[0];
$_ = $self->SUPER::sql_orderby;
s/(?<!_)([my][\dt]+rank)/_$1/g;
return $_;
}
# colval: Output a list column value
sub colval : method {
local ($_, %_);
my ($self, $col, %row);
($self, $col, %row) = @_;
# Null/no value
return h(t_none()) if !defined $row{$col};
# Show as date
return h(fmtdate($row{$col})) if $col eq "begindate";
# Run the parent method
return $self->SUPER::colval($col, %row);
}
# html_search: Display the search box
sub html_search : method {
local ($_, %_);
my ($self, $prompt, $label, $query, $request_file);
($self, $prompt) = @_;
$prompt = __("Search for a fund:") if !defined $prompt;
# No search box is displayed if no records yet
if ( $self->{"fetched"}
&& defined $self->{"total"} && $self->{"total"} == 0
&& !defined $self->{"query"}) {
return;
}
$request_file = h($REQUEST_FILE);
$query = defined $self->{"query"}? h($self->{"query"}): "";
$label = h(__("Search"));
print << "EOT";
<form action="$request_file" method="get" accept-charset="<!--selima:charset-->">
<div class="searchbox">
EOT
# Embed the caller information
if ($self->{"is_called_form"}) {
my ($caller, $cformid);
$caller = h($self->{"caller"});
$cformid = h($self->{"cformid"});
print << "EOT";
<input type="hidden" name="caller" value="$caller" />
<input type="hidden" name="cformid" value="$cformid" />
EOT
}
if (defined $prompt) {
$_ = h($prompt);
print << "EOT";
<label for="query">$_</label>
EOT
}
print << "EOT";
<input id="query" type="text" name="query" value="$query" /><br />
EOT
# The advanced filter
print "<label for=\"filternone\">" . h(__("Advanced filter:")) . "</label>\n";
print "<input id=\"filternone\" type=\"radio\" name=\"filter\" value=\"none\""
. (!defined $GET->param("filter") || $GET->param("filter") eq "none"?
" checked=\"checked\"": "")
. " />\n"
. "<label for=\"filternone\">" . h_abbr(t_none()) . "</label><br />\n";
for (my $i = 0; $i < @{$self->{"pre_filter"}}; $i++) {
print "<input id=\"filter" . h($i + 1) . "\" type=\"radio\" name=\"filter\""
. " value=\"" . h($i + 1) . "\""
. ( defined $GET->param("filter")
&& $GET->param("filter") eq $i + 1?
" checked=\"checked\"": "")
. " />\n"
. "<label for=\"filter" . h($i + 1) . "\">"
. (@{${$self->{"pre_filter"}}[$i]} > 1?
h(sprintf("%s (%s)", ${${$self->{"pre_filter"}}[$i]}[1],
${${$self->{"pre_filter"}}[$i]}[0])):
h(${${$self->{"pre_filter"}}[$i]}[0]))
. "</label><br />\n";
}
print "<input id=\"filterfree\" type=\"radio\" name=\"filter\" value=\"free\""
. (defined $GET->param("filter") && $GET->param("filter") eq "free"?
" checked=\"checked\"": "")
. " />\n"
. "<input id=\"filtertext\" type=\"text\" name=\"filtertext\" size=\"100\""
. (defined $GET->param("filtertext")?
" value=\"" . h($GET->param("filtertext")) . "\"": "")
. " /><br />\n";
print << "EOT";
<input type="hidden" name="charset" value="<!--selima:charset-->" /><input
type="submit" value="$label" />
</div>
</form>
EOT
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return __("Your query found [*,_1,fund].", $self->{"total"});
# List result
} else {
return __("[*,_1,fund].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return __("Your query found [*,_1,fund], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return __("[*,_1,fund], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
return 1;

View File

@@ -0,0 +1,47 @@
# Emily Wu's Website
# Guestbook.pm: The administrative guestbook message list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::List::Guestbook;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Guestbook);
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[1] = "guestbook" if !defined $_[1];
$self = $class->SUPER::new(@_);
# The page title
$self->{"title"} = $self->{"is_called_form"}?
__("Select a Message"):
__("Manage the Guestbook");
# Column labels
$self->col_labels(
"identity" => __("Occupation"),
);
return $self;
}
return 1;

View File

@@ -0,0 +1,27 @@
# Emily Wu's Website
# Public.pm: The guestbook message list.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-10-24
package Selima::emily::List::Guestbook::Public;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List::Guestbook::Public);
return 1;

View File

@@ -0,0 +1,181 @@
# Emily Wu's Website
# Search.pm: The web site full-text search result list.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-04-11
package Selima::emily::List::Search;
use 5.008;
use strict;
use warnings;
use base qw(Selima::List);
use Selima::Logging;
use Selima::ShortCut;
# new: Initialize the handler
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$self = $class->SUPER::new(@_);
# The page title
if (!defined $self->{"query"}) {
$self->{"title"} = __("Full Text Search");
} else {
$self->{"title"} = __("Search Result");
}
$self->{"view"} = "search_list";
$self->{"COLS_NO_SEARCH"} = [qw(section path date html)];
return $self;
}
# fetch: Fetch the current list
sub fetch : method {
local ($_, %_);
my $self;
$self = $_[0];
# No search specified
if (!defined $self->{"query"}) {
$self->{"total"} = undef;
return $self->{"error"};
}
# Check the query phrase
# Regularize it
$self->{"query"} =~ s/^\s*(.*?)\s*$/$1/;
# Check if it is filled
if ($self->{"query"} eq"") {
$self->{"total"} = undef;
$self->{"error"} = {"msg"=>N_("Please fill in your query.")};
return $self->{"error"};
}
# Run the parent method
$self->SUPER::fetch;
# Add an activity log record
actlog("Query with phrase \"" . $self->{"query"} . "\".");
# Done
return $self->{"error"};
}
# sql_orderby: Get the SQL ORDER BY phase
# Always return nothing
sub sql_orderby : method { return ""; }
# html_newlink: Display a link to add a new item
# Make it a null function
sub html_newlink : method {}
# html_search: Display the search box
sub html_search : method {
# Run the parent method
return $_[0]->SUPER::html_search(__("Search in the website:"));
}
# liststat_message: Return the current list statistics message
sub liststat_message : method {
local ($_, %_);
my $self;
$self = $_[0];
# No record to list
if ($self->{"total"} == 0) {
# Inherit the empty list statistics message
return $self->SUPER::liststat_message;
# Fit in one page
} elsif ($self->{"total"} <= $self->{"pagesize"}) {
# Result comes from a query
if (defined $self->{"query"}) {
return __("Your query found [*,_1,article].", $self->{"total"});
# List result
} else {
return __("[*,_1,article].", $self->{"total"});
}
# More than one page
} else {
# Result comes from a query
if (defined $self->{"query"}) {
return __("Your query found [*,_1,article], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
# List result
} else {
return __("[*,_1,article], listing [#,_2] to [#,_3].",
$self->{"total"}, $self->{"startno"}+1, $self->{"endno"}+1);
}
}
}
# html_list: List the items
sub html_list : method {
local ($_, %_);
my ($self);
$self = $_[0];
# Do not show the list
return if !defined $self->{"total"};
# No record to be listed
return if $self->{"total"} == 0;
print << "EOT";
<ol class="searchresult">
EOT
# Print each record
foreach my $current (@{$self->{"current"}}) {
my ($url, $abstract);
$url = h($$current{"path"});
$abstract = $self->query_abstract($current);
if ($$current{"section"} eq "pages") {
my $title;
$title = h($$current{"title"});
print << "EOT";
<li><h3><a href="$url">$title</a></h3>
EOT
} elsif ($$current{"section"} eq "links") {
my ($title, $sectitle);
$title = h($$current{"title"});
$sectitle = h(__("Related Links"));
print << "EOT";
<li><h3><a href="$url">$title</a></h3>
<address><a href="/links/">$sectitle</a></address>
EOT
} elsif ($$current{"section"} eq "guestbook") {
my ($author, $title, $sectitle);
$author = defined $$current{"author"}?
" <span class=\"note\">" . h($$current{"author"}) . "</span>": "";
$title = h(__("Guestbook Message on [_1]", $$current{"date"}));
$sectitle = h(__("Guestbook"));
print << "EOT";
<li><h3><a href="$url">$title</a>$author</h3>
<address><a href="/cgi-bin/guestbook.cgi">$sectitle</a></address>
EOT
}
print "\n<p>$abstract</p>\n" if defined $abstract;
print << "EOT";
</li>
EOT
}
print << "EOT";
</ol>
EOT
return;
}
return 1;

View File

@@ -0,0 +1,142 @@
# Emily Wu's Website
# Public.pm: The guestbook data processor.
# Copyright (c) 2006-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2006-03-19
package Selima::emily::Processor::Guestbook::Public;
use 5.008;
use utf8;
use strict;
use warnings;
use base qw(Selima::Processor::Guestbook);
use Selima::Country;
use Selima::DataVars qw(:env :input :scptconf);
use Selima::Format;
use Selima::Guest;
use Selima::GeoIP;
use Selima::RemoHost;
use Selima::Unicode;
# new: Initialize the processor
sub new : method {
local ($_, %_);
my ($self, $class);
($class, @_) = @_;
$_[0]->param("form", "new");
$_[0]->param("confirm", 1);
$self = $class->SUPER::new(@_);
$self->{"notify"} = 1;
$self->{"debug"} = 1;
return $self;
}
# _save_cols: Save the column deposit
sub _save_cols : method {
local ($_, %_);
my $self;
($self, @_) = @_;
$self->SUPER::_save_cols(@_);
$self->{"cols"}->{"login"} = 723676436;
return;
}
# _other_tasks: Perform tasks other than column updates
sub _other_tasks : method {
local ($_, %_);
my ($self, $form);
my ($mail, $body, $charset);
$self = $_[0];
return unless $self->{"notify"};
$form = $self->{"form"};
# Compose the mail body
$body = "";
$body .= "若要編輯或刪除這則留言,請連上以下網址:\n";
$body .= "http://" . $ENV{"SERVER_NAME"} . "/magicat/cgi-bin/guestbook.cgi"
. "?form=cur&sn=" . $self->{"sn"} . "\n\n";
$body .= "日期: " . fmttime . "\n";
@_ = qw();
push @_, ctname_zhtw country_lookup;
push @_, remote_host if defined remote_host;
$body .= "來自: " . $ENV{"REMOTE_ADDR"}
. " (" . join(", ", @_) . ")\n";
$body .= "簽名: " . $form->param("name") . "\n"
if $form->param("name") ne "";
$body .= "職業: " . $form->param("identity") . "\n"
if $form->param("identity") ne "";
$body .= "所在地: " . $form->param("location") . "\n"
if $form->param("location") ne "";
$body .= "E-mail " . $form->param("email") . "\n"
if $form->param("email") ne "";
$body .= "網站網址: " . $form->param("url") . "\n"
if $form->param("url") ne "" && $form->param("url") ne "http://";
$body .= "留言:\n\n" . $form->param("message") . "\n\n";
$body .= "原始內容:\n" . $USER_INPUT{"POST_RAWDATA"} . "\n";
# Collecting Debugging infomation
if ($self->{"debug"}) {
$body .= "\n";
$body .= "===== Start Debugging Infomation =====\n";
if ($IS_MODPERL) {
$_ = $IS_MP2? Apache2::RequestUtil->request->as_string:
Apache->request->as_string;
s/^X-Selima-[^\n]+\n//mg;
s/^((?:[^\n]+\n)+).+?$/$1/s;
$body .= $_;
} else {
foreach (sort grep !/^HTTP_X_SELIMA_/, grep /^HTTP_/, keys %ENV) {
my $hname;
$hname = $_;
$hname =~ s/^HTTP_//;
$hname =~ s/_/-/g;
$hname =~ s/(\w)(\w+)/$1 . lc $2/ge;
$body .= "$hname: $ENV{$_}\n";
}
}
$body .= "===== End Debugging Infomation =====\n";
}
# Set the best appropriate output character set
$charset = is_charset($body, "Big5")? "Big5": "UTF-8";
# Compose the mail
$mail = new Selima::Mail;
$mail->charset($charset);
$mail->from($THIS_FILE . "\@" . $ENV{"SERVER_NAME"}, "吳芳美網站留言板");
$mail->to("emily6wu\@ms27.hinet.net", "吳芳美");
$mail->cc("imacat\@mail.imacat.idv.tw", "楊士青");
$mail->subject("[Emily] 留言板留言通知 " . fmtdate);
$mail->body($body);
# Send it
$mail->send;
return;
}
# _actlog: Log the activity
sub _actlog : method {
local ($_, %_);
my $self;
$self = $_[0];
# A form to create a new item
return gactlog "Post a new message on " . fmtdate($self->{"date"})
. " with s/n " . $self->{"sn"} . ".";
}
no utf8;
return 1;

View File

@@ -0,0 +1,283 @@
# Emily Wu's Website
# Rebuild.pm: The subroutines to rebuild the web pages.
# Copyright (c) 2004-2018 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Author: imacat <imacat@mail.imacat.idv.tw>
# First written: 2004-11-02
package Selima::emily::Rebuild;
use 5.008;
use strict;
use warnings;
use base qw(Exporter);
use vars qw(@EXPORT @EXPORT_OK);
BEGIN {
@EXPORT = qw(rebuild_all rebuild_pages rebuild_links compose_page);
@EXPORT_OK = @EXPORT;
# Prototype declaration
sub rebuild_all();
sub rebuild_pages(;$);
sub rebuild_links(;$);
sub compose_page($;$);
}
use Data::Dumper qw();
use Fcntl qw(:flock);
use IO::NestedCapture qw(CAPTURE_STDOUT);
use Selima::DataVars qw($DBH :output :rebuild);
use Selima::GetLang;
use Selima::Guest;
use Selima::PageFunc;
use Selima::ShortCut;
use Selima::emily::HTML;
use vars qw($PKGL10N);
# rebuild_all: Rebuild everything
sub rebuild_all() {
local ($_, %_);
# Lock the required tables
$DBH->lock(map { $_ => LOCK_SH } @REBUILD_TABLES);
# Rebuild the pages
rebuild_pages;
# Rebuild the links
rebuild_links;
# Rebuild the index
# To be done
#rebuild_index;
return;
}
# rebuild_pages: Rebuild the pages
sub rebuild_pages(;$) {
local ($_, %_);
my ($sql, $sth, $count, $rebuild_everything);
my $lang;
$sql = $_[0];
$lang = getlang;
# Rebuild everything
$rebuild_everything = !defined $sql;
if ($rebuild_everything) {
$sql = "SELECT * FROM pages"
. " WHERE NOT hid;\n";
}
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
# Bounce if no pages to build on a partial rebuild
# This prevents needless sitemap rebuilding
return if !$rebuild_everything && $count == 0;
# Build each page
for (my $i = 0; $i < $count; $i++) {
my ($page, $html);
$page = $sth->fetchrow_hashref;
# Read the picture into the picture deposit
# To be done
$html = compose_page($page, $lang);
goutpage $html, $$page{"path"}, $lang
if defined $html;
# Output related pictures only when rebuilding everything
# To be done
}
return;
}
# rebuild_links: Rebuild the links
sub rebuild_links(;$) {
local ($_, %_);
my ($sql, $sth, $count, $FD, $rebuild_everything);
my ($lang, $args, $html);
$sql = $_[0];
$lang = getlang;
# Rebuild everything
$rebuild_everything = !defined $sql;
if ($rebuild_everything) {
@_ = $DBH->cols("linkcat");
push @_, $DBH->strcat("'/links'", "linkcat_path(sn, id, parent)")
. " AS path";
$sql = "SELECT " . join(", ", @_) . " FROM linkcat"
. " WHERE linkcat_isshown(sn, hid, parent);\n";
}
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for (my $i = 0; $i < $count; $i++) {
my ($page, $sql1, $sth1, $count1, $row1);
$page = $sth->fetchrow_hashref;
# Find the ancesters
@_ = $DBH->cols("linkcat");
push @_, $DBH->strcat("'/links'", "linkcat_path(sn, id, parent)")
. " AS path";
$sql1 = "SELECT " . join(", ", @_) . " FROM linkcat"
. " WHERE linkcat_ischild(sn, " . $$page{"sn"} . ")"
. " ORDER BY linkcat_fullord(parent, ord);\n";
$sth1 = $DBH->prepare($sql1);
$sth1->execute;
$count1 = $sth1->rows;
for (my $i = 0, $$page{"parents"} = []; $i < $count1; $i++) {
push @{$$page{"parents"}}, $sth1->fetchrow_hashref;
}
# Find the subcategories
@_ = $DBH->cols("linkcat");
push @_, $DBH->strcat("'/links'", "linkcat_path(sn, id, parent)")
. " AS path";
$sql1 = "SELECT " . join(", ", @_) . " FROM linkcat"
. " WHERE parent=" . $$page{"sn"}
. " AND linkcat_isshown(sn, hid, parent)"
. " ORDER BY ord;\n";
$sth1 = $DBH->prepare($sql1);
$sth1->execute;
$count1 = $sth1->rows;
for (my $i = 0, $$page{"scats"} = []; $i < $count1; $i++) {
my ($sql2, $sth2, $row2);
$row1 = $sth1->fetchrow_hashref;
# Find the belonging links
$sql2 = "SELECT count(linkcatz.sn) AS count FROM linkcatz"
. " INNER JOIN links ON linkcatz.link=links.sn"
. " INNER JOIN linkcat ON linkcatz.cat=linkcat.sn"
. " WHERE linkcatz.cat=" . $$row1{"sn"}
. " AND NOT links.hid;\n";
$sth2 = $DBH->prepare($sql2);
$sth2->execute;
$row2 = $sth2->fetchrow_hashref;
$$row1{"links"} = $$row2{"count"};
push @{$$page{"scats"}}, $row1;
}
# Find the belonging links
@_ = map "links.$_", $DBH->cols("links");
$sql1 = "SELECT " . join(", ", @_) . " FROM links"
. " INNER JOIN linkcatz ON linkcatz.link=links.sn"
. " WHERE linkcatz.cat=" . $$page{"sn"}
. " AND NOT links.hid;\n";
$sth1 = $DBH->prepare($sql1);
$sth1->execute;
$count1 = $sth1->rows;
for (my $i = 0, $$page{"links"} = []; $i < $count1; $i++) {
push @{$$page{"links"}}, $sth1->fetchrow_hashref;
}
$html = compose_page($page, $lang);
goutpage $html, $$page{"path"}, $lang
if defined $html;
}
# Build the root index page
@_ = $DBH->cols("linkcat");
push @_, $DBH->strcat("'/links'", "linkcat_path(sn, id, parent)")
. " AS path";
$sql = "SELECT " . join(", ", @_) . " FROM linkcat"
. " WHERE parent IS NULL"
. " AND linkcat_isshown(sn, hid, parent)"
. " ORDER BY ord;\n";
$sth = $DBH->prepare($sql);
$sth->execute;
$count = $sth->rows;
for ($_ = 0, @_ = qw(); $_ < $count; $_++) {
my ($cat, $sql1, $sth1, $count1);
$cat = $sth->fetchrow_hashref;
# Find the belonging links
$sql1 = "SELECT count(linkcatz.sn) AS count FROM linkcatz"
. " INNER JOIN links ON linkcatz.link=links.sn"
. " INNER JOIN linkcat ON linkcatz.cat=linkcat.sn"
. " WHERE linkcatz.cat=" . $$cat{"sn"}
. " AND NOT links.hid;\n";
$sth1 = $DBH->prepare($sql1);
$sth1->execute;
$$cat{"links"} = ${$sth1->fetch}[0];
push @_, $cat;
}
$ALT_PAGE_PARAM = {
"path" => "/links/",
"lang" => $lang,
"keywords" => __("related links"),
"class" => "links",
"static" => 1,
"all_linguas" => [$lang]};
$args = page_param;
# Obtain the page
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
html_header __("Related Links"), $args;
html_links_index @_, $args;
html_footer $args;
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$html = join "", <$FD>;
undef $ALT_PAGE_PARAM;
goutpage $html, "/links/", $lang;
return;
}
# compose_page: Compose a page
sub compose_page($;$) {
local ($_, %_);
my ($page, $lang, $args, $FD);
($page, $lang) = @_;
$lang = getlang if !defined $lang;
$ALT_PAGE_PARAM = {
"path" => $$page{"path"},
"lang" => $lang,
"keywords" => $$page{"kw"},
"static" => 1,
"all_linguas" => [$lang]};
$$ALT_PAGE_PARAM{"preview"} = $page
if exists $$page{"preview"};
if (exists $$page{"class"} && defined $$page{"class"} && $$page{"class"} ne "") {
$$ALT_PAGE_PARAM{"class"} = $$page{"class"};
} elsif ($$page{"path"} =~ /^\/links\//) {
$$ALT_PAGE_PARAM{"class"} = "links";
}
$args = page_param;
# Obtain the page
IO::NestedCapture->start(CAPTURE_STDOUT);
binmode IO::NestedCapture->instance->{"STDOUT_current"}[-1], ":utf8";
html_header $$page{"title"}, $args;
if ($$page{"path"} =~ /^\/links\/$/) {
#html_links_index $page, $args;
} elsif ($$page{"path"} =~ /^\/links\/.+$/) {
html_links $page, $args;
} else {
html_body $page, $args;
}
html_footer $args;
IO::NestedCapture->stop(CAPTURE_STDOUT);
$FD = IO::NestedCapture->get_last_out;
$_ = join "", <$FD>;
undef $ALT_PAGE_PARAM;
return $_;
}
return 1;