Fossil SCM

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.

stephan 2023-02-21 00:52 trunk
Commit 4d664bfe55c756acf9eed6140b7c38020c16623ab5e373541dfa9487c197d904
--- src/default.css
+++ src/default.css
@@ -898,10 +898,19 @@
898898
padding-left: 1ex;
899899
padding-right: 1ex;
900900
margin-top: 1ex;
901901
display: flex;
902902
flex-direction: column;
903
+}
904
+div.forumClosed {
905
+ border-style: dotted;
906
+ opacity: 0.7;
907
+}
908
+div.forumClosed > *:first-child::before {
909
+ content: "[LOCKED] ";
910
+ color: red;
911
+ opacity: 0.7;
903912
}
904913
.forum div > form {
905914
margin: 0.5em 0;
906915
}
907916
.forum-post-collapser {
908917
--- src/default.css
+++ src/default.css
@@ -898,10 +898,19 @@
898 padding-left: 1ex;
899 padding-right: 1ex;
900 margin-top: 1ex;
901 display: flex;
902 flex-direction: column;
 
 
 
 
 
 
 
 
 
903 }
904 .forum div > form {
905 margin: 0.5em 0;
906 }
907 .forum-post-collapser {
908
--- src/default.css
+++ src/default.css
@@ -898,10 +898,19 @@
898 padding-left: 1ex;
899 padding-right: 1ex;
900 margin-top: 1ex;
901 display: flex;
902 flex-direction: column;
903 }
904 div.forumClosed {
905 border-style: dotted;
906 opacity: 0.7;
907 }
908 div.forumClosed > *:first-child::before {
909 content: "[LOCKED] ";
910 color: red;
911 opacity: 0.7;
912 }
913 .forum div > form {
914 margin: 0.5em 0;
915 }
916 .forum-post-collapser {
917
+41 -11
--- src/forum.c
+++ src/forum.c
@@ -47,10 +47,11 @@
4747
ForumPost *pNext; /* Next in chronological order */
4848
ForumPost *pPrev; /* Previous in chronological order */
4949
ForumPost *pDisplay; /* Next in display order */
5050
int nEdit; /* Number of edits to this post */
5151
int nIndent; /* Number of levels of indentation for this post */
52
+ int fClosed; /* tagxref.tagtype if this (sub)thread has a closed tag. */
5253
};
5354
5455
/*
5556
** A single instance of the following tracks all entries for a thread.
5657
*/
@@ -77,10 +78,27 @@
7778
db_bind_int(&q, "$rid", rid);
7879
res = db_step(&q)==SQLITE_ROW;
7980
db_reset(&q);
8081
return res;
8182
}
83
+
84
+/*
85
+** Returns true if p, or any parent of p, has an active "closed" tag.
86
+** Returns 0 if !p. For an edited chain of post, the tag is checked on
87
+** the final edit in the chain, as that permits that a post can be
88
+** locked and later unlocked.
89
+*/
90
+int forum_post_is_closed(ForumPost *p){
91
+ if( !p ) return 0;
92
+ if( p->pEditTail ) p = p->pEditTail;
93
+ if( p->fClosed ) return p->fClosed;
94
+ else if( p->pIrt ){
95
+ return forum_post_is_closed(p->pIrt->pEditTail
96
+ ? p->pIrt->pEditTail : p->pIrt);
97
+ }
98
+ return 0;
99
+}
82100
83101
/*
84102
** Delete a complete ForumThread and all its entries.
85103
*/
86104
static void forumthread_delete(ForumThread *pThread){
@@ -215,10 +233,11 @@
215233
for(; p; p=p->pEditPrev ){
216234
p->nEdit = pPost->nEdit;
217235
p->pEditTail = pPost;
218236
}
219237
}
238
+ pPost->fClosed = rid_has_active_tag_name(pPost->fpid, "closed");
220239
}
221240
db_finalize(&q);
222241
223242
if( computeHierarchy ){
224243
/* Compute the hierarchical display order */
@@ -300,13 +319,15 @@
300319
pThread = forumthread_create(froot, 1);
301320
fossil_print("Chronological:\n");
302321
fossil_print(
303322
/* 0 1 2 3 4 5 6 7 */
304323
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
305
- " sid rev fpid pIrt pEditPrev pEditTail hash\n");
324
+ " sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
306325
for(p=pThread->pFirst; p; p=p->pNext){
307
- fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev,
326
+ fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
327
+ p->sid, p->rev,
328
+ p->fClosed,
308329
p->fpid, p->pIrt ? p->pIrt->fpid : 0,
309330
p->pEditPrev ? p->pEditPrev->fpid : 0,
310331
p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
311332
}
312333
fossil_print("\nDisplay\n");
@@ -458,23 +479,25 @@
458479
char *zHist; /* History query string */
459480
Manifest *pManifest; /* Manifest comprising the current post */
460481
int bPrivate; /* True for posts awaiting moderation */
461482
int bSameUser; /* True if author is also the reader */
462483
int iIndent; /* Indent level */
484
+ int fClosed; /* True if (sub)thread is closed */
463485
const char *zMimetype;/* Formatting MIME type */
464486
465487
/* Get the manifest for the post. Abort if not found (e.g. shunned). */
466488
pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
467489
if( !pManifest ) return;
468
-
490
+ fClosed = forum_post_is_closed(p);
469491
/* When not in raw mode, create the border around the post. */
470492
if( !bRaw ){
471493
/* Open the <div> enclosing the post. Set the class string to mark the post
472494
** as selected and/or obsolete. */
473495
iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
474496
@ <div id='forum%d(p->fpid)' class='forumTime\
475497
@ %s(bSelect ? " forumSel" : "")\
498
+ @ %s(fClosed ? " forumClosed" : "")\
476499
@ %s(p->pEditTail ? " forumObs" : "")' \
477500
if( iIndent && iIndentScale ){
478501
@ style='margin-left:%d(iIndent*iIndentScale)ex;'>
479502
}else{
480503
@ >
@@ -490,11 +513,11 @@
490513
** * The post is unedited
491514
** * The post was last edited by the original author
492515
** * The post was last edited by a different person
493516
*/
494517
if( p->pEditHead ){
495
- zDate = db_text(0, "SELECT datetime(%.17g,toLocal())",
518
+ zDate = db_text(0, "SELECT datetime(%.17g,toLocal())",
496519
p->pEditHead->rDate);
497520
}else{
498521
zPosterName = forum_post_display_name(p, pManifest);
499522
zEditorName = zPosterName;
500523
}
@@ -502,11 +525,11 @@
502525
if( p->pEditPrev ){
503526
zPosterName = forum_post_display_name(p->pEditHead, 0);
504527
zEditorName = forum_post_display_name(p, pManifest);
505528
zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist";
506529
@ <h3 class='forumPostHdr'>(%d(p->sid)\
507
- @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) \
530
+ @ .%0*d(fossil_num_digits(p->nEdit))(p->rev))
508531
if( fossil_strcmp(zPosterName, zEditorName)==0 ){
509532
@ By %s(zPosterName) on %h(zDate) edited from \
510533
@ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
511534
@ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
512535
}else{
@@ -515,11 +538,11 @@
515538
@ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
516539
@ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
517540
}
518541
}else{
519542
zPosterName = forum_post_display_name(p, pManifest);
520
- @ <h3 class='forumPostHdr'>(%d(p->sid)) \
543
+ @ <h3 class='forumPostHdr'>(%d(p->sid))
521544
@ By %s(zPosterName) on %h(zDate)
522545
}
523546
fossil_free(zDate);
524547
525548
@@ -581,15 +604,22 @@
581604
** edited, create a form with various interaction buttons. */
582605
if( g.perm.WrForum && !p->pEditTail ){
583606
@ <div><form action="%R/forumedit" method="POST">
584607
@ <input type="hidden" name="fpid" value="%s(p->zUuid)">
585608
if( !bPrivate ){
586
- /* Reply and Edit are only available if the post has been approved. */
587
- @ <input type="submit" name="reply" value="Reply">
588
- if( g.perm.Admin || bSameUser ){
589
- @ <input type="submit" name="edit" value="Edit">
590
- @ <input type="submit" name="nullout" value="Delete">
609
+ /* Reply and Edit are only available if the post has been
610
+ ** approved. Closed threads can only be edited or replied to
611
+ ** by an admin but a user may delete their own posts even if
612
+ ** they are closed. */
613
+ if( g.perm.Admin || !fClosed ){
614
+ @ <input type="submit" name="reply" value="Reply">
615
+ if( g.perm.Admin || (bSameUser && !fClosed) ){
616
+ @ <input type="submit" name="edit" value="Edit">
617
+ }
618
+ if( g.perm.Admin || bSameUser ){
619
+ @ <input type="submit" name="nullout" value="Delete">
620
+ }
591621
}
592622
}else if( g.perm.ModForum ){
593623
/* Allow moderators to approve or reject pending posts. Also allow
594624
** forum supervisors to mark non-special users as trusted and therefore
595625
** able to post unmoderated. */
596626
--- src/forum.c
+++ src/forum.c
@@ -47,10 +47,11 @@
47 ForumPost *pNext; /* Next in chronological order */
48 ForumPost *pPrev; /* Previous in chronological order */
49 ForumPost *pDisplay; /* Next in display order */
50 int nEdit; /* Number of edits to this post */
51 int nIndent; /* Number of levels of indentation for this post */
 
52 };
53
54 /*
55 ** A single instance of the following tracks all entries for a thread.
56 */
@@ -77,10 +78,27 @@
77 db_bind_int(&q, "$rid", rid);
78 res = db_step(&q)==SQLITE_ROW;
79 db_reset(&q);
80 return res;
81 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
83 /*
84 ** Delete a complete ForumThread and all its entries.
85 */
86 static void forumthread_delete(ForumThread *pThread){
@@ -215,10 +233,11 @@
215 for(; p; p=p->pEditPrev ){
216 p->nEdit = pPost->nEdit;
217 p->pEditTail = pPost;
218 }
219 }
 
220 }
221 db_finalize(&q);
222
223 if( computeHierarchy ){
224 /* Compute the hierarchical display order */
@@ -300,13 +319,15 @@
300 pThread = forumthread_create(froot, 1);
301 fossil_print("Chronological:\n");
302 fossil_print(
303 /* 0 1 2 3 4 5 6 7 */
304 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
305 " sid rev fpid pIrt pEditPrev pEditTail hash\n");
306 for(p=pThread->pFirst; p; p=p->pNext){
307 fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev,
 
 
308 p->fpid, p->pIrt ? p->pIrt->fpid : 0,
309 p->pEditPrev ? p->pEditPrev->fpid : 0,
310 p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
311 }
312 fossil_print("\nDisplay\n");
@@ -458,23 +479,25 @@
458 char *zHist; /* History query string */
459 Manifest *pManifest; /* Manifest comprising the current post */
460 int bPrivate; /* True for posts awaiting moderation */
461 int bSameUser; /* True if author is also the reader */
462 int iIndent; /* Indent level */
 
463 const char *zMimetype;/* Formatting MIME type */
464
465 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
466 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
467 if( !pManifest ) return;
468
469 /* When not in raw mode, create the border around the post. */
470 if( !bRaw ){
471 /* Open the <div> enclosing the post. Set the class string to mark the post
472 ** as selected and/or obsolete. */
473 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
474 @ <div id='forum%d(p->fpid)' class='forumTime\
475 @ %s(bSelect ? " forumSel" : "")\
 
476 @ %s(p->pEditTail ? " forumObs" : "")' \
477 if( iIndent && iIndentScale ){
478 @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
479 }else{
480 @ >
@@ -490,11 +513,11 @@
490 ** * The post is unedited
491 ** * The post was last edited by the original author
492 ** * The post was last edited by a different person
493 */
494 if( p->pEditHead ){
495 zDate = db_text(0, "SELECT datetime(%.17g,toLocal())",
496 p->pEditHead->rDate);
497 }else{
498 zPosterName = forum_post_display_name(p, pManifest);
499 zEditorName = zPosterName;
500 }
@@ -502,11 +525,11 @@
502 if( p->pEditPrev ){
503 zPosterName = forum_post_display_name(p->pEditHead, 0);
504 zEditorName = forum_post_display_name(p, pManifest);
505 zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist";
506 @ <h3 class='forumPostHdr'>(%d(p->sid)\
507 @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) \
508 if( fossil_strcmp(zPosterName, zEditorName)==0 ){
509 @ By %s(zPosterName) on %h(zDate) edited from \
510 @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
511 @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
512 }else{
@@ -515,11 +538,11 @@
515 @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
516 @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
517 }
518 }else{
519 zPosterName = forum_post_display_name(p, pManifest);
520 @ <h3 class='forumPostHdr'>(%d(p->sid)) \
521 @ By %s(zPosterName) on %h(zDate)
522 }
523 fossil_free(zDate);
524
525
@@ -581,15 +604,22 @@
581 ** edited, create a form with various interaction buttons. */
582 if( g.perm.WrForum && !p->pEditTail ){
583 @ <div><form action="%R/forumedit" method="POST">
584 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
585 if( !bPrivate ){
586 /* Reply and Edit are only available if the post has been approved. */
587 @ <input type="submit" name="reply" value="Reply">
588 if( g.perm.Admin || bSameUser ){
589 @ <input type="submit" name="edit" value="Edit">
590 @ <input type="submit" name="nullout" value="Delete">
 
 
 
 
 
 
 
591 }
592 }else if( g.perm.ModForum ){
593 /* Allow moderators to approve or reject pending posts. Also allow
594 ** forum supervisors to mark non-special users as trusted and therefore
595 ** able to post unmoderated. */
596
--- src/forum.c
+++ src/forum.c
@@ -47,10 +47,11 @@
47 ForumPost *pNext; /* Next in chronological order */
48 ForumPost *pPrev; /* Previous in chronological order */
49 ForumPost *pDisplay; /* Next in display order */
50 int nEdit; /* Number of edits to this post */
51 int nIndent; /* Number of levels of indentation for this post */
52 int fClosed; /* tagxref.tagtype if this (sub)thread has a closed tag. */
53 };
54
55 /*
56 ** A single instance of the following tracks all entries for a thread.
57 */
@@ -77,10 +78,27 @@
78 db_bind_int(&q, "$rid", rid);
79 res = db_step(&q)==SQLITE_ROW;
80 db_reset(&q);
81 return res;
82 }
83
84 /*
85 ** Returns true if p, or any parent of p, has an active "closed" tag.
86 ** Returns 0 if !p. For an edited chain of post, the tag is checked on
87 ** the final edit in the chain, as that permits that a post can be
88 ** locked and later unlocked.
89 */
90 int forum_post_is_closed(ForumPost *p){
91 if( !p ) return 0;
92 if( p->pEditTail ) p = p->pEditTail;
93 if( p->fClosed ) return p->fClosed;
94 else if( p->pIrt ){
95 return forum_post_is_closed(p->pIrt->pEditTail
96 ? p->pIrt->pEditTail : p->pIrt);
97 }
98 return 0;
99 }
100
101 /*
102 ** Delete a complete ForumThread and all its entries.
103 */
104 static void forumthread_delete(ForumThread *pThread){
@@ -215,10 +233,11 @@
233 for(; p; p=p->pEditPrev ){
234 p->nEdit = pPost->nEdit;
235 p->pEditTail = pPost;
236 }
237 }
238 pPost->fClosed = rid_has_active_tag_name(pPost->fpid, "closed");
239 }
240 db_finalize(&q);
241
242 if( computeHierarchy ){
243 /* Compute the hierarchical display order */
@@ -300,13 +319,15 @@
319 pThread = forumthread_create(froot, 1);
320 fossil_print("Chronological:\n");
321 fossil_print(
322 /* 0 1 2 3 4 5 6 7 */
323 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
324 " sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
325 for(p=pThread->pFirst; p; p=p->pNext){
326 fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
327 p->sid, p->rev,
328 p->fClosed,
329 p->fpid, p->pIrt ? p->pIrt->fpid : 0,
330 p->pEditPrev ? p->pEditPrev->fpid : 0,
331 p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
332 }
333 fossil_print("\nDisplay\n");
@@ -458,23 +479,25 @@
479 char *zHist; /* History query string */
480 Manifest *pManifest; /* Manifest comprising the current post */
481 int bPrivate; /* True for posts awaiting moderation */
482 int bSameUser; /* True if author is also the reader */
483 int iIndent; /* Indent level */
484 int fClosed; /* True if (sub)thread is closed */
485 const char *zMimetype;/* Formatting MIME type */
486
487 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
488 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
489 if( !pManifest ) return;
490 fClosed = forum_post_is_closed(p);
491 /* When not in raw mode, create the border around the post. */
492 if( !bRaw ){
493 /* Open the <div> enclosing the post. Set the class string to mark the post
494 ** as selected and/or obsolete. */
495 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
496 @ <div id='forum%d(p->fpid)' class='forumTime\
497 @ %s(bSelect ? " forumSel" : "")\
498 @ %s(fClosed ? " forumClosed" : "")\
499 @ %s(p->pEditTail ? " forumObs" : "")' \
500 if( iIndent && iIndentScale ){
501 @ style='margin-left:%d(iIndent*iIndentScale)ex;'>
502 }else{
503 @ >
@@ -490,11 +513,11 @@
513 ** * The post is unedited
514 ** * The post was last edited by the original author
515 ** * The post was last edited by a different person
516 */
517 if( p->pEditHead ){
518 zDate = db_text(0, "SELECT datetime(%.17g,toLocal())",
519 p->pEditHead->rDate);
520 }else{
521 zPosterName = forum_post_display_name(p, pManifest);
522 zEditorName = zPosterName;
523 }
@@ -502,11 +525,11 @@
525 if( p->pEditPrev ){
526 zPosterName = forum_post_display_name(p->pEditHead, 0);
527 zEditorName = forum_post_display_name(p, pManifest);
528 zHist = bHist ? "" : zQuery[0]==0 ? "?hist" : "&hist";
529 @ <h3 class='forumPostHdr'>(%d(p->sid)\
530 @ .%0*d(fossil_num_digits(p->nEdit))(p->rev))
531 if( fossil_strcmp(zPosterName, zEditorName)==0 ){
532 @ By %s(zPosterName) on %h(zDate) edited from \
533 @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
534 @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
535 }else{
@@ -515,11 +538,11 @@
538 @ %z(href("%R/forumpost/%S%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
539 @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
540 }
541 }else{
542 zPosterName = forum_post_display_name(p, pManifest);
543 @ <h3 class='forumPostHdr'>(%d(p->sid))
544 @ By %s(zPosterName) on %h(zDate)
545 }
546 fossil_free(zDate);
547
548
@@ -581,15 +604,22 @@
604 ** edited, create a form with various interaction buttons. */
605 if( g.perm.WrForum && !p->pEditTail ){
606 @ <div><form action="%R/forumedit" method="POST">
607 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
608 if( !bPrivate ){
609 /* Reply and Edit are only available if the post has been
610 ** approved. Closed threads can only be edited or replied to
611 ** by an admin but a user may delete their own posts even if
612 ** they are closed. */
613 if( g.perm.Admin || !fClosed ){
614 @ <input type="submit" name="reply" value="Reply">
615 if( g.perm.Admin || (bSameUser && !fClosed) ){
616 @ <input type="submit" name="edit" value="Edit">
617 }
618 if( g.perm.Admin || bSameUser ){
619 @ <input type="submit" name="nullout" value="Delete">
620 }
621 }
622 }else if( g.perm.ModForum ){
623 /* Allow moderators to approve or reject pending posts. Also allow
624 ** forum supervisors to mark non-special users as trusted and therefore
625 ** able to post unmoderated. */
626
+27
--- src/tag.c
+++ src/tag.c
@@ -903,5 +903,32 @@
903903
" AND tag.tagid=%d"
904904
" AND tagxref.tagid=tag.tagid",
905905
rid, tagId
906906
);
907907
}
908
+
909
+
910
+/*
911
+** Returns tagxref.tagtype if the given blob.rid has a tagxref.rid
912
+** entry an active tag matching the given rid and tag name string,
913
+** else returns 0. Note that this function does not distinguish
914
+** between a non-existent tag and a cancelled tag.
915
+*/
916
+int rid_has_active_tag_name(int rid, const char *zTagName){
917
+ static Stmt q = empty_Stmt_m;
918
+ int rc = 0;
919
+
920
+ assert( 0 != zTagName );
921
+ db_static_prepare(&q,
922
+ "SELECT tagxref.tagtype FROM tagxref, tag"
923
+ " WHERE tagxref.rid=:rid AND tagtype>0 "
924
+ " AND tag.tagname=:tagname"
925
+ " AND tagxref.tagid=tag.tagid"
926
+ );
927
+ db_bind_int(&q, ":rid", rid);
928
+ db_bind_text(&q, ":tagname", zTagName);
929
+ if( SQLITE_ROW==db_step(&q) ){
930
+ rc = db_column_int(&q, 0);
931
+ }
932
+ db_reset(&q);
933
+ return rc;
934
+}
908935
--- src/tag.c
+++ src/tag.c
@@ -903,5 +903,32 @@
903 " AND tag.tagid=%d"
904 " AND tagxref.tagid=tag.tagid",
905 rid, tagId
906 );
907 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
--- src/tag.c
+++ src/tag.c
@@ -903,5 +903,32 @@
903 " AND tag.tagid=%d"
904 " AND tagxref.tagid=tag.tagid",
905 rid, tagId
906 );
907 }
908
909
910 /*
911 ** Returns tagxref.tagtype if the given blob.rid has a tagxref.rid
912 ** entry an active tag matching the given rid and tag name string,
913 ** else returns 0. Note that this function does not distinguish
914 ** between a non-existent tag and a cancelled tag.
915 */
916 int rid_has_active_tag_name(int rid, const char *zTagName){
917 static Stmt q = empty_Stmt_m;
918 int rc = 0;
919
920 assert( 0 != zTagName );
921 db_static_prepare(&q,
922 "SELECT tagxref.tagtype FROM tagxref, tag"
923 " WHERE tagxref.rid=:rid AND tagtype>0 "
924 " AND tag.tagname=:tagname"
925 " AND tagxref.tagid=tag.tagid"
926 );
927 db_bind_int(&q, ":rid", rid);
928 db_bind_text(&q, ":tagname", zTagName);
929 if( SQLITE_ROW==db_step(&q) ){
930 rc = db_column_int(&q, 0);
931 }
932 db_reset(&q);
933 return rc;
934 }
935

Keyboard Shortcuts

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