Fossil SCM

Initial WIP of adding attachments to forum posts. It can currently create the attachment but does not yet provide access to them via the forum (required hand-editing URLs).

stephan 2026-05-22 16:36 UTC trunk
Commit 55d9faf99e30d57f099c60859a0971fbded929e7b6d3d2c3c280eef8796cf7b0
+112 -57
--- src/attach.c
+++ src/attach.c
@@ -40,10 +40,11 @@
4040
*/
4141
void attachlist_page(void){
4242
const char *zPage = P("page");
4343
const char *zTkt = P("tkt");
4444
const char *zTechNote = P("technote");
45
+ const char *zForumPost = P("forumpost");
4546
Blob sql;
4647
Stmt q;
4748
4849
if( zPage && zTkt ) zTkt = 0;
4950
login_check_credentials();
@@ -55,14 +56,28 @@
5556
" (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
5657
" (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
5758
" THEN 1"
5859
" WHEN 'event-'||target IN (SELECT tagname FROM tag)"
5960
" THEN 2"
61
+ " WHEN 'wiki-'||target IN (SELECT tagname FROM tag)"
62
+ " THEN 3"
6063
" ELSE 0 END)"
6164
" FROM attachment"
6265
);
63
- if( zPage ){
66
+ if( zForumPost ){
67
+ int fnid;
68
+ char *zUuid;
69
+ if( g.perm.RdForum==0 ){ login_needed(g.anon.RdForum); return; }
70
+ style_header("Attachments To %h", zForumPost);
71
+ fnid = forumpost_head_rid2(zForumPost);
72
+ if( fnid<=0 ){
73
+ fossil_fatal("Invalid forum post ID: %h", zForumPost);
74
+ }
75
+ zUuid = rid_to_uuid(fnid);
76
+ blob_append_sql(&sql, " WHERE target=%Q", zUuid);
77
+ fossil_free(zUuid);
78
+ }else if( zPage ){
6479
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
6580
style_header("Attachments To %h", zPage);
6681
blob_append_sql(&sql, " WHERE target=%Q", zPage);
6782
}else if( zTkt ){
6883
if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
@@ -117,25 +132,33 @@
117132
@ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
118133
if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
119134
if( zComment && zComment[0] ){
120135
@ %!W(zComment)<br>
121136
}
122
- if( zPage==0 && zTkt==0 && zTechNote==0 ){
137
+ if( zForumPost==0 && zPage==0 && zTkt==0 && zTechNote==0 ){
123138
if( zSrc==0 || zSrc[0]==0 ){
124139
zSrc = "Deleted from";
125140
}else {
126141
zSrc = "Added to";
127142
}
128
- if( type==1 ){
129
- @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
130
- @ %S(zTarget)</a>
131
- }else if( type==2 ){
132
- @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
133
- @ %S(zTarget)</a>
134
- }else{
143
+ switch( type ){
144
+ case 1:
145
+ @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
146
+ @ %S(zTarget)</a>
147
+ break;
148
+ case 2:
149
+ @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
150
+ @ %S(zTarget)</a>
151
+ break;
152
+ case 3:
135153
@ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
136154
@ %h(zTarget)</a>
155
+ break;
156
+ case 0:
157
+ @ %s(zSrc) forum post <a href="%R/forumpost/%s(zTarget)">
158
+ @ %h(zTarget)</a>
159
+ break;
137160
}
138161
}else{
139162
if( zSrc==0 || zSrc[0]==0 ){
140163
@ Deleted
141164
}else {
@@ -314,35 +337,49 @@
314337
** Add a new attachment.
315338
**
316339
** tkt=HASH
317340
** page=WIKIPAGE
318341
** technote=HASH
342
+** forumpost=HASH
319343
** from=URL
320344
**
321345
*/
322346
void attachadd_page(void){
323347
const char *zPage = P("page");
348
+ const char *zForumPost = P("forumpost");
324349
const char *zTkt = P("tkt");
325350
const char *zTechNote = P("technote");
326351
const char *zFrom = P("from");
327352
const char *aContent = P("f");
328353
const char *zName = PD("f:filename","unknown");
329354
const char *zTarget;
330355
char *zTargetType;
356
+ char *zExtraFree = 0;
331357
int szContent = atoi(PD("f:bytes","0"));
332358
int goodCaptcha = 1;
333359
360
+ if( zFrom==0 ) zFrom = mprintf("%R/home");
334361
if( P("cancel") ) cgi_redirect(zFrom);
335
- if( (zPage && zTkt)
336
- || (zPage && zTechNote)
337
- || (zTkt && zTechNote)
338
- ){
339
- fossil_redirect_home();
362
+ if( (!!zPage + !!zTkt + !!zTechNote + !!zForumPost)!=1 ){
363
+ //fossil_redirect_home();
364
+ fossil_fatal("Requires exactly one one: page=X, tkt=X, forumpost=X, or technote=X");
340365
}
341
- if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
342366
login_check_credentials();
343
- if( zPage ){
367
+ if( zForumPost ){
368
+ int fpid;
369
+ if( g.perm.AttachForum==0 ){
370
+ login_needed(g.anon.AttachForum);
371
+ return;
372
+ }
373
+ fpid = forumpost_head_rid2(zForumPost);
374
+ if( fpid<=0 ){
375
+ fossil_fatal("Invalid forum post ID: %h", zForumPost);
376
+ }
377
+ zTarget = zExtraFree = rid_to_uuid(fpid);
378
+ zTargetType = mprintf("Forum post <a href=\"%R/forumpost/%S\">%h</a>",
379
+ zTarget, zForumPost);
380
+ }else if( zPage ){
344381
if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
345382
login_needed(g.anon.ApndWiki && g.anon.Attach);
346383
return;
347384
}
348385
if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
@@ -364,10 +401,11 @@
364401
zTarget = zTechNote;
365402
zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
366403
zTechNote, zTechNote);
367404
368405
}else{
406
+ assert( zTkt );
369407
if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
370408
login_needed(g.anon.ApndTkt && g.anon.Attach);
371409
return;
372410
}
373411
if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
@@ -377,16 +415,13 @@
377415
}
378416
zTarget = zTkt;
379417
zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
380418
zTkt, zTkt);
381419
}
382
- if( zFrom==0 ) zFrom = mprintf("%R/home");
383
- if( P("cancel") ){
384
- cgi_redirect(zFrom);
385
- }
386420
if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
387
- int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
421
+ int needModerator = (zForumPost!=0 && forum_need_moderation()) ||
422
+ (zTkt!=0 && ticket_need_moderation(0)) ||
388423
(zPage!=0 && wiki_need_moderation(0));
389424
const char *zComment = PD("comment", "");
390425
attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
391426
cgi_redirect(zFrom);
392427
}
@@ -400,11 +435,13 @@
400435
@ <div>
401436
@ File to Attach:
402437
@ <input type="file" name="f" size="60"><br>
403438
@ Description:<br>
404439
@ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br>
405
- if( zTkt ){
440
+ if( zForumPost ){
441
+ @ <input type="hidden" name="forumpost" value="%h(zTarget)">
442
+ }else if( zTkt ){
406443
@ <input type="hidden" name="tkt" value="%h(zTkt)">
407444
}else if( zTechNote ){
408445
@ <input type="hidden" name="technote" value="%h(zTechNote)">
409446
}else{
410447
@ <input type="hidden" name="page" value="%h(zPage)">
@@ -415,10 +452,11 @@
415452
@ </div>
416453
captcha_generate(0);
417454
@ </form>
418455
style_finish_page();
419456
fossil_free(zTargetType);
457
+ fossil_free(zExtraFree);
420458
}
421459
422460
/*
423461
** WEBPAGE: ainfo
424462
** URL: /ainfo?name=ARTIFACTID
@@ -436,65 +474,73 @@
436474
const char *zName; /* Name of the attached file */
437475
const char *zDesc; /* Description of the attached file */
438476
const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
439477
const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
440478
const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
479
+ const char *zForumPost = 0; /* Forum post UID when attached to a forum post */
441480
int modPending; /* True if awaiting moderation */
442481
const char *zModAction; /* Moderation action or NULL */
443482
int isModerator; /* TRUE if user is the moderator */
444483
const char *zMime; /* MIME Type */
445484
Blob attach; /* Content of the attachment */
446485
int fShowContent = 0;
486
+ int bUserIsOwner = 0;
487
+ int showDelMenu = 0;
447488
const char *zLn = P("ln");
448489
449490
login_check_credentials();
450491
if( !g.perm.RdTkt && !g.perm.RdWiki ){
451492
login_needed(g.anon.RdTkt || g.anon.RdWiki);
452493
return;
453494
}
454495
rid = name_to_rid_www("name");
455496
if( rid==0 ){ fossil_redirect_home(); }
456
- zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
497
+ zUuid = rid_to_uuid(rid);
457498
pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0);
458499
if( pAttach==0 ) fossil_redirect_home();
500
+ bUserIsOwner = fossil_strcmp(pAttach->zUser, login_name());
459501
zTarget = pAttach->zAttachTarget;
460502
zSrc = pAttach->zAttachSrc;
461503
ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc);
462504
zName = pAttach->zAttachName;
463505
zDesc = pAttach->zComment;
464506
zMime = mimetype_from_name(zName);
465507
fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
466
- if( validate16(zTarget, strlen(zTarget))
508
+ if( db_int(0,"SELECT 1 FROM event WHERE objid=%d and type='f'", rid) ){
509
+ if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
510
+ showDelMenu = g.perm.Admin || bUserIsOwner;
511
+ zForumPost = zTarget;
512
+ }else if( validate16(zTarget, strlen(zTarget))
467513
&& db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
468514
){
469
- zTktUuid = zTarget;
470
- if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
471
- if( g.perm.WrTkt ){
472
- style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
473
- }
474
- }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
475
- zWikiName = zTarget;
476
- if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
477
- if( g.perm.WrWiki ){
478
- style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
479
- }
480
- }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
481
- zTNUuid = zTarget;
482
- if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
483
- if( g.perm.Write && g.perm.WrWiki ){
484
- style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
485
- }
515
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
516
+ zTktUuid = zTarget;
517
+ showDelMenu = g.perm.WrTkt;
518
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
519
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
520
+ zWikiName = zTarget;
521
+ showDelMenu = g.perm.WrWiki;
522
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
523
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
524
+ zTNUuid = zTarget;
525
+ showDelMenu = g.perm.Write && g.perm.WrWiki;
526
+ }
527
+ if( showDelMenu ){
528
+ style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
486529
}
487530
zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
488531
489
- if( P("confirm")
490
- && ((zTktUuid && g.perm.WrTkt) ||
532
+ if( P("confirm") &&
533
+ ((zForumPost
534
+ && (g.perm.Admin || (g.perm.AttachForum && bUserIsOwner))) ||
535
+ (zTktUuid && g.perm.WrTkt) ||
491536
(zWikiName && g.perm.WrWiki) ||
492537
(zTNUuid && g.perm.Write && g.perm.WrWiki))
493538
){
539
+ /* Delete attachment. */
494540
int i, n, rid;
495
- char *zDate;
541
+ char *zNewDate;
496542
Blob manifest;
497543
Blob cksum;
498544
const char *zFile = zName;
499545
500546
db_begin_transaction();
@@ -502,24 +548,26 @@
502548
for(i=n=0; zFile[i]; i++){
503549
if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
504550
}
505551
zFile += n;
506552
if( zFile[0]==0 ) zFile = "unknown";
507
- blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
508
- zDate = date_in_standard_format("now");
509
- blob_appendf(&manifest, "D %s\n", zDate);
553
+ blob_appendf(&manifest, "A %F\n", zFile);
554
+ zNewDate = date_in_standard_format("now");
555
+ blob_appendf(&manifest, "D %s\n", zNewDate);
510556
blob_appendf(&manifest, "U %F\n", login_name());
511557
md5sum_blob(&manifest, &cksum);
512558
blob_appendf(&manifest, "Z %b\n", &cksum);
513559
rid = content_put(&manifest);
514560
manifest_crosslink(rid, &manifest, MC_NONE);
515561
db_end_transaction(0);
516562
@ <p>The attachment below has been deleted.</p>
563
+ fossil_free(zNewDate);
517564
}
518565
519566
if( P("del")
520
- && ((zTktUuid && g.perm.WrTkt) ||
567
+ && ((zForumPost && (g.perm.Admin || bUserIsOwner)) ||
568
+ (zTktUuid && g.perm.WrTkt) ||
521569
(zWikiName && g.perm.WrWiki) ||
522570
(zTNUuid && g.perm.Write && g.perm.WrWiki))
523571
){
524572
form_begin(0, "%R/ainfo/%!S", zUuid);
525573
@ <p>Confirm you want to delete the attachment shown below.
@@ -526,23 +574,29 @@
526574
@ <input type="submit" name="confirm" value="Confirm">
527575
@ </form>
528576
}
529577
530578
isModerator = g.perm.Admin ||
579
+ (zForumPost && g.perm.ModForum) ||
531580
(zTktUuid && g.perm.ModTkt) ||
532581
(zWikiName && g.perm.ModWiki);
533
- if( isModerator && (zModAction = P("modaction"))!=0 ){
582
+ zModAction = P("modaction");
583
+ if( zModAction!=0 ){
534584
if( strcmp(zModAction,"delete")==0 ){
535
- moderation_disapprove(rid);
536
- if( zTktUuid ){
585
+ if( isModerator ){
586
+ moderation_disapprove(rid);
587
+ }
588
+ if( zForumPost ){
589
+ cgi_redirectf("%R/forumpost/%!S", zForumPost);
590
+ }else if( zTktUuid ){
537591
cgi_redirectf("%R/tktview/%!S", zTktUuid);
538592
}else{
539593
cgi_redirectf("%R/wiki?name=%t", zWikiName);
540594
}
541595
return;
542596
}
543
- if( strcmp(zModAction,"approve")==0 ){
597
+ if( isModerator && strcmp(zModAction,"approve")==0 ){
544598
moderation_approve('a', rid);
545599
}
546600
}
547601
style_set_current_feature("attach");
548602
style_header("Attachment Details");
@@ -558,19 +612,20 @@
558612
@ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
559613
if( g.perm.Setup ){
560614
@ (%d(rid))
561615
}
562616
modPending = moderation_pending_www(rid);
563
- if( zTktUuid ){
617
+ if( zForumPost ){
618
+ @ <tr><th>Forum&nbsp;Post:</th>
619
+ @ <td>%z(href("%R/forumpost/%s",zForumPost))%h(zForumPost)</a></td></tr>
620
+ }else if( zTktUuid ){
564621
@ <tr><th>Ticket:</th>
565622
@ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
566
- }
567
- if( zTNUuid ){
623
+ }else if( zTNUuid ){
568624
@ <tr><th>Tech Note:</th>
569625
@ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
570
- }
571
- if( zWikiName ){
626
+ }else if( zWikiName ){
572627
@ <tr><th>Wiki&nbsp;Page:</th>
573628
@ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
574629
}
575630
@ <tr><th>Date:</th><td>
576631
hyperlink_to_date(zDate, "</td></tr>");
577632
--- src/attach.c
+++ src/attach.c
@@ -40,10 +40,11 @@
40 */
41 void attachlist_page(void){
42 const char *zPage = P("page");
43 const char *zTkt = P("tkt");
44 const char *zTechNote = P("technote");
 
45 Blob sql;
46 Stmt q;
47
48 if( zPage && zTkt ) zTkt = 0;
49 login_check_credentials();
@@ -55,14 +56,28 @@
55 " (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
56 " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
57 " THEN 1"
58 " WHEN 'event-'||target IN (SELECT tagname FROM tag)"
59 " THEN 2"
 
 
60 " ELSE 0 END)"
61 " FROM attachment"
62 );
63 if( zPage ){
 
 
 
 
 
 
 
 
 
 
 
 
64 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
65 style_header("Attachments To %h", zPage);
66 blob_append_sql(&sql, " WHERE target=%Q", zPage);
67 }else if( zTkt ){
68 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
@@ -117,25 +132,33 @@
117 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
118 if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
119 if( zComment && zComment[0] ){
120 @ %!W(zComment)<br>
121 }
122 if( zPage==0 && zTkt==0 && zTechNote==0 ){
123 if( zSrc==0 || zSrc[0]==0 ){
124 zSrc = "Deleted from";
125 }else {
126 zSrc = "Added to";
127 }
128 if( type==1 ){
129 @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
130 @ %S(zTarget)</a>
131 }else if( type==2 ){
132 @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
133 @ %S(zTarget)</a>
134 }else{
 
 
 
135 @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
136 @ %h(zTarget)</a>
 
 
 
 
 
137 }
138 }else{
139 if( zSrc==0 || zSrc[0]==0 ){
140 @ Deleted
141 }else {
@@ -314,35 +337,49 @@
314 ** Add a new attachment.
315 **
316 ** tkt=HASH
317 ** page=WIKIPAGE
318 ** technote=HASH
 
319 ** from=URL
320 **
321 */
322 void attachadd_page(void){
323 const char *zPage = P("page");
 
324 const char *zTkt = P("tkt");
325 const char *zTechNote = P("technote");
326 const char *zFrom = P("from");
327 const char *aContent = P("f");
328 const char *zName = PD("f:filename","unknown");
329 const char *zTarget;
330 char *zTargetType;
 
331 int szContent = atoi(PD("f:bytes","0"));
332 int goodCaptcha = 1;
333
 
334 if( P("cancel") ) cgi_redirect(zFrom);
335 if( (zPage && zTkt)
336 || (zPage && zTechNote)
337 || (zTkt && zTechNote)
338 ){
339 fossil_redirect_home();
340 }
341 if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
342 login_check_credentials();
343 if( zPage ){
 
 
 
 
 
 
 
 
 
 
 
 
 
344 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
345 login_needed(g.anon.ApndWiki && g.anon.Attach);
346 return;
347 }
348 if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
@@ -364,10 +401,11 @@
364 zTarget = zTechNote;
365 zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
366 zTechNote, zTechNote);
367
368 }else{
 
369 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
370 login_needed(g.anon.ApndTkt && g.anon.Attach);
371 return;
372 }
373 if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
@@ -377,16 +415,13 @@
377 }
378 zTarget = zTkt;
379 zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
380 zTkt, zTkt);
381 }
382 if( zFrom==0 ) zFrom = mprintf("%R/home");
383 if( P("cancel") ){
384 cgi_redirect(zFrom);
385 }
386 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
387 int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
 
388 (zPage!=0 && wiki_need_moderation(0));
389 const char *zComment = PD("comment", "");
390 attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
391 cgi_redirect(zFrom);
392 }
@@ -400,11 +435,13 @@
400 @ <div>
401 @ File to Attach:
402 @ <input type="file" name="f" size="60"><br>
403 @ Description:<br>
404 @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br>
405 if( zTkt ){
 
 
406 @ <input type="hidden" name="tkt" value="%h(zTkt)">
407 }else if( zTechNote ){
408 @ <input type="hidden" name="technote" value="%h(zTechNote)">
409 }else{
410 @ <input type="hidden" name="page" value="%h(zPage)">
@@ -415,10 +452,11 @@
415 @ </div>
416 captcha_generate(0);
417 @ </form>
418 style_finish_page();
419 fossil_free(zTargetType);
 
420 }
421
422 /*
423 ** WEBPAGE: ainfo
424 ** URL: /ainfo?name=ARTIFACTID
@@ -436,65 +474,73 @@
436 const char *zName; /* Name of the attached file */
437 const char *zDesc; /* Description of the attached file */
438 const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
439 const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
440 const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
 
441 int modPending; /* True if awaiting moderation */
442 const char *zModAction; /* Moderation action or NULL */
443 int isModerator; /* TRUE if user is the moderator */
444 const char *zMime; /* MIME Type */
445 Blob attach; /* Content of the attachment */
446 int fShowContent = 0;
 
 
447 const char *zLn = P("ln");
448
449 login_check_credentials();
450 if( !g.perm.RdTkt && !g.perm.RdWiki ){
451 login_needed(g.anon.RdTkt || g.anon.RdWiki);
452 return;
453 }
454 rid = name_to_rid_www("name");
455 if( rid==0 ){ fossil_redirect_home(); }
456 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
457 pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0);
458 if( pAttach==0 ) fossil_redirect_home();
 
459 zTarget = pAttach->zAttachTarget;
460 zSrc = pAttach->zAttachSrc;
461 ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc);
462 zName = pAttach->zAttachName;
463 zDesc = pAttach->zComment;
464 zMime = mimetype_from_name(zName);
465 fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
466 if( validate16(zTarget, strlen(zTarget))
 
 
 
 
467 && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
468 ){
469 zTktUuid = zTarget;
470 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
471 if( g.perm.WrTkt ){
472 style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
473 }
474 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
475 zWikiName = zTarget;
476 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
477 if( g.perm.WrWiki ){
478 style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
479 }
480 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
481 zTNUuid = zTarget;
482 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
483 if( g.perm.Write && g.perm.WrWiki ){
484 style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
485 }
486 }
487 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
488
489 if( P("confirm")
490 && ((zTktUuid && g.perm.WrTkt) ||
 
 
491 (zWikiName && g.perm.WrWiki) ||
492 (zTNUuid && g.perm.Write && g.perm.WrWiki))
493 ){
 
494 int i, n, rid;
495 char *zDate;
496 Blob manifest;
497 Blob cksum;
498 const char *zFile = zName;
499
500 db_begin_transaction();
@@ -502,24 +548,26 @@
502 for(i=n=0; zFile[i]; i++){
503 if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
504 }
505 zFile += n;
506 if( zFile[0]==0 ) zFile = "unknown";
507 blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
508 zDate = date_in_standard_format("now");
509 blob_appendf(&manifest, "D %s\n", zDate);
510 blob_appendf(&manifest, "U %F\n", login_name());
511 md5sum_blob(&manifest, &cksum);
512 blob_appendf(&manifest, "Z %b\n", &cksum);
513 rid = content_put(&manifest);
514 manifest_crosslink(rid, &manifest, MC_NONE);
515 db_end_transaction(0);
516 @ <p>The attachment below has been deleted.</p>
 
517 }
518
519 if( P("del")
520 && ((zTktUuid && g.perm.WrTkt) ||
 
521 (zWikiName && g.perm.WrWiki) ||
522 (zTNUuid && g.perm.Write && g.perm.WrWiki))
523 ){
524 form_begin(0, "%R/ainfo/%!S", zUuid);
525 @ <p>Confirm you want to delete the attachment shown below.
@@ -526,23 +574,29 @@
526 @ <input type="submit" name="confirm" value="Confirm">
527 @ </form>
528 }
529
530 isModerator = g.perm.Admin ||
 
531 (zTktUuid && g.perm.ModTkt) ||
532 (zWikiName && g.perm.ModWiki);
533 if( isModerator && (zModAction = P("modaction"))!=0 ){
 
534 if( strcmp(zModAction,"delete")==0 ){
535 moderation_disapprove(rid);
536 if( zTktUuid ){
 
 
 
 
537 cgi_redirectf("%R/tktview/%!S", zTktUuid);
538 }else{
539 cgi_redirectf("%R/wiki?name=%t", zWikiName);
540 }
541 return;
542 }
543 if( strcmp(zModAction,"approve")==0 ){
544 moderation_approve('a', rid);
545 }
546 }
547 style_set_current_feature("attach");
548 style_header("Attachment Details");
@@ -558,19 +612,20 @@
558 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
559 if( g.perm.Setup ){
560 @ (%d(rid))
561 }
562 modPending = moderation_pending_www(rid);
563 if( zTktUuid ){
 
 
 
564 @ <tr><th>Ticket:</th>
565 @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
566 }
567 if( zTNUuid ){
568 @ <tr><th>Tech Note:</th>
569 @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
570 }
571 if( zWikiName ){
572 @ <tr><th>Wiki&nbsp;Page:</th>
573 @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
574 }
575 @ <tr><th>Date:</th><td>
576 hyperlink_to_date(zDate, "</td></tr>");
577
--- src/attach.c
+++ src/attach.c
@@ -40,10 +40,11 @@
40 */
41 void attachlist_page(void){
42 const char *zPage = P("page");
43 const char *zTkt = P("tkt");
44 const char *zTechNote = P("technote");
45 const char *zForumPost = P("forumpost");
46 Blob sql;
47 Stmt q;
48
49 if( zPage && zTkt ) zTkt = 0;
50 login_check_credentials();
@@ -55,14 +56,28 @@
56 " (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
57 " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
58 " THEN 1"
59 " WHEN 'event-'||target IN (SELECT tagname FROM tag)"
60 " THEN 2"
61 " WHEN 'wiki-'||target IN (SELECT tagname FROM tag)"
62 " THEN 3"
63 " ELSE 0 END)"
64 " FROM attachment"
65 );
66 if( zForumPost ){
67 int fnid;
68 char *zUuid;
69 if( g.perm.RdForum==0 ){ login_needed(g.anon.RdForum); return; }
70 style_header("Attachments To %h", zForumPost);
71 fnid = forumpost_head_rid2(zForumPost);
72 if( fnid<=0 ){
73 fossil_fatal("Invalid forum post ID: %h", zForumPost);
74 }
75 zUuid = rid_to_uuid(fnid);
76 blob_append_sql(&sql, " WHERE target=%Q", zUuid);
77 fossil_free(zUuid);
78 }else if( zPage ){
79 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
80 style_header("Attachments To %h", zPage);
81 blob_append_sql(&sql, " WHERE target=%Q", zPage);
82 }else if( zTkt ){
83 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
@@ -117,25 +132,33 @@
132 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
133 if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
134 if( zComment && zComment[0] ){
135 @ %!W(zComment)<br>
136 }
137 if( zForumPost==0 && zPage==0 && zTkt==0 && zTechNote==0 ){
138 if( zSrc==0 || zSrc[0]==0 ){
139 zSrc = "Deleted from";
140 }else {
141 zSrc = "Added to";
142 }
143 switch( type ){
144 case 1:
145 @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
146 @ %S(zTarget)</a>
147 break;
148 case 2:
149 @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
150 @ %S(zTarget)</a>
151 break;
152 case 3:
153 @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
154 @ %h(zTarget)</a>
155 break;
156 case 0:
157 @ %s(zSrc) forum post <a href="%R/forumpost/%s(zTarget)">
158 @ %h(zTarget)</a>
159 break;
160 }
161 }else{
162 if( zSrc==0 || zSrc[0]==0 ){
163 @ Deleted
164 }else {
@@ -314,35 +337,49 @@
337 ** Add a new attachment.
338 **
339 ** tkt=HASH
340 ** page=WIKIPAGE
341 ** technote=HASH
342 ** forumpost=HASH
343 ** from=URL
344 **
345 */
346 void attachadd_page(void){
347 const char *zPage = P("page");
348 const char *zForumPost = P("forumpost");
349 const char *zTkt = P("tkt");
350 const char *zTechNote = P("technote");
351 const char *zFrom = P("from");
352 const char *aContent = P("f");
353 const char *zName = PD("f:filename","unknown");
354 const char *zTarget;
355 char *zTargetType;
356 char *zExtraFree = 0;
357 int szContent = atoi(PD("f:bytes","0"));
358 int goodCaptcha = 1;
359
360 if( zFrom==0 ) zFrom = mprintf("%R/home");
361 if( P("cancel") ) cgi_redirect(zFrom);
362 if( (!!zPage + !!zTkt + !!zTechNote + !!zForumPost)!=1 ){
363 //fossil_redirect_home();
364 fossil_fatal("Requires exactly one one: page=X, tkt=X, forumpost=X, or technote=X");
 
 
365 }
 
366 login_check_credentials();
367 if( zForumPost ){
368 int fpid;
369 if( g.perm.AttachForum==0 ){
370 login_needed(g.anon.AttachForum);
371 return;
372 }
373 fpid = forumpost_head_rid2(zForumPost);
374 if( fpid<=0 ){
375 fossil_fatal("Invalid forum post ID: %h", zForumPost);
376 }
377 zTarget = zExtraFree = rid_to_uuid(fpid);
378 zTargetType = mprintf("Forum post <a href=\"%R/forumpost/%S\">%h</a>",
379 zTarget, zForumPost);
380 }else if( zPage ){
381 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
382 login_needed(g.anon.ApndWiki && g.anon.Attach);
383 return;
384 }
385 if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
@@ -364,10 +401,11 @@
401 zTarget = zTechNote;
402 zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
403 zTechNote, zTechNote);
404
405 }else{
406 assert( zTkt );
407 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
408 login_needed(g.anon.ApndTkt && g.anon.Attach);
409 return;
410 }
411 if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
@@ -377,16 +415,13 @@
415 }
416 zTarget = zTkt;
417 zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
418 zTkt, zTkt);
419 }
 
 
 
 
420 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
421 int needModerator = (zForumPost!=0 && forum_need_moderation()) ||
422 (zTkt!=0 && ticket_need_moderation(0)) ||
423 (zPage!=0 && wiki_need_moderation(0));
424 const char *zComment = PD("comment", "");
425 attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
426 cgi_redirect(zFrom);
427 }
@@ -400,11 +435,13 @@
435 @ <div>
436 @ File to Attach:
437 @ <input type="file" name="f" size="60"><br>
438 @ Description:<br>
439 @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br>
440 if( zForumPost ){
441 @ <input type="hidden" name="forumpost" value="%h(zTarget)">
442 }else if( zTkt ){
443 @ <input type="hidden" name="tkt" value="%h(zTkt)">
444 }else if( zTechNote ){
445 @ <input type="hidden" name="technote" value="%h(zTechNote)">
446 }else{
447 @ <input type="hidden" name="page" value="%h(zPage)">
@@ -415,10 +452,11 @@
452 @ </div>
453 captcha_generate(0);
454 @ </form>
455 style_finish_page();
456 fossil_free(zTargetType);
457 fossil_free(zExtraFree);
458 }
459
460 /*
461 ** WEBPAGE: ainfo
462 ** URL: /ainfo?name=ARTIFACTID
@@ -436,65 +474,73 @@
474 const char *zName; /* Name of the attached file */
475 const char *zDesc; /* Description of the attached file */
476 const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
477 const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
478 const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
479 const char *zForumPost = 0; /* Forum post UID when attached to a forum post */
480 int modPending; /* True if awaiting moderation */
481 const char *zModAction; /* Moderation action or NULL */
482 int isModerator; /* TRUE if user is the moderator */
483 const char *zMime; /* MIME Type */
484 Blob attach; /* Content of the attachment */
485 int fShowContent = 0;
486 int bUserIsOwner = 0;
487 int showDelMenu = 0;
488 const char *zLn = P("ln");
489
490 login_check_credentials();
491 if( !g.perm.RdTkt && !g.perm.RdWiki ){
492 login_needed(g.anon.RdTkt || g.anon.RdWiki);
493 return;
494 }
495 rid = name_to_rid_www("name");
496 if( rid==0 ){ fossil_redirect_home(); }
497 zUuid = rid_to_uuid(rid);
498 pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0);
499 if( pAttach==0 ) fossil_redirect_home();
500 bUserIsOwner = fossil_strcmp(pAttach->zUser, login_name());
501 zTarget = pAttach->zAttachTarget;
502 zSrc = pAttach->zAttachSrc;
503 ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc);
504 zName = pAttach->zAttachName;
505 zDesc = pAttach->zComment;
506 zMime = mimetype_from_name(zName);
507 fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
508 if( db_int(0,"SELECT 1 FROM event WHERE objid=%d and type='f'", rid) ){
509 if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }
510 showDelMenu = g.perm.Admin || bUserIsOwner;
511 zForumPost = zTarget;
512 }else if( validate16(zTarget, strlen(zTarget))
513 && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
514 ){
515 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
516 zTktUuid = zTarget;
517 showDelMenu = g.perm.WrTkt;
518 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
519 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
520 zWikiName = zTarget;
521 showDelMenu = g.perm.WrWiki;
522 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
523 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
524 zTNUuid = zTarget;
525 showDelMenu = g.perm.Write && g.perm.WrWiki;
526 }
527 if( showDelMenu ){
528 style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
 
 
 
529 }
530 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
531
532 if( P("confirm") &&
533 ((zForumPost
534 && (g.perm.Admin || (g.perm.AttachForum && bUserIsOwner))) ||
535 (zTktUuid && g.perm.WrTkt) ||
536 (zWikiName && g.perm.WrWiki) ||
537 (zTNUuid && g.perm.Write && g.perm.WrWiki))
538 ){
539 /* Delete attachment. */
540 int i, n, rid;
541 char *zNewDate;
542 Blob manifest;
543 Blob cksum;
544 const char *zFile = zName;
545
546 db_begin_transaction();
@@ -502,24 +548,26 @@
548 for(i=n=0; zFile[i]; i++){
549 if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
550 }
551 zFile += n;
552 if( zFile[0]==0 ) zFile = "unknown";
553 blob_appendf(&manifest, "A %F\n", zFile);
554 zNewDate = date_in_standard_format("now");
555 blob_appendf(&manifest, "D %s\n", zNewDate);
556 blob_appendf(&manifest, "U %F\n", login_name());
557 md5sum_blob(&manifest, &cksum);
558 blob_appendf(&manifest, "Z %b\n", &cksum);
559 rid = content_put(&manifest);
560 manifest_crosslink(rid, &manifest, MC_NONE);
561 db_end_transaction(0);
562 @ <p>The attachment below has been deleted.</p>
563 fossil_free(zNewDate);
564 }
565
566 if( P("del")
567 && ((zForumPost && (g.perm.Admin || bUserIsOwner)) ||
568 (zTktUuid && g.perm.WrTkt) ||
569 (zWikiName && g.perm.WrWiki) ||
570 (zTNUuid && g.perm.Write && g.perm.WrWiki))
571 ){
572 form_begin(0, "%R/ainfo/%!S", zUuid);
573 @ <p>Confirm you want to delete the attachment shown below.
@@ -526,23 +574,29 @@
574 @ <input type="submit" name="confirm" value="Confirm">
575 @ </form>
576 }
577
578 isModerator = g.perm.Admin ||
579 (zForumPost && g.perm.ModForum) ||
580 (zTktUuid && g.perm.ModTkt) ||
581 (zWikiName && g.perm.ModWiki);
582 zModAction = P("modaction");
583 if( zModAction!=0 ){
584 if( strcmp(zModAction,"delete")==0 ){
585 if( isModerator ){
586 moderation_disapprove(rid);
587 }
588 if( zForumPost ){
589 cgi_redirectf("%R/forumpost/%!S", zForumPost);
590 }else if( zTktUuid ){
591 cgi_redirectf("%R/tktview/%!S", zTktUuid);
592 }else{
593 cgi_redirectf("%R/wiki?name=%t", zWikiName);
594 }
595 return;
596 }
597 if( isModerator && strcmp(zModAction,"approve")==0 ){
598 moderation_approve('a', rid);
599 }
600 }
601 style_set_current_feature("attach");
602 style_header("Attachment Details");
@@ -558,19 +612,20 @@
612 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
613 if( g.perm.Setup ){
614 @ (%d(rid))
615 }
616 modPending = moderation_pending_www(rid);
617 if( zForumPost ){
618 @ <tr><th>Forum&nbsp;Post:</th>
619 @ <td>%z(href("%R/forumpost/%s",zForumPost))%h(zForumPost)</a></td></tr>
620 }else if( zTktUuid ){
621 @ <tr><th>Ticket:</th>
622 @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
623 }else if( zTNUuid ){
 
624 @ <tr><th>Tech Note:</th>
625 @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
626 }else if( zWikiName ){
 
627 @ <tr><th>Wiki&nbsp;Page:</th>
628 @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
629 }
630 @ <tr><th>Date:</th><td>
631 hyperlink_to_date(zDate, "</td></tr>");
632
+14 -2
--- src/forum.c
+++ src/forum.c
@@ -85,11 +85,11 @@
8585
/*
8686
** Given a valid forumpost.fpid value, this function returns the first
8787
** fpid in the chain of edits for that forum post, or rid if no prior
8888
** versions are found.
8989
*/
90
-static int forumpost_head_rid(int rid){
90
+int forumpost_head_rid(int rid){
9191
Stmt q;
9292
int rcRid = rid;
9393
9494
db_prepare(&q, "SELECT fprev FROM forumpost"
9595
" WHERE fpid=:rid AND fprev IS NOT NULL");
@@ -100,10 +100,22 @@
100100
db_bind_int(&q, ":rid", rcRid);
101101
}
102102
db_finalize(&q);
103103
return rcRid;
104104
}
105
+
106
+/*
107
+** Works like forumpost_head_rid() but expects zUuid to be an
108
+** unambiguous forum post name.
109
+*/
110
+int forumpost_head_rid2(const char *zUuid){
111
+ const int fpid = symbolic_name_to_rid(zUuid, "f");
112
+ return fpid>0
113
+ ? forumpost_head_rid(fpid)
114
+ : 0;
115
+}
116
+
105117
106118
/*
107119
** Returns true if p, or any parent of p, has a non-zero iClosed
108120
** value. Returns 0 if !p. For an edited chain of post, the tag is
109121
** checked on the pEditHead entry, to simplify subsequent unlocking of
@@ -1280,11 +1292,11 @@
12801292
}
12811293
12821294
/*
12831295
** Return true if a forum post should be moderated.
12841296
*/
1285
-static int forum_need_moderation(void){
1297
+int forum_need_moderation(void){
12861298
if( P("domod") ) return 1;
12871299
if( g.perm.WrTForum ) return 0;
12881300
if( g.perm.ModForum ) return 0;
12891301
return 1;
12901302
}
12911303
--- src/forum.c
+++ src/forum.c
@@ -85,11 +85,11 @@
85 /*
86 ** Given a valid forumpost.fpid value, this function returns the first
87 ** fpid in the chain of edits for that forum post, or rid if no prior
88 ** versions are found.
89 */
90 static int forumpost_head_rid(int rid){
91 Stmt q;
92 int rcRid = rid;
93
94 db_prepare(&q, "SELECT fprev FROM forumpost"
95 " WHERE fpid=:rid AND fprev IS NOT NULL");
@@ -100,10 +100,22 @@
100 db_bind_int(&q, ":rid", rcRid);
101 }
102 db_finalize(&q);
103 return rcRid;
104 }
 
 
 
 
 
 
 
 
 
 
 
 
105
106 /*
107 ** Returns true if p, or any parent of p, has a non-zero iClosed
108 ** value. Returns 0 if !p. For an edited chain of post, the tag is
109 ** checked on the pEditHead entry, to simplify subsequent unlocking of
@@ -1280,11 +1292,11 @@
1280 }
1281
1282 /*
1283 ** Return true if a forum post should be moderated.
1284 */
1285 static int forum_need_moderation(void){
1286 if( P("domod") ) return 1;
1287 if( g.perm.WrTForum ) return 0;
1288 if( g.perm.ModForum ) return 0;
1289 return 1;
1290 }
1291
--- src/forum.c
+++ src/forum.c
@@ -85,11 +85,11 @@
85 /*
86 ** Given a valid forumpost.fpid value, this function returns the first
87 ** fpid in the chain of edits for that forum post, or rid if no prior
88 ** versions are found.
89 */
90 int forumpost_head_rid(int rid){
91 Stmt q;
92 int rcRid = rid;
93
94 db_prepare(&q, "SELECT fprev FROM forumpost"
95 " WHERE fpid=:rid AND fprev IS NOT NULL");
@@ -100,10 +100,22 @@
100 db_bind_int(&q, ":rid", rcRid);
101 }
102 db_finalize(&q);
103 return rcRid;
104 }
105
106 /*
107 ** Works like forumpost_head_rid() but expects zUuid to be an
108 ** unambiguous forum post name.
109 */
110 int forumpost_head_rid2(const char *zUuid){
111 const int fpid = symbolic_name_to_rid(zUuid, "f");
112 return fpid>0
113 ? forumpost_head_rid(fpid)
114 : 0;
115 }
116
117
118 /*
119 ** Returns true if p, or any parent of p, has a non-zero iClosed
120 ** value. Returns 0 if !p. For an edited chain of post, the tag is
121 ** checked on the pEditHead entry, to simplify subsequent unlocking of
@@ -1280,11 +1292,11 @@
1292 }
1293
1294 /*
1295 ** Return true if a forum post should be moderated.
1296 */
1297 int forum_need_moderation(void){
1298 if( P("domod") ) return 1;
1299 if( g.perm.WrTForum ) return 0;
1300 if( g.perm.ModForum ) return 0;
1301 return 1;
1302 }
1303
+3 -1
--- src/login.c
+++ src/login.c
@@ -1708,11 +1708,12 @@
17081708
p->NewTkt = p->Password = p->RdAddr =
17091709
p->TktFmt = p->Attach = p->ApndTkt =
17101710
p->ModWiki = p->ModTkt =
17111711
p->RdForum = p->WrForum = p->ModForum =
17121712
p->WrTForum = p->AdminForum = p->Chat =
1713
- p->EmailAlert = p->Announce = p->Debug = 1;
1713
+ p->EmailAlert = p->Announce = p->AttachForum =
1714
+ p->Debug = 1;
17141715
/* Fall thru into Read/Write */
17151716
case 'i': p->Read = p->Write = 1; break;
17161717
case 'o': p->Read = 1; break;
17171718
case 'z': p->Zip = 1; break;
17181719
@@ -1744,10 +1745,11 @@
17441745
case '3': p->WrForum = 1;
17451746
case '2': p->RdForum = 1; break;
17461747
17471748
case '7': p->EmailAlert = 1; break;
17481749
case 'A': p->Announce = 1; break;
1750
+ case 'B': p->AttachForum = 1; break;
17491751
case 'C': p->Chat = 1; break;
17501752
case 'D': p->Debug = 1; break;
17511753
17521754
/* The "u" privilege recursively
17531755
** inherits all privileges of the user named "reader" */
17541756
--- src/login.c
+++ src/login.c
@@ -1708,11 +1708,12 @@
1708 p->NewTkt = p->Password = p->RdAddr =
1709 p->TktFmt = p->Attach = p->ApndTkt =
1710 p->ModWiki = p->ModTkt =
1711 p->RdForum = p->WrForum = p->ModForum =
1712 p->WrTForum = p->AdminForum = p->Chat =
1713 p->EmailAlert = p->Announce = p->Debug = 1;
 
1714 /* Fall thru into Read/Write */
1715 case 'i': p->Read = p->Write = 1; break;
1716 case 'o': p->Read = 1; break;
1717 case 'z': p->Zip = 1; break;
1718
@@ -1744,10 +1745,11 @@
1744 case '3': p->WrForum = 1;
1745 case '2': p->RdForum = 1; break;
1746
1747 case '7': p->EmailAlert = 1; break;
1748 case 'A': p->Announce = 1; break;
 
1749 case 'C': p->Chat = 1; break;
1750 case 'D': p->Debug = 1; break;
1751
1752 /* The "u" privilege recursively
1753 ** inherits all privileges of the user named "reader" */
1754
--- src/login.c
+++ src/login.c
@@ -1708,11 +1708,12 @@
1708 p->NewTkt = p->Password = p->RdAddr =
1709 p->TktFmt = p->Attach = p->ApndTkt =
1710 p->ModWiki = p->ModTkt =
1711 p->RdForum = p->WrForum = p->ModForum =
1712 p->WrTForum = p->AdminForum = p->Chat =
1713 p->EmailAlert = p->Announce = p->AttachForum =
1714 p->Debug = 1;
1715 /* Fall thru into Read/Write */
1716 case 'i': p->Read = p->Write = 1; break;
1717 case 'o': p->Read = 1; break;
1718 case 'z': p->Zip = 1; break;
1719
@@ -1744,10 +1745,11 @@
1745 case '3': p->WrForum = 1;
1746 case '2': p->RdForum = 1; break;
1747
1748 case '7': p->EmailAlert = 1; break;
1749 case 'A': p->Announce = 1; break;
1750 case 'B': p->AttachForum = 1; break;
1751 case 'C': p->Chat = 1; break;
1752 case 'D': p->Debug = 1; break;
1753
1754 /* The "u" privilege recursively
1755 ** inherits all privileges of the user named "reader" */
1756
+2 -1
--- src/main.c
+++ src/main.c
@@ -97,11 +97,11 @@
9797
char RdTkt; /* r: view tickets via web */
9898
char NewTkt; /* n: create new tickets */
9999
char ApndTkt; /* c: append to tickets via the web */
100100
char WrTkt; /* w: make changes to tickets via web */
101101
char ModTkt; /* q: approve and publish ticket changes (Moderator) */
102
- char Attach; /* b: add attachments */
102
+ char Attach; /* b: add attachments to wiki or tickets */
103103
char TktFmt; /* t: create new ticket report formats */
104104
char RdAddr; /* e: read email addresses or other private data */
105105
char Zip; /* z: download zipped artifact via /zip URL */
106106
char Private; /* x: can send and receive private content */
107107
char WrUnver; /* y: can push unversioned content */
@@ -110,10 +110,11 @@
110110
char WrTForum; /* 4: Post to forums not subject to moderation */
111111
char ModForum; /* 5: Moderate (approve or reject) forum posts */
112112
char AdminForum; /* 6: Grant capability 4 to other users */
113113
char EmailAlert; /* 7: Sign up for email notifications */
114114
char Announce; /* A: Send announcements */
115
+ char AttachForum; /* B: add attachments to forum */
115116
char Chat; /* C: read or write the chatroom */
116117
char Debug; /* D: show extra Fossil debugging features */
117118
/* These last two are included to block infinite recursion */
118119
char XReader; /* u: Inherit all privileges of "reader" */
119120
char XDeveloper; /* v: Inherit all privileges of "developer" */
120121
--- src/main.c
+++ src/main.c
@@ -97,11 +97,11 @@
97 char RdTkt; /* r: view tickets via web */
98 char NewTkt; /* n: create new tickets */
99 char ApndTkt; /* c: append to tickets via the web */
100 char WrTkt; /* w: make changes to tickets via web */
101 char ModTkt; /* q: approve and publish ticket changes (Moderator) */
102 char Attach; /* b: add attachments */
103 char TktFmt; /* t: create new ticket report formats */
104 char RdAddr; /* e: read email addresses or other private data */
105 char Zip; /* z: download zipped artifact via /zip URL */
106 char Private; /* x: can send and receive private content */
107 char WrUnver; /* y: can push unversioned content */
@@ -110,10 +110,11 @@
110 char WrTForum; /* 4: Post to forums not subject to moderation */
111 char ModForum; /* 5: Moderate (approve or reject) forum posts */
112 char AdminForum; /* 6: Grant capability 4 to other users */
113 char EmailAlert; /* 7: Sign up for email notifications */
114 char Announce; /* A: Send announcements */
 
115 char Chat; /* C: read or write the chatroom */
116 char Debug; /* D: show extra Fossil debugging features */
117 /* These last two are included to block infinite recursion */
118 char XReader; /* u: Inherit all privileges of "reader" */
119 char XDeveloper; /* v: Inherit all privileges of "developer" */
120
--- src/main.c
+++ src/main.c
@@ -97,11 +97,11 @@
97 char RdTkt; /* r: view tickets via web */
98 char NewTkt; /* n: create new tickets */
99 char ApndTkt; /* c: append to tickets via the web */
100 char WrTkt; /* w: make changes to tickets via web */
101 char ModTkt; /* q: approve and publish ticket changes (Moderator) */
102 char Attach; /* b: add attachments to wiki or tickets */
103 char TktFmt; /* t: create new ticket report formats */
104 char RdAddr; /* e: read email addresses or other private data */
105 char Zip; /* z: download zipped artifact via /zip URL */
106 char Private; /* x: can send and receive private content */
107 char WrUnver; /* y: can push unversioned content */
@@ -110,10 +110,11 @@
110 char WrTForum; /* 4: Post to forums not subject to moderation */
111 char ModForum; /* 5: Moderate (approve or reject) forum posts */
112 char AdminForum; /* 6: Grant capability 4 to other users */
113 char EmailAlert; /* 7: Sign up for email notifications */
114 char Announce; /* A: Send announcements */
115 char AttachForum; /* B: add attachments to forum */
116 char Chat; /* C: read or write the chatroom */
117 char Debug; /* D: show extra Fossil debugging features */
118 /* These last two are included to block infinite recursion */
119 char XReader; /* u: Inherit all privileges of "reader" */
120 char XDeveloper; /* v: Inherit all privileges of "developer" */
121
--- src/setupuser.c
+++ src/setupuser.c
@@ -821,10 +821,12 @@
821821
@ Check-Out%s(B('o'))</label>
822822
@ <li><label><input type="checkbox" name="ah"%s(oa['h'])>
823823
@ Hyperlinks%s(B('h'))</label>
824824
@ <li><label><input type="checkbox" name="ab"%s(oa['b'])>
825825
@ Attachments%s(B('b'))</label>
826
+ @ <li><label><input type="checkbox" name="aB"%s(oa['B'])>
827
+ @ Forum Attachments%s(B('B'))</label>
826828
@ <li><label><input type="checkbox" name="ag"%s(oa['g'])>
827829
@ Clone%s(B('g'))</label>
828830
@ <li><label><input type="checkbox" name="aj"%s(oa['j'])>
829831
@ Read Wiki%s(B('j'))</label>
830832
@ <li><label><input type="checkbox" name="af"%s(oa['f'])>
831833
--- src/setupuser.c
+++ src/setupuser.c
@@ -821,10 +821,12 @@
821 @ Check-Out%s(B('o'))</label>
822 @ <li><label><input type="checkbox" name="ah"%s(oa['h'])>
823 @ Hyperlinks%s(B('h'))</label>
824 @ <li><label><input type="checkbox" name="ab"%s(oa['b'])>
825 @ Attachments%s(B('b'))</label>
 
 
826 @ <li><label><input type="checkbox" name="ag"%s(oa['g'])>
827 @ Clone%s(B('g'))</label>
828 @ <li><label><input type="checkbox" name="aj"%s(oa['j'])>
829 @ Read Wiki%s(B('j'))</label>
830 @ <li><label><input type="checkbox" name="af"%s(oa['f'])>
831
--- src/setupuser.c
+++ src/setupuser.c
@@ -821,10 +821,12 @@
821 @ Check-Out%s(B('o'))</label>
822 @ <li><label><input type="checkbox" name="ah"%s(oa['h'])>
823 @ Hyperlinks%s(B('h'))</label>
824 @ <li><label><input type="checkbox" name="ab"%s(oa['b'])>
825 @ Attachments%s(B('b'))</label>
826 @ <li><label><input type="checkbox" name="aB"%s(oa['B'])>
827 @ Forum Attachments%s(B('B'))</label>
828 @ <li><label><input type="checkbox" name="ag"%s(oa['g'])>
829 @ Clone%s(B('g'))</label>
830 @ <li><label><input type="checkbox" name="aj"%s(oa['j'])>
831 @ Read Wiki%s(B('j'))</label>
832 @ <li><label><input type="checkbox" name="af"%s(oa['f'])>
833
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -439,12 +439,12 @@
439439
<b>U</b> <i>user-name</i><br />
440440
<b>Z</b> <i>checksum</i>
441441
</div>
442442
443443
The <b>A</b> card specifies a filename for the attachment in its first argument.
444
-The second argument to the <b>A</b> card is the name of the wiki page or
445
-ticket or technical note to which the attachment is connected. The
444
+The second argument to the <b>A</b> card is the name of the wiki page,
445
+ticket, technical note, or forum post to which the attachment is connected. The
446446
third argument is either missing or else it is the lower-case artifact
447447
ID of the attachment itself. A missing third argument means that the
448448
attachment should be deleted.
449449
450450
The <b>C</b> card is an optional comment describing what the attachment is about.
451451
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -439,12 +439,12 @@
439 <b>U</b> <i>user-name</i><br />
440 <b>Z</b> <i>checksum</i>
441 </div>
442
443 The <b>A</b> card specifies a filename for the attachment in its first argument.
444 The second argument to the <b>A</b> card is the name of the wiki page or
445 ticket or technical note to which the attachment is connected. The
446 third argument is either missing or else it is the lower-case artifact
447 ID of the attachment itself. A missing third argument means that the
448 attachment should be deleted.
449
450 The <b>C</b> card is an optional comment describing what the attachment is about.
451
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -439,12 +439,12 @@
439 <b>U</b> <i>user-name</i><br />
440 <b>Z</b> <i>checksum</i>
441 </div>
442
443 The <b>A</b> card specifies a filename for the attachment in its first argument.
444 The second argument to the <b>A</b> card is the name of the wiki page,
445 ticket, technical note, or forum post to which the attachment is connected. The
446 third argument is either missing or else it is the lower-case artifact
447 ID of the attachment itself. A missing third argument means that the
448 attachment should be deleted.
449
450 The <b>C</b> card is an optional comment describing what the attachment is about.
451

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button