Fossil SCM

Teach tickets and tech-notes to use the new attachment approach. Document a shortcoming of the new approach compared to the old, namely that the new one requires that X in target=X be a full ID rather than an event/ticket prefix.

stephan 2026-06-03 19:25 UTC attach-v2
Commit 2f71c2bb50bec049493ab6405afda4a3f66fc584fee5a2d76581913b9ca97bc0
--- src/attach.c
+++ src/attach.c
@@ -29,10 +29,18 @@
2929
**
3030
** In the case of CFTYPE_FORUM, it is up to the caller to ensure that,
3131
** if needed, they resolve zTarget using forumpost_head_rid2() so that
3232
** they get the RID of the earliest version of the post, as that is
3333
** the only one which attachments should target.
34
+**
35
+** FIXME (2026-06-03)? Figuring out if an event or a ticket are a
36
+** unique prefix is more work than this function performs. This makes
37
+** it unsuitable as a drop-in replacement for legacy
38
+** /attachadd?x=SHORT_ID links using newer /attachadd?target=ID URLs
39
+** because the the former, via /attachadd?technote=ID or ?ticket=ID,
40
+** accept unique prefixes whereas the latter, via this function, does
41
+** not.
3442
*/
3543
int attachment_target_type(const char *zTarget){
3644
static Stmt q = empty_Stmt_m;
3745
int rc = 0;
3846
if( !zTarget || !zTarget[0] || strlen(zTarget)>64/*vs. abuse*/ ){
3947
--- src/attach.c
+++ src/attach.c
@@ -29,10 +29,18 @@
29 **
30 ** In the case of CFTYPE_FORUM, it is up to the caller to ensure that,
31 ** if needed, they resolve zTarget using forumpost_head_rid2() so that
32 ** they get the RID of the earliest version of the post, as that is
33 ** the only one which attachments should target.
 
 
 
 
 
 
 
 
34 */
35 int attachment_target_type(const char *zTarget){
36 static Stmt q = empty_Stmt_m;
37 int rc = 0;
38 if( !zTarget || !zTarget[0] || strlen(zTarget)>64/*vs. abuse*/ ){
39
--- src/attach.c
+++ src/attach.c
@@ -29,10 +29,18 @@
29 **
30 ** In the case of CFTYPE_FORUM, it is up to the caller to ensure that,
31 ** if needed, they resolve zTarget using forumpost_head_rid2() so that
32 ** they get the RID of the earliest version of the post, as that is
33 ** the only one which attachments should target.
34 **
35 ** FIXME (2026-06-03)? Figuring out if an event or a ticket are a
36 ** unique prefix is more work than this function performs. This makes
37 ** it unsuitable as a drop-in replacement for legacy
38 ** /attachadd?x=SHORT_ID links using newer /attachadd?target=ID URLs
39 ** because the the former, via /attachadd?technote=ID or ?ticket=ID,
40 ** accept unique prefixes whereas the latter, via this function, does
41 ** not.
42 */
43 int attachment_target_type(const char *zTarget){
44 static Stmt q = empty_Stmt_m;
45 int rc = 0;
46 if( !zTarget || !zTarget[0] || strlen(zTarget)>64/*vs. abuse*/ ){
47
+6 -6
--- src/event.c
+++ src/event.c
@@ -117,11 +117,15 @@
117117
style_header("No Such Tech-Note");
118118
@ Cannot locate a technical note called <b>%h(zId)</b>.
119119
style_finish_page();
120120
return;
121121
}
122
- zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
122
+ zUuid = rid_to_uuid(rid);
123
+ zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
124
+ " FROM tag"
125
+ " WHERE tagname GLOB 'event-%q*'",
126
+ zId);
123127
zVerbose = P("v");
124128
if( !zVerbose ){
125129
zVerbose = P("verbose");
126130
}
127131
if( !zVerbose ){
@@ -157,11 +161,11 @@
157161
style_header("%s", blob_str(&title));
158162
if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
159163
style_submenu_element("Edit", "%R/technoteedit?name=%!S", zId);
160164
if( g.perm.Attach ){
161165
style_submenu_element("Attach",
162
- "%R/attachadd?technote=%!S&from=%R/technote/%!S", zId, zId);
166
+ "%R/attachadd?target=%s&from=%R/technote/%!S", zFullId, zId);
163167
}
164168
}
165169
zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
166170
style_submenu_element("Context", "%R/timeline?c=%.20s", zId);
167171
if( g.perm.Hyperlink ){
@@ -225,14 +229,10 @@
225229
}else{
226230
@ <pre>
227231
@ %h(blob_str(&fullbody))
228232
@ </pre>
229233
}
230
- zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
231
- " FROM tag"
232
- " WHERE tagname GLOB 'event-%q*'",
233
- zId);
234234
attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
235235
document_emit_js();
236236
style_finish_page();
237237
manifest_destroy(pTNote);
238238
}
239239
--- src/event.c
+++ src/event.c
@@ -117,11 +117,15 @@
117 style_header("No Such Tech-Note");
118 @ Cannot locate a technical note called <b>%h(zId)</b>.
119 style_finish_page();
120 return;
121 }
122 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
 
 
 
 
123 zVerbose = P("v");
124 if( !zVerbose ){
125 zVerbose = P("verbose");
126 }
127 if( !zVerbose ){
@@ -157,11 +161,11 @@
157 style_header("%s", blob_str(&title));
158 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
159 style_submenu_element("Edit", "%R/technoteedit?name=%!S", zId);
160 if( g.perm.Attach ){
161 style_submenu_element("Attach",
162 "%R/attachadd?technote=%!S&from=%R/technote/%!S", zId, zId);
163 }
164 }
165 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
166 style_submenu_element("Context", "%R/timeline?c=%.20s", zId);
167 if( g.perm.Hyperlink ){
@@ -225,14 +229,10 @@
225 }else{
226 @ <pre>
227 @ %h(blob_str(&fullbody))
228 @ </pre>
229 }
230 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
231 " FROM tag"
232 " WHERE tagname GLOB 'event-%q*'",
233 zId);
234 attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
235 document_emit_js();
236 style_finish_page();
237 manifest_destroy(pTNote);
238 }
239
--- src/event.c
+++ src/event.c
@@ -117,11 +117,15 @@
117 style_header("No Such Tech-Note");
118 @ Cannot locate a technical note called <b>%h(zId)</b>.
119 style_finish_page();
120 return;
121 }
122 zUuid = rid_to_uuid(rid);
123 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
124 " FROM tag"
125 " WHERE tagname GLOB 'event-%q*'",
126 zId);
127 zVerbose = P("v");
128 if( !zVerbose ){
129 zVerbose = P("verbose");
130 }
131 if( !zVerbose ){
@@ -157,11 +161,11 @@
161 style_header("%s", blob_str(&title));
162 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
163 style_submenu_element("Edit", "%R/technoteedit?name=%!S", zId);
164 if( g.perm.Attach ){
165 style_submenu_element("Attach",
166 "%R/attachadd?target=%s&from=%R/technote/%!S", zFullId, zId);
167 }
168 }
169 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
170 style_submenu_element("Context", "%R/timeline?c=%.20s", zId);
171 if( g.perm.Hyperlink ){
@@ -225,14 +229,10 @@
229 }else{
230 @ <pre>
231 @ %h(blob_str(&fullbody))
232 @ </pre>
233 }
 
 
 
 
234 attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
235 document_emit_js();
236 style_finish_page();
237 manifest_destroy(pTNote);
238 }
239
+5 -5
--- src/tkt.c
+++ src/tkt.c
@@ -749,13 +749,16 @@
749749
}
750750
}
751751
if( g.anon.NewTkt ){
752752
style_submenu_element("New Ticket", "%R/tktnew");
753753
}
754
+ zFullName = db_text(0,
755
+ "SELECT tkt_uuid FROM ticket"
756
+ " WHERE tkt_uuid GLOB '%q*'", zUuid);
754757
if( g.anon.ApndTkt && g.anon.Attach ){
755
- style_submenu_element("Attach", "%R/attachadd?tkt=%T&from=%R/tktview/%t",
756
- zUuid, zUuid);
758
+ style_submenu_element("Attach", "%R/attachadd?target=%T&from=%R/tktview/%t",
759
+ zFullName, zUuid);
757760
}
758761
if( P("plaintext") ){
759762
style_submenu_element("Formatted", "%R/tktview/%s", zUuid);
760763
}else{
761764
style_submenu_element("Plaintext", "%R/tktview/%s?plaintext", zUuid);
@@ -773,13 +776,10 @@
773776
}
774777
}
775778
if( !showTimeline && g.perm.Hyperlink ){
776779
style_submenu_element("Timeline", "%R/info/%T", zUuid);
777780
}
778
- zFullName = db_text(0,
779
- "SELECT tkt_uuid FROM ticket"
780
- " WHERE tkt_uuid GLOB '%q*'", zUuid);
781781
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
782782
ticket_init();
783783
initializeVariablesFromCGI();
784784
getAllTicketFields();
785785
initializeVariablesFromDb();
786786
--- src/tkt.c
+++ src/tkt.c
@@ -749,13 +749,16 @@
749 }
750 }
751 if( g.anon.NewTkt ){
752 style_submenu_element("New Ticket", "%R/tktnew");
753 }
 
 
 
754 if( g.anon.ApndTkt && g.anon.Attach ){
755 style_submenu_element("Attach", "%R/attachadd?tkt=%T&from=%R/tktview/%t",
756 zUuid, zUuid);
757 }
758 if( P("plaintext") ){
759 style_submenu_element("Formatted", "%R/tktview/%s", zUuid);
760 }else{
761 style_submenu_element("Plaintext", "%R/tktview/%s?plaintext", zUuid);
@@ -773,13 +776,10 @@
773 }
774 }
775 if( !showTimeline && g.perm.Hyperlink ){
776 style_submenu_element("Timeline", "%R/info/%T", zUuid);
777 }
778 zFullName = db_text(0,
779 "SELECT tkt_uuid FROM ticket"
780 " WHERE tkt_uuid GLOB '%q*'", zUuid);
781 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
782 ticket_init();
783 initializeVariablesFromCGI();
784 getAllTicketFields();
785 initializeVariablesFromDb();
786
--- src/tkt.c
+++ src/tkt.c
@@ -749,13 +749,16 @@
749 }
750 }
751 if( g.anon.NewTkt ){
752 style_submenu_element("New Ticket", "%R/tktnew");
753 }
754 zFullName = db_text(0,
755 "SELECT tkt_uuid FROM ticket"
756 " WHERE tkt_uuid GLOB '%q*'", zUuid);
757 if( g.anon.ApndTkt && g.anon.Attach ){
758 style_submenu_element("Attach", "%R/attachadd?target=%T&from=%R/tktview/%t",
759 zFullName, zUuid);
760 }
761 if( P("plaintext") ){
762 style_submenu_element("Formatted", "%R/tktview/%s", zUuid);
763 }else{
764 style_submenu_element("Plaintext", "%R/tktview/%s?plaintext", zUuid);
@@ -773,13 +776,10 @@
776 }
777 }
778 if( !showTimeline && g.perm.Hyperlink ){
779 style_submenu_element("Timeline", "%R/info/%T", zUuid);
780 }
 
 
 
781 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
782 ticket_init();
783 initializeVariablesFromCGI();
784 getAllTicketFields();
785 initializeVariablesFromDb();
786

Keyboard Shortcuts

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