<bigwig> service: webboard
service {
html MsgDoc = <html>
<head><title></title></head>
<body bgcolor="#ffffff">
<h1><[title]></h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[msg]>
</td></tr></table>
</body>
</html>;
html EnterEmailDoc = <html>
<head><title>Webboard: enter email</title></head>
<body bgcolor="#ffffff">
<h1>Enter Your Email</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Please enter your email address:
<br>
Email: <input type="text" name="email">
</td></tr></table>
<format field="email"><bigwig-regexp idref="Email"/></format>
<continue type=button>Continue</continue>
</body>
</html>;
html KeyCheckDoc = <html>
<head><title>Webboard: key check</title></head>
<body bgcolor="#ffffff">
<h1>WebBoard Key Verification</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
A (random) key has been sent to you by email. Please enter it below:
<br>
Key: <input type="text" name="key">
</td></tr></table>
<format field="key"><bigwig-regexp idref="Number"/></format>
</body>
</html>;
html GuestLoginDoc = <html>
<head><title>Webboard: guest login</title></head>
<body bgcolor="#ffffff">
<h1>Guest Login</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
You are logged in as a `guest' user. Restrictions apply.
</td></tr></table>
<continue type=button>Continue</continue>
</body>
</html>;
html RegisterDoc = <html>
<head><title>Webboard: register</title></head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard Register</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<table>
<tr>
<td>Login:</td>
<td><input type="text" name="login" value=[login]></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" value=[password]></td>
</tr>
<tr>
<td>Name:</td>
<td><input type="text" name="name" value=[name]></td>
</tr>
<tr>
<td>Email<sup>*</sup>:</td>
<td><input type="text" name="email" value=[email]></td>
</tr>
</table>
</td></tr></table>
<sup>*</sup>) As a security you will receive a (random) key via email.
<p>
<continue type=button value="REGISTER">Register</continue>
<br>
<continue ignoreformats type=button value="CANCEL">Cancel</continue>
<continue ignoreformats type=button value="QUIT">Quit</continue>
<format field="login"><bigwig-regexp idref="Word"/></format>
<format field="password"><bigwig-regexp idref="Word"/></format>
<format field="email"><bigwig-regexp idref="Email"/></format>
</body>
</html>;
html LoginDoc = <html>
<head><title>Webboard Login</title></head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard Login</h1>
<continue ignoreformats type=button value="GUEST">Guest</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<table>
<tr>
<td>Login:</td>
<td><input type="text" name="login" value=[login]></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" value=[password]></td>
</tr>
</table>
</td></tr></table>
<br>
<p>
<continue type=button value="LOGIN">Login</continue>
<continue ignoreformats type=button value="REGISTER">Register</continue>
<br>
<continue ignoreformats type=button value="FORGOT">Forgot Password</continue>
<continue ignoreformats type=button value="QUIT">Quit</continue>
<format field="login"><bigwig-regexp idref="Word"/></format>
<format field="password"><bigwig-regexp idref="Word"/></format>
</p>
</body>
</html>;
html PreferenceDoc = <html>
<head><title>Webboard: preferences</title></head>
<body bgcolor="#ffffff">
<h1>Preferences</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<h3>Identity:</h3>
<table>
<tr>
<td>Login:</td>
<td><input type="text" size=10 name="login" value=[login]></td>
<td> </td>
<td>Name:</td>
<td><b><[name]></b></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" size=10 name="password" value=[password]></td>
<td> </td>
<td>Email:</td>
<td><b><[email]></b></td>
</tr>
</table>
<format field="login"><bigwig-regexp idref="Word"/></format>
<format field="password"><bigwig-regexp idref="Word"/></format>
<hr>
<h3>Notification:</h3>
Send me new postings by email:<br>
<input type="radio" name="notify" value="n" [notify_no]> Never.<br>
<input type="radio" name="notify" value=" " [notify_my]> Only replies to my postings. <br>
<input type="radio" name="notify" value="y" [notify_yes]> Always.
<hr>
<h3>Filter:</h3>
Enable filter:
<input type="checkbox" name="filter" value="true" [filter]>
<br>
<input type="radio" name="custom_filter" value="true" [custom_filter_true]>
Filter messages older than
<input type="text" name="filter_days" size="4" value=[filter_days]> days.
<format field="filter_days"><bigwig-regexp idref="Number"/></format>
<br>
<input type="radio" name="custom_filter" value="false" [custom_filter_false]>
Filter messages before (last logout): <em><[logout_time]></em>
<br>
<br>
Enable grayout:
<input type="checkbox" name="grayout" value="true" [grayout]>
<hr>
<h3>Appearance:</h3>
Thread view:
<input type="checkbox" name="thread" value="true" [thread]>
<br>
Newest postings at the:
<input type="radio" name="new_first" value="true" [new_first_true]>
top
/
<input type="radio" name="new_first" value="false" [new_first_false]>
bottom
<br>
Display time using am/pm: <input type="checkbox" name="ampm" value="true" [ampm]><br>
Local timezone (GMT offset): GMT +<input type="text" name="gmtoffset" size="3" maxlength="3" value=[gmtoffset]><br>
Reply message indent level: <input type="text" size="1" maxlength="1" name="indent" value=[indent]><br>
<format field="gmtoffset"><bigwig-regexp idref="GmtOffset"/></format>
<format field="indent"><bigwig-regexp idref="Digit"/></format>
</td></tr></table>
<continue type=button>Continue</continue>
</body>
</html>;
html ShowBoardDoc = <html>
<head>
<title>Webboard: show board</title>
<style type="text/css">
span.GrayOut {
color : #999999;
}
span.GrayOut a { color: #9999ff; }
</style>
</head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard</h1>
<continue type=button value="NEW">Post New Message</continue>
<continue type=button value="VIEW">Refresh</continue>
<continue type=button value="PREFS">Preferences</continue>
<continue type=button value="SEARCH">Search</continue>
<continue type=button value="QUIT">Quit</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[msgs]>
</td></tr></table>
<br>
Number of messages: <[no_msgs]> (<[total_no_msgs]>).
<p>
<continue type=button value="NEW">Post New Message</continue>
<continue type=button value="VIEW">Refresh</continue>
<continue type=button value="PREFS">Preferences</continue>
<continue type=button value="SEARCH">Search</continue>
<continue type=button value="QUIT">Quit</continue>
</p>
</body>
</html>;
html MainDoc = <html>
<[replies]>
</html>;
html MinusDoc = <html><img alt="" border=0 src="../minus.gif"></html>;
html PlusDoc = <html><img alt="" border=0 src="../plus.gif"></html>;
html CollapseDoc = <html>
<continue value=[collapse_id]><[collapse_symbol]></continue>
</html>;
html NoIndentDoc = <html><[indent]></html>;
html IndentDoc = <html> <[indent]></html>;
html ReadSubjectDoc = <html><[subject]></html>;
html UnReadSubjectDoc = <html><b><[subject]></b></html>;
html GrayoutMessageDoc = <html>
<span class="GrayOut"><[indent]><[collapse]>"<continue value=[id]><[subject]></continue>" - <i><[name]></i> (<[pretty_time]>)</span><br>
<[replies]>
</html>;
html MessageDoc = <html>
<[indent]><[collapse]>"<continue value=[id]><[subject]></continue>" - <i><[name]></i> (<[pretty_time]>)<br>
<[replies]>
</html>;
html NewFirstDoc = <html>
<[reply]>
<[replies]>
</html>;
html NewLastDoc = <html>
<[replies]>
<[reply]>
</html>;
html ReplyDoc = <html>
<head><title>Webboard: reply</title></head>
<body bgcolor="#ffffff">
<h1>Enter Reply</h1>
<continue type="button" value="true">Submit</continue>
<continue type="button" value="false">Cancel</continue>
<br>
<table border="1" bgcolor="#ffffcc" width="100%"><tr><td>
Subject:<br>
<input type="text" name="subject" value=[subject]>
<p>
Message:<br>
<textarea name="content" cols=[cols] rows="20"><[content]></textarea>
</p>
<p>
Wrap lines: (
yes <input type="radio" name="linewrap" value="true" checked> /
no <input type="radio" name="linewrap" value="false"> ) ?
</p>
<br>
</td></tr></table>
<p>
<continue type="button" value="true">Submit</continue>
<continue type="button" value="false">Cancel</continue>
</p>
</body>
</html>;
html NavBarDoc = <html>
<hr noshade size="3">
<h3>Local Navigation:</h3>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[navbar]>
</td></tr></table>
</html>;
html ReadMessageDoc = <html>
<head><title>Webboard: read message</title></head>
<body bgcolor="#ffffff">
<h1><[subject]></h1>
<continue type="button" value="REPLY">Reply</continue>
<continue type="button" value="BACK">Back</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<pre><[content]></pre>
<p align="right">
<em>/<[name]></em>
</p>
</td></tr></table>
<font size="-1"><[pretty_time]></font>
<p>
<continue type="button" value="REPLY">Reply</continue>
<continue type="button" value="BACK">Back</continue>
</p>
<[navbar]>
</body>
</html>;
html SearchDoc = <html>
<head><title>Webboard: search</title></head>
<body bgcolor="#ffffff">
<h1><[title]></h1>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Please enter your substring regexp query:<br>
Query: <input type="text" name="query">
<br>
Regexp Syntax:<br>
<table><tr>
<td><code>c</code></td><td>the character 'c'</td>
</tr><tr>
<td><code>.</code></td><td>any character</td>
</tr><tr>
<td><code>[a-z]</code></td><td>any character n the range from 'a' to 'z'</td>
</tr><tr>
<td><code><em>RS</em></code></td><td>concatenation of <em>R</em> and <em>S</em></td>
</tr><tr>
<td><code><em>R</em>*</code></td><td>zero-or-more repetitions of <em>R</em></td>
</tr><tr>
<td><code><em>R</em>+</code></td><td>one-or-more repetitions of <em>R</em></td>
</tr><tr>
<td><code><em>R</em>\{m,n\}</code></td><td>m to n repetitions of <em>R</em></td>
</tr></table>
<p>
See <code>man regexp</code> for more details.
</p>
</td></tr></table>
<p>
<continue type="button" value="SEARCH">Search</continue>
<continue type="button" value="BACK">Back</continue>
</p>
</body>
</html>;
html BrowseSearchDoc = <html>
<head><title>Webboard: browse search</title></head>
<body bgcolor="#ffffff">
<h1>Search Results</h1>
<continue type=button value="BACK">Exit Search Mode</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[msgs]>
</td></tr></table>
<p>
Number of search results: <[no_msgs]>.
</p>
<continue type=button value="BACK">Exit Search Mode</continue>
</body>
</html>;
html GoodbyeInfoDoc = <html>
You <i>read</i> <[no_read]> message<[no_read_s]> and <i>wrote</i> <[no_write]>.
<br>
Number of times you have logged in: <[no_user_login]>.
<br>
</html>;
html GoodbyeDoc = <html>
<head><title>Webboard: goodbye</title></head>
<body bgcolor="#ffffff">
<h1>Goodbye <[name]>.</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Thank you for using the WebBoard.
<p>
<[info]>
Total number of logins to the WebBoard: <[no_login]>.
</p>
</td></tr></table>
</body>
</html>;
string WEBBOARD_NAME = "<bigwig>";
string SENDER_EMAIL = "bigwig@brics.dk";
int SERVER_GMTOFFSET = +1;
int DEFAULT_INDENT = 3;
int WRAP = 72;
string QUOTE = "> ";
tuple User DEFAULT_USER = tuple {
login="", password="", name="", email="", no_login=0,
indent=DEFAULT_INDENT, ampm=false, notify='n', gmtoffset=SERVER_GMTOFFSET,
filter=true, grayout=true, custom_filter=false, logout_time=notime,
filter_days=7, navbar=true, new_first=false, thread=true };
schema Message {
int id, reply_to;
string name, email, subject, content;
time time_stamp;
}
schema User {
string login, password, name, email;
int no_login, indent, gmtoffset, filter_days;
bool ampm, filter, grayout, custom_filter, navbar, new_first, thread;
char notify;
time logout_time;
}
schema MsgPrefs {
int msg_id;
string user_login;
}
protected shared int no_login ;
protected shared relation tuple Message board ;
protected shared int next_id ;
protected shared relation tuple User users ;
protected shared relation tuple MsgPrefs collapse ;
protected shared relation tuple MsgPrefs has_read ;
format Re = concat("[Re: ", star(anychar), "]");
format Digit = range('0', '9');
format Number = plus ( Digit );
format Alpha = union(range('a', 'z'), range('A', 'Z'));
format Word = plus ( union(Alpha, Digit, "-", "_") );
format Email = concat(Word, "@", Word, star(concat(".", Word)));
format GmtOffset = relax(-12, 12);
format Line = concat([line = star(charcomplement ( '\n' ))], "\n", [rest = star(anychar)]);
tuple User user;
int no_read, no_write;
session Notify(int new_msg_id) {
relation tuple User U;
tuple Message m;
relation tuple Message B;
string content;
if (new_msg_id != 0) {
flash <html>Notifying users of new WebBoard message...</html>;
reader (board) B = board;
m = getMessage(B, new_msg_id);
content = "This is an autogenerated message.\nA new message has arrived to the " + WEBBOARD_NAME + " WebBoard:\n\n==================================================\nSUBJECT: " + m.subject + "\nAUTHOR: " + m.name + " (" + m.email + ")\nTIME: " + prettyTime(m.time_stamp, false) + "\n--------------------------------------------------\n" + m.content + "\n==================================================\n\nThe " + WEBBOARD_NAME + " WebBoard: \n" + url Use();
reader (users) U = users;
factor(U; notify, email) {
if (#.notify == 'y' || #.notify == ' ' && getFather(B, new_msg_id).email == #.email) {
sendemail ( #.email , SENDER_EMAIL , "New " + WEBBOARD_NAME + " WebBoard Message" , content )
/* empty */;
}
};
}
}
time client2serverTime(time t) {
if (t != notime) return setHour ( t , getHour(t) - (user.gmtoffset - SERVER_GMTOFFSET) );
return t;
}
time server2clientTime(time t) {
if (t != notime) return setHour ( t , getHour(t) + user.gmtoffset - SERVER_GMTOFFSET );
return t;
}
string prettyMonth(time t) {
return vector { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }[(getMonth(t) - 1)];
}
string twoDigits(int n) {
if (n < 10) return "0" + n;
else return (string)n;
}
string prettyTime(time t, bool ampm) {
string date;
if (t == notime) return "notime";
t = server2clientTime(t);
date = prettyMonth(t) + " " + getDay(t) + ", " + getYear(t) + " @ ";
if (ampm) {
date += (getHour(t) + 11) % 12 + 1 + ":" + twoDigits(getMinute(t)) + " ";
if (getHour(t) < 12) date += "am";
else date += "pm";
}
else {
date += twoDigits(getHour(t)) + ":" + twoDigits(getMinute(t));
}
return date;
}
tuple Message getMessage(relation tuple Message B, int id) {
tuple Message m;
factor(B) {
if (#.id == id) m = #;
};
return m;
}
tuple Message getFather(relation tuple Message B, int id) {
tuple Message m, father;
m = getMessage(B, id);
factor(B) {
if (#.id == m.reply_to) father = #;
};
return father;
}
relation tuple Message getChildren(relation tuple Message B, int id) {
return Select * from B where #.reply_to == id;
}
tuple User changePreferences(tuple User user) {
html P = PreferenceDoc;
P = P <[login=user.login,
password=user.password,
name=user.name,
email=user.email,
filter=user.filter ? "checked" : "",
grayout=user.grayout ? "checked" : "",
thread=user.thread ? "checked" : "",
custom_filter_true=user.custom_filter ? "checked" : "",
custom_filter_false=!user.custom_filter ? "checked" : "",
filter_days=user.filter_days,
logout_time=prettyTime(server2clientTime(user.logout_time), user.ampm),
indent=user.indent,
gmtoffset=user.gmtoffset,
notify_no=user.notify == 'n' ? "checked" : "",
notify_my=user.notify == ' ' ? "checked" : "",
notify_yes=user.notify == 'y' ? "checked" : "",
ampm=user.ampm ? "checked" : "",
new_first_true=user.new_first ? "checked" : "",
new_first_false=!user.new_first ? "checked" : ""];
show P receive [user.login=login,
user.password=password,
user.new_first=new_first,
user.ampm=ampm,
user.notify=notify,
user.filter=filter,
user.grayout=grayout,
user.thread=thread,
user.custom_filter=custom_filter,
user.filter_days=filter_days,
user.indent=indent,
user.gmtoffset=gmtoffset];
return user;
}
void quit() {
int n;
html H;
string name;
if (user.login != "") {
name = user.name;
H = GoodbyeInfoDoc <[no_read=no_read,
no_read_s=no_read != 1 ? "s" : "",
no_write=no_write,
no_user_login=user.no_login];
}
else {
name = "guest";
}
reader (no_login) n = no_login;
exit GoodbyeDoc <[name=name,
no_login=n,
info=H];
}
bool filter(time msg_time) {
time filter_time;
if (user.custom_filter) {
filter_time = server2clientTime(now());
filter_time = (setHour ( filter_time , 0 ));
filter_time = (setMinute ( filter_time , 0 ));
filter_time = (setSecond ( filter_time , 0 ));
filter_time = client2serverTime(filter_time);
filter_time = (setDay ( filter_time , getDay(filter_time) - user.filter_days ));
}
else {
filter_time = user.logout_time;
}
return user.filter && msg_time <= filter_time;
}
html renderMessage(int indent, tuple Message message, relation tuple MsgPrefs Read, html CollapseImgDoc) {
int i;
html H = MainDoc;
html I = NoIndentDoc;
html SubjectDoc = UnReadSubjectDoc;
factor(Read) {
if (message.id == #.msg_id) SubjectDoc = ReadSubjectDoc;
};
for (i = 0 ; i < indent ; i++) I =<[indent=IndentDoc];
if (user.grayout || !filter(message.time_stamp)) {
if (user.grayout && filter(message.time_stamp)) {
H = GrayoutMessageDoc;
}
else {
H = MessageDoc;
}
H =<[indent=I,
id=message.id,
subject=SubjectDoc <[subject=message.subject],
name=message.name,
pretty_time=prettyTime(message.time_stamp, user.ampm),
collapse=CollapseDoc <[collapse_symbol=CollapseImgDoc,
collapse_id=-message.id]];
}
return H;
}
html getCollapseDoc(int id, relation tuple MsgPrefs Collapse) {
html H = MinusDoc;
factor(Collapse) {
if (id == #.msg_id) H = PlusDoc;
};
return H;
}
html makeBoardRec(int indent, int id, relation tuple Message B, relation tuple MsgPrefs Collapse, relation tuple MsgPrefs Read) {
int i;
html H;
tuple Message message;
vector tuple Message children;
if (id != 0) {
message = getMessage(B, id);
H = renderMessage(indent, message, Read, getCollapseDoc(message.id, Collapse));
}
else {
H = MainDoc;
}
children = sort((vector tuple Message)getChildren(B, id); id);
for (i = 0 ; i < |children| ; i++) {
html M;
if (user.new_first) M = NewLastDoc;
else M = NewFirstDoc;
H =<[replies=M <[reply=makeBoardRec(indent + user.indent, children[i].id, B, Collapse, Read)]];
}
return H <[replies=""];
}
html makeBoardList(int indent, relation tuple Message B, relation tuple MsgPrefs Collapse, relation tuple MsgPrefs Read) {
html H = MainDoc;
vector tuple Message V = sort((vector tuple Message)B; id);
int i;
if (user.new_first) {
for (i = |V| - 1 ; i >= 0 ; i--) {
H =<[replies=renderMessage(indent, V[i], Read, getCollapseDoc(V[i].id, Collapse))];
}
}
else {
for (i = 0 ; i < |V| ; i++) {
H =<[replies=renderMessage(indent, V[i], Read, getCollapseDoc(V[i].id, Collapse))];
}
}
return H;
}
relation tuple Message collapseFilter(relation tuple Message B, relation tuple MsgPrefs Collapse) {
relation tuple MsgPrefs C;
if (|Collapse| == 0) return B;
B = factor(B, rename in Collapse from msg_id to reply_to) {
if (|@2| == 0) {
return cart(@1, relation { # });
}
else {
C = (Union (C, factor(@1) {
return tuple { msg_id=#.id, user_login="" };
}));
}
};
return collapseFilter(B, C);
}
html makeBoard() {
html x;
int total;
relation tuple Message B;
relation tuple MsgPrefs Collapse, Read;
reader (board) B = board;
total = |B|;
if (user.login != "") {
reader (collapse) {
Collapse = (Select * from collapse where #.user_login == user.login);
}
reader (has_read) {
Read = (Select * from has_read where #.user_login == user.login);
}
}
B = collapseFilter(B, Collapse);
if (user.thread) {
x = makeBoardRec(0, 0, B, Collapse, Read);
}
else {
x = makeBoardList(DEFAULT_INDENT, B, Collapse, Read);
}
return ShowBoardDoc <[no_msgs=|B|, total_no_msgs=total, msgs=x];
}
session Use() {
string action;
void updateUser() {
writer (users) {
users = (Union (Select * from users where #.login != user.login,
relation { user }));
}
}
void register(string login, string password, string name, string email) {
int ran;
int key = -1;
relation tuple User U;
vector tuple User V;
string action;
show RegisterDoc <[login=login, password=password, name=name, email=email] receive [
login=login, password=password, name=name, email=email, action=continue];
switch (action) {
case "REGISTER":
reader (users) U = users;
V = (vector tuple User)(Select * from U where #.login == login);
if (|V| > 0) {
show MsgDoc <[title="Login Already Taken", msg="Please choose another..."];
register("", password, name, email);
}
else {
user = DEFAULT_USER << tuple { login=login, password=password, name=name, email=email };
ran = random(1000000);
sendemail ( email , SENDER_EMAIL , "WebBoard Security Key" , "This is an autogenerated message.\nYour WebBoard security key is: " + ran + "\n\nClick here to continue: \n" + url + "\n\n/" + WEBBOARD_NAME + " WebBoard" )
/* empty */;
while (key != ran) {
show KeyCheckDoc receive [key=key];
}
user = changePreferences(user);
updateUser();
}
break;
case "CANCEL":
doLogin(login, password);
break;
case "QUIT":
quit();
break;
}
}
void doLogin(string login, string password) {
relation tuple User U;
vector tuple User V;
string action;
show LoginDoc <[login=login, password=password] receive [
action=continue, login=login, password=password];
switch (action) {
case "GUEST":
user = DEFAULT_USER;
show GuestLoginDoc;
break;
case "REGISTER":
reader (users) U = users;
V = (vector tuple User)(Select * from U where #.login == login);
if (|V| > 0) {
show MsgDoc <[title="Login Already Taken", msg="Please choose another..."];
doLogin(login, password);
}
else {
register(login, password, "", "");
}
break;
case "LOGIN":
reader (users) U = users;
V = (vector tuple User)(Select * from U where #.login == login);
if (|V| > 0 && V[0].password == password) {
user = V[0];
user.no_login++;
updateUser();
}
else {
show MsgDoc <[title="Login Incorrect", msg="Login or password incorrect. Please try again..."];
doLogin(login, password);
}
break;
case "FORGOT":
sendPassword();
doLogin("", "");
break;
case "QUIT":
quit();
break;
}
}
string addReply(string s) {
if (match (s, Re) []) return s;
else return "[Re: " + s + "]";
}
int lineCut(string s) {
int i;
for (i = WRAP - 1 ; i > 0 ; i--) if (s[i] == ' ') return i + 1;
return WRAP;
}
string copyQuotes(string s) {
if (|s| >= |QUOTE| && s[0..|QUOTE|] == QUOTE) return QUOTE + copyQuotes(s[|QUOTE|..|s|]);
return "";
}
string lineWrap(string rest) {
string x, line;
rest += "\n";
while (match (rest, Line) [line=line, rest=rest]) {
while (|line| > WRAP) {
string new;
int cut;
cut = lineCut(line);
new = line[0..cut];
x += new + "\n";
line = copyQuotes(new) + line[cut..|line|];
}
x += line + "\n";
}
return x + rest;
}
string quote(string rest) {
string x, line;
while (match (rest, Line) [line=line, rest=rest]) {
x += QUOTE + line + "\n";
}
return x + QUOTE + rest;
}
void replyMessage(int reply_to) {
int new_id;
bool submit, linewrap;
relation tuple Message B;
tuple Message New, Old;
string subject, content;
if (user.login == "") {
doLogin("", "");
if (user.login == "") return;
}
if (reply_to != 0) {
reader (board) B = board;
Old = getMessage(B, reply_to);
subject = addReply(Old.subject);
content = quote(Old.content);
}
show ReplyDoc <[cols=WRAP,
subject=subject,
content=content] receive [subject=subject,
content=content,
linewrap=linewrap,
submit=continue];
if (submit) {
if (subject == "") subject = "no subject";
if (linewrap) content = lineWrap(content);
writer (next_id) new_id = ++next_id;
New = tuple { id=new_id,
reply_to=reply_to,
name=user.name,
email=user.email,
subject=subject,
content=content,
time_stamp=now() };
no_write++;
writer (board) board = (Union (board, relation { New }));
writer (has_read) has_read = (Union (has_read, relation { tuple { user_login=user.login, msg_id=new_id } }));
get(url Notify(New.id)) [];
}
}
void readMessage(tuple Message message) {
string action;
relation tuple Message B;
html N;
no_read++;
if (user.login != "") {
writer (has_read) {
has_read = (Union (has_read, relation { tuple { msg_id=message.id, user_login=user.login } }));
}
}
if (user.navbar) {
int father_id;
tuple Message father;
relation tuple Message B, children;
relation tuple MsgPrefs C, R;
reader (board) B = board;
father_id = message.reply_to;
father = getMessage(B, father_id);
children = getChildren(B, message.id);
B = (Union (relation { message }, children));
if (father.id != 0) B = (Union (B, relation { father }));
if (user.login != "") {
reader (collapse) {
C = (Select * from collapse where #.user_login == user.login);
}
reader (has_read) {
R = (Select * from has_read where #.user_login == user.login);
}
}
N = NavBarDoc <[navbar=makeBoardRec(user.indent, father.id, B, C, R)];
}
show ReadMessageDoc <[name=message.name,
subject=message.subject,
content=message.content,
pretty_time=prettyTime(message.time_stamp, user.ampm),
navbar=N] receive [action=continue];
if (match (action, Number) []) {
reader (board) B = board;
readMessage(getMessage(B, (int)action));
}
else {
if (action == "REPLY") replyMessage(message.id);
}
}
void sendPassword() {
int i;
string email;
relation tuple User U;
vector tuple User V;
show EnterEmailDoc receive [email=email];
reader (users) U = users;
V = (vector tuple User)(Select * from U where #.email == email);
if (|V| > 0) {
for (i = 0 ; i < |V| ; i++) {
sendemail ( email , SENDER_EMAIL , WEBBOARD_NAME + " WebBoard password" , "This is an autogenerated message.\nHere is your password for the " + WEBBOARD_NAME + " WebBoard:\n\nLOGIN: " + V[i].login + "\nPASSWORD: " + V[i].password + "\n\nThe " + WEBBOARD_NAME + " WebBoard: \n" + url Use() )
/* empty */;
}
show MsgDoc <[title="Password Sent", msg="Your login and password have been sent to you by email."];
}
else {
show MsgDoc <[title="No Such User", msg="Sorry, you are not listed as a user."];
}
}
void search(string title) {
string query, action;
relation tuple Message B;
relation tuple MsgPrefs Read;
show SearchDoc <[title=title] receive [query=query, action=continue];
if (action == "SEARCH" && query != "") {
reader (board) B = board;
B = factor(B) {
if (match (#.content, query) []) return #;
};
if (|B| != 0) {
html H;
reader (has_read) Read = has_read;
H = makeBoardList(DEFAULT_INDENT, B, relation { }, Read);
H = BrowseSearchDoc <[no_msgs=|B|, msgs=H];
repeat {
show H receive [action=continue];
if (action != "BACK") handleRead((int)action);
} until ( action == "BACK" ) ;
}
else {
search("No Results Found");
}
}
}
void invertMsgPrefs(int msg_id, string user_login) {
bool was_collapsed;
relation tuple MsgPrefs C;
tuple MsgPrefs t = tuple { msg_id=msg_id, user_login=user_login };
writer (collapse) {
C = factor(collapse) {
if (# == t) was_collapsed = true;
else return #;
};
if (was_collapsed) collapse = C;
else collapse = (Union (C, relation { t }));
}
}
void handleRead(int msg_id) {
if (msg_id > 0) {
relation tuple Message B;
reader (board) B = board;
readMessage(getMessage(B, msg_id));
}
else if (msg_id < 0) {
if (user.login != "") {
invertMsgPrefs(-msg_id, user.login);
}
else {
doLogin("", "");
}
}
}
doLogin("", "");
writer (no_login) no_login++;
while (action != "QUIT") {
show makeBoard() receive [action=continue];
switch (action) {
case "NEW":
replyMessage(0);
break;
case "PREFS":
if (user.login == "") {
doLogin("", "");
}
else {
user = changePreferences(user);
updateUser();
}
break;
case "SEARCH":
search("Search");
break;
default:
handleRead((int)action);
break;
}
}
if (user.login != "") {
user.logout_time = now();
updateUser();
}
quit();
}
html AdminLoginDoc = <html>
<head><title>Webboard: admin login</title></head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard Administration Login</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Please enter the administration password:
<table>
<tr>
<td>Password:</td>
<td><input type="password" name="pass"></td>
<format field="pass"><bigwig-regexp idref="Word"/></format>
</tr>
</table>
</td></tr></table>
<continue type=button value="LOGIN">Login</continue>
<continue type=button value="CHANGE">Change Password</continue>
</body>
</html>;
html UpdatePasswordDoc = <html>
<head><title>Webboard: admin update password</title></head>
<body bgcolor="#ffffff">
<h1>Update Password</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Please enter your new password (twice):
<table>
<tr>
<td>Password:</td>
<td><input type="password" name="pass1"></td>
<format field="pass1"><bigwig-regexp idref="Word"/></format>
</tr>
<tr>
<td>(Again):</td>
<td><input type="password" name="pass2"></td>
<format field="pass2"><bigwig-regexp idref="Word"/></format>
</tr>
</table>
</td></tr></table>
<continue type=button value="UPDATE">Update</continue>
<continue type=button value="CANCEL">Cancel</continue>
</body>
</html>;
html UserDoc = <html>
<tr>
<td><input type="checkbox" name="del" value=[login_id]></td>
<td><b><[login]></b></td>
<td><[name]></td>
<td><a href=[mailto]><[email]></a></td>
<td><[notify]></td>
<td><[filter]></td>
<td><[grayout]></td>
<td><[filter_days]></td>
<td><[custom_filter]></td>
<td><[logout_time]></td>
<td><[navbar]></td>
<td><[indent]></td>
<td><[ampm]></td>
<td><[gmtoffset]></td>
<td><[no_login]></td>
</tr>
<[users]>
</html>;
html DeleteUserDoc = <html>
<head><title>Webboard: admin delete user</title></head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard Users</h1>
<continue type="button" value="DELETE">Delete</continue>
<continue type="button" value="BACK">Back</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<table border=1>
<tr>
<th> </th>
<th colspan="3">Identity</th>
<th> </th>
<th colspan="5">Filter</th>
<th colspan="4">Misc.</th>
<th> </th>
</tr>
<tr>
<th>Delete?</th>
<th>Login</th>
<th>Name</th>
<th>Email</th>
<th>notify</th>
<th>filter</th>
<th>grayout</th>
<th>days</th>
<th>custom</th>
<th>last</th>
<th>navbar</th>
<th>indent</th>
<th>ampm</th>
<th>gmt</th>
<th>#Logins</th>
</tr>
<[users]>
</table>
<br>
</td></tr></table>
<p>
Total number of users: <[no_users]>
</p>
<p>
<continue type="button" value="DELETE">Delete</continue>
<continue type="button" value="BACK">Back</continue>
</p>
</body>
</html>;
html AdminShowBoardDoc = <html>
<head><title>Webboard: admin show board</title></head>
<body bgcolor="#ffffff">
<h1><tt><font color="#ff0000"><</font>bigwig<font color="#ff0000">></font></tt> WebBoard Administration</h1>
<continue type=button value="USERS">Show Users</continue>
<continue type=button value="QUIT">Quit</continue>
<br>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[]><continue value="0"><b>MESSAGES:</b></continue>
<br>
<[msgs]>
</td></tr></table>
<br>
Total number of messages: <[no_msgs]>
<br>
<hr noshade size=3>
<br>
<h3>Cut'n'Paste Buffer</h3>
<input type="radio" name="cut" checked value="true"> <b>Cut</b>:
Click on a message to move it (along with all its replies)
to the cut'n'paste buffer.<br>
<input type="radio" name="cut" value="false"> <b>Paste</b>:
Click on a message to move the cut'n'paste buffer onto the
board as a reply to it.
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
<[cutpaste]>
</td></tr></table>
<br>
Total number of messages: <[no_cutpaste_msgs]>
<p>
<continue type=button value="EMPTY">Empty Buffer</continue>
</p>
<br>
<hr noshade size=3>
<br>
<continue type=button value="USERS">Show Users</continue>
<continue type=button value="QUIT">Quit</continue>
</body>
</html>;
html AdminByeByeDoc = <html>
<head><title>Webboard: admin goodbye</title></head>
<body bgcolor="#ffffff">
<h1>Goodbye Administrator.</h1>
<table border=1 bgcolor="#ffffcc" width="100%"><tr><td>
Thank you for using the WebBoard.
<p>
You made <[no_cut]> <i>cut<[cut_s]></i> and <[no_paste]> <i>paste<[paste_s]></i>.
</p>
</td></tr></table>
</body>
</html>;
session Administrate() {
protected shared string password ;
int id, no_cut, no_paste;
string action;
bool cut;
void updateAdminPassword() {
bool done;
string pass1, pass2;
while (!done) {
show UpdatePasswordDoc receive [pass1=pass1, pass2=pass2, action=continue];
if (action == "CANCEL") {
done = true;
show MsgDoc <[title="Password Update Cancelled", msg="Password not updated"];
}
else {
if (pass1 != pass2) {
show MsgDoc <[title="Passwords Not Equal", msg="The two passwords are not equal as they should be"];
}
else {
done = true;
writer (password) password = pass1;
show MsgDoc <[title="Password Updated", msg="Your password has been updated"];
}
}
}
}
void adminLogin() {
bool done;
string pass, passwd;
reader (password) passwd = password;
if (passwd == "") updateAdminPassword();
while (pass != passwd) {
show AdminLoginDoc receive [pass=pass, action=continue];
if (pass != passwd) show MsgDoc <[title="Incorrect Password", msg="Please try again..."];
}
if (action == "CHANGE") {
updateAdminPassword();
}
}
void showUsers() {
int i;
html H;
string action;
vector string del;
relation tuple User U;
vector tuple User V;
relation tuple MsgPrefs M;
while (action != "BACK") {
H = DeleteUserDoc;
reader (users) V = sort((vector tuple User)users; login);
for (i = 0 ; i < |V| ; i++) H = H <[users=UserDoc <[login_id=V[i].login,
login=V[i].login,
name=V[i].name,
mailto="mailto:" + V[i].email,
email=V[i].email,
notify=V[i].notify,
filter=V[i].filter,
grayout=V[i].grayout,
filter_days=V[i].filter_days,
custom_filter=V[i].custom_filter,
logout_time=V[i].logout_time,
navbar=V[i].navbar,
indent=V[i].indent,
ampm=V[i].ampm,
gmtoffset=V[i].gmtoffset,
no_login=V[i].no_login]];
show H <[no_users=|V|] receive [del=del, action=continue];
if (action == "DELETE") {
writer (users) {
U = users;
for (i = 0 ; i < |del| ; i++) {
U = (Select * from U where #.login != del[i]);
}
users = U;
}
writer (collapse) {
M = collapse;
for (i = 0 ; i < |del| ; i++) {
M = (Select * from M where #.user_login != del[i]);
}
collapse = M;
}
writer (has_read) {
M = has_read;
for (i = 0 ; i < |del| ; i++) {
M = (Select * from M where #.user_login != del[i]);
}
has_read = M;
}
}
}
}
shared relation tuple Message cutpaste, source, target;
shared int cutpaste_id;
void moveMessage(int id) {
int i;
tuple Message message;
vector tuple Message children;
target = (Union (target, Select * from source where #.id == id));
source = (Select * from source where #.id != id);
children = (vector tuple Message)getChildren(source, id);
for (i = 0 ; i < |children| ; i++) {
moveMessage(children[i].id);
}
}
void cutMessage(int id) {
source = board;
target = relation { };
moveMessage(id);
board = source;
cutpaste = factor(target) {
if (#.id == id) return # << tuple { reply_to=0 };
else return #;
};
cutpaste_id = id;
}
void pasteMessage(int id) {
source = factor(cutpaste) {
if (#.id == cutpaste_id) return # << tuple { reply_to=id };
else return #;
};
target = board;
moveMessage(cutpaste_id);
board = target;
cutpaste = relation { };
cutpaste_id = 0;
}
html makeAdminBoard() {
relation tuple Message B, C;
relation tuple MsgPrefs empty;
reader (board) {
B = board;
C = cutpaste;
}
return AdminShowBoardDoc <[no_msgs=|B|,
msgs=makeBoardRec(0, 0, B, empty, empty),
no_cutpaste_msgs=|C|,
cutpaste=makeBoardRec(0, 0, C, empty, empty)];
}
adminLogin();
user.indent = DEFAULT_INDENT;
while (action != "QUIT") {
show makeAdminBoard() receive [action=continue, cut=cut];
switch (action) {
case "EMPTY":
writer (board) {
cutpaste = relation { };
cutpaste_id = 0;
}
break;
case "USERS":
showUsers();
break;
default:
if (match (action, Number) []) {
tuple Message m;
id = (int)action;
reader (board) m = getMessage(board, id);
if (id == m.id) {
if (cut) {
writer (board) cutMessage(id);
no_cut++;
}
else {
writer (board) pasteMessage(id);
no_paste++;
}
}
}
break;
}
}
exit AdminByeByeDoc <[no_cut=no_cut,
no_paste=no_paste];
}
}