/* ** Copyright (c) 2018 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to generate the user forum. */ #include "config.h" #include #include "forum.h" /* ** 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 @ ); ; /* ** Create the forum tables in the schema if they do not already ** exist. */ static void forum_verify_schema(void){ if( !db_table_exists("repository","forumpost") ){ db_multi_exec(zForumInit /*works-like:""*/); } } /* ** 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 ); @ 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; @ @ @ @ @ @ @ } @
%h(zUser) %z(zAge) ago
blob_init(&body, db_column_text(&q,6), db_column_bytes(&q,6)); wiki_render_by_mimetype(&body, zMime); blob_reset(&body); @
if( g.perm.WrForum ){ if( g.perm.AdminForum || fossil_strcmp(g.zLogin, zUser)==0 ){ @ Edit } @ Reply } @
}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"); } @

Recent Forum Threads 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 ){ @
    } @
  1. @ %z(href("%R/forum?item=%d",itemid))%h(zTitle)
    @ %d(n) post%s(n==1?"":"s")
  2. } if( i ){ @
} } 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"); @
if( itemId ){ @ } if( parentId ){ @ } if( P("p") ){ Blob x; @
if( P("s") ){ @

%h(PT("s"))

} @
blob_init(&x, PT("b"), -1); wiki_render_by_mimetype(&x, PT("m")); blob_reset(&x); @
@
} @ if( itemId==0 && parentId==0 ){ zSub = PT("s"); } @
@
style_footer(); }