| ︙ | | |
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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; /* tagxref.tagtype if this (sub)thread has a closed tag. */
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 */
|
| ︙ | | |
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
|
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
|
-
+
+
+
+
+
+
-
+
-
+
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
return res;
}
/*
** Returns true if p, or any parent of p, has an active "closed" tag.
** Returns 0 if !p. For an edited chain of post, the tag is checked on
** the final edit in the chain, as that permits that a post can be
** locked and later unlocked.
** locked and later unlocked. The return value is the tagxref.rowid
** value of the tagxref entry which applies the "closed" tag, or 0 if
** no active tag is found.
**
** 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 forum_post_is_closed(ForumPost *p, int bCheckParents){
if( !p ) return 0;
if( p->pEditTail ) p = p->pEditTail;
if( p->fClosed ) return p->fClosed;
if( p->fClosed || !bCheckParents ) return p->fClosed;
else if( p->pIrt ){
return forum_post_is_closed(p->pIrt->pEditTail
? p->pIrt->pEditTail : p->pIrt);
? p->pIrt->pEditTail : p->pIrt,
bCheckParents);
}
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.
** Given a forum post RID, this function returns true if that post has
** an active "closed" tag. If bCheckParents is true, the latest
** version of each parent post is also checked (recursively), else
** they are not. When checking parents, the first parent which is
** closed ends the search.
**
** The return value is one of:
**
** - 0 if no "closed" tag is found.
**
** - The tagxref.rowid of the tagxref entry for the closure if rid is
** the artifact to which the closure applies.
**
** - (-tagxref.rowid) if the given rid inherits a "closed" tag from an
** ancestor forum post.
*/
int forum_rid_is_closed(int rid){
static int forum_rid_is_closed(int rid, int bCheckParents){
static Stmt qIrt = empty_Stmt_m;
int rc;
int rc = 0;
/* 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;
if( rc || !bCheckParents ) 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;
rid = SQLITE_ROW==db_step(&qIrt) ? db_column_int(&qIrt, 0) : 0;
db_reset(&qIrt);
if( rid ){
rc = forum_rid_is_closed(rid, 1);
}
return rc>0 ? forum_rid_is_closed(rc) : 0;
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>
}
}
}
/*
** Emits a warning that the current forum post is CLOSED and can only
** be edited or responded to by an administrator. */
static void forumpost_error_closed(void){
@ <div class='error'>This (sub)thread is CLOSED and can only be
@ edited or replied to by an admin user.</div>
}
/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
ForumPost *pPost, *pNext;
|
| ︙ | | |
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
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 = rid_has_active_tag_name(pPost->fpid, "closed");
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;
|
| ︙ | | |
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
|
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
|
-
+
|
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);
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" : "")\
|
| ︙ | | |
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
|
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
|
+
+
+
+
+
|
char *zG;
int iBasis;
Blob x, cksum, formatCheck, errMsg;
Manifest *pPost;
int nContent = zContent ? (int)strlen(zContent) : 0;
schema_forum();
if( !g.perm.Admin && (iEdit || iInReplyTo)
&& forum_rid_is_closed(iEdit ? iEdit : iInReplyTo, 1) ){
forumpost_error_closed();
return 0;
}
if( iEdit==0 && whitespace_only(zContent) ){
return 0;
}
if( iInReplyTo==0 && iEdit>0 ){
iBasis = iEdit;
iInReplyTo = db_int(0, "SELECT firt FROM forumpost WHERE fpid=%d", iEdit);
}else{
|
| ︙ | | |
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
1295
1296
1297
|
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
|
+
+
+
+
-
+
+
+
+
+
-
+
-
|
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);
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;
}
bPreview = P("preview")!=0;
fClosed = forum_rid_is_closed(fpid);
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 && isCsrfSafe ){
if( g.perm.ModForum && P("approve") ){
if( P("approve") ){
const char *zUserToTrust;
moderation_approve('f', fpid);
if( g.perm.AdminForum
&& PB("trust")
&& (zUserToTrust = P("trustuser"))!=0
){
db_unprotect(PROTECT_USER);
|
| ︙ | | |
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
|
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
|
-
+
+
|
if( zTitle==0 && pPost->zThreadTitle!=0 ){
zTitle = fossil_strdup(pPost->zThreadTitle);
}
style_header("Edit %s", zTitle ? "Post" : "Reply");
@ <h2>Original Post:</h2>
forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
"forumEdit", 1);
if( P("preview") ){
if( bPreview ){
@ <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","");
|
| ︙ | | |
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
|
1456
1457
1458
1459
1460
1461
1462
1463
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
1492
|
-
+
+
-
-
+
+
+
+
+
+
+
|
@ </h2>
zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", pPost->rDate);
zDisplayName = display_name_from_login(pPost->zUser);
@ <h3 class='forumPostHdr'>By %s(zDisplayName) on %h(zDate)</h3>
fossil_free(zDisplayName);
fossil_free(zDate);
forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit", 1);
if( P("preview") && !whitespace_only(zContent) ){
if( bPreview && !whitespace_only(zContent) ){
@ <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( (P("preview") && !whitespace_only(zContent)) || isDelete ){
@ <input type="submit" name="submit" value="Submit">
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>
@ <br><label><input type="checkbox" name="domod" %s(PCK("domod"))> \
|
| ︙ | | |