Fossil

Check-in [464f4d175f]
Login

Check-in [464f4d175f]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add capability to determine whether a given sub-thread inherits a lock from a parent. Re-label "locked" to "closed" per /chat feedback.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | forumpost-locking
Files: files | file ages | folders
SHA3-256: 464f4d175f503806837051c4bc6b404a29c72c3a7ea2141e492c1c5d4ce26ef7
User & Date: stephan 2023-02-21 01:58:29.078
Context
2023-02-21
03:49
Closed forum threads can no longer be edited by non-admins. Fix broken ability of non-builtin users to delete their own pending-moderation post. UI controls for closing/reing-open threads are still TODO. ... (check-in: 8f02c1d4a8 user: stephan tags: forumpost-locking)
01:58
Add capability to determine whether a given sub-thread inherits a lock from a parent. Re-label "locked" to "closed" per /chat feedback. ... (check-in: 464f4d175f user: stephan tags: forumpost-locking)
00:52
Initial bits for "locking" forum (sub)threads using a "closed" tag. This currently affects the display but does not hinder edits made via malicious misuse because the pieces needed for such validation do not yet have access to the relevant ForumPost objects. ... (check-in: 4d664bfe55 user: stephan tags: forumpost-locking)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
  flex-direction: column;
}
div.forumClosed {
  border-style: dotted;
  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[LOCKED] ";
  color: red;
  opacity: 0.7;
}
.forum div > form {
  margin: 0.5em 0;
}
.forum-post-collapser {







|







902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
  flex-direction: column;
}
div.forumClosed {
  border-style: dotted;
  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[CLOSED] ";
  color: red;
  opacity: 0.7;
}
.forum div > form {
  margin: 0.5em 0;
}
.forum-post-collapser {
Changes to src/forum.c.
93
94
95
96
97
98
99


























100
101
102
103
104
105
106
  if( p->fClosed ) return p->fClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt);
  }
  return 0;
}



























/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
  ForumPost *pPost, *pNext;
  for(pPost=pThread->pFirst; pPost; pPost = pNext){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
  if( p->fClosed ) return p->fClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt);
  }
  return 0;
}

/*
** Given a forum post RID, this function returns true if that post or
** the latest version of any parent post in its hierarchy have an
** active "closed" tag.
*/
int forum_rid_is_closed(int rid){
  static Stmt qIrt = empty_Stmt_m;
  int rc;

  /* TODO: this can probably be turned into a CTE, rather than a
  ** recursive call into this function, by someone with superior
  ** SQL-fu. */
  rc = rid_has_active_tag_name(rid, "closed");
  if( rc ) return rc;
  else if( !qIrt.pStmt ) {
    db_static_prepare(&qIrt,
      "SELECT firt FROM forumpost "
      "WHERE fpid=$fpid ORDER BY fmtime DESC"
    );
  }
  db_bind_int(&qIrt, "$fpid", rid);
  rc = SQLITE_ROW==db_step(&qIrt) ? db_column_int(&qIrt, 0) : 0;
  db_reset(&qIrt);
  return rc>0 ? forum_rid_is_closed(rc) : 0;
}

/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
  ForumPost *pPost, *pNext;
  for(pPost=pThread->pFirst; pPost; pPost = pNext){
1103
1104
1105
1106
1107
1108
1109

1110
1111
1112
1113
1114
1115
1116
**
** Start a new thread on the forum or reply to an existing thread.
** But first prompt to see if the user would like to log in.
*/
void forum_page_init(void){
  int isEdit;
  char *zGoto;

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  if( sqlite3_strglob("*edit*", g.zPath)==0 ){
    zGoto = mprintf("forume2?fpid=%S",PD("fpid",""));







>







1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
**
** Start a new thread on the forum or reply to an existing thread.
** But first prompt to see if the user would like to log in.
*/
void forum_page_init(void){
  int isEdit;
  char *zGoto;

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  if( sqlite3_strglob("*edit*", g.zPath)==0 ){
    zGoto = mprintf("forume2?fpid=%S",PD("fpid",""));
1234
1235
1236
1237
1238
1239
1240

1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258

1259
1260
1261
1262
1263
1264
1265
  const char *zMimetype = 0;
  const char *zContent = 0;
  const char *zTitle = 0;
  char *zDate = 0;
  const char *zFpid = PD("fpid","");
  int isCsrfSafe;
  int isDelete = 0;


  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  fpid = symbolic_name_to_rid(zFpid, "f");
  if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){
    webpage_error("Missing or invalid fpid query parameter");
  }
  froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
  if( froot==0 || (pRootPost = manifest_get(froot, CFTYPE_FORUM, 0))==0 ){
    webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid);
  }
  if( P("cancel") ){
    cgi_redirectf("%R/forumpost/%S",P("fpid"));
    return;
  }

  isCsrfSafe = cgi_csrf_safe(1);
  if( g.perm.ModForum && isCsrfSafe ){
    if( P("approve") ){
      const char *zUserToTrust;
      moderation_approve('f', fpid);
      if( g.perm.AdminForum
       && PB("trust")







>


















>







1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
  const char *zMimetype = 0;
  const char *zContent = 0;
  const char *zTitle = 0;
  char *zDate = 0;
  const char *zFpid = PD("fpid","");
  int isCsrfSafe;
  int isDelete = 0;
  int fClosed = 0;

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  fpid = symbolic_name_to_rid(zFpid, "f");
  if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){
    webpage_error("Missing or invalid fpid query parameter");
  }
  froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
  if( froot==0 || (pRootPost = manifest_get(froot, CFTYPE_FORUM, 0))==0 ){
    webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid);
  }
  if( P("cancel") ){
    cgi_redirectf("%R/forumpost/%S",P("fpid"));
    return;
  }
  fClosed = forum_rid_is_closed(fpid);
  isCsrfSafe = cgi_csrf_safe(1);
  if( g.perm.ModForum && isCsrfSafe ){
    if( P("approve") ){
      const char *zUserToTrust;
      moderation_approve('f', fpid);
      if( g.perm.AdminForum
       && PB("trust")
Changes to src/tag.c.
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920

921
922
923
924
925
926

927
928
929
930
931
932
933
934
     " AND tagxref.tagid=tag.tagid",
     rid, tagId
  );
}


/*
** Returns tagxref.tagtype if the given blob.rid has a tagxref.rid
** entry an active tag matching the given rid and tag name string,
** else returns 0. Note that this function does not distinguish
** between a non-existent tag and a cancelled tag.
*/
int rid_has_active_tag_name(int rid, const char *zTagName){
  static Stmt q = empty_Stmt_m;
  int rc = 0;

  assert( 0 != zTagName );

  db_static_prepare(&q,
     "SELECT tagxref.tagtype FROM tagxref, tag"
     " WHERE tagxref.rid=:rid AND tagtype>0 "
     " AND tag.tagname=:tagname"
     " AND tagxref.tagid=tag.tagid"
  );

  db_bind_int(&q, ":rid", rid);
  db_bind_text(&q, ":tagname", zTagName);
  if( SQLITE_ROW==db_step(&q) ){
    rc = db_column_int(&q, 0);
  }
  db_reset(&q);
  return rc;
}







|
|
|
|






>
|
|
|
|
|
|
>
|
|






904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
     " AND tagxref.tagid=tag.tagid",
     rid, tagId
  );
}


/*
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
** an active (non-cancelled) tag matching the given rid and tag name
** string, else returns 0. Note that this function does not
** distinguish between a non-existent tag and a cancelled tag.
*/
int rid_has_active_tag_name(int rid, const char *zTagName){
  static Stmt q = empty_Stmt_m;
  int rc = 0;

  assert( 0 != zTagName );
  if( !q.pStmt ){
    db_static_prepare(&q,
       "SELECT tagxref.rowid FROM tagxref, tag"
       " WHERE tagxref.rid=$rid AND tagtype>0 "
       " AND tag.tagname=$tagname"
       " AND tagxref.tagid=tag.tagid"
    );
  }
  db_bind_int(&q, "$rid", rid);
  db_bind_text(&q, "$tagname", zTagName);
  if( SQLITE_ROW==db_step(&q) ){
    rc = db_column_int(&q, 0);
  }
  db_reset(&q);
  return rc;
}