Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Rough and untested implementation for forum display and reply. Add two new capabilities for posting to the forum not subject to moderation, and for the ability to edit posts from others. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | forum-brainstorm-1 |
| Files: | files | file ages | folders |
| SHA3-256: |
f8927901c2c6579c8f926e38aa136ba2 |
| User & Date: | drh 2018-06-15 20:48:18.182 |
Context
|
2018-06-16
| ||
| 13:36 | Progress toward getting the forum to actually work. This is an incremental check-in. ... (check-in: 4814c41a9a user: drh tags: forum-brainstorm-1) | |
|
2018-06-15
| ||
| 20:48 | Rough and untested implementation for forum display and reply. Add two new capabilities for posting to the forum not subject to moderation, and for the ability to edit posts from others. ... (check-in: f8927901c2 user: drh tags: forum-brainstorm-1) | |
|
2018-06-14
| ||
| 19:17 | This code demonstrates ideas on how to implement a Forum feature in Fossil. This is just ideas - it is not even a working prototype. This change was originally stashed, but then I thought it better to check it in on a branch for the historical record. ... (check-in: 1e3637392a user: drh tags: forum-brainstorm-1) | |
Changes
Changes to src/forum.c.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 | /* ** The schema for the tables that manage the forum, if forum is ** enabled. */ static const char zForumInit[] = @ CREATE TABLE repository.forumpost( | | > | | < < > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /* ** The schema for the tables that manage the forum, if forum is ** enabled. */ static const char zForumInit[] = @ CREATE TABLE repository.forumpost( @ mpostid INTEGER PRIMARY KEY, -- unique id for each post (local) @ mposthash TEXT, -- uuid for this post @ mthreadid INTEGER, -- thread to which this post belongs @ uname TEXT, -- name of user @ mtime REAL, -- julian day number @ mstatus TEXT, -- status. NULL=ok. 'mod'=pending moderation @ mimetype TEXT, -- Mimetype for mbody @ ipaddr TEXT, -- IP address of post origin @ inreplyto INT, -- Parent posting @ mbody TEXT -- Content of the post @ ); @ CREATE INDEX repository.forumpost_x1 ON @ forumpost(inreplyto,mtime); @ CREATE TABLE repository.forumthread( @ mthreadid INTEGER PRIMARY KEY, @ mthreadhash TEXT, -- uuid for this thread @ mtitle TEXT, -- Title or subject line @ mtime REAL, -- Most recent update @ npost INT -- Number of posts on this thread @ ); ; /* |
| ︙ | ︙ | |||
60 61 62 63 64 65 66 | } /* ** WEBPAGE: forum ** URL: /forum ** Query parameters: ** | < < > > > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > | < < > | > | > > > > > | | > | > > | | > > > | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
}
/*
** WEBPAGE: forum
** URL: /forum
** Query parameters:
**
** item=N Show post N and its replies
**
*/
void forum_page(void){
int itemId;
Stmt q;
int i;
login_check_credentials();
if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
forum_verify_schema();
style_header("Forum");
itemId = atoi(PD("item","0"));
if( itemId>0 ){
double rNow = db_double(0.0, "SELECT julianday('now')");
/* Show the post given by itemId and all its descendents */
db_prepare(&q,
"WITH RECURSIVE"
" post(id,uname,mstat,mime,ipaddr,parent,mbody,depth,mtime) AS ("
" SELECT mpostid, uname, mstatus, mimetype, ipaddr, inreplyto, mbody,"
" 0, 1 FROM forumpost WHERE mpostid=%d"
" UNION"
" SELECT f.mpostid, f.uname, f.mstatus, f.mimetype, f.ipaddr,"
" f.inreplyto, f.mbody, p.depth+1 AS xdepth, f.mtime AS xtime"
" FROM forumpost AS f, post AS p"
" WHERE forumpost.inreplyto=post.id"
" ORDER BY xdepth DESC, xtime ASC"
") SELECT * FROM post;",
itemId
);
@ <table border=0 class="forumtable">
while( db_step(&q)==SQLITE_ROW ){
int id = db_column_int(&q, 0);
const char *zUser = db_column_text(&q, 1);
const char *zStat = db_column_text(&q, 2);
const char *zMime = db_column_text(&q, 3);
const char *zIp = db_column_text(&q, 4);
int iDepth = db_column_int(&q, 7);
double rMTime = db_column_double(&q, 8);
char *zAge = db_timespan_name(rNow - rMTime);
Blob body;
@ <!-- Forum post %d(id) -->
@ <tr>
@ <td class="forum_margin" width="%d((iDepth-1)*10)" rowspan="3"></td>
@ <td>%h(zUser) %z(zAge) ago</td>
@ </tr>
@ <tr><td class="forum_body">
blob_init(&body, db_column_text(&q,6), db_column_bytes(&q,6));
wiki_render_by_mimetype(&body, zMime);
blob_reset(&body);
@ </td></tr>
@ <tr><td class="forum_buttons">
if( g.perm.WrForum ){
if( g.perm.AdminForum || fossil_strcmp(g.zLogin, zUser)==0 ){
@ <a href='%R/forumedit?item=%d(id)'>Edit</a>
}
@ <a href='%R/forumedit?replyto=%d(id)'>Reply</a>
}
@ </td></tr>
}
@ </table>
}else{
/* If we reach this point, that means the users wants a list of
** recent threads.
*/
i = 0;
db_prepare(&q,
"SELECT a.mtitle, a.npost, b.mpostid"
" FROM forumthread AS a, forumpost AS b "
" WHERE a.mthreadid=b.mthreadid"
" AND b.inreplyto IS NULL"
" ORDER BY a.mtime DESC LIMIT 40"
);
if( g.perm.WrForum ){
style_submenu_element("New", "%R/forumedit");
}
@ <h1>Recent Forum Threads</h>
while( db_step(&q)==SQLITE_OK ){
int n = db_column_int(&q,1);
int itemid = db_column_int(&q,2);
const char *zTitle = db_column_text(&q,0);
if( i==0 ){
@ <ol>
}
@ <li>
@ %z(href("%R/forum?item=%d",itemid))%h(zTitle)</a><br>
@ %d(n) post%s(n==1?"":"s")</li>
}
if( i ){
@ </ol>
}
}
style_footer();
}
/*
** Use content in CGI parameters "s" (subject), "b" (body), and
** "m" (mimetype) to create a new forum entry.
** Return the id of the new forum entry.
**
** If any problems occur, return 0 and set *pzErr to a description of
** the problem.
**
** Cases:
**
** itemId==0 && parentId==0 Starting a new thread.
** itemId==0 && parentId>0 New reply to parentId
** itemId>0 && parentId==0 Edit existing post itemId
*/
static int forum_post(int itemId, int parentId, char **pzErr){
const char *zSubject = 0;
int threadId;
double rNow = db_double(0.0, "SELECT julianday('now')");
if( itemId==0 && parentId==0 ){
/* Start a new thread. Subject required. */
sqlite3_uint64 r1, r2;
zSubject = PT("s");
if( zSubject==0 || zSubject[0]==0 ){
*pzErr = "\"Subject\" required to start a new thread";
return 0;
}
sqlite3_randomness(sizeof(r1), &r1);
sqlite3_randomness(sizeof(r2), &r2);
db_multi_exec(
"INSERT INTO forumthread(mthreadhash, mtitle, mtime, npost)"
"VALUES(lower(hex(randomblob(32))),%Q,%!.17g,1)",
zSubject, rNow
);
threadId = db_last_insert_rowid();
}
if( itemId ){
db_multi_exec(
"UPDATE forumpost SET"
" mtime=%!.17g,"
" mimetype=%Q,"
" ipaddr=%Q,"
" mbody=%Q"
" WHERE mpostid=%d",
rNow, PT("m"), P("REMOTE_ADDR"), PT("b"), itemId
);
}else{
db_multi_exec(
"INSERT INTO forumpost(mposthash,mthreadid,uname,mtime,"
" mstatus,mimetype,ipaddr,inreplyto,mbody) VALUES"
" (lower(hex(randomblob(32))),%d,%Q,%!.17g,%Q,%Q,%Q,NULL,%Q)",
threadId,g.zLogin,rNow,NULL,P("m"),P("REMOTE_ADDR"),P("b"));
itemId = db_last_insert_rowid();
}
if( zSubject==0 ){
db_multi_exec(
"UPDATE forumthread SET mtime=%!.17g"
" WHERE mthreadid=(SELECT mthreadid FROM forumpost WHERE mpostid=%d)",
rNow, itemId
);
}
return itemId;
}
/*
** WEBPAGE: forumedit
**
** Query parameters:
**
** replyto=N Enter a reply to forum item N
** item=N Edit item N
** s=SUBJECT Subject. New thread only. Omitted for replies
** b=BODY Body of the post
** m=MIMETYPE Mimetype for the body of the post
** x Submit changes
** p Preview changes
*/
static void forum_reply_page(void){
int itemId;
int parentId;
const char *zErr = 0;
login_check_credentials();
const char *zBody;
const char *zMime;
const char *zSub;
if( !g.perm.WrForum ){ login_needed(g.anon.WrForum); return; }
forum_verify_schema();
itemId = atoi(PD("item","0"));
parentId = atoi(PD("replyto","0"));
if( P("x")!=0 && cgi_csrf_safe(1) ){
itemId = forum_post(itemId,parentId,&zErr);
if( itemId ){
cgi_redirectf("%R/forum?item=%d",itemId);
return;
}
}
style_header("Edit Forum Post");
@ <form method="POST">
if( itemId ){
@ <input type="hidden" name="item" value="%d(itemId)">
}
if( parentId ){
@ <input type="hidden" name="replyto" value="%d(parentId)">
}
if( P("p") ){
Blob x;
@ <div class="forumpreview">
if( P("s") ){
@ <h1>%h(PT("s"))</h1>
}
@ <div class="forumpreviewbody">
blob_init(&x, PT("b"), -1);
wiki_render_by_mimetype(&x, PT("m"));
blob_reset(&x);
@ </div>
@ </div>
}
@ <table border="0" class="forumeditform">
if( itemId==0 && parentId==0 ){
zSub = PT("s");
}
@ </table>
@ </form>
style_footer();
}
|
Changes to src/login.c.
| ︙ | ︙ | |||
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 |
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
p->RdWiki = p->WrWiki = p->NewWiki =
p->ApndWiki = p->Hyperlink = p->Clone =
p->NewTkt = p->Password = p->RdAddr =
p->TktFmt = p->Attach = p->ApndTkt =
p->ModWiki = p->ModTkt = p->Delete =
p->RdForum = p->WrForum = p->ModForum =
p->WrUnver = p->Private = 1;
/* Fall thru into Read/Write */
case 'i': p->Read = p->Write = 1; break;
case 'o': p->Read = 1; break;
case 'z': p->Zip = 1; break;
case 'd': p->Delete = 1; break;
| > | 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 |
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
p->RdWiki = p->WrWiki = p->NewWiki =
p->ApndWiki = p->Hyperlink = p->Clone =
p->NewTkt = p->Password = p->RdAddr =
p->TktFmt = p->Attach = p->ApndTkt =
p->ModWiki = p->ModTkt = p->Delete =
p->RdForum = p->WrForum = p->ModForum =
p->WrTForum = p->AdminForum =
p->WrUnver = p->Private = 1;
/* Fall thru into Read/Write */
case 'i': p->Read = p->Write = 1; break;
case 'o': p->Read = 1; break;
case 'z': p->Zip = 1; break;
case 'd': p->Delete = 1; break;
|
| ︙ | ︙ | |||
1224 1225 1226 1227 1228 1229 1230 |
case 'c': p->ApndTkt = 1; break;
case 'q': p->ModTkt = 1; break;
case 't': p->TktFmt = 1; break;
case 'b': p->Attach = 1; break;
case 'x': p->Private = 1; break;
case 'y': p->WrUnver = 1; break;
| > | > | | | 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 |
case 'c': p->ApndTkt = 1; break;
case 'q': p->ModTkt = 1; break;
case 't': p->TktFmt = 1; break;
case 'b': p->Attach = 1; break;
case 'x': p->Private = 1; break;
case 'y': p->WrUnver = 1; break;
case '6': p->AdminForum = 1;
case '5': p->ModForum = 1;
case '4': p->WrTForum = 1;
case '3': p->WrForum = 1;
case '2': p->RdForum = 1; break;
/* The "u" privileges is a little different. It recursively
** inherits all privileges of the user named "reader" */
case 'u': {
if( (flags & LOGIN_IGNORE_UV)==0 ){
const char *zUser;
zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
|
| ︙ | ︙ |
Changes to src/main.c.
| ︙ | ︙ | |||
81 82 83 84 85 86 87 | char ModTkt; /* q: approve and publish ticket changes (Moderator) */ char Attach; /* b: add attachments */ char TktFmt; /* t: create new ticket report formats */ char RdAddr; /* e: read email addresses or other private data */ char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ char WrUnver; /* y: can push unversioned content */ | | | > | > | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | char ModTkt; /* q: approve and publish ticket changes (Moderator) */ char Attach; /* b: add attachments */ char TktFmt; /* t: create new ticket report formats */ char RdAddr; /* e: read email addresses or other private data */ char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ char WrUnver; /* y: can push unversioned content */ char RdForum; /* 2: Read forum posts */ char WrForum; /* 3: Create new forum posts */ char WrTForum; /* 4: Post to forums not subject to moderation */ char ModForum; /* 5: Moderate (approve or reject) forum posts */ char AdminForum; /* 6: Edit forum posts by other users */ }; #ifdef FOSSIL_ENABLE_TCL /* ** All Tcl related context information is in this structure. This structure ** definition has been copied from and should be kept in sync with the one in ** "th_tcl.c". |
| ︙ | ︙ |
Changes to src/setup.c.
| ︙ | ︙ | |||
345 346 347 348 349 350 351 |
@ <tr><th valign="top">x</th>
@ <td><i>Private:</i> Push and/or pull private branches</td></tr>
@ <tr><th valign="top">y</th>
@ <td><i>Write-Unver:</i> Push unversioned files</td></tr>
@ <tr><th valign="top">z</th>
@ <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
@ <tr><th valign="top">2</th>
| | | > > | > > > | 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
@ <tr><th valign="top">x</th>
@ <td><i>Private:</i> Push and/or pull private branches</td></tr>
@ <tr><th valign="top">y</th>
@ <td><i>Write-Unver:</i> Push unversioned files</td></tr>
@ <tr><th valign="top">z</th>
@ <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
@ <tr><th valign="top">2</th>
@ <td><i>Forum-Read:</i> Read forum posts by others </td></tr>
@ <tr><th valign="top">3</th>
@ <td><i>Forum-Append:</i> Add new forum posts</td></tr>
@ <tr><th valign="top">4</th>
@ <td><i>Forum-Trusted:</i> Add pre-approved forum posts </td></tr>
@ <tr><th valign="top">5</th>
@ <td><i>Forum-Moderator:</i> Approve or disapprove forum posts</td></tr>
@ <tr><th valign="top">6</th>
@ <td><i>Forum-Supervisor:</i> \
@ Edit forum posts submitted by others</td></tr>
@ </table>
}
/*
** WEBPAGE: setup_ulist_notes
**
** A documentation page showing notes about user configuration. This information
|
| ︙ | ︙ | |||
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
@ <table border=0><tr><td valign="top">
if( g.perm.Setup ){
@ <label><input type="checkbox" name="as"%s(oa['s']) />
@ Setup%s(B('s'))</label><br />
}
@ <label><input type="checkbox" name="aa"%s(oa['a']) />
@ Admin%s(B('a'))</label><br />
@ <label><input type="checkbox" name="ad"%s(oa['d']) />
@ Delete%s(B('d'))</label><br />
@ <label><input type="checkbox" name="ae"%s(oa['e']) />
@ Email%s(B('e'))</label><br />
@ <label><input type="checkbox" name="ap"%s(oa['p']) />
@ Password%s(B('p'))</label><br />
@ <label><input type="checkbox" name="ai"%s(oa['i']) />
@ Check-In%s(B('i'))</label><br />
@ <label><input type="checkbox" name="ao"%s(oa['o']) />
@ Check-Out%s(B('o'))</label><br />
@ <label><input type="checkbox" name="ah"%s(oa['h']) />
@ Hyperlinks%s(B('h'))</label><br />
@ <label><input type="checkbox" name="ab"%s(oa['b']) />
| > > > > | | < < < | | < | > > > > | > > > | 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 |
@ <table border=0><tr><td valign="top">
if( g.perm.Setup ){
@ <label><input type="checkbox" name="as"%s(oa['s']) />
@ Setup%s(B('s'))</label><br />
}
@ <label><input type="checkbox" name="aa"%s(oa['a']) />
@ Admin%s(B('a'))</label><br />
@ <label><input type="checkbox" name="au"%s(oa['u']) />
@ Reader%s(B('u'))</label><br>
@ <label><input type="checkbox" name="av"%s(oa['v']) />
@ Developer%s(B('v'))</label><br />
@ <label><input type="checkbox" name="ad"%s(oa['d']) />
@ Delete%s(B('d'))</label><br />
@ <label><input type="checkbox" name="ae"%s(oa['e']) />
@ Email%s(B('e'))</label><br />
@ <label><input type="checkbox" name="ap"%s(oa['p']) />
@ Password%s(B('p'))</label><br />
@ <label><input type="checkbox" name="ai"%s(oa['i']) />
@ Check-In%s(B('i'))</label><br />
@ <label><input type="checkbox" name="ao"%s(oa['o']) />
@ Check-Out%s(B('o'))</label><br />
@ <label><input type="checkbox" name="ah"%s(oa['h']) />
@ Hyperlinks%s(B('h'))</label><br />
@ <label><input type="checkbox" name="ab"%s(oa['b']) />
@ Attachments%s(B('b'))</label>
@ </td><td><td width="40"></td><td valign="top">
@ <label><input type="checkbox" name="ag"%s(oa['g']) />
@ Clone%s(B('g'))</label><br />
@ <label><input type="checkbox" name="aj"%s(oa['j']) />
@ Read Wiki%s(B('j'))</label><br>
@ <label><input type="checkbox" name="af"%s(oa['f']) />
@ New Wiki%s(B('f'))</label><br />
@ <label><input type="checkbox" name="am"%s(oa['m']) />
@ Append Wiki%s(B('m'))</label><br />
@ <label><input type="checkbox" name="ak"%s(oa['k']) />
@ Write Wiki%s(B('k'))</label><br />
@ <label><input type="checkbox" name="al"%s(oa['l']) />
@ Moderate Wiki%s(B('l'))</label><br />
@ <label><input type="checkbox" name="ar"%s(oa['r']) />
@ Read Ticket%s(B('r'))</label><br />
@ <label><input type="checkbox" name="an"%s(oa['n']) />
@ New Tickets%s(B('n'))</label><br />
@ <label><input type="checkbox" name="ac"%s(oa['c']) />
@ Append To Ticket%s(B('c'))</label><br>
@ <label><input type="checkbox" name="aw"%s(oa['w']) />
@ Write Tickets%s(B('w'))</label><br />
@ <label><input type="checkbox" name="aq"%s(oa['q']) />
@ Moderate Tickets%s(B('q'))</label>
@ </td><td><td width="40"></td><td valign="top">
@ <label><input type="checkbox" name="at"%s(oa['t']) />
@ Ticket Report%s(B('t'))</label><br />
@ <label><input type="checkbox" name="ax"%s(oa['x']) />
@ Private%s(B('x'))</label><br />
@ <label><input type="checkbox" name="ay"%s(oa['y']) />
@ Write Unversioned%s(B('y'))</label><br />
@ <label><input type="checkbox" name="az"%s(oa['z']) />
@ Download Zip%s(B('z'))</label><br />
@ <label><input type="checkbox" name="a2"%s(oa['2']) />
@ Read Forum%s(B('2'))</label><br />
@ <label><input type="checkbox" name="a3"%s(oa['3']) />
@ Write Forum%s(B('3'))</label><br />
@ <label><input type="checkbox" name="a4"%s(oa['4']) />
@ WriteTrusted Forum%s(B('4'))</label><br>
@ <label><input type="checkbox" name="a5"%s(oa['5']) />
@ Moderate Forum%s(B('5'))</label><br>
@ <label><input type="checkbox" name="a6"%s(oa['6']) />
@ Supervise Forum%s(B('6'))</label>
@ </td></tr>
@ </table>
@ </td>
@ </tr>
@ <tr>
@ <td class="usetupEditLabel">Selected Cap.:</td>
@ <td>
@ <span id="usetupEditCapability">(missing JS?)</span>
@ <a href="%R/setup_ucap_list">(key)</a>
@ </td>
@ </tr>
if( !login_is_special(zLogin) ){
@ <tr>
@ <td align="right">Password:</td>
if( zPw[0] ){
/* Obscure the password for all users */
|
| ︙ | ︙ |