Fossil

Check-in [9fc70f6f63]
Login

Check-in [9fc70f6f63]

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

Overview
Comment:Coding style tweaks.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | forumpost-locking
Files: files | file ages | folders
SHA3-256: 9fc70f6f63ff5eaddbc0b5e515d07782aaedba180ac1087e56e30ddc8e5f49ef
User & Date: stephan 2023-02-21 08:47:16.977
Context
2023-02-21
09:52
Add db_add_unsent() and replace numerous "INSERT OR IGNORE INTO unset" statements with that. ... (check-in: 98d4ee73d7 user: stephan tags: forumpost-locking)
08:47
Coding style tweaks. ... (check-in: 9fc70f6f63 user: stephan tags: forumpost-locking)
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)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
  padding-left: 1ex;
  padding-right: 1ex;
  margin-top: 1ex;
  display: flex;
  flex-direction: column;
}
div.forumClosed {
  border-style: dotted;
  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[CLOSED] ";
  color: red;
  opacity: 0.7;
}







<







898
899
900
901
902
903
904

905
906
907
908
909
910
911
  padding-left: 1ex;
  padding-right: 1ex;
  margin-top: 1ex;
  display: flex;
  flex-direction: column;
}
div.forumClosed {

  opacity: 0.7;
}
div.forumClosed > *:first-child::before {
  content: "[CLOSED] ";
  color: red;
  opacity: 0.7;
}
Changes to src/forum.c.
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  ForumPost *pEditNext;  /* This post is edited by pEditNext */
  ForumPost *pEditPrev;  /* This post is an edit of pEditPrev */
  ForumPost *pNext;      /* Next in chronological order */
  ForumPost *pPrev;      /* Previous in chronological order */
  ForumPost *pDisplay;   /* Next in display order */
  int nEdit;             /* Number of edits to this post */
  int nIndent;           /* Number of levels of indentation for this post */
  int fClosed;           /* See forum_rid_is_closed() */
};

/*
** A single instance of the following tracks all entries for a thread.
*/
struct ForumThread {
  ForumPost *pFirst;     /* First post in chronological order */







|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  ForumPost *pEditNext;  /* This post is edited by pEditNext */
  ForumPost *pEditPrev;  /* This post is an edit of pEditPrev */
  ForumPost *pNext;      /* Next in chronological order */
  ForumPost *pPrev;      /* Previous in chronological order */
  ForumPost *pDisplay;   /* Next in display order */
  int nEdit;             /* Number of edits to this post */
  int nIndent;           /* Number of levels of indentation for this post */
  int iClosed;           /* See forum_rid_is_closed() */
};

/*
** A single instance of the following tracks all entries for a thread.
*/
struct ForumThread {
  ForumPost *pFirst;     /* First post in chronological order */
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
**
** If bCheckParents is true then p's thread parents are checked
** (recursively) for closure, else only p is checked.
*/
int forum_post_is_closed(ForumPost *p, int bCheckParents){
  if( !p ) return 0;
  if( p->pEditTail ) p = p->pEditTail;
  if( p->fClosed || !bCheckParents ) return p->fClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt,
                                bCheckParents);
  }
  return 0;
}







|







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
**
** If bCheckParents is true then p's thread parents are checked
** (recursively) for closure, else only p is checked.
*/
int forum_post_is_closed(ForumPost *p, int bCheckParents){
  if( !p ) return 0;
  if( p->pEditTail ) p = p->pEditTail;
  if( p->iClosed || !bCheckParents ) return p->iClosed;
  else if( p->pIrt ){
    return forum_post_is_closed(p->pIrt->pEditTail
                                ? p->pIrt->pEditTail : p->pIrt,
                                bCheckParents);
  }
  return 0;
}
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
  if( rid ){
    rc = forum_rid_is_closed(rid, 1);
  }
  return rc>0 ? -rc : rc;
}

/*
** If fClosed is true and the current user has admin privileges, this
** renders either a checkbox to unlock forum post fpid (if fClosed>0)
** or a SPAN.warning element that the given post inherits the CLOSED
** status from a parent post (if fClosed<0). If neither of the initial
** conditions is true, this is a no-op.
*/
static void forumpost_emit_unlock_checkbox(int fClosed, int fpid){
  if( fClosed && g.perm.Admin ){
    if( fClosed>0 ){
      /* Only show the "unlock" checkbox on a post which is actually
      ** closed, not on a post which inherits that state. */
      @ <label class='warning'><input type="checkbox" name="reopen" value="1">
      @ Re-open this CLOSED post? (NOT YET IMPLEMENTED)</label>
    }else{
      @ <span class='warning'>This post is CLOSED via a parent post</span>
    }







|
|

|


|
|
|







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
  if( rid ){
    rc = forum_rid_is_closed(rid, 1);
  }
  return rc>0 ? -rc : rc;
}

/*
** If iClosed is true and the current user has admin privileges, this
** renders either a checkbox to unlock forum post fpid (if iClosed>0)
** or a SPAN.warning element that the given post inherits the CLOSED
** status from a parent post (if iClosed<0). If neither of the initial
** conditions is true, this is a no-op.
*/
static void forumpost_emit_unlock_checkbox(int iClosed, int fpid){
  if( iClosed && g.perm.Admin ){
    if( iClosed>0 ){
      /* Only show the "unlock" checkbox on a post which is actually
      ** closed, not on a post which inherits that state. */
      @ <label class='warning'><input type="checkbox" name="reopen" value="1">
      @ Re-open this CLOSED post? (NOT YET IMPLEMENTED)</label>
    }else{
      @ <span class='warning'>This post is CLOSED via a parent post</span>
    }
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
      pPost->pEditPrev = p;
      pPost->pEditHead = p->pEditHead ? p->pEditHead : p;
      for(; p; p=p->pEditPrev ){
        p->nEdit = pPost->nEdit;
        p->pEditTail = pPost;
      }
    }
    pPost->fClosed = forum_rid_is_closed(pPost->fpid, 1);
  }
  db_finalize(&q);

  if( computeHierarchy ){
    /* Compute the hierarchical display order */
    pPost = pThread->pFirst;
    pPost->nIndent = 1;







|







306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
      pPost->pEditPrev = p;
      pPost->pEditHead = p->pEditHead ? p->pEditHead : p;
      for(; p; p=p->pEditPrev ){
        p->nEdit = pPost->nEdit;
        p->pEditTail = pPost;
      }
    }
    pPost->iClosed = forum_rid_is_closed(pPost->fpid, 1);
  }
  db_finalize(&q);

  if( computeHierarchy ){
    /* Compute the hierarchical display order */
    pPost = pThread->pFirst;
    pPost->nIndent = 1;
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
  fossil_print(
/* 0         1         2         3         4         5         6         7    */
/*  123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
  " sid  rev  closed      fpid      pIrt pEditPrev pEditTail hash\n");
  for(p=pThread->pFirst; p; p=p->pNext){
    fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
       p->sid, p->rev,
       p->fClosed,
       p->fpid, p->pIrt ? p->pIrt->fpid : 0,
       p->pEditPrev ? p->pEditPrev->fpid : 0,
       p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
  }
  fossil_print("\nDisplay\n");
  for(p=pThread->pDisplay; p; p=p->pDisplay){
    fossil_print("%*s", (p->nIndent-1)*3, "");







|







396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
  fossil_print(
/* 0         1         2         3         4         5         6         7    */
/*  123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
  " sid  rev  closed      fpid      pIrt pEditPrev pEditTail hash\n");
  for(p=pThread->pFirst; p; p=p->pNext){
    fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
       p->sid, p->rev,
       p->iClosed,
       p->fpid, p->pIrt ? p->pIrt->fpid : 0,
       p->pEditPrev ? p->pEditPrev->fpid : 0,
       p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
  }
  fossil_print("\nDisplay\n");
  for(p=pThread->pDisplay; p; p=p->pDisplay){
    fossil_print("%*s", (p->nIndent-1)*3, "");
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
  char *zEditorName;    /* Name of user who provided the current edit */
  char *zDate;          /* The time/date string */
  char *zHist;          /* History query string */
  Manifest *pManifest;  /* Manifest comprising the current post */
  int bPrivate;         /* True for posts awaiting moderation */
  int bSameUser;        /* True if author is also the reader */
  int iIndent;          /* Indent level */
  int fClosed;          /* True if (sub)thread is closed */
  const char *zMimetype;/* Formatting MIME type */

  /* Get the manifest for the post.  Abort if not found (e.g. shunned). */
  pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
  if( !pManifest ) return;
  fClosed = forum_post_is_closed(p, 1);
  /* When not in raw mode, create the border around the post. */
  if( !bRaw ){
    /* Open the <div> enclosing the post.  Set the class string to mark the post
    ** as selected and/or obsolete. */
    iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
    @ <div id='forum%d(p->fpid)' class='forumTime\
    @ %s(bSelect ? " forumSel" : "")\
    @ %s(fClosed ? " forumClosed" : "")\
    @ %s(p->pEditTail ? " forumObs" : "")' \
    if( iIndent && iIndentScale ){
      @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
    }else{
      @ >
    }








|





|







|







552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
  char *zEditorName;    /* Name of user who provided the current edit */
  char *zDate;          /* The time/date string */
  char *zHist;          /* History query string */
  Manifest *pManifest;  /* Manifest comprising the current post */
  int bPrivate;         /* True for posts awaiting moderation */
  int bSameUser;        /* True if author is also the reader */
  int iIndent;          /* Indent level */
  int iClosed;          /* True if (sub)thread is closed */
  const char *zMimetype;/* Formatting MIME type */

  /* Get the manifest for the post.  Abort if not found (e.g. shunned). */
  pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
  if( !pManifest ) return;
  iClosed = forum_post_is_closed(p, 1);
  /* When not in raw mode, create the border around the post. */
  if( !bRaw ){
    /* Open the <div> enclosing the post.  Set the class string to mark the post
    ** as selected and/or obsolete. */
    iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
    @ <div id='forum%d(p->fpid)' class='forumTime\
    @ %s(bSelect ? " forumSel" : "")\
    @ %s(iClosed ? " forumClosed" : "")\
    @ %s(p->pEditTail ? " forumObs" : "")' \
    if( iIndent && iIndentScale ){
      @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
    }else{
      @ >
    }

681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
      @ <div><form action="%R/forumedit" method="POST">
      @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
      if( !bPrivate ){
        /* Reply and Edit are only available if the post has been
        ** approved.  Closed threads can only be edited or replied to
        ** by an admin but a user may delete their own posts even if
        ** they are closed. */
        if( g.perm.Admin || !fClosed ){
          @ <input type="submit" name="reply" value="Reply">
          if( g.perm.Admin || (bSameUser && !fClosed) ){
            @ <input type="submit" name="edit" value="Edit">
          }
          if( g.perm.Admin || bSameUser ){
            @ <input type="submit" name="nullout" value="Delete">
          }
        }
      }else if( g.perm.ModForum ){







|

|







681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
      @ <div><form action="%R/forumedit" method="POST">
      @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
      if( !bPrivate ){
        /* Reply and Edit are only available if the post has been
        ** approved.  Closed threads can only be edited or replied to
        ** by an admin but a user may delete their own posts even if
        ** they are closed. */
        if( g.perm.Admin || !iClosed ){
          @ <input type="submit" name="reply" value="Reply">
          if( g.perm.Admin || (bSameUser && !iClosed) ){
            @ <input type="submit" name="edit" value="Edit">
          }
          if( g.perm.Admin || bSameUser ){
            @ <input type="submit" name="nullout" value="Delete">
          }
        }
      }else if( g.perm.ModForum ){
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
  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;
  int bSameUser;        /* True if author is also the reader */
  int bPreview;         /* True in preview mode. */
  int bPrivate;         /* True if post is private (not yet moderated) */

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);







|







1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
  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 iClosed = 0;
  int bSameUser;        /* True if author is also the reader */
  int bPreview;         /* True in preview mode. */
  int bPrivate;         /* True if post is private (not yet moderated) */

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
    webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid);
  }
  if( P("cancel") ){
    cgi_redirectf("%R/forumpost/%S",P("fpid"));
    return;
  }
  bPreview = P("preview")!=0;
  fClosed = forum_rid_is_closed(fpid, froot!=fpid);
  isCsrfSafe = cgi_csrf_safe(1);
  bPrivate = content_is_private(fpid);
  bSameUser = login_is_individual()
    && fossil_strcmp(pPost->zUser, g.zLogin)==0;
  if( isCsrfSafe && (g.perm.ModForum || (bPrivate && bSameUser)) ){
    if( g.perm.ModForum && P("approve") ){
      const char *zUserToTrust;







|







1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
    webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid);
  }
  if( P("cancel") ){
    cgi_redirectf("%R/forumpost/%S",P("fpid"));
    return;
  }
  bPreview = P("preview")!=0;
  iClosed = forum_rid_is_closed(fpid, froot!=fpid);
  isCsrfSafe = cgi_csrf_safe(1);
  bPrivate = content_is_private(fpid);
  bSameUser = login_is_individual()
    && fossil_strcmp(pPost->zUser, g.zLogin)==0;
  if( isCsrfSafe && (g.perm.ModForum || (bPrivate && bSameUser)) ){
    if( g.perm.ModForum && P("approve") ){
      const char *zUserToTrust;
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
      @ <h2>Preview of Edited Post:</h2>
      forum_render(zTitle, zMimetype, zContent,"forumEdit", 1);
    }
    @ <h2>Revised Message:</h2>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="edit" value="1">
    if( fClosed ) forumpost_error_closed();
    forum_from_line();
    forum_post_widget(zTitle, zMimetype, zContent);
  }else{
    /* Reply */
    char *zDisplayName;
    zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
    zContent = PDT("content","");







|







1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
      @ <h2>Preview of Edited Post:</h2>
      forum_render(zTitle, zMimetype, zContent,"forumEdit", 1);
    }
    @ <h2>Revised Message:</h2>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="edit" value="1">
    if( iClosed ) forumpost_error_closed();
    forum_from_line();
    forum_post_widget(zTitle, zMimetype, zContent);
  }else{
    /* Reply */
    char *zDisplayName;
    zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
    zContent = PDT("content","");
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
      @ <h2>Preview:</h2>
      forum_render(0, zMimetype,zContent, "forumEdit", 1);
    }
    @ <h2>Enter Reply:</h2>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="reply" value="1">
    if( fClosed ) forumpost_error_closed();
    forum_from_line();
    forum_post_widget(0, zMimetype, zContent);
  }
  if( !isDelete ){
    @ <input type="submit" name="preview" value="Preview">
  }
  @ <input type="submit" name="cancel" value="Cancel">
  if( (bPreview && !whitespace_only(zContent)) || isDelete ){
    if( !fClosed || g.perm.Admin ) {
      @ <input type="submit" name="submit" value="Submit">
    }
    forumpost_emit_unlock_checkbox(fClosed, fpid);
  }else if( !bPreview && fClosed ){
    @ <span class='warning'>This post is CLOSED</span>
  }
  if( g.perm.Debug ){
    /* For the test-forumnew page add these extra debugging controls */
    @ <div class="debug">
    @ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
    @ Dry run</label>







|








|


|
|







1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
      @ <h2>Preview:</h2>
      forum_render(0, zMimetype,zContent, "forumEdit", 1);
    }
    @ <h2>Enter Reply:</h2>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="reply" value="1">
    if( iClosed ) forumpost_error_closed();
    forum_from_line();
    forum_post_widget(0, zMimetype, zContent);
  }
  if( !isDelete ){
    @ <input type="submit" name="preview" value="Preview">
  }
  @ <input type="submit" name="cancel" value="Cancel">
  if( (bPreview && !whitespace_only(zContent)) || isDelete ){
    if( !iClosed || g.perm.Admin ) {
      @ <input type="submit" name="submit" value="Submit">
    }
    forumpost_emit_unlock_checkbox(iClosed, fpid);
  }else if( !bPreview && iClosed ){
    @ <span class='warning'>This post is CLOSED</span>
  }
  if( g.perm.Debug ){
    /* For the test-forumnew page add these extra debugging controls */
    @ <div class="debug">
    @ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
    @ Dry run</label>
Changes to src/tag.c.
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
     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;

  assert( 0 != zTagName );







|
|







905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
     rid, tagId
  );
}


/*
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
** of 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;

  assert( 0 != zTagName );