Fossil SCM

Enhancements to ticket processing. There are now two tables: TICKET and TICKETCHNG. There is one row in TICKETCHNG for each ticket artifact. Fields from ticket artifacts go into either or both of TICKET and TICKETCHNG, whichever contain matching column names. Default ticket edit and viewing scripts are updated to use TICKETCHNG. The TH1 scripting language is enhanced to support this, including the new "query" command for doing SQL queries against the repository database. All changes should be backwards compatible.

drh 2012-11-27 16:26 trunk merge
Commit 4f8c8975bc4d1303a604da339f869c32eb0da960
+2 -1
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530530
@ <pre>
531531
@ %h(z)
532532
@ </pre>
533533
}
534534
}else if( strncmp(zMime, "image/", 6)==0 ){
535
- @ <img src="%R/raw?name=%s(zSrc)&m=%s(zMime)"></img>
535
+ @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
536
+ style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
536537
}else{
537538
int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
538539
@ <i>(file is %d(sz) bytes of binary data)</i>
539540
}
540541
@ </blockquote>
541542
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530 @ <pre>
531 @ %h(z)
532 @ </pre>
533 }
534 }else if( strncmp(zMime, "image/", 6)==0 ){
535 @ <img src="%R/raw?name=%s(zSrc)&m=%s(zMime)"></img>
 
536 }else{
537 int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
538 @ <i>(file is %d(sz) bytes of binary data)</i>
539 }
540 @ </blockquote>
541
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530 @ <pre>
531 @ %h(z)
532 @ </pre>
533 }
534 }else if( strncmp(zMime, "image/", 6)==0 ){
535 @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
536 style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
537 }else{
538 int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
539 @ <i>(file is %d(sz) bytes of binary data)</i>
540 }
541 @ </blockquote>
542
+2 -1
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530530
@ <pre>
531531
@ %h(z)
532532
@ </pre>
533533
}
534534
}else if( strncmp(zMime, "image/", 6)==0 ){
535
- @ <img src="%R/raw?name=%s(zSrc)&m=%s(zMime)"></img>
535
+ @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
536
+ style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
536537
}else{
537538
int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
538539
@ <i>(file is %d(sz) bytes of binary data)</i>
539540
}
540541
@ </blockquote>
541542
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530 @ <pre>
531 @ %h(z)
532 @ </pre>
533 }
534 }else if( strncmp(zMime, "image/", 6)==0 ){
535 @ <img src="%R/raw?name=%s(zSrc)&m=%s(zMime)"></img>
 
536 }else{
537 int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
538 @ <i>(file is %d(sz) bytes of binary data)</i>
539 }
540 @ </blockquote>
541
--- src/attach.c
+++ src/attach.c
@@ -530,11 +530,12 @@
530 @ <pre>
531 @ %h(z)
532 @ </pre>
533 }
534 }else if( strncmp(zMime, "image/", 6)==0 ){
535 @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
536 style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
537 }else{
538 int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
539 @ <i>(file is %d(sz) bytes of binary data)</i>
540 }
541 @ </blockquote>
542
+32 -1
--- src/cgi.c
+++ src/cgi.c
@@ -783,10 +783,38 @@
783783
}
784784
fossil_exit( g.isHTTP ? 0 : 1);
785785
}
786786
#endif /* FOSSIL_ENABLE_JSON */
787787
788
+/*
789
+** Log HTTP traffic to a file. Begin the log on first use. Close the log
790
+** when the argument is NULL.
791
+*/
792
+void cgi_trace(const char *z){
793
+ static FILE *pLog = 0;
794
+ if( g.fHttpTrace==0 ) return;
795
+ if( z==0 ){
796
+ if( pLog ) fclose(pLog);
797
+ pLog = 0;
798
+ return;
799
+ }
800
+ if( pLog==0 ){
801
+ char zFile[50];
802
+ unsigned r;
803
+ sqlite3_randomness(sizeof(r), &r);
804
+ sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%08x.txt", r);
805
+ pLog = fossil_fopen(zFile, "wb");
806
+ if( pLog ){
807
+ fprintf(stderr, "# open log on %s\n", zFile);
808
+ }else{
809
+ fprintf(stderr, "# failed to open %s\n", zFile);
810
+ return;
811
+ }
812
+ }
813
+ fputs(z, pLog);
814
+}
815
+
788816
789817
/*
790818
** Initialize the query parameter database. Information is pulled from
791819
** the QUERY_STRING environment variable (if it exists), from standard
792820
** input if there is POST data, and from HTTP_COOKIE.
@@ -825,10 +853,11 @@
825853
if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
826854
|| strncmp(zType,"multipart/form-data",19)==0 ){
827855
z = fossil_malloc( len+1 );
828856
len = fread(z, 1, len, g.httpIn);
829857
z[len] = 0;
858
+ cgi_trace(z);
830859
if( zType[0]=='a' ){
831860
add_param_list(z, '&');
832861
}else{
833862
process_multipart_form_data(z, len);
834863
}
@@ -1145,10 +1174,11 @@
11451174
char zLine[2000]; /* A single line of input. */
11461175
g.fullHttpReply = 1;
11471176
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
11481177
malformed_request();
11491178
}
1179
+ cgi_trace(zLine);
11501180
zToken = extract_token(zLine, &z);
11511181
if( zToken==0 ){
11521182
malformed_request();
11531183
}
11541184
if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
@@ -1181,10 +1211,11 @@
11811211
*/
11821212
while( fgets(zLine,sizeof(zLine),g.httpIn) ){
11831213
char *zFieldName;
11841214
char *zVal;
11851215
1216
+ cgi_trace(zLine);
11861217
zFieldName = extract_token(zLine,&zVal);
11871218
if( zFieldName==0 || *zFieldName==0 ) break;
11881219
while( fossil_isspace(*zVal) ){ zVal++; }
11891220
i = strlen(zVal);
11901221
while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
@@ -1212,12 +1243,12 @@
12121243
#endif
12131244
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
12141245
cgi_setenv("HTTP_USER_AGENT", zVal);
12151246
}
12161247
}
1217
-
12181248
cgi_init();
1249
+ cgi_trace(0);
12191250
}
12201251
12211252
#if INTERFACE
12221253
/*
12231254
** Bitmap values for the flags parameter to cgi_http_server().
12241255
--- src/cgi.c
+++ src/cgi.c
@@ -783,10 +783,38 @@
783 }
784 fossil_exit( g.isHTTP ? 0 : 1);
785 }
786 #endif /* FOSSIL_ENABLE_JSON */
787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
789 /*
790 ** Initialize the query parameter database. Information is pulled from
791 ** the QUERY_STRING environment variable (if it exists), from standard
792 ** input if there is POST data, and from HTTP_COOKIE.
@@ -825,10 +853,11 @@
825 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
826 || strncmp(zType,"multipart/form-data",19)==0 ){
827 z = fossil_malloc( len+1 );
828 len = fread(z, 1, len, g.httpIn);
829 z[len] = 0;
 
830 if( zType[0]=='a' ){
831 add_param_list(z, '&');
832 }else{
833 process_multipart_form_data(z, len);
834 }
@@ -1145,10 +1174,11 @@
1145 char zLine[2000]; /* A single line of input. */
1146 g.fullHttpReply = 1;
1147 if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1148 malformed_request();
1149 }
 
1150 zToken = extract_token(zLine, &z);
1151 if( zToken==0 ){
1152 malformed_request();
1153 }
1154 if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
@@ -1181,10 +1211,11 @@
1181 */
1182 while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1183 char *zFieldName;
1184 char *zVal;
1185
 
1186 zFieldName = extract_token(zLine,&zVal);
1187 if( zFieldName==0 || *zFieldName==0 ) break;
1188 while( fossil_isspace(*zVal) ){ zVal++; }
1189 i = strlen(zVal);
1190 while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
@@ -1212,12 +1243,12 @@
1212 #endif
1213 }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1214 cgi_setenv("HTTP_USER_AGENT", zVal);
1215 }
1216 }
1217
1218 cgi_init();
 
1219 }
1220
1221 #if INTERFACE
1222 /*
1223 ** Bitmap values for the flags parameter to cgi_http_server().
1224
--- src/cgi.c
+++ src/cgi.c
@@ -783,10 +783,38 @@
783 }
784 fossil_exit( g.isHTTP ? 0 : 1);
785 }
786 #endif /* FOSSIL_ENABLE_JSON */
787
788 /*
789 ** Log HTTP traffic to a file. Begin the log on first use. Close the log
790 ** when the argument is NULL.
791 */
792 void cgi_trace(const char *z){
793 static FILE *pLog = 0;
794 if( g.fHttpTrace==0 ) return;
795 if( z==0 ){
796 if( pLog ) fclose(pLog);
797 pLog = 0;
798 return;
799 }
800 if( pLog==0 ){
801 char zFile[50];
802 unsigned r;
803 sqlite3_randomness(sizeof(r), &r);
804 sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%08x.txt", r);
805 pLog = fossil_fopen(zFile, "wb");
806 if( pLog ){
807 fprintf(stderr, "# open log on %s\n", zFile);
808 }else{
809 fprintf(stderr, "# failed to open %s\n", zFile);
810 return;
811 }
812 }
813 fputs(z, pLog);
814 }
815
816
817 /*
818 ** Initialize the query parameter database. Information is pulled from
819 ** the QUERY_STRING environment variable (if it exists), from standard
820 ** input if there is POST data, and from HTTP_COOKIE.
@@ -825,10 +853,11 @@
853 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
854 || strncmp(zType,"multipart/form-data",19)==0 ){
855 z = fossil_malloc( len+1 );
856 len = fread(z, 1, len, g.httpIn);
857 z[len] = 0;
858 cgi_trace(z);
859 if( zType[0]=='a' ){
860 add_param_list(z, '&');
861 }else{
862 process_multipart_form_data(z, len);
863 }
@@ -1145,10 +1174,11 @@
1174 char zLine[2000]; /* A single line of input. */
1175 g.fullHttpReply = 1;
1176 if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1177 malformed_request();
1178 }
1179 cgi_trace(zLine);
1180 zToken = extract_token(zLine, &z);
1181 if( zToken==0 ){
1182 malformed_request();
1183 }
1184 if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
@@ -1181,10 +1211,11 @@
1211 */
1212 while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1213 char *zFieldName;
1214 char *zVal;
1215
1216 cgi_trace(zLine);
1217 zFieldName = extract_token(zLine,&zVal);
1218 if( zFieldName==0 || *zFieldName==0 ) break;
1219 while( fossil_isspace(*zVal) ){ zVal++; }
1220 i = strlen(zVal);
1221 while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
@@ -1212,12 +1243,12 @@
1243 #endif
1244 }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1245 cgi_setenv("HTTP_USER_AGENT", zVal);
1246 }
1247 }
 
1248 cgi_init();
1249 cgi_trace(0);
1250 }
1251
1252 #if INTERFACE
1253 /*
1254 ** Bitmap values for the flags parameter to cgi_http_server().
1255
+3
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
13121312
" VALUES('project-code', lower(hex(randomblob(20))),now());"
13131313
);
13141314
}
13151315
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
13161316
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1317
+ if( !db_is_global("timeline-plaintext") ){
1318
+ db_set_int("timeline-plaintext", 1, 0);
1319
+ }
13171320
db_create_default_users(0, zDefaultUser);
13181321
if( zDefaultUser ) g.zLogin = zDefaultUser;
13191322
user_select();
13201323
13211324
if( zTemplate ){
13221325
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
1312 " VALUES('project-code', lower(hex(randomblob(20))),now());"
1313 );
1314 }
1315 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1316 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
 
 
 
1317 db_create_default_users(0, zDefaultUser);
1318 if( zDefaultUser ) g.zLogin = zDefaultUser;
1319 user_select();
1320
1321 if( zTemplate ){
1322
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
1312 " VALUES('project-code', lower(hex(randomblob(20))),now());"
1313 );
1314 }
1315 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1316 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1317 if( !db_is_global("timeline-plaintext") ){
1318 db_set_int("timeline-plaintext", 1, 0);
1319 }
1320 db_create_default_users(0, zDefaultUser);
1321 if( zDefaultUser ) g.zLogin = zDefaultUser;
1322 user_select();
1323
1324 if( zTemplate ){
1325
+3
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
13121312
" VALUES('project-code', lower(hex(randomblob(20))),now());"
13131313
);
13141314
}
13151315
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
13161316
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1317
+ if( !db_is_global("timeline-plaintext") ){
1318
+ db_set_int("timeline-plaintext", 1, 0);
1319
+ }
13171320
db_create_default_users(0, zDefaultUser);
13181321
if( zDefaultUser ) g.zLogin = zDefaultUser;
13191322
user_select();
13201323
13211324
if( zTemplate ){
13221325
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
1312 " VALUES('project-code', lower(hex(randomblob(20))),now());"
1313 );
1314 }
1315 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1316 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
 
 
 
1317 db_create_default_users(0, zDefaultUser);
1318 if( zDefaultUser ) g.zLogin = zDefaultUser;
1319 user_select();
1320
1321 if( zTemplate ){
1322
--- src/db.c
+++ src/db.c
@@ -1312,10 +1312,13 @@
1312 " VALUES('project-code', lower(hex(randomblob(20))),now());"
1313 );
1314 }
1315 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1316 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1317 if( !db_is_global("timeline-plaintext") ){
1318 db_set_int("timeline-plaintext", 1, 0);
1319 }
1320 db_create_default_users(0, zDefaultUser);
1321 if( zDefaultUser ) g.zLogin = zDefaultUser;
1322 user_select();
1323
1324 if( zTemplate ){
1325
+11 -3
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
11111111
cnt++;
11121112
if( pDownloadName && blob_size(pDownloadName)==0 ){
11131113
blob_append(pDownloadName, zName, -1);
11141114
}
11151115
}
1116
- @ </ul></ul>
1116
+ @ </ul>
11171117
free(prevName);
11181118
db_finalize(&q);
11191119
db_prepare(&q,
11201120
"SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
11211121
" coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
16541654
@ <pre>
16551655
@ %h(z)
16561656
@ </pre>
16571657
}
16581658
}else if( strncmp(zMime, "image/", 6)==0 ){
1659
- @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img>
1659
+ @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
1660
+ style_submenu_element("Image", "Image",
1661
+ "%R/raw/%S?m=%s", zUuid, zMime);
16601662
}else{
16611663
@ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
16621664
}
16631665
@ </blockquote>
16641666
}
@@ -1711,10 +1713,16 @@
17111713
style_header("Ticket Change Details");
17121714
style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
17131715
style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
17141716
style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
17151717
style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
1718
+ if( P("plaintext") ){
1719
+ style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid);
1720
+ }else{
1721
+ style_submenu_element("Plaintext", "Plaintext",
1722
+ "%R/info/%S?plaintext", zUuid);
1723
+ }
17161724
17171725
@ <div class="section">Overview</div>
17181726
@ <p><table class="label-value">
17191727
@ <tr><th>Artifact&nbsp;ID:</th>
17201728
@ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
17471755
@ </blockquote>
17481756
}
17491757
17501758
@ <div class="section">Changes</div>
17511759
@ <p>
1752
- ticket_output_change_artifact(pTktChng);
1760
+ ticket_output_change_artifact(pTktChng, 0);
17531761
manifest_destroy(pTktChng);
17541762
style_footer();
17551763
}
17561764
17571765
17581766
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
1111 cnt++;
1112 if( pDownloadName && blob_size(pDownloadName)==0 ){
1113 blob_append(pDownloadName, zName, -1);
1114 }
1115 }
1116 @ </ul></ul>
1117 free(prevName);
1118 db_finalize(&q);
1119 db_prepare(&q,
1120 "SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
1121 " coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
1654 @ <pre>
1655 @ %h(z)
1656 @ </pre>
1657 }
1658 }else if( strncmp(zMime, "image/", 6)==0 ){
1659 @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img>
 
 
1660 }else{
1661 @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
1662 }
1663 @ </blockquote>
1664 }
@@ -1711,10 +1713,16 @@
1711 style_header("Ticket Change Details");
1712 style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
1713 style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
1714 style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
1715 style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
 
 
 
 
 
 
1716
1717 @ <div class="section">Overview</div>
1718 @ <p><table class="label-value">
1719 @ <tr><th>Artifact&nbsp;ID:</th>
1720 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
1747 @ </blockquote>
1748 }
1749
1750 @ <div class="section">Changes</div>
1751 @ <p>
1752 ticket_output_change_artifact(pTktChng);
1753 manifest_destroy(pTktChng);
1754 style_footer();
1755 }
1756
1757
1758
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
1111 cnt++;
1112 if( pDownloadName && blob_size(pDownloadName)==0 ){
1113 blob_append(pDownloadName, zName, -1);
1114 }
1115 }
1116 @ </ul>
1117 free(prevName);
1118 db_finalize(&q);
1119 db_prepare(&q,
1120 "SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
1121 " coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
1654 @ <pre>
1655 @ %h(z)
1656 @ </pre>
1657 }
1658 }else if( strncmp(zMime, "image/", 6)==0 ){
1659 @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
1660 style_submenu_element("Image", "Image",
1661 "%R/raw/%S?m=%s", zUuid, zMime);
1662 }else{
1663 @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
1664 }
1665 @ </blockquote>
1666 }
@@ -1711,10 +1713,16 @@
1713 style_header("Ticket Change Details");
1714 style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
1715 style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
1716 style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
1717 style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
1718 if( P("plaintext") ){
1719 style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid);
1720 }else{
1721 style_submenu_element("Plaintext", "Plaintext",
1722 "%R/info/%S?plaintext", zUuid);
1723 }
1724
1725 @ <div class="section">Overview</div>
1726 @ <p><table class="label-value">
1727 @ <tr><th>Artifact&nbsp;ID:</th>
1728 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
1755 @ </blockquote>
1756 }
1757
1758 @ <div class="section">Changes</div>
1759 @ <p>
1760 ticket_output_change_artifact(pTktChng, 0);
1761 manifest_destroy(pTktChng);
1762 style_footer();
1763 }
1764
1765
1766
+11 -3
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
11111111
cnt++;
11121112
if( pDownloadName && blob_size(pDownloadName)==0 ){
11131113
blob_append(pDownloadName, zName, -1);
11141114
}
11151115
}
1116
- @ </ul></ul>
1116
+ @ </ul>
11171117
free(prevName);
11181118
db_finalize(&q);
11191119
db_prepare(&q,
11201120
"SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
11211121
" coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
16541654
@ <pre>
16551655
@ %h(z)
16561656
@ </pre>
16571657
}
16581658
}else if( strncmp(zMime, "image/", 6)==0 ){
1659
- @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img>
1659
+ @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
1660
+ style_submenu_element("Image", "Image",
1661
+ "%R/raw/%S?m=%s", zUuid, zMime);
16601662
}else{
16611663
@ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
16621664
}
16631665
@ </blockquote>
16641666
}
@@ -1711,10 +1713,16 @@
17111713
style_header("Ticket Change Details");
17121714
style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
17131715
style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
17141716
style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
17151717
style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
1718
+ if( P("plaintext") ){
1719
+ style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid);
1720
+ }else{
1721
+ style_submenu_element("Plaintext", "Plaintext",
1722
+ "%R/info/%S?plaintext", zUuid);
1723
+ }
17161724
17171725
@ <div class="section">Overview</div>
17181726
@ <p><table class="label-value">
17191727
@ <tr><th>Artifact&nbsp;ID:</th>
17201728
@ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
17471755
@ </blockquote>
17481756
}
17491757
17501758
@ <div class="section">Changes</div>
17511759
@ <p>
1752
- ticket_output_change_artifact(pTktChng);
1760
+ ticket_output_change_artifact(pTktChng, 0);
17531761
manifest_destroy(pTktChng);
17541762
style_footer();
17551763
}
17561764
17571765
17581766
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
1111 cnt++;
1112 if( pDownloadName && blob_size(pDownloadName)==0 ){
1113 blob_append(pDownloadName, zName, -1);
1114 }
1115 }
1116 @ </ul></ul>
1117 free(prevName);
1118 db_finalize(&q);
1119 db_prepare(&q,
1120 "SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
1121 " coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
1654 @ <pre>
1655 @ %h(z)
1656 @ </pre>
1657 }
1658 }else if( strncmp(zMime, "image/", 6)==0 ){
1659 @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img>
 
 
1660 }else{
1661 @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
1662 }
1663 @ </blockquote>
1664 }
@@ -1711,10 +1713,16 @@
1711 style_header("Ticket Change Details");
1712 style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
1713 style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
1714 style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
1715 style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
 
 
 
 
 
 
1716
1717 @ <div class="section">Overview</div>
1718 @ <p><table class="label-value">
1719 @ <tr><th>Artifact&nbsp;ID:</th>
1720 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
1747 @ </blockquote>
1748 }
1749
1750 @ <div class="section">Changes</div>
1751 @ <p>
1752 ticket_output_change_artifact(pTktChng);
1753 manifest_destroy(pTktChng);
1754 style_footer();
1755 }
1756
1757
1758
--- src/info.c
+++ src/info.c
@@ -1111,11 +1111,11 @@
1111 cnt++;
1112 if( pDownloadName && blob_size(pDownloadName)==0 ){
1113 blob_append(pDownloadName, zName, -1);
1114 }
1115 }
1116 @ </ul>
1117 free(prevName);
1118 db_finalize(&q);
1119 db_prepare(&q,
1120 "SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
1121 " coalesce(event.euser, event.user)"
@@ -1654,11 +1654,13 @@
1654 @ <pre>
1655 @ %h(z)
1656 @ </pre>
1657 }
1658 }else if( strncmp(zMime, "image/", 6)==0 ){
1659 @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
1660 style_submenu_element("Image", "Image",
1661 "%R/raw/%S?m=%s", zUuid, zMime);
1662 }else{
1663 @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
1664 }
1665 @ </blockquote>
1666 }
@@ -1711,10 +1713,16 @@
1713 style_header("Ticket Change Details");
1714 style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
1715 style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
1716 style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
1717 style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
1718 if( P("plaintext") ){
1719 style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid);
1720 }else{
1721 style_submenu_element("Plaintext", "Plaintext",
1722 "%R/info/%S?plaintext", zUuid);
1723 }
1724
1725 @ <div class="section">Overview</div>
1726 @ <p><table class="label-value">
1727 @ <tr><th>Artifact&nbsp;ID:</th>
1728 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
@@ -1747,11 +1755,11 @@
1755 @ </blockquote>
1756 }
1757
1758 @ <div class="section">Changes</div>
1759 @ <p>
1760 ticket_output_change_artifact(pTktChng, 0);
1761 manifest_destroy(pTktChng);
1762 style_footer();
1763 }
1764
1765
1766
+5 -1
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
16321632
blob_read_from_file(&config, zFile);
16331633
while( blob_line(&config, &line) ){
16341634
if( !blob_token(&line, &key) ) continue;
16351635
if( blob_buffer(&key)[0]=='#' ) continue;
16361636
if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637
- g.fDebug = fossil_fopen(blob_str(&value), "a");
1637
+ g.fDebug = fossil_fopen(blob_str(&value), "ab");
16381638
blob_reset(&value);
16391639
continue;
16401640
}
16411641
if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
16421642
cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
18501850
**
18511851
** COMMAND: test-http
18521852
** Works like the http command but gives setup permission to all users.
18531853
*/
18541854
void cmd_test_http(void){
1855
+ g.thTrace = find_option("th-trace", 0, 0)!=0;
1856
+ if( g.thTrace ){
1857
+ blob_zero(&g.thLog);
1858
+ }
18551859
login_set_capabilities("sx", 0);
18561860
g.useLocalauth = 1;
18571861
cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
18581862
g.httpIn = stdin;
18591863
g.httpOut = stdout;
18601864
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
1632 blob_read_from_file(&config, zFile);
1633 while( blob_line(&config, &line) ){
1634 if( !blob_token(&line, &key) ) continue;
1635 if( blob_buffer(&key)[0]=='#' ) continue;
1636 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637 g.fDebug = fossil_fopen(blob_str(&value), "a");
1638 blob_reset(&value);
1639 continue;
1640 }
1641 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
1642 cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
1850 **
1851 ** COMMAND: test-http
1852 ** Works like the http command but gives setup permission to all users.
1853 */
1854 void cmd_test_http(void){
 
 
 
 
1855 login_set_capabilities("sx", 0);
1856 g.useLocalauth = 1;
1857 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
1858 g.httpIn = stdin;
1859 g.httpOut = stdout;
1860
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
1632 blob_read_from_file(&config, zFile);
1633 while( blob_line(&config, &line) ){
1634 if( !blob_token(&line, &key) ) continue;
1635 if( blob_buffer(&key)[0]=='#' ) continue;
1636 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637 g.fDebug = fossil_fopen(blob_str(&value), "ab");
1638 blob_reset(&value);
1639 continue;
1640 }
1641 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
1642 cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
1850 **
1851 ** COMMAND: test-http
1852 ** Works like the http command but gives setup permission to all users.
1853 */
1854 void cmd_test_http(void){
1855 g.thTrace = find_option("th-trace", 0, 0)!=0;
1856 if( g.thTrace ){
1857 blob_zero(&g.thLog);
1858 }
1859 login_set_capabilities("sx", 0);
1860 g.useLocalauth = 1;
1861 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
1862 g.httpIn = stdin;
1863 g.httpOut = stdout;
1864
+5 -1
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
16321632
blob_read_from_file(&config, zFile);
16331633
while( blob_line(&config, &line) ){
16341634
if( !blob_token(&line, &key) ) continue;
16351635
if( blob_buffer(&key)[0]=='#' ) continue;
16361636
if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637
- g.fDebug = fossil_fopen(blob_str(&value), "a");
1637
+ g.fDebug = fossil_fopen(blob_str(&value), "ab");
16381638
blob_reset(&value);
16391639
continue;
16401640
}
16411641
if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
16421642
cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
18501850
**
18511851
** COMMAND: test-http
18521852
** Works like the http command but gives setup permission to all users.
18531853
*/
18541854
void cmd_test_http(void){
1855
+ g.thTrace = find_option("th-trace", 0, 0)!=0;
1856
+ if( g.thTrace ){
1857
+ blob_zero(&g.thLog);
1858
+ }
18551859
login_set_capabilities("sx", 0);
18561860
g.useLocalauth = 1;
18571861
cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
18581862
g.httpIn = stdin;
18591863
g.httpOut = stdout;
18601864
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
1632 blob_read_from_file(&config, zFile);
1633 while( blob_line(&config, &line) ){
1634 if( !blob_token(&line, &key) ) continue;
1635 if( blob_buffer(&key)[0]=='#' ) continue;
1636 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637 g.fDebug = fossil_fopen(blob_str(&value), "a");
1638 blob_reset(&value);
1639 continue;
1640 }
1641 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
1642 cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
1850 **
1851 ** COMMAND: test-http
1852 ** Works like the http command but gives setup permission to all users.
1853 */
1854 void cmd_test_http(void){
 
 
 
 
1855 login_set_capabilities("sx", 0);
1856 g.useLocalauth = 1;
1857 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
1858 g.httpIn = stdin;
1859 g.httpOut = stdout;
1860
--- src/main.c
+++ src/main.c
@@ -1632,11 +1632,11 @@
1632 blob_read_from_file(&config, zFile);
1633 while( blob_line(&config, &line) ){
1634 if( !blob_token(&line, &key) ) continue;
1635 if( blob_buffer(&key)[0]=='#' ) continue;
1636 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1637 g.fDebug = fossil_fopen(blob_str(&value), "ab");
1638 blob_reset(&value);
1639 continue;
1640 }
1641 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
1642 cgi_setenv("HOME", blob_str(&value));
@@ -1850,10 +1850,14 @@
1850 **
1851 ** COMMAND: test-http
1852 ** Works like the http command but gives setup permission to all users.
1853 */
1854 void cmd_test_http(void){
1855 g.thTrace = find_option("th-trace", 0, 0)!=0;
1856 if( g.thTrace ){
1857 blob_zero(&g.thLog);
1858 }
1859 login_set_capabilities("sx", 0);
1860 g.useLocalauth = 1;
1861 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
1862 g.httpIn = stdin;
1863 g.httpOut = stdout;
1864
+8 -6
--- src/manifest.c
+++ src/manifest.c
@@ -1552,11 +1552,11 @@
15521552
if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
15531553
zNewStatus = pManifest->aField[i].zValue;
15541554
}
15551555
}
15561556
if( zNewStatus ){
1557
- blob_appendf(&comment, "%h ticket [%.10s]: <i>%s</i>",
1557
+ blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>",
15581558
zNewStatus, pManifest->zTicketUuid, zTitle
15591559
);
15601560
if( pManifest->nField>1 ){
15611561
blob_appendf(&comment, " plus %d other change%s",
15621562
pManifest->nField-1, pManifest->nField==2 ? "" : "s");
@@ -1566,11 +1566,11 @@
15661566
}else{
15671567
zNewStatus = db_text("unknown",
15681568
"SELECT %s FROM ticket WHERE tkt_uuid='%s'",
15691569
zStatusColumn, pManifest->zTicketUuid
15701570
);
1571
- blob_appendf(&comment, "Ticket [%.10s] <i>%s</i> status still %h with "
1571
+ blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with "
15721572
"%d other change%s",
15731573
pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
15741574
pManifest->nField==1 ? "" : "s"
15751575
);
15761576
free(zNewStatus);
@@ -1868,12 +1868,13 @@
18681868
if( strlen(p->zAttachTarget)!=UUID_SIZE
18691869
|| !validate16(p->zAttachTarget, UUID_SIZE)
18701870
){
18711871
char *zComment;
18721872
if( p->zAttachSrc && p->zAttachSrc[0] ){
1873
- zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
1874
- p->zAttachName, p->zAttachTarget);
1873
+ zComment = mprintf(
1874
+ "Add attachment [%R/artifact/%S|%h] to wiki page [%h]",
1875
+ p->zAttachSrc, p->zAttachName, p->zAttachTarget);
18751876
}else{
18761877
zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
18771878
p->zAttachName, p->zAttachTarget);
18781879
}
18791880
db_multi_exec(
@@ -1883,12 +1884,13 @@
18831884
);
18841885
free(zComment);
18851886
}else{
18861887
char *zComment;
18871888
if( p->zAttachSrc && p->zAttachSrc[0] ){
1888
- zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
1889
- p->zAttachName, p->zAttachTarget);
1889
+ zComment = mprintf(
1890
+ "Add attachment [%R/artifact/%S|%h] to ticket [%S]",
1891
+ p->zAttachSrc, p->zAttachName, p->zAttachTarget);
18901892
}else{
18911893
zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
18921894
p->zAttachName, p->zAttachTarget);
18931895
}
18941896
db_multi_exec(
18951897
--- src/manifest.c
+++ src/manifest.c
@@ -1552,11 +1552,11 @@
1552 if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
1553 zNewStatus = pManifest->aField[i].zValue;
1554 }
1555 }
1556 if( zNewStatus ){
1557 blob_appendf(&comment, "%h ticket [%.10s]: <i>%s</i>",
1558 zNewStatus, pManifest->zTicketUuid, zTitle
1559 );
1560 if( pManifest->nField>1 ){
1561 blob_appendf(&comment, " plus %d other change%s",
1562 pManifest->nField-1, pManifest->nField==2 ? "" : "s");
@@ -1566,11 +1566,11 @@
1566 }else{
1567 zNewStatus = db_text("unknown",
1568 "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
1569 zStatusColumn, pManifest->zTicketUuid
1570 );
1571 blob_appendf(&comment, "Ticket [%.10s] <i>%s</i> status still %h with "
1572 "%d other change%s",
1573 pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
1574 pManifest->nField==1 ? "" : "s"
1575 );
1576 free(zNewStatus);
@@ -1868,12 +1868,13 @@
1868 if( strlen(p->zAttachTarget)!=UUID_SIZE
1869 || !validate16(p->zAttachTarget, UUID_SIZE)
1870 ){
1871 char *zComment;
1872 if( p->zAttachSrc && p->zAttachSrc[0] ){
1873 zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
1874 p->zAttachName, p->zAttachTarget);
 
1875 }else{
1876 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
1877 p->zAttachName, p->zAttachTarget);
1878 }
1879 db_multi_exec(
@@ -1883,12 +1884,13 @@
1883 );
1884 free(zComment);
1885 }else{
1886 char *zComment;
1887 if( p->zAttachSrc && p->zAttachSrc[0] ){
1888 zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
1889 p->zAttachName, p->zAttachTarget);
 
1890 }else{
1891 zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
1892 p->zAttachName, p->zAttachTarget);
1893 }
1894 db_multi_exec(
1895
--- src/manifest.c
+++ src/manifest.c
@@ -1552,11 +1552,11 @@
1552 if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
1553 zNewStatus = pManifest->aField[i].zValue;
1554 }
1555 }
1556 if( zNewStatus ){
1557 blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>",
1558 zNewStatus, pManifest->zTicketUuid, zTitle
1559 );
1560 if( pManifest->nField>1 ){
1561 blob_appendf(&comment, " plus %d other change%s",
1562 pManifest->nField-1, pManifest->nField==2 ? "" : "s");
@@ -1566,11 +1566,11 @@
1566 }else{
1567 zNewStatus = db_text("unknown",
1568 "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
1569 zStatusColumn, pManifest->zTicketUuid
1570 );
1571 blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with "
1572 "%d other change%s",
1573 pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
1574 pManifest->nField==1 ? "" : "s"
1575 );
1576 free(zNewStatus);
@@ -1868,12 +1868,13 @@
1868 if( strlen(p->zAttachTarget)!=UUID_SIZE
1869 || !validate16(p->zAttachTarget, UUID_SIZE)
1870 ){
1871 char *zComment;
1872 if( p->zAttachSrc && p->zAttachSrc[0] ){
1873 zComment = mprintf(
1874 "Add attachment [%R/artifact/%S|%h] to wiki page [%h]",
1875 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
1876 }else{
1877 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
1878 p->zAttachName, p->zAttachTarget);
1879 }
1880 db_multi_exec(
@@ -1883,12 +1884,13 @@
1884 );
1885 free(zComment);
1886 }else{
1887 char *zComment;
1888 if( p->zAttachSrc && p->zAttachSrc[0] ){
1889 zComment = mprintf(
1890 "Add attachment [%R/artifact/%S|%h] to ticket [%S]",
1891 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
1892 }else{
1893 zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
1894 p->zAttachName, p->zAttachTarget);
1895 }
1896 db_multi_exec(
1897
+32 -6
--- src/report.c
+++ src/report.c
@@ -488,14 +488,21 @@
488488
@ is assumed to hold a ticket number. A hyperlink will be created from
489489
@ that column to a detailed view of the ticket.</p></li>
490490
@
491491
@ <li><p>If a column of the result set is named "bgcolor" then the content
492492
@ of that column determines the background color of the row.</p></li>
493
+ @
494
+ @ <li><p>The text of all columns prior to the first column whose name begins
495
+ @ with underscore ("_") is shown character-for-character as it appears in
496
+ @ the database. In other words, it is assumed to have a mimetype of
497
+ @ text/plain.
493498
@
494499
@ <li><p>The first column whose name begins with underscore ("_") and all
495
- @ subsequent columns are shown on their own rows in the table. This might
496
- @ be useful for displaying the description of tickets.
500
+ @ subsequent columns are shown on their own rows in the table and with
501
+ @ wiki formatting. In other words, such rows are shown with a mimetype
502
+ @ of text/x-fossil-wiki. This is recommended for the "description" field
503
+ @ of tickets.
497504
@ </p></li>
498505
@
499506
@ <li><p>The query can join other tables in the database besides TICKET.
500507
@ </p></li>
501508
@ </ul>
@@ -589,12 +596,12 @@
589596
@ sdate(changetime) AS 'Changed',
590597
@ assignedto AS 'Assigned',
591598
@ severity AS 'Svr',
592599
@ priority AS 'Pri',
593600
@ title AS 'Title',
594
- @ description AS '_Description', -- When the column name begins with '_'
595
- @ remarks AS '_Remarks' -- the data is shown on a separate row.
601
+ @ description AS '_Description', -- When the column name begins with '_'
602
+ @ remarks AS '_Remarks' -- content is rendered as wiki
596603
@ FROM ticket
597604
@ </pre></blockquote>
598605
@
599606
@ <p>Or, to see part of the description on the same row, use the
600607
@ <b>wiki()</b> function with some string manipulation. Using the
@@ -619,10 +626,13 @@
619626
int nCount; /* Row number */
620627
int nCol; /* Number of columns */
621628
int isMultirow; /* True if multiple table rows per query result row */
622629
int iNewRow; /* Index of first column that goes on separate row */
623630
int iBg; /* Index of column that defines background color */
631
+ int wikiFlags; /* Flags passed into wiki_convert() */
632
+ const char *zWikiStart; /* HTML before display of multi-line wiki */
633
+ const char *zWikiEnd; /* HTML after display of multi-line wiki */
624634
};
625635
626636
/*
627637
** The callback function for db_query
628638
*/
@@ -663,10 +673,23 @@
663673
}
664674
if( !pState->isMultirow ){
665675
if( azName[i][0]=='_' ){
666676
pState->isMultirow = 1;
667677
pState->iNewRow = i;
678
+ pState->wikiFlags = WIKI_NOBADLINKS;
679
+ pState->zWikiStart = "";
680
+ pState->zWikiEnd = "";
681
+ if( P("plaintext") ){
682
+ pState->wikiFlags |= WIKI_LINKSONLY;
683
+ pState->zWikiStart = "<pre class='verbatim'>";
684
+ pState->zWikiEnd = "</pre>";
685
+ style_submenu_element("Formatted", "Formatted",
686
+ "%R/rptview?rn=%d", pState->rn);
687
+ }else{
688
+ style_submenu_element("Plaintext", "Plaintext",
689
+ "%R/rptview?rn=%d&plaintext", pState->rn);
690
+ }
668691
}else{
669692
pState->nCol++;
670693
}
671694
}
672695
}
@@ -728,14 +751,17 @@
728751
@ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
729752
zTid = 0;
730753
}
731754
if( zData[0] ){
732755
Blob content;
733
- @ </tr><tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)>
756
+ @ </tr>
757
+ @ <tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)>
758
+ @ %s(pState->zWikiStart)
734759
blob_init(&content, zData, -1);
735
- wiki_convert(&content, 0, WIKI_NOBADLINKS);
760
+ wiki_convert(&content, 0, pState->wikiFlags);
736761
blob_reset(&content);
762
+ @ %s(pState->zWikiEnd)
737763
}
738764
}else if( azName[i][0]=='#' ){
739765
zTid = zData;
740766
@ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td>
741767
}else if( zData[0]==0 ){
742768
--- src/report.c
+++ src/report.c
@@ -488,14 +488,21 @@
488 @ is assumed to hold a ticket number. A hyperlink will be created from
489 @ that column to a detailed view of the ticket.</p></li>
490 @
491 @ <li><p>If a column of the result set is named "bgcolor" then the content
492 @ of that column determines the background color of the row.</p></li>
 
 
 
 
 
493 @
494 @ <li><p>The first column whose name begins with underscore ("_") and all
495 @ subsequent columns are shown on their own rows in the table. This might
496 @ be useful for displaying the description of tickets.
 
 
497 @ </p></li>
498 @
499 @ <li><p>The query can join other tables in the database besides TICKET.
500 @ </p></li>
501 @ </ul>
@@ -589,12 +596,12 @@
589 @ sdate(changetime) AS 'Changed',
590 @ assignedto AS 'Assigned',
591 @ severity AS 'Svr',
592 @ priority AS 'Pri',
593 @ title AS 'Title',
594 @ description AS '_Description', -- When the column name begins with '_'
595 @ remarks AS '_Remarks' -- the data is shown on a separate row.
596 @ FROM ticket
597 @ </pre></blockquote>
598 @
599 @ <p>Or, to see part of the description on the same row, use the
600 @ <b>wiki()</b> function with some string manipulation. Using the
@@ -619,10 +626,13 @@
619 int nCount; /* Row number */
620 int nCol; /* Number of columns */
621 int isMultirow; /* True if multiple table rows per query result row */
622 int iNewRow; /* Index of first column that goes on separate row */
623 int iBg; /* Index of column that defines background color */
 
 
 
624 };
625
626 /*
627 ** The callback function for db_query
628 */
@@ -663,10 +673,23 @@
663 }
664 if( !pState->isMultirow ){
665 if( azName[i][0]=='_' ){
666 pState->isMultirow = 1;
667 pState->iNewRow = i;
 
 
 
 
 
 
 
 
 
 
 
 
 
668 }else{
669 pState->nCol++;
670 }
671 }
672 }
@@ -728,14 +751,17 @@
728 @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
729 zTid = 0;
730 }
731 if( zData[0] ){
732 Blob content;
733 @ </tr><tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)>
 
 
734 blob_init(&content, zData, -1);
735 wiki_convert(&content, 0, WIKI_NOBADLINKS);
736 blob_reset(&content);
 
737 }
738 }else if( azName[i][0]=='#' ){
739 zTid = zData;
740 @ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td>
741 }else if( zData[0]==0 ){
742
--- src/report.c
+++ src/report.c
@@ -488,14 +488,21 @@
488 @ is assumed to hold a ticket number. A hyperlink will be created from
489 @ that column to a detailed view of the ticket.</p></li>
490 @
491 @ <li><p>If a column of the result set is named "bgcolor" then the content
492 @ of that column determines the background color of the row.</p></li>
493 @
494 @ <li><p>The text of all columns prior to the first column whose name begins
495 @ with underscore ("_") is shown character-for-character as it appears in
496 @ the database. In other words, it is assumed to have a mimetype of
497 @ text/plain.
498 @
499 @ <li><p>The first column whose name begins with underscore ("_") and all
500 @ subsequent columns are shown on their own rows in the table and with
501 @ wiki formatting. In other words, such rows are shown with a mimetype
502 @ of text/x-fossil-wiki. This is recommended for the "description" field
503 @ of tickets.
504 @ </p></li>
505 @
506 @ <li><p>The query can join other tables in the database besides TICKET.
507 @ </p></li>
508 @ </ul>
@@ -589,12 +596,12 @@
596 @ sdate(changetime) AS 'Changed',
597 @ assignedto AS 'Assigned',
598 @ severity AS 'Svr',
599 @ priority AS 'Pri',
600 @ title AS 'Title',
601 @ description AS '_Description', -- When the column name begins with '_'
602 @ remarks AS '_Remarks' -- content is rendered as wiki
603 @ FROM ticket
604 @ </pre></blockquote>
605 @
606 @ <p>Or, to see part of the description on the same row, use the
607 @ <b>wiki()</b> function with some string manipulation. Using the
@@ -619,10 +626,13 @@
626 int nCount; /* Row number */
627 int nCol; /* Number of columns */
628 int isMultirow; /* True if multiple table rows per query result row */
629 int iNewRow; /* Index of first column that goes on separate row */
630 int iBg; /* Index of column that defines background color */
631 int wikiFlags; /* Flags passed into wiki_convert() */
632 const char *zWikiStart; /* HTML before display of multi-line wiki */
633 const char *zWikiEnd; /* HTML after display of multi-line wiki */
634 };
635
636 /*
637 ** The callback function for db_query
638 */
@@ -663,10 +673,23 @@
673 }
674 if( !pState->isMultirow ){
675 if( azName[i][0]=='_' ){
676 pState->isMultirow = 1;
677 pState->iNewRow = i;
678 pState->wikiFlags = WIKI_NOBADLINKS;
679 pState->zWikiStart = "";
680 pState->zWikiEnd = "";
681 if( P("plaintext") ){
682 pState->wikiFlags |= WIKI_LINKSONLY;
683 pState->zWikiStart = "<pre class='verbatim'>";
684 pState->zWikiEnd = "</pre>";
685 style_submenu_element("Formatted", "Formatted",
686 "%R/rptview?rn=%d", pState->rn);
687 }else{
688 style_submenu_element("Plaintext", "Plaintext",
689 "%R/rptview?rn=%d&plaintext", pState->rn);
690 }
691 }else{
692 pState->nCol++;
693 }
694 }
695 }
@@ -728,14 +751,17 @@
751 @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
752 zTid = 0;
753 }
754 if( zData[0] ){
755 Blob content;
756 @ </tr>
757 @ <tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)>
758 @ %s(pState->zWikiStart)
759 blob_init(&content, zData, -1);
760 wiki_convert(&content, 0, pState->wikiFlags);
761 blob_reset(&content);
762 @ %s(pState->zWikiEnd)
763 }
764 }else if( azName[i][0]=='#' ){
765 zTid = zData;
766 @ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td>
767 }else if( zData[0]==0 ){
768
+11
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397397
@ private_contact TEXT,
398398
@ resolution TEXT,
399399
@ title TEXT,
400400
@ comment TEXT
401401
@ );
402
+@ CREATE TABLE ticketchng(
403
+@ -- Do not change any column that begins with tkt_
404
+@ tkt_id INTEGER REFERENCES ticket,
405
+@ tkt_mtime DATE,
406
+@ -- Add as many fields as required below this line
407
+@ login TEXT,
408
+@ username TEXT,
409
+@ mimetype TEXT,
410
+@ icomment TEXT
411
+@ );
412
+@ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
402413
;
403414
404415
/*
405416
** Predefined tagid values
406417
*/
407418
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397 @ private_contact TEXT,
398 @ resolution TEXT,
399 @ title TEXT,
400 @ comment TEXT
401 @ );
 
 
 
 
 
 
 
 
 
 
 
402 ;
403
404 /*
405 ** Predefined tagid values
406 */
407
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397 @ private_contact TEXT,
398 @ resolution TEXT,
399 @ title TEXT,
400 @ comment TEXT
401 @ );
402 @ CREATE TABLE ticketchng(
403 @ -- Do not change any column that begins with tkt_
404 @ tkt_id INTEGER REFERENCES ticket,
405 @ tkt_mtime DATE,
406 @ -- Add as many fields as required below this line
407 @ login TEXT,
408 @ username TEXT,
409 @ mimetype TEXT,
410 @ icomment TEXT
411 @ );
412 @ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
413 ;
414
415 /*
416 ** Predefined tagid values
417 */
418
+7
--- src/th.c
+++ src/th.c
@@ -1148,10 +1148,17 @@
11481148
return TH_ERROR;
11491149
}
11501150
11511151
return Th_SetResult(interp, pValue->zData, pValue->nData);
11521152
}
1153
+
1154
+/*
1155
+** Return true if variable (zVar, nVar) exists.
1156
+*/
1157
+int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1158
+ return thFindValue(interp, zVar, nVar, 0, 0)!=0;
1159
+}
11531160
11541161
/*
11551162
** String (zVar, nVar) must contain the name of a scalar variable or
11561163
** array member. If the variable does not exist it is created. The
11571164
** variable is set to the value supplied in string (zValue, nValue).
11581165
--- src/th.c
+++ src/th.c
@@ -1148,10 +1148,17 @@
1148 return TH_ERROR;
1149 }
1150
1151 return Th_SetResult(interp, pValue->zData, pValue->nData);
1152 }
 
 
 
 
 
 
 
1153
1154 /*
1155 ** String (zVar, nVar) must contain the name of a scalar variable or
1156 ** array member. If the variable does not exist it is created. The
1157 ** variable is set to the value supplied in string (zValue, nValue).
1158
--- src/th.c
+++ src/th.c
@@ -1148,10 +1148,17 @@
1148 return TH_ERROR;
1149 }
1150
1151 return Th_SetResult(interp, pValue->zData, pValue->nData);
1152 }
1153
1154 /*
1155 ** Return true if variable (zVar, nVar) exists.
1156 */
1157 int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1158 return thFindValue(interp, zVar, nVar, 0, 0)!=0;
1159 }
1160
1161 /*
1162 ** String (zVar, nVar) must contain the name of a scalar variable or
1163 ** array member. If the variable does not exist it is created. The
1164 ** variable is set to the value supplied in string (zValue, nValue).
1165
+1
--- src/th.h
+++ src/th.h
@@ -49,10 +49,11 @@
4949
5050
/*
5151
** Access TH variables in the current stack frame. If the variable name
5252
** begins with "::", the lookup is in the top level (global) frame.
5353
*/
54
+int Th_ExistsVar(Th_Interp *, const char *, int);
5455
int Th_GetVar(Th_Interp *, const char *, int);
5556
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
5657
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
5758
int Th_UnsetVar(Th_Interp *, const char *, int);
5859
5960
--- src/th.h
+++ src/th.h
@@ -49,10 +49,11 @@
49
50 /*
51 ** Access TH variables in the current stack frame. If the variable name
52 ** begins with "::", the lookup is in the top level (global) frame.
53 */
 
54 int Th_GetVar(Th_Interp *, const char *, int);
55 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
56 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
57 int Th_UnsetVar(Th_Interp *, const char *, int);
58
59
--- src/th.h
+++ src/th.h
@@ -49,10 +49,11 @@
49
50 /*
51 ** Access TH variables in the current stack frame. If the variable name
52 ** begins with "::", the lookup is in the top level (global) frame.
53 */
54 int Th_ExistsVar(Th_Interp *, const char *, int);
55 int Th_GetVar(Th_Interp *, const char *, int);
56 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
57 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
58 int Th_UnsetVar(Th_Interp *, const char *, int);
59
60
+33 -2
--- src/th_lang.c
+++ src/th_lang.c
@@ -817,10 +817,38 @@
817817
return TH_OK;
818818
}
819819
820820
/*
821821
** TH Syntax:
822
+**
823
+** string trim STRING
824
+** string trimleft STRING
825
+** string trimright STRING
826
+*/
827
+static int string_trim_command(
828
+ Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
829
+){
830
+ int n;
831
+ const char *z;
832
+
833
+ if( argc!=3 ){
834
+ return Th_WrongNumArgs(interp, "string trim string");
835
+ }
836
+ z = argv[2];
837
+ n = argl[2];
838
+ if( argl[1]<5 || argv[1][4]=='l' ){
839
+ while( n && th_isspace(z[0]) ){ z++; n--; }
840
+ }
841
+ if( argl[1]<5 || argv[1][4]=='r' ){
842
+ while( n && th_isspace(z[n-1]) ){ n--; }
843
+ }
844
+ Th_SetResult(interp, z, n);
845
+ return TH_OK;
846
+}
847
+
848
+/*
849
+** TH Syntax:
822850
**
823851
** info exists VAR
824852
*/
825853
static int info_exists_command(
826854
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
@@ -828,12 +856,12 @@
828856
int rc;
829857
830858
if( argc!=3 ){
831859
return Th_WrongNumArgs(interp, "info exists var");
832860
}
833
- rc = Th_GetVar(interp, argv[2], argl[2]);
834
- Th_SetResultInt(interp, rc?0:1);
861
+ rc = Th_ExistsVar(interp, argv[2], argl[2]);
862
+ Th_SetResultInt(interp, rc);
835863
return TH_OK;
836864
}
837865
838866
/*
839867
** TH Syntax:
@@ -897,10 +925,13 @@
897925
{ "is", string_is_command },
898926
{ "last", string_last_command },
899927
{ "length", string_length_command },
900928
{ "range", string_range_command },
901929
{ "repeat", string_repeat_command },
930
+ { "trim", string_trim_command },
931
+ { "trimleft", string_trim_command },
932
+ { "trimright", string_trim_command },
902933
{ 0, 0 }
903934
};
904935
return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
905936
}
906937
907938
--- src/th_lang.c
+++ src/th_lang.c
@@ -817,10 +817,38 @@
817 return TH_OK;
818 }
819
820 /*
821 ** TH Syntax:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822 **
823 ** info exists VAR
824 */
825 static int info_exists_command(
826 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
@@ -828,12 +856,12 @@
828 int rc;
829
830 if( argc!=3 ){
831 return Th_WrongNumArgs(interp, "info exists var");
832 }
833 rc = Th_GetVar(interp, argv[2], argl[2]);
834 Th_SetResultInt(interp, rc?0:1);
835 return TH_OK;
836 }
837
838 /*
839 ** TH Syntax:
@@ -897,10 +925,13 @@
897 { "is", string_is_command },
898 { "last", string_last_command },
899 { "length", string_length_command },
900 { "range", string_range_command },
901 { "repeat", string_repeat_command },
 
 
 
902 { 0, 0 }
903 };
904 return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
905 }
906
907
--- src/th_lang.c
+++ src/th_lang.c
@@ -817,10 +817,38 @@
817 return TH_OK;
818 }
819
820 /*
821 ** TH Syntax:
822 **
823 ** string trim STRING
824 ** string trimleft STRING
825 ** string trimright STRING
826 */
827 static int string_trim_command(
828 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
829 ){
830 int n;
831 const char *z;
832
833 if( argc!=3 ){
834 return Th_WrongNumArgs(interp, "string trim string");
835 }
836 z = argv[2];
837 n = argl[2];
838 if( argl[1]<5 || argv[1][4]=='l' ){
839 while( n && th_isspace(z[0]) ){ z++; n--; }
840 }
841 if( argl[1]<5 || argv[1][4]=='r' ){
842 while( n && th_isspace(z[n-1]) ){ n--; }
843 }
844 Th_SetResult(interp, z, n);
845 return TH_OK;
846 }
847
848 /*
849 ** TH Syntax:
850 **
851 ** info exists VAR
852 */
853 static int info_exists_command(
854 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
@@ -828,12 +856,12 @@
856 int rc;
857
858 if( argc!=3 ){
859 return Th_WrongNumArgs(interp, "info exists var");
860 }
861 rc = Th_ExistsVar(interp, argv[2], argl[2]);
862 Th_SetResultInt(interp, rc);
863 return TH_OK;
864 }
865
866 /*
867 ** TH Syntax:
@@ -897,10 +925,13 @@
925 { "is", string_is_command },
926 { "last", string_last_command },
927 { "length", string_length_command },
928 { "range", string_range_command },
929 { "repeat", string_repeat_command },
930 { "trim", string_trim_command },
931 { "trimleft", string_trim_command },
932 { "trimright", string_trim_command },
933 { 0, 0 }
934 };
935 return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
936 }
937
938
+92 -3
--- src/th_main.c
+++ src/th_main.c
@@ -18,10 +18,11 @@
1818
** This file contains an interface between the TH scripting language
1919
** (an independent project) and fossil.
2020
*/
2121
#include "config.h"
2222
#include "th_main.h"
23
+#include "sqlite3.h"
2324
2425
/*
2526
** Global variable counting the number of outstanding calls to malloc()
2627
** made by the th1 implementation. This is used to catch memory leaks
2728
** in the interpreter. Obviously, it also means th1 is not threadsafe.
@@ -72,14 +73,19 @@
7273
void *p,
7374
int argc,
7475
const char **argv,
7576
int *argl
7677
){
77
- if( argc!=2 ){
78
- return Th_WrongNumArgs(interp, "enable_output BOOLEAN");
78
+ int rc;
79
+ if( argc<2 || argc>3 ){
80
+ return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
81
+ }
82
+ rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
83
+ if( g.thTrace ){
84
+ Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
7985
}
80
- return Th_ToInt(interp, argv[1], argl[1], &enableOutput);
86
+ return rc;
8187
}
8288
8389
/*
8490
** Return a name for a TH1 return code.
8591
*/
@@ -119,16 +125,19 @@
119125
if( encode ) free((char*)z);
120126
}
121127
}
122128
123129
static void sendError(const char *z, int n, int forceCgi){
130
+ int savedEnable = enableOutput;
131
+ enableOutput = 1;
124132
if( forceCgi || g.cgiOutput ){
125133
sendText("<hr><p class=\"thmainError\">", -1, 0);
126134
}
127135
sendText("ERROR: ", -1, 0);
128136
sendText((char*)z, n, 1);
129137
sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
138
+ enableOutput = savedEnable;
130139
}
131140
132141
/*
133142
** TH command: puts STRING
134143
** TH command: html STRING
@@ -567,10 +576,86 @@
567576
encode16(aRand, zOut, n);
568577
Th_SetResult(interp, (const char *)zOut, -1);
569578
return TH_OK;
570579
}
571580
581
+/*
582
+** TH1 command: query SQL CODE
583
+**
584
+** Run the SQL query given by the SQL argument. For each row in the result
585
+** set, run CODE.
586
+**
587
+** In SQL, parameters such as $var are filled in using the value of variable
588
+** "var". Result values are stored in variables with the column name prior
589
+** to each invocation of CODE.
590
+*/
591
+static int queryCmd(
592
+ Th_Interp *interp,
593
+ void *p,
594
+ int argc,
595
+ const char **argv,
596
+ int *argl
597
+){
598
+ sqlite3_stmt *pStmt;
599
+ int rc;
600
+ const char *zSql;
601
+ int nSql;
602
+ const char *zTail;
603
+ int n, i;
604
+ int res = TH_OK;
605
+ int nVar;
606
+
607
+ if( argc!=3 ){
608
+ return Th_WrongNumArgs(interp, "query SQL CODE");
609
+ }
610
+ if( g.db==0 ){
611
+ Th_ErrorMessage(interp, "database is not open", 0, 0);
612
+ return TH_ERROR;
613
+ }
614
+ zSql = argv[1];
615
+ nSql = argl[1];
616
+ while( res==TH_OK && nSql>0 ){
617
+ rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
618
+ if( rc!=0 ){
619
+ Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
620
+ return TH_ERROR;
621
+ }
622
+ n = (int)(zTail - zSql);
623
+ zSql += n;
624
+ nSql -= n;
625
+ if( pStmt==0 ) continue;
626
+ nVar = sqlite3_bind_parameter_count(pStmt);
627
+ for(i=1; i<=nVar; i++){
628
+ const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
629
+ int szVar = zVar ? th_strlen(zVar) : 0;
630
+ if( szVar>1 && zVar[0]=='$'
631
+ && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
632
+ int nVal;
633
+ const char *zVal = Th_GetResult(interp, &nVal);
634
+ sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
635
+ }
636
+ }
637
+ while( res==TH_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
638
+ int nCol = sqlite3_column_count(pStmt);
639
+ for(i=0; i<nCol; i++){
640
+ const char *zCol = sqlite3_column_name(pStmt, i);
641
+ int szCol = th_strlen(zCol);
642
+ const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
643
+ int szVal = sqlite3_column_bytes(pStmt, i);
644
+ Th_SetVar(interp, zCol, szCol, zVal, szVal);
645
+ }
646
+ res = Th_Eval(interp, 0, argv[2], argl[2]);
647
+ if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
648
+ }
649
+ rc = sqlite3_finalize(pStmt);
650
+ if( rc!=SQLITE_OK ){
651
+ Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
652
+ return TH_ERROR;
653
+ }
654
+ }
655
+ return res;
656
+}
572657
573658
/*
574659
** Make sure the interpreter has been initialized. Initialize it if
575660
** it has not been already.
576661
**
@@ -593,10 +678,11 @@
593678
{"hasfeature", hasfeatureCmd, 0},
594679
{"html", putsCmd, (void*)&aFlags[0]},
595680
{"htmlize", htmlizeCmd, 0},
596681
{"linecount", linecntCmd, 0},
597682
{"puts", putsCmd, (void*)&aFlags[1]},
683
+ {"query", queryCmd, 0},
598684
{"randhex", randhexCmd, 0},
599685
{"repository", repositoryCmd, 0},
600686
{"stime", stimeCmd, 0},
601687
{"utime", utimeCmd, 0},
602688
{"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -801,10 +887,13 @@
801887
sendText((char*)zResult, n, encode);
802888
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
803889
sendText(z, i, 0);
804890
z += i+5;
805891
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
892
+ if( g.thTrace ){
893
+ Th_Trace("eval {<pre>%#h</pre>}<br>", i, z);
894
+ }
806895
rc = Th_Eval(g.interp, 0, (const char*)z, i);
807896
if( rc!=TH_OK ) break;
808897
z += i;
809898
if( z[0] ){ z += 6; }
810899
i = 0;
811900
--- src/th_main.c
+++ src/th_main.c
@@ -18,10 +18,11 @@
18 ** This file contains an interface between the TH scripting language
19 ** (an independent project) and fossil.
20 */
21 #include "config.h"
22 #include "th_main.h"
 
23
24 /*
25 ** Global variable counting the number of outstanding calls to malloc()
26 ** made by the th1 implementation. This is used to catch memory leaks
27 ** in the interpreter. Obviously, it also means th1 is not threadsafe.
@@ -72,14 +73,19 @@
72 void *p,
73 int argc,
74 const char **argv,
75 int *argl
76 ){
77 if( argc!=2 ){
78 return Th_WrongNumArgs(interp, "enable_output BOOLEAN");
 
 
 
 
 
79 }
80 return Th_ToInt(interp, argv[1], argl[1], &enableOutput);
81 }
82
83 /*
84 ** Return a name for a TH1 return code.
85 */
@@ -119,16 +125,19 @@
119 if( encode ) free((char*)z);
120 }
121 }
122
123 static void sendError(const char *z, int n, int forceCgi){
 
 
124 if( forceCgi || g.cgiOutput ){
125 sendText("<hr><p class=\"thmainError\">", -1, 0);
126 }
127 sendText("ERROR: ", -1, 0);
128 sendText((char*)z, n, 1);
129 sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
 
130 }
131
132 /*
133 ** TH command: puts STRING
134 ** TH command: html STRING
@@ -567,10 +576,86 @@
567 encode16(aRand, zOut, n);
568 Th_SetResult(interp, (const char *)zOut, -1);
569 return TH_OK;
570 }
571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
573 /*
574 ** Make sure the interpreter has been initialized. Initialize it if
575 ** it has not been already.
576 **
@@ -593,10 +678,11 @@
593 {"hasfeature", hasfeatureCmd, 0},
594 {"html", putsCmd, (void*)&aFlags[0]},
595 {"htmlize", htmlizeCmd, 0},
596 {"linecount", linecntCmd, 0},
597 {"puts", putsCmd, (void*)&aFlags[1]},
 
598 {"randhex", randhexCmd, 0},
599 {"repository", repositoryCmd, 0},
600 {"stime", stimeCmd, 0},
601 {"utime", utimeCmd, 0},
602 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -801,10 +887,13 @@
801 sendText((char*)zResult, n, encode);
802 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
803 sendText(z, i, 0);
804 z += i+5;
805 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
 
 
 
806 rc = Th_Eval(g.interp, 0, (const char*)z, i);
807 if( rc!=TH_OK ) break;
808 z += i;
809 if( z[0] ){ z += 6; }
810 i = 0;
811
--- src/th_main.c
+++ src/th_main.c
@@ -18,10 +18,11 @@
18 ** This file contains an interface between the TH scripting language
19 ** (an independent project) and fossil.
20 */
21 #include "config.h"
22 #include "th_main.h"
23 #include "sqlite3.h"
24
25 /*
26 ** Global variable counting the number of outstanding calls to malloc()
27 ** made by the th1 implementation. This is used to catch memory leaks
28 ** in the interpreter. Obviously, it also means th1 is not threadsafe.
@@ -72,14 +73,19 @@
73 void *p,
74 int argc,
75 const char **argv,
76 int *argl
77 ){
78 int rc;
79 if( argc<2 || argc>3 ){
80 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
81 }
82 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
83 if( g.thTrace ){
84 Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
85 }
86 return rc;
87 }
88
89 /*
90 ** Return a name for a TH1 return code.
91 */
@@ -119,16 +125,19 @@
125 if( encode ) free((char*)z);
126 }
127 }
128
129 static void sendError(const char *z, int n, int forceCgi){
130 int savedEnable = enableOutput;
131 enableOutput = 1;
132 if( forceCgi || g.cgiOutput ){
133 sendText("<hr><p class=\"thmainError\">", -1, 0);
134 }
135 sendText("ERROR: ", -1, 0);
136 sendText((char*)z, n, 1);
137 sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
138 enableOutput = savedEnable;
139 }
140
141 /*
142 ** TH command: puts STRING
143 ** TH command: html STRING
@@ -567,10 +576,86 @@
576 encode16(aRand, zOut, n);
577 Th_SetResult(interp, (const char *)zOut, -1);
578 return TH_OK;
579 }
580
581 /*
582 ** TH1 command: query SQL CODE
583 **
584 ** Run the SQL query given by the SQL argument. For each row in the result
585 ** set, run CODE.
586 **
587 ** In SQL, parameters such as $var are filled in using the value of variable
588 ** "var". Result values are stored in variables with the column name prior
589 ** to each invocation of CODE.
590 */
591 static int queryCmd(
592 Th_Interp *interp,
593 void *p,
594 int argc,
595 const char **argv,
596 int *argl
597 ){
598 sqlite3_stmt *pStmt;
599 int rc;
600 const char *zSql;
601 int nSql;
602 const char *zTail;
603 int n, i;
604 int res = TH_OK;
605 int nVar;
606
607 if( argc!=3 ){
608 return Th_WrongNumArgs(interp, "query SQL CODE");
609 }
610 if( g.db==0 ){
611 Th_ErrorMessage(interp, "database is not open", 0, 0);
612 return TH_ERROR;
613 }
614 zSql = argv[1];
615 nSql = argl[1];
616 while( res==TH_OK && nSql>0 ){
617 rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
618 if( rc!=0 ){
619 Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
620 return TH_ERROR;
621 }
622 n = (int)(zTail - zSql);
623 zSql += n;
624 nSql -= n;
625 if( pStmt==0 ) continue;
626 nVar = sqlite3_bind_parameter_count(pStmt);
627 for(i=1; i<=nVar; i++){
628 const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
629 int szVar = zVar ? th_strlen(zVar) : 0;
630 if( szVar>1 && zVar[0]=='$'
631 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
632 int nVal;
633 const char *zVal = Th_GetResult(interp, &nVal);
634 sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
635 }
636 }
637 while( res==TH_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
638 int nCol = sqlite3_column_count(pStmt);
639 for(i=0; i<nCol; i++){
640 const char *zCol = sqlite3_column_name(pStmt, i);
641 int szCol = th_strlen(zCol);
642 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
643 int szVal = sqlite3_column_bytes(pStmt, i);
644 Th_SetVar(interp, zCol, szCol, zVal, szVal);
645 }
646 res = Th_Eval(interp, 0, argv[2], argl[2]);
647 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
648 }
649 rc = sqlite3_finalize(pStmt);
650 if( rc!=SQLITE_OK ){
651 Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
652 return TH_ERROR;
653 }
654 }
655 return res;
656 }
657
658 /*
659 ** Make sure the interpreter has been initialized. Initialize it if
660 ** it has not been already.
661 **
@@ -593,10 +678,11 @@
678 {"hasfeature", hasfeatureCmd, 0},
679 {"html", putsCmd, (void*)&aFlags[0]},
680 {"htmlize", htmlizeCmd, 0},
681 {"linecount", linecntCmd, 0},
682 {"puts", putsCmd, (void*)&aFlags[1]},
683 {"query", queryCmd, 0},
684 {"randhex", randhexCmd, 0},
685 {"repository", repositoryCmd, 0},
686 {"stime", stimeCmd, 0},
687 {"utime", utimeCmd, 0},
688 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -801,10 +887,13 @@
887 sendText((char*)zResult, n, encode);
888 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
889 sendText(z, i, 0);
890 z += i+5;
891 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
892 if( g.thTrace ){
893 Th_Trace("eval {<pre>%#h</pre>}<br>", i, z);
894 }
895 rc = Th_Eval(g.interp, 0, (const char*)z, i);
896 if( rc!=TH_OK ) break;
897 z += i;
898 if( z[0] ){ z += 6; }
899 i = 0;
900
+5 -1
--- src/timeline.c
+++ src/timeline.c
@@ -344,11 +344,15 @@
344344
hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
345345
}else if( (tmFlags & TIMELINE_ARTID)!=0 ){
346346
hyperlink_to_uuid(zUuid);
347347
}
348348
db_column_blob(pQuery, commentColumn, &comment);
349
- if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
349
+ if( zType[0]!='c' ){
350
+ /* Comments for anything other than a check-in are generated by
351
+ ** "fossil rebuild" and expect to be rendered as text/x-fossil-wiki */
352
+ wiki_convert(&comment, 0, WIKI_INLINE);
353
+ }else if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
350354
Blob truncated;
351355
blob_zero(&truncated);
352356
blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
353357
blob_append(&truncated, "...", 3);
354358
@ %w(blob_str(&truncated))
355359
--- src/timeline.c
+++ src/timeline.c
@@ -344,11 +344,15 @@
344 hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
345 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
346 hyperlink_to_uuid(zUuid);
347 }
348 db_column_blob(pQuery, commentColumn, &comment);
349 if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
 
 
 
 
350 Blob truncated;
351 blob_zero(&truncated);
352 blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
353 blob_append(&truncated, "...", 3);
354 @ %w(blob_str(&truncated))
355
--- src/timeline.c
+++ src/timeline.c
@@ -344,11 +344,15 @@
344 hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
345 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
346 hyperlink_to_uuid(zUuid);
347 }
348 db_column_blob(pQuery, commentColumn, &comment);
349 if( zType[0]!='c' ){
350 /* Comments for anything other than a check-in are generated by
351 ** "fossil rebuild" and expect to be rendered as text/x-fossil-wiki */
352 wiki_convert(&comment, 0, WIKI_INLINE);
353 }else if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
354 Blob truncated;
355 blob_zero(&truncated);
356 blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
357 blob_append(&truncated, "...", 3);
358 @ %w(blob_str(&truncated))
359
+251 -145
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
2626
** The list of database user-defined fields in the TICKET table.
2727
** The real table also contains some addition fields for internal
2828
** used. The internal-use fields begin with "tkt_".
2929
*/
3030
static int nField = 0;
31
-static char **azField = 0; /* Names of database fields */
32
-static char **azValue = 0; /* Original values */
33
-static char **azAppend = 0; /* Value to be appended */
31
+static struct tktFieldInfo {
32
+ char *zName; /* Name of the database field */
33
+ char *zValue; /* Value to store */
34
+ char *zAppend; /* Value to append */
35
+ unsigned mUsed; /* 01: TICKET 02: TICKETCHNG */
36
+} *aField;
37
+#define USEDBY_TICKET 01
38
+#define USEDBY_TICKETCHNG 02
39
+static int haveTicket = 0; /* True if the TICKET table exists */
40
+static int haveTicketChng = 0; /* True if the TICKETCHNG table exists */
3441
3542
/*
36
-** Compare two entries in azField for sorting purposes
43
+** Compare two entries in aField[] for sorting purposes
3744
*/
3845
static int nameCmpr(const void *a, const void *b){
39
- return fossil_strcmp(*(char**)a, *(char**)b);
46
+ return fossil_strcmp(((const struct tktFieldInfo*)a)->zName,
47
+ ((const struct tktFieldInfo*)b)->zName);
48
+}
49
+
50
+/*
51
+** Return the index into aField[] of the given field name.
52
+** Return -1 if zFieldName is not in aField[].
53
+*/
54
+static int fieldId(const char *zFieldName){
55
+ int i;
56
+ for(i=0; i<nField; i++){
57
+ if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
58
+ }
59
+ return -1;
4060
}
4161
4262
/*
43
-** Obtain a list of all fields of the TICKET table. Put them
44
-** in sorted order in azField[].
63
+** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them
64
+** in sorted order in aField[].
4565
**
46
-** Also allocate space for azValue[] and azAppend[] and initialize
47
-** all the values there to zero.
66
+** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
67
+** TICKETCHANGE tables exist, respectively.
4868
*/
4969
static void getAllTicketFields(void){
5070
Stmt q;
5171
int i;
52
- if( nField>0 ) return;
72
+ static int once = 0;
73
+ if( once ) return;
74
+ once = 1;
5375
db_prepare(&q, "PRAGMA table_info(ticket)");
5476
while( db_step(&q)==SQLITE_ROW ){
55
- const char *zField = db_column_text(&q, 1);
56
- if( strncmp(zField,"tkt_",4)==0 ) continue;
77
+ const char *zFieldName = db_column_text(&q, 1);
78
+ haveTicket = 1;
79
+ if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
80
+ if( nField%10==0 ){
81
+ aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
82
+ }
83
+ aField[nField].zName = mprintf("%s", zFieldName);
84
+ aField[nField].mUsed = USEDBY_TICKET;
85
+ nField++;
86
+ }
87
+ db_finalize(&q);
88
+ db_prepare(&q, "PRAGMA table_info(ticketchng)");
89
+ while( db_step(&q)==SQLITE_ROW ){
90
+ const char *zFieldName = db_column_text(&q, 1);
91
+ haveTicketChng = 1;
92
+ if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
93
+ if( (i = fieldId(zFieldName))>=0 ){
94
+ aField[i].mUsed |= USEDBY_TICKETCHNG;
95
+ continue;
96
+ }
5797
if( nField%10==0 ){
58
- azField = fossil_realloc(azField, sizeof(azField)*3*(nField+10) );
98
+ aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
5999
}
60
- azField[nField] = mprintf("%s", zField);
100
+ aField[nField].zName = mprintf("%s", zFieldName);
101
+ aField[nField].mUsed = USEDBY_TICKETCHNG;
61102
nField++;
62103
}
63104
db_finalize(&q);
64
- qsort(azField, nField, sizeof(azField[0]), nameCmpr);
65
- azAppend = &azField[nField];
66
- memset(azAppend, 0, sizeof(azAppend[0])*nField);
67
- azValue = &azAppend[nField];
68
- for(i=0; i<nField; i++){
69
- azValue[i] = "";
70
- }
71
-}
72
-
73
-/*
74
-** Return the index into azField[] of the given field name.
75
-** Return -1 if zField is not in azField[].
76
-*/
77
-static int fieldId(const char *zField){
78
- int i;
79
- for(i=0; i<nField; i++){
80
- if( fossil_strcmp(azField[i], zField)==0 ) return i;
81
- }
82
- return -1;
105
+ qsort(aField, nField, sizeof(aField[0]), nameCmpr);
106
+ for(i=0; i<nField; i++){
107
+ aField[i].zValue = "";
108
+ aField[i].zAppend = 0;
109
+ }
83110
}
84111
85112
/*
86113
** Query the database for all TICKET fields for the specific
87114
** ticket whose name is given by the "name" CGI parameter.
@@ -114,38 +141,24 @@
114141
if( zVal==0 ){
115142
zVal = "";
116143
}else if( strncmp(zName, "private_", 8)==0 ){
117144
zVal = zRevealed = db_reveal(zVal);
118145
}
119
- for(j=0; j<nField; j++){
120
- if( fossil_strcmp(azField[j],zName)==0 ){
121
- azValue[j] = mprintf("%s", zVal);
122
- break;
123
- }
124
- }
125
- if( Th_Fetch(zName, &size)==0 ){
146
+ if( (j = fieldId(zName))>=0 ){
147
+ aField[j].zValue = mprintf("%s", zVal);
148
+ }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
126149
Th_Store(zName, zVal);
127150
}
128151
free(zRevealed);
129152
}
130
- }else{
131
- db_finalize(&q);
132
- db_prepare(&q, "PRAGMA table_info(ticket)");
133
- if( Th_Fetch("tkt_uuid",&size)==0 ){
134
- Th_Store("tkt_uuid",zName);
135
- }
136
- while( db_step(&q)==SQLITE_ROW ){
137
- const char *zField = db_column_text(&q, 1);
138
- if( Th_Fetch(zField, &size)==0 ){
139
- Th_Store(zField, "");
140
- }
141
- }
142
- if( Th_Fetch("tkt_datetime",&size)==0 ){
143
- Th_Store("tkt_datetime","");
144
- }
145
- }
146
- db_finalize(&q);
153
+ }
154
+ db_finalize(&q);
155
+ for(i=0; i<nField; i++){
156
+ if( Th_Fetch(aField[i].zName, &size)==0 ){
157
+ Th_Store(aField[i].zName, aField[i].zValue);
158
+ }
159
+ }
147160
}
148161
149162
/*
150163
** Transfer all CGI parameters to variables in the interpreter.
151164
*/
@@ -157,56 +170,73 @@
157170
Th_Store(z, P(z));
158171
}
159172
}
160173
161174
/*
162
-** Update an entry of the TICKET table according to the information
163
-** in the control file given in p. Attempt to create the appropriate
164
-** TICKET table entry if createFlag is true. If createFlag is false,
165
-** that means we already know the entry exists and so we can save the
166
-** work of trying to create it.
175
+** Update an entry of the TICKET and TICKETCHNG tables according to the
176
+** information in the ticket artifact given in p. Attempt to create
177
+** the appropriate TICKET table entry if tktid is zero. If tktid is nonzero
178
+** then it will be the ROWID of an existing TICKET entry.
179
+**
180
+** Parameter rid is the recordID for the ticket artifact in the BLOB table.
167181
**
168
-** Return TRUE if a new TICKET entry was created and FALSE if an
169
-** existing entry was revised.
182
+** Return the new rowid of the TICKET table entry.
170183
*/
171
-int ticket_insert(const Manifest *p, int createFlag, int rid){
172
- Blob sql;
184
+static int ticket_insert(const Manifest *p, int rid, int tktid){
185
+ Blob sql1, sql2, sql3;
173186
Stmt q;
174
- int i;
175
- int rc = 0;
187
+ int i, j;
176188
177
- getAllTicketFields();
178
- if( createFlag ){
179
- db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
189
+ if( tktid==0 ){
190
+ db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
180191
"VALUES(%Q, 0)", p->zTicketUuid);
181
- rc = db_changes();
192
+ tktid = db_last_insert_rowid();
182193
}
183
- blob_zero(&sql);
184
- blob_appendf(&sql, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
194
+ blob_zero(&sql1);
195
+ blob_zero(&sql2);
196
+ blob_zero(&sql3);
197
+ blob_appendf(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
185198
for(i=0; i<p->nField; i++){
186199
const char *zName = p->aField[i].zName;
187200
if( zName[0]=='+' ){
188201
zName++;
189
- if( fieldId(zName)<0 ) continue;
190
- blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
191
- zName, zName, p->aField[i].zValue);
202
+ if( (j = fieldId(zName))<0 ) continue;
203
+ if( aField[j].mUsed & USEDBY_TICKET ){
204
+ blob_appendf(&sql1,", %s=coalesce(%s,'') || %Q",
205
+ zName, zName, p->aField[i].zValue);
206
+ }
192207
}else{
193
- if( fieldId(zName)<0 ) continue;
194
- blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
208
+ if( (j = fieldId(zName))<0 ) continue;
209
+ if( aField[j].mUsed & USEDBY_TICKET ){
210
+ blob_appendf(&sql1,", %s=%Q", zName, p->aField[i].zValue);
211
+ }
212
+ }
213
+ if( aField[j].mUsed & USEDBY_TICKETCHNG ){
214
+ blob_appendf(&sql2, ",%s", zName);
215
+ blob_appendf(&sql3, ",%Q", p->aField[i].zValue);
195216
}
196217
if( rid>0 ){
197218
wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
198219
}
199220
}
200
- blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
201
- p->zTicketUuid);
202
- db_prepare(&q, "%s", blob_str(&sql));
221
+ blob_appendf(&sql1, " WHERE tkt_id=%d", tktid);
222
+ db_prepare(&q, "%s", blob_str(&sql1));
203223
db_bind_double(&q, ":mtime", p->rDate);
204224
db_step(&q);
205225
db_finalize(&q);
206
- blob_reset(&sql);
207
- return rc;
226
+ blob_reset(&sql1);
227
+ if( blob_size(&sql2)>0 ){
228
+ db_prepare(&q, "INSERT INTO ticketchng(tkt_id,tkt_mtime%s)"
229
+ "VALUES(%d,:mtime%s)",
230
+ blob_str(&sql2), tktid, blob_str(&sql3));
231
+ db_bind_double(&q, ":mtime", p->rDate);
232
+ db_step(&q);
233
+ db_finalize(&q);
234
+ }
235
+ blob_reset(&sql2);
236
+ blob_reset(&sql3);
237
+ return tktid;
208238
}
209239
210240
/*
211241
** Rebuild an entire entry in the TICKET table
212242
*/
@@ -213,67 +243,78 @@
213243
void ticket_rebuild_entry(const char *zTktUuid){
214244
char *zTag = mprintf("tkt-%s", zTktUuid);
215245
int tagid = tag_findid(zTag, 1);
216246
Stmt q;
217247
Manifest *pTicket;
248
+ int tktid;
218249
int createFlag = 1;
219250
220
- fossil_free(zTag);
221
- db_multi_exec(
222
- "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
223
- );
251
+ fossil_free(zTag);
252
+ getAllTicketFields();
253
+ if( haveTicket==0 ) return;
254
+ tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid);
255
+ if( haveTicketChng ){
256
+ db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid);
257
+ }
258
+ db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid);
259
+ tktid = 0;
224260
db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
225261
while( db_step(&q)==SQLITE_ROW ){
226262
int rid = db_column_int(&q, 0);
227263
pTicket = manifest_get(rid, CFTYPE_TICKET);
228264
if( pTicket ){
229
- ticket_insert(pTicket, createFlag, rid);
265
+ tktid = ticket_insert(pTicket, rid, tktid);
230266
manifest_ticket_event(rid, pTicket, createFlag, tagid);
231267
manifest_destroy(pTicket);
232268
}
233269
createFlag = 0;
234270
}
235271
db_finalize(&q);
236272
}
237273
238274
/*
239
-** Create the subscript interpreter and load the "common" code.
275
+** Create the TH1 interpreter and load the "common" code.
240276
*/
241277
void ticket_init(void){
242278
const char *zConfig;
243279
Th_FossilInit(0, 0);
244280
zConfig = ticket_common_code();
245281
Th_Eval(g.interp, 0, zConfig, -1);
246282
}
247283
248284
/*
249
-** Create the subscript interpreter and load the "change" code.
285
+** Create the TH1 interpreter and load the "change" code.
250286
*/
251287
int ticket_change(void){
252288
const char *zConfig;
253289
Th_FossilInit(0, 0);
254290
zConfig = ticket_change_code();
255291
return Th_Eval(g.interp, 0, zConfig, -1);
256292
}
257293
258294
/*
259
-** Recreate the ticket table.
295
+** Recreate the TICKET and TICKETCHNG tables.
260296
*/
261297
void ticket_create_table(int separateConnection){
262298
const char *zSql;
263299
264
- db_multi_exec("DROP TABLE IF EXISTS ticket;");
300
+ db_multi_exec(
301
+ "DROP TABLE IF EXISTS ticket;"
302
+ "DROP TABLE IF EXISTS ticketchng;"
303
+ );
265304
zSql = ticket_table_schema();
266305
if( separateConnection ){
306
+ db_end_transaction(0);
267307
db_init_database(g.zRepositoryName, zSql, 0);
268308
}else{
269309
db_multi_exec("%s", zSql);
270310
}
271311
}
272312
273313
/*
274
-** Repopulate the ticket table
314
+** Repopulate the TICKET and TICKETCHNG tables from scratch using all
315
+** available ticket artifacts.
275316
*/
276317
void ticket_rebuild(void){
277318
Stmt q;
278319
ticket_create_table(1);
279320
db_begin_transaction();
@@ -287,10 +328,30 @@
287328
ticket_rebuild_entry(zName);
288329
}
289330
db_finalize(&q);
290331
db_end_transaction(0);
291332
}
333
+
334
+/*
335
+** For trouble-shooting purposes, render a dump of the aField[] table to
336
+** the webpage currently under construction.
337
+*/
338
+static void showAllFields(void){
339
+ int i;
340
+ @ <font color="blue">
341
+ @ <p>Database fields:</p><ul>
342
+ for(i=0; i<nField; i++){
343
+ @ <li>aField[%d(i)].zName = "%h(aField[i].zName)";
344
+ @ originally = "%h(aField[i].zValue)";
345
+ @ currently = "%h(PD(aField[i].zName,""))"";
346
+ if( aField[i].zAppend ){
347
+ @ zAppend = "%h(aField[i].zAppend)";
348
+ }
349
+ @ mUsed = %d(aField[i].mUsed);
350
+ }
351
+ @ </ul></font>
352
+}
292353
293354
/*
294355
** WEBPAGE: tktview
295356
** URL: tktview?name=UUID
296357
**
@@ -322,15 +383,24 @@
322383
if( g.perm.ApndTkt && g.perm.Attach ){
323384
style_submenu_element("Attach", "Add An Attachment",
324385
"%s/attachadd?tkt=%T&from=%s/tktview/%t",
325386
g.zTop, zUuid, g.zTop, zUuid);
326387
}
388
+ if( P("plaintext") ){
389
+ style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid);
390
+ }else{
391
+ style_submenu_element("Plaintext", "Plaintext",
392
+ "%R/tktview/%S?plaintext", zUuid);
393
+ }
327394
style_header("View Ticket");
328395
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
329396
ticket_init();
397
+ initializeVariablesFromCGI();
398
+ getAllTicketFields();
330399
initializeVariablesFromDb();
331400
zScript = ticket_viewpage_code();
401
+ if( P("showfields")!=0 ) showAllFields();
332402
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
333403
Th_Render(zScript);
334404
if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
335405
336406
zFullName = db_text(0,
@@ -366,20 +436,20 @@
366436
if( g.thTrace ){
367437
Th_Trace("append_field %#h {%#h}<br />\n",
368438
argl[1], argv[1], argl[2], argv[2]);
369439
}
370440
for(idx=0; idx<nField; idx++){
371
- if( strncmp(azField[idx], argv[1], argl[1])==0
372
- && azField[idx][argl[1]]==0 ){
441
+ if( memcmp(aField[idx].zName, argv[1], argl[1])==0
442
+ && aField[idx].zName[argl[1]]==0 ){
373443
break;
374444
}
375445
}
376446
if( idx>=nField ){
377447
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
378448
return TH_ERROR;
379449
}
380
- azAppend[idx] = mprintf("%.*s", argl[2], argv[2]);
450
+ aField[idx].zAppend = mprintf("%.*s", argl[2], argv[2]);
381451
return TH_OK;
382452
}
383453
384454
/*
385455
** Write a ticket into the repository.
@@ -440,29 +510,32 @@
440510
blob_zero(&tktchng);
441511
zDate = date_in_standard_format("now");
442512
blob_appendf(&tktchng, "D %s\n", zDate);
443513
free(zDate);
444514
for(i=0; i<nField; i++){
445
- if( azAppend[i] ){
446
- blob_appendf(&tktchng, "J +%s %z\n", azField[i],
447
- fossilize(azAppend[i], -1));
515
+ if( aField[i].zAppend ){
516
+ blob_appendf(&tktchng, "J +%s %z\n", aField[i].zName,
517
+ fossilize(aField[i].zAppend, -1));
448518
++nJ;
449519
}
450520
}
451521
for(i=0; i<nField; i++){
452522
const char *zValue;
453523
int nValue;
454
- if( azAppend[i] ) continue;
455
- zValue = Th_Fetch(azField[i], &nValue);
524
+ if( aField[i].zAppend ) continue;
525
+ zValue = Th_Fetch(aField[i].zName, &nValue);
456526
if( zValue ){
457527
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
458
- if( strncmp(zValue, azValue[i], nValue) || strlen(azValue[i])!=nValue ){
459
- if( strncmp(azField[i], "private_", 8)==0 ){
528
+ if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
529
+ || memcmp(zValue, aField[i].zValue, nValue)!=0
530
+ || strlen(aField[i].zValue)!=nValue
531
+ ){
532
+ if( memcmp(aField[i].zName, "private_", 8)==0 ){
460533
zValue = db_conceal(zValue, nValue);
461
- blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue);
534
+ blob_appendf(&tktchng, "J %s %s\n", aField[i].zName, zValue);
462535
}else{
463
- blob_appendf(&tktchng, "J %s %#F\n", azField[i], nValue, zValue);
536
+ blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
464537
}
465538
nJ++;
466539
}
467540
}
468541
}
@@ -523,19 +596,19 @@
523596
cgi_redirect("home");
524597
}
525598
style_header("New Ticket");
526599
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
527600
ticket_init();
601
+ initializeVariablesFromCGI();
528602
getAllTicketFields();
529603
initializeVariablesFromDb();
530
- initializeVariablesFromCGI();
604
+ if( g.zPath[0]=='d' ) showAllFields();
531605
form_begin(0, "%R/%s", g.zPath);
532606
login_insert_csrf_secret();
533607
if( P("date_override") && g.perm.Setup ){
534608
@ <input type="hidden" name="date_override" value="%h(P("date_override"))">
535609
}
536
- @ </p>
537610
zScript = ticket_newpage_code();
538611
Th_Store("login", g.zLogin ? g.zLogin : "nobody");
539612
Th_Store("date", db_text(0, "SELECT datetime('now')"));
540613
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
541614
(void*)&zNewUuid, 0);
@@ -596,14 +669,14 @@
596669
if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);
597670
ticket_init();
598671
getAllTicketFields();
599672
initializeVariablesFromCGI();
600673
initializeVariablesFromDb();
674
+ if( g.zPath[0]=='d' ) showAllFields();
601675
form_begin(0, "%R/%s", g.zPath);
602676
@ <input type="hidden" name="name" value="%s(zName)" />
603677
login_insert_csrf_secret();
604
- @ </p>
605678
zScript = ticket_editpage_code();
606679
Th_Store("login", g.zLogin ? g.zLogin : "nobody");
607680
Th_Store("date", db_text(0, "SELECT datetime('now')"));
608681
Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
609682
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
@@ -635,18 +708,23 @@
635708
sqlite3_close(db);
636709
return zErr;
637710
}
638711
rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
639712
0, 0, 0);
640
- sqlite3_close(db);
641713
if( rc!=SQLITE_OK ){
642
- zErr = mprintf("schema fails to define a valid ticket table "
643
- "containing all required fields");
644
- return zErr;
714
+ zErr = mprintf("schema fails to define valid a TICKET "
715
+ "table containing all required fields");
716
+ }else{
717
+ rc = sqlite3_exec(db, "SELECT tkt_id, tkt_mtime FROM ticketchng", 0,0,0);
718
+ if( rc!=SQLITE_OK ){
719
+ zErr = mprintf("schema fails to define valid a TICKETCHNG "
720
+ "table containing all required fields");
721
+ }
645722
}
723
+ sqlite3_close(db);
646724
}
647
- return 0;
725
+ return zErr;
648726
}
649727
650728
/*
651729
** WEBPAGE: tkttimeline
652730
** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -734,10 +812,11 @@
734812
void tkthistory_page(void){
735813
Stmt q;
736814
char *zTitle;
737815
const char *zUuid;
738816
int tagid;
817
+ int nChng = 0;
739818
740819
login_check_credentials();
741820
if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
742821
zUuid = PD("name","");
743822
zTitle = mprintf("History Of Ticket %h", zUuid);
@@ -745,10 +824,17 @@
745824
"%s/info/%s", g.zTop, zUuid);
746825
style_submenu_element("Check-ins", "Check-ins",
747826
"%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
748827
style_submenu_element("Timeline", "Timeline",
749828
"%s/tkttimeline?name=%s", g.zTop, zUuid);
829
+ if( P("plaintext")!=0 ){
830
+ style_submenu_element("Formatted", "Formatted",
831
+ "%R/tkthistory/%S", zUuid);
832
+ }else{
833
+ style_submenu_element("Plaintext", "Plaintext",
834
+ "%R/tkthistory/%S?plaintext", zUuid);
835
+ }
750836
style_header(zTitle);
751837
free(zTitle);
752838
753839
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
754840
if( tagid==0 ){
@@ -764,11 +850,11 @@
764850
" UNION "
765851
"SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
766852
" FROM attachment, blob"
767853
" WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
768854
" AND blob.rid=attachid"
769
- " ORDER BY 1 DESC",
855
+ " ORDER BY 1",
770856
tagid, tagid
771857
);
772858
while( db_step(&q)==SQLITE_ROW ){
773859
Manifest *pTicket;
774860
char zShort[12];
@@ -776,40 +862,48 @@
776862
int rid = db_column_int(&q, 1);
777863
const char *zChngUuid = db_column_text(&q, 2);
778864
const char *zFile = db_column_text(&q, 4);
779865
memcpy(zShort, zChngUuid, 10);
780866
zShort[10] = 0;
867
+ if( nChng==0 ){
868
+ @ <ol>
869
+ }
870
+ nChng++;
781871
if( zFile!=0 ){
782872
const char *zSrc = db_column_text(&q, 3);
783873
const char *zUser = db_column_text(&q, 5);
784874
if( zSrc==0 || zSrc[0]==0 ){
785875
@
786
- @ <p>Delete attachment "%h(zFile)"
876
+ @ <li><p>Delete attachment "%h(zFile)"
787877
}else{
788878
@
789
- @ <p>Add attachment "%h(zFile)"
879
+ @ <li><p>Add attachment
880
+ @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>"
790881
}
791882
@ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
792883
@ (rid %d(rid)) by
793884
hyperlink_to_user(zUser,zDate," on");
794885
hyperlink_to_date(zDate, ".</p>");
795886
}else{
796887
pTicket = manifest_get(rid, CFTYPE_TICKET);
797888
if( pTicket ){
798889
@
799
- @ <p>Ticket change
890
+ @ <li><p>Ticket change
800891
@ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
801892
@ (rid %d(rid)) by
802893
hyperlink_to_user(pTicket->zUser,zDate," on");
803894
hyperlink_to_date(zDate, ":");
804895
@ </p>
805
- ticket_output_change_artifact(pTicket);
896
+ ticket_output_change_artifact(pTicket, "a");
806897
}
807898
manifest_destroy(pTicket);
808899
}
809900
}
810901
db_finalize(&q);
902
+ if( nChng ){
903
+ @ </ol>
904
+ }
811905
style_footer();
812906
}
813907
814908
/*
815909
** Return TRUE if the given BLOB contains a newline character.
@@ -825,26 +919,35 @@
825919
826920
/*
827921
** The pTkt object is a ticket change artifact. Output a detailed
828922
** description of this object.
829923
*/
830
-void ticket_output_change_artifact(Manifest *pTkt){
924
+void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){
831925
int i;
832
- @ <ol>
926
+ int wikiFlags = WIKI_NOBADLINKS;
927
+ const char *zBlock = "<blockquote>";
928
+ const char *zEnd = "</blockquote>";
929
+ if( P("plaintext")!=0 ){
930
+ wikiFlags |= WIKI_LINKSONLY;
931
+ zBlock = "<blockquote><pre class='verbatim'>";
932
+ zEnd = "</pre></blockquote>";
933
+ }
934
+ if( zListType==0 ) zListType = "1";
935
+ @ <ol type="%s(zListType)">
833936
for(i=0; i<pTkt->nField; i++){
834937
Blob val;
835938
const char *z;
836939
z = pTkt->aField[i].zName;
837940
blob_set(&val, pTkt->aField[i].zValue);
838941
if( z[0]=='+' ){
839
- @ <li>Appended to %h(&z[1]):<blockquote>
840
- wiki_convert(&val, 0, WIKI_NOBADLINKS);
841
- @ </blockquote></li>
842
- }else if( blob_size(&val)<=50 && contains_newline(&val) ){
843
- @ <li>Change %h(z) to:<blockquote>
844
- wiki_convert(&val, 0, WIKI_NOBADLINKS);
845
- @ </blockquote></li>
942
+ @ <li>Appended to %h(&z[1]):%s(zBlock)
943
+ wiki_convert(&val, 0, wikiFlags);
944
+ @ %s(zEnd)</li>
945
+ }else if( blob_size(&val)>50 || contains_newline(&val) ){
946
+ @ <li>Change %h(z) to:%s(zBlock)
947
+ wiki_convert(&val, 0, wikiFlags);
948
+ @ %s(zEnd)</li>
846949
}else{
847950
@ <li>Change %h(z) to "%h(blob_str(&val))"</li>
848951
}
849952
blob_reset(&val);
850953
}
@@ -966,11 +1069,11 @@
9661069
int i;
9671070
9681071
/* read all available ticket fields */
9691072
getAllTicketFields();
9701073
for(i=0; i<nField; i++){
971
- printf("%s\n",azField[i]);
1074
+ printf("%s\n",aField[i].zName);
9721075
}
9731076
}else if( !strncmp(g.argv[3],"reports",n) ){
9741077
rpt_list_reports();
9751078
}else{
9761079
fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1041,21 +1144,23 @@
10411144
int tagid;
10421145
10431146
if ( i != g.argc ){
10441147
fossil_fatal("no other parameters expected to %s!",g.argv[2]);
10451148
}
1046
- tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid);
1149
+ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
1150
+ zTktUuid);
10471151
if( tagid==0 ){
10481152
fossil_fatal("no such ticket %h", zTktUuid);
10491153
}
10501154
db_prepare(&q,
10511155
"SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
10521156
" FROM event, blob"
10531157
" WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
10541158
" AND blob.rid=event.objid"
10551159
" UNION "
1056
- "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
1160
+ "SELECT datetime(mtime,'localtime'), attachid, uuid, src, "
1161
+ " filename, user"
10571162
" FROM attachment, blob"
10581163
" WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
10591164
" AND blob.rid=attachid"
10601165
" ORDER BY 1 DESC",
10611166
tagid, tagid
@@ -1071,21 +1176,22 @@
10711176
zShort[10] = 0;
10721177
if( zFile!=0 ){
10731178
const char *zSrc = db_column_text(&q, 3);
10741179
const char *zUser = db_column_text(&q, 5);
10751180
if( zSrc==0 || zSrc[0]==0 ){
1076
- fossil_print("Delete attachment %h\n", zFile);
1181
+ fossil_print("Delete attachment %s\n", zFile);
10771182
}else{
1078
- fossil_print("Add attachment %h\n", zFile);
1183
+ fossil_print("Add attachment %s\n", zFile);
10791184
}
1080
- fossil_print(" by %h on %h\n", zUser, zDate);
1185
+ fossil_print(" by %s on %s\n", zUser, zDate);
10811186
}else{
10821187
pTicket = manifest_get(rid, CFTYPE_TICKET);
10831188
if( pTicket ){
10841189
int i;
10851190
1086
- fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate);
1191
+ fossil_print("Ticket Change by %s on %s:\n",
1192
+ pTicket->zUser, zDate);
10871193
for(i=0; i<pTicket->nField; i++){
10881194
Blob val;
10891195
const char *z;
10901196
z = pTicket->aField[i].zName;
10911197
blob_set(&val, pTicket->aField[i].zValue);
@@ -1114,11 +1220,11 @@
11141220
/* read all given ticket field/value pairs from command line */
11151221
if( i==g.argc ){
11161222
fossil_fatal("empty %s command aborted!",g.argv[2]);
11171223
}
11181224
getAllTicketFields();
1119
- /* read commandline and assign fields in the azValue array */
1225
+ /* read commandline and assign fields in the aField[].zValue array */
11201226
while( i<g.argc ){
11211227
char *zFName;
11221228
char *zFValue;
11231229
int j;
11241230
int append = 0;
@@ -1139,13 +1245,13 @@
11391245
j = fieldId(zFName);
11401246
if( j == -1 ){
11411247
fossil_fatal("unknown field name '%s'!",zFName);
11421248
}else{
11431249
if (append) {
1144
- azAppend[j] = zFValue;
1250
+ aField[j].zAppend = zFValue;
11451251
} else {
1146
- azValue[j] = zFValue;
1252
+ aField[j].zValue = zFValue;
11471253
}
11481254
}
11491255
}
11501256
11511257
/* now add the needed artifacts to the repository */
@@ -1155,25 +1261,25 @@
11551261
/* append defined elements */
11561262
for(i=0; i<nField; i++){
11571263
char *zValue = 0;
11581264
char *zPfx;
11591265
1160
- if (azAppend[i] && azAppend[i][0] ){
1266
+ if (aField[i].zAppend && aField[i].zAppend[0] ){
11611267
zPfx = " +";
1162
- zValue = azAppend[i];
1163
- } else if( azValue[i] && azValue[i][0] ){
1268
+ zValue = aField[i].zAppend;
1269
+ } else if( aField[i].zValue && aField[i].zValue[0] ){
11641270
zPfx = " ";
1165
- zValue = azValue[i];
1271
+ zValue = aField[i].zValue;
11661272
} else {
11671273
continue;
11681274
}
1169
- if( strncmp(azField[i], "private_", 8)==0 ){
1275
+ if( memcmp(aField[i].zName, "private_", 8)==0 ){
11701276
zValue = db_conceal(zValue, strlen(zValue));
1171
- blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue);
1277
+ blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue);
11721278
}else{
11731279
blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1174
- azField[i], strlen(zValue), zValue);
1280
+ aField[i].zName, strlen(zValue), zValue);
11751281
}
11761282
}
11771283
blob_appendf(&tktchng, "K %s\n", zTktUuid);
11781284
blob_appendf(&tktchng, "U %F\n", zUser);
11791285
md5sum_blob(&tktchng, &cksum);
11801286
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
26 ** The list of database user-defined fields in the TICKET table.
27 ** The real table also contains some addition fields for internal
28 ** used. The internal-use fields begin with "tkt_".
29 */
30 static int nField = 0;
31 static char **azField = 0; /* Names of database fields */
32 static char **azValue = 0; /* Original values */
33 static char **azAppend = 0; /* Value to be appended */
 
 
 
 
 
 
 
34
35 /*
36 ** Compare two entries in azField for sorting purposes
37 */
38 static int nameCmpr(const void *a, const void *b){
39 return fossil_strcmp(*(char**)a, *(char**)b);
 
 
 
 
 
 
 
 
 
 
 
 
 
40 }
41
42 /*
43 ** Obtain a list of all fields of the TICKET table. Put them
44 ** in sorted order in azField[].
45 **
46 ** Also allocate space for azValue[] and azAppend[] and initialize
47 ** all the values there to zero.
48 */
49 static void getAllTicketFields(void){
50 Stmt q;
51 int i;
52 if( nField>0 ) return;
 
 
53 db_prepare(&q, "PRAGMA table_info(ticket)");
54 while( db_step(&q)==SQLITE_ROW ){
55 const char *zField = db_column_text(&q, 1);
56 if( strncmp(zField,"tkt_",4)==0 ) continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57 if( nField%10==0 ){
58 azField = fossil_realloc(azField, sizeof(azField)*3*(nField+10) );
59 }
60 azField[nField] = mprintf("%s", zField);
 
61 nField++;
62 }
63 db_finalize(&q);
64 qsort(azField, nField, sizeof(azField[0]), nameCmpr);
65 azAppend = &azField[nField];
66 memset(azAppend, 0, sizeof(azAppend[0])*nField);
67 azValue = &azAppend[nField];
68 for(i=0; i<nField; i++){
69 azValue[i] = "";
70 }
71 }
72
73 /*
74 ** Return the index into azField[] of the given field name.
75 ** Return -1 if zField is not in azField[].
76 */
77 static int fieldId(const char *zField){
78 int i;
79 for(i=0; i<nField; i++){
80 if( fossil_strcmp(azField[i], zField)==0 ) return i;
81 }
82 return -1;
83 }
84
85 /*
86 ** Query the database for all TICKET fields for the specific
87 ** ticket whose name is given by the "name" CGI parameter.
@@ -114,38 +141,24 @@
114 if( zVal==0 ){
115 zVal = "";
116 }else if( strncmp(zName, "private_", 8)==0 ){
117 zVal = zRevealed = db_reveal(zVal);
118 }
119 for(j=0; j<nField; j++){
120 if( fossil_strcmp(azField[j],zName)==0 ){
121 azValue[j] = mprintf("%s", zVal);
122 break;
123 }
124 }
125 if( Th_Fetch(zName, &size)==0 ){
126 Th_Store(zName, zVal);
127 }
128 free(zRevealed);
129 }
130 }else{
131 db_finalize(&q);
132 db_prepare(&q, "PRAGMA table_info(ticket)");
133 if( Th_Fetch("tkt_uuid",&size)==0 ){
134 Th_Store("tkt_uuid",zName);
135 }
136 while( db_step(&q)==SQLITE_ROW ){
137 const char *zField = db_column_text(&q, 1);
138 if( Th_Fetch(zField, &size)==0 ){
139 Th_Store(zField, "");
140 }
141 }
142 if( Th_Fetch("tkt_datetime",&size)==0 ){
143 Th_Store("tkt_datetime","");
144 }
145 }
146 db_finalize(&q);
147 }
148
149 /*
150 ** Transfer all CGI parameters to variables in the interpreter.
151 */
@@ -157,56 +170,73 @@
157 Th_Store(z, P(z));
158 }
159 }
160
161 /*
162 ** Update an entry of the TICKET table according to the information
163 ** in the control file given in p. Attempt to create the appropriate
164 ** TICKET table entry if createFlag is true. If createFlag is false,
165 ** that means we already know the entry exists and so we can save the
166 ** work of trying to create it.
 
167 **
168 ** Return TRUE if a new TICKET entry was created and FALSE if an
169 ** existing entry was revised.
170 */
171 int ticket_insert(const Manifest *p, int createFlag, int rid){
172 Blob sql;
173 Stmt q;
174 int i;
175 int rc = 0;
176
177 getAllTicketFields();
178 if( createFlag ){
179 db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
180 "VALUES(%Q, 0)", p->zTicketUuid);
181 rc = db_changes();
182 }
183 blob_zero(&sql);
184 blob_appendf(&sql, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
 
 
185 for(i=0; i<p->nField; i++){
186 const char *zName = p->aField[i].zName;
187 if( zName[0]=='+' ){
188 zName++;
189 if( fieldId(zName)<0 ) continue;
190 blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
191 zName, zName, p->aField[i].zValue);
 
 
192 }else{
193 if( fieldId(zName)<0 ) continue;
194 blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
 
 
 
 
 
 
195 }
196 if( rid>0 ){
197 wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
198 }
199 }
200 blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
201 p->zTicketUuid);
202 db_prepare(&q, "%s", blob_str(&sql));
203 db_bind_double(&q, ":mtime", p->rDate);
204 db_step(&q);
205 db_finalize(&q);
206 blob_reset(&sql);
207 return rc;
 
 
 
 
 
 
 
 
 
 
208 }
209
210 /*
211 ** Rebuild an entire entry in the TICKET table
212 */
@@ -213,67 +243,78 @@
213 void ticket_rebuild_entry(const char *zTktUuid){
214 char *zTag = mprintf("tkt-%s", zTktUuid);
215 int tagid = tag_findid(zTag, 1);
216 Stmt q;
217 Manifest *pTicket;
 
218 int createFlag = 1;
219
220 fossil_free(zTag);
221 db_multi_exec(
222 "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
223 );
 
 
 
 
 
224 db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
225 while( db_step(&q)==SQLITE_ROW ){
226 int rid = db_column_int(&q, 0);
227 pTicket = manifest_get(rid, CFTYPE_TICKET);
228 if( pTicket ){
229 ticket_insert(pTicket, createFlag, rid);
230 manifest_ticket_event(rid, pTicket, createFlag, tagid);
231 manifest_destroy(pTicket);
232 }
233 createFlag = 0;
234 }
235 db_finalize(&q);
236 }
237
238 /*
239 ** Create the subscript interpreter and load the "common" code.
240 */
241 void ticket_init(void){
242 const char *zConfig;
243 Th_FossilInit(0, 0);
244 zConfig = ticket_common_code();
245 Th_Eval(g.interp, 0, zConfig, -1);
246 }
247
248 /*
249 ** Create the subscript interpreter and load the "change" code.
250 */
251 int ticket_change(void){
252 const char *zConfig;
253 Th_FossilInit(0, 0);
254 zConfig = ticket_change_code();
255 return Th_Eval(g.interp, 0, zConfig, -1);
256 }
257
258 /*
259 ** Recreate the ticket table.
260 */
261 void ticket_create_table(int separateConnection){
262 const char *zSql;
263
264 db_multi_exec("DROP TABLE IF EXISTS ticket;");
 
 
 
265 zSql = ticket_table_schema();
266 if( separateConnection ){
 
267 db_init_database(g.zRepositoryName, zSql, 0);
268 }else{
269 db_multi_exec("%s", zSql);
270 }
271 }
272
273 /*
274 ** Repopulate the ticket table
 
275 */
276 void ticket_rebuild(void){
277 Stmt q;
278 ticket_create_table(1);
279 db_begin_transaction();
@@ -287,10 +328,30 @@
287 ticket_rebuild_entry(zName);
288 }
289 db_finalize(&q);
290 db_end_transaction(0);
291 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
293 /*
294 ** WEBPAGE: tktview
295 ** URL: tktview?name=UUID
296 **
@@ -322,15 +383,24 @@
322 if( g.perm.ApndTkt && g.perm.Attach ){
323 style_submenu_element("Attach", "Add An Attachment",
324 "%s/attachadd?tkt=%T&from=%s/tktview/%t",
325 g.zTop, zUuid, g.zTop, zUuid);
326 }
 
 
 
 
 
 
327 style_header("View Ticket");
328 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
329 ticket_init();
 
 
330 initializeVariablesFromDb();
331 zScript = ticket_viewpage_code();
 
332 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
333 Th_Render(zScript);
334 if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
335
336 zFullName = db_text(0,
@@ -366,20 +436,20 @@
366 if( g.thTrace ){
367 Th_Trace("append_field %#h {%#h}<br />\n",
368 argl[1], argv[1], argl[2], argv[2]);
369 }
370 for(idx=0; idx<nField; idx++){
371 if( strncmp(azField[idx], argv[1], argl[1])==0
372 && azField[idx][argl[1]]==0 ){
373 break;
374 }
375 }
376 if( idx>=nField ){
377 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
378 return TH_ERROR;
379 }
380 azAppend[idx] = mprintf("%.*s", argl[2], argv[2]);
381 return TH_OK;
382 }
383
384 /*
385 ** Write a ticket into the repository.
@@ -440,29 +510,32 @@
440 blob_zero(&tktchng);
441 zDate = date_in_standard_format("now");
442 blob_appendf(&tktchng, "D %s\n", zDate);
443 free(zDate);
444 for(i=0; i<nField; i++){
445 if( azAppend[i] ){
446 blob_appendf(&tktchng, "J +%s %z\n", azField[i],
447 fossilize(azAppend[i], -1));
448 ++nJ;
449 }
450 }
451 for(i=0; i<nField; i++){
452 const char *zValue;
453 int nValue;
454 if( azAppend[i] ) continue;
455 zValue = Th_Fetch(azField[i], &nValue);
456 if( zValue ){
457 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
458 if( strncmp(zValue, azValue[i], nValue) || strlen(azValue[i])!=nValue ){
459 if( strncmp(azField[i], "private_", 8)==0 ){
 
 
 
460 zValue = db_conceal(zValue, nValue);
461 blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue);
462 }else{
463 blob_appendf(&tktchng, "J %s %#F\n", azField[i], nValue, zValue);
464 }
465 nJ++;
466 }
467 }
468 }
@@ -523,19 +596,19 @@
523 cgi_redirect("home");
524 }
525 style_header("New Ticket");
526 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
527 ticket_init();
 
528 getAllTicketFields();
529 initializeVariablesFromDb();
530 initializeVariablesFromCGI();
531 form_begin(0, "%R/%s", g.zPath);
532 login_insert_csrf_secret();
533 if( P("date_override") && g.perm.Setup ){
534 @ <input type="hidden" name="date_override" value="%h(P("date_override"))">
535 }
536 @ </p>
537 zScript = ticket_newpage_code();
538 Th_Store("login", g.zLogin ? g.zLogin : "nobody");
539 Th_Store("date", db_text(0, "SELECT datetime('now')"));
540 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
541 (void*)&zNewUuid, 0);
@@ -596,14 +669,14 @@
596 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);
597 ticket_init();
598 getAllTicketFields();
599 initializeVariablesFromCGI();
600 initializeVariablesFromDb();
 
601 form_begin(0, "%R/%s", g.zPath);
602 @ <input type="hidden" name="name" value="%s(zName)" />
603 login_insert_csrf_secret();
604 @ </p>
605 zScript = ticket_editpage_code();
606 Th_Store("login", g.zLogin ? g.zLogin : "nobody");
607 Th_Store("date", db_text(0, "SELECT datetime('now')"));
608 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
609 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
@@ -635,18 +708,23 @@
635 sqlite3_close(db);
636 return zErr;
637 }
638 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
639 0, 0, 0);
640 sqlite3_close(db);
641 if( rc!=SQLITE_OK ){
642 zErr = mprintf("schema fails to define a valid ticket table "
643 "containing all required fields");
644 return zErr;
 
 
 
 
 
645 }
 
646 }
647 return 0;
648 }
649
650 /*
651 ** WEBPAGE: tkttimeline
652 ** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -734,10 +812,11 @@
734 void tkthistory_page(void){
735 Stmt q;
736 char *zTitle;
737 const char *zUuid;
738 int tagid;
 
739
740 login_check_credentials();
741 if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
742 zUuid = PD("name","");
743 zTitle = mprintf("History Of Ticket %h", zUuid);
@@ -745,10 +824,17 @@
745 "%s/info/%s", g.zTop, zUuid);
746 style_submenu_element("Check-ins", "Check-ins",
747 "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
748 style_submenu_element("Timeline", "Timeline",
749 "%s/tkttimeline?name=%s", g.zTop, zUuid);
 
 
 
 
 
 
 
750 style_header(zTitle);
751 free(zTitle);
752
753 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
754 if( tagid==0 ){
@@ -764,11 +850,11 @@
764 " UNION "
765 "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
766 " FROM attachment, blob"
767 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
768 " AND blob.rid=attachid"
769 " ORDER BY 1 DESC",
770 tagid, tagid
771 );
772 while( db_step(&q)==SQLITE_ROW ){
773 Manifest *pTicket;
774 char zShort[12];
@@ -776,40 +862,48 @@
776 int rid = db_column_int(&q, 1);
777 const char *zChngUuid = db_column_text(&q, 2);
778 const char *zFile = db_column_text(&q, 4);
779 memcpy(zShort, zChngUuid, 10);
780 zShort[10] = 0;
 
 
 
 
781 if( zFile!=0 ){
782 const char *zSrc = db_column_text(&q, 3);
783 const char *zUser = db_column_text(&q, 5);
784 if( zSrc==0 || zSrc[0]==0 ){
785 @
786 @ <p>Delete attachment "%h(zFile)"
787 }else{
788 @
789 @ <p>Add attachment "%h(zFile)"
 
790 }
791 @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
792 @ (rid %d(rid)) by
793 hyperlink_to_user(zUser,zDate," on");
794 hyperlink_to_date(zDate, ".</p>");
795 }else{
796 pTicket = manifest_get(rid, CFTYPE_TICKET);
797 if( pTicket ){
798 @
799 @ <p>Ticket change
800 @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
801 @ (rid %d(rid)) by
802 hyperlink_to_user(pTicket->zUser,zDate," on");
803 hyperlink_to_date(zDate, ":");
804 @ </p>
805 ticket_output_change_artifact(pTicket);
806 }
807 manifest_destroy(pTicket);
808 }
809 }
810 db_finalize(&q);
 
 
 
811 style_footer();
812 }
813
814 /*
815 ** Return TRUE if the given BLOB contains a newline character.
@@ -825,26 +919,35 @@
825
826 /*
827 ** The pTkt object is a ticket change artifact. Output a detailed
828 ** description of this object.
829 */
830 void ticket_output_change_artifact(Manifest *pTkt){
831 int i;
832 @ <ol>
 
 
 
 
 
 
 
 
 
833 for(i=0; i<pTkt->nField; i++){
834 Blob val;
835 const char *z;
836 z = pTkt->aField[i].zName;
837 blob_set(&val, pTkt->aField[i].zValue);
838 if( z[0]=='+' ){
839 @ <li>Appended to %h(&z[1]):<blockquote>
840 wiki_convert(&val, 0, WIKI_NOBADLINKS);
841 @ </blockquote></li>
842 }else if( blob_size(&val)<=50 && contains_newline(&val) ){
843 @ <li>Change %h(z) to:<blockquote>
844 wiki_convert(&val, 0, WIKI_NOBADLINKS);
845 @ </blockquote></li>
846 }else{
847 @ <li>Change %h(z) to "%h(blob_str(&val))"</li>
848 }
849 blob_reset(&val);
850 }
@@ -966,11 +1069,11 @@
966 int i;
967
968 /* read all available ticket fields */
969 getAllTicketFields();
970 for(i=0; i<nField; i++){
971 printf("%s\n",azField[i]);
972 }
973 }else if( !strncmp(g.argv[3],"reports",n) ){
974 rpt_list_reports();
975 }else{
976 fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1041,21 +1144,23 @@
1041 int tagid;
1042
1043 if ( i != g.argc ){
1044 fossil_fatal("no other parameters expected to %s!",g.argv[2]);
1045 }
1046 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid);
 
1047 if( tagid==0 ){
1048 fossil_fatal("no such ticket %h", zTktUuid);
1049 }
1050 db_prepare(&q,
1051 "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
1052 " FROM event, blob"
1053 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
1054 " AND blob.rid=event.objid"
1055 " UNION "
1056 "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
 
1057 " FROM attachment, blob"
1058 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
1059 " AND blob.rid=attachid"
1060 " ORDER BY 1 DESC",
1061 tagid, tagid
@@ -1071,21 +1176,22 @@
1071 zShort[10] = 0;
1072 if( zFile!=0 ){
1073 const char *zSrc = db_column_text(&q, 3);
1074 const char *zUser = db_column_text(&q, 5);
1075 if( zSrc==0 || zSrc[0]==0 ){
1076 fossil_print("Delete attachment %h\n", zFile);
1077 }else{
1078 fossil_print("Add attachment %h\n", zFile);
1079 }
1080 fossil_print(" by %h on %h\n", zUser, zDate);
1081 }else{
1082 pTicket = manifest_get(rid, CFTYPE_TICKET);
1083 if( pTicket ){
1084 int i;
1085
1086 fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate);
 
1087 for(i=0; i<pTicket->nField; i++){
1088 Blob val;
1089 const char *z;
1090 z = pTicket->aField[i].zName;
1091 blob_set(&val, pTicket->aField[i].zValue);
@@ -1114,11 +1220,11 @@
1114 /* read all given ticket field/value pairs from command line */
1115 if( i==g.argc ){
1116 fossil_fatal("empty %s command aborted!",g.argv[2]);
1117 }
1118 getAllTicketFields();
1119 /* read commandline and assign fields in the azValue array */
1120 while( i<g.argc ){
1121 char *zFName;
1122 char *zFValue;
1123 int j;
1124 int append = 0;
@@ -1139,13 +1245,13 @@
1139 j = fieldId(zFName);
1140 if( j == -1 ){
1141 fossil_fatal("unknown field name '%s'!",zFName);
1142 }else{
1143 if (append) {
1144 azAppend[j] = zFValue;
1145 } else {
1146 azValue[j] = zFValue;
1147 }
1148 }
1149 }
1150
1151 /* now add the needed artifacts to the repository */
@@ -1155,25 +1261,25 @@
1155 /* append defined elements */
1156 for(i=0; i<nField; i++){
1157 char *zValue = 0;
1158 char *zPfx;
1159
1160 if (azAppend[i] && azAppend[i][0] ){
1161 zPfx = " +";
1162 zValue = azAppend[i];
1163 } else if( azValue[i] && azValue[i][0] ){
1164 zPfx = " ";
1165 zValue = azValue[i];
1166 } else {
1167 continue;
1168 }
1169 if( strncmp(azField[i], "private_", 8)==0 ){
1170 zValue = db_conceal(zValue, strlen(zValue));
1171 blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue);
1172 }else{
1173 blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1174 azField[i], strlen(zValue), zValue);
1175 }
1176 }
1177 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1178 blob_appendf(&tktchng, "U %F\n", zUser);
1179 md5sum_blob(&tktchng, &cksum);
1180
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
26 ** The list of database user-defined fields in the TICKET table.
27 ** The real table also contains some addition fields for internal
28 ** used. The internal-use fields begin with "tkt_".
29 */
30 static int nField = 0;
31 static struct tktFieldInfo {
32 char *zName; /* Name of the database field */
33 char *zValue; /* Value to store */
34 char *zAppend; /* Value to append */
35 unsigned mUsed; /* 01: TICKET 02: TICKETCHNG */
36 } *aField;
37 #define USEDBY_TICKET 01
38 #define USEDBY_TICKETCHNG 02
39 static int haveTicket = 0; /* True if the TICKET table exists */
40 static int haveTicketChng = 0; /* True if the TICKETCHNG table exists */
41
42 /*
43 ** Compare two entries in aField[] for sorting purposes
44 */
45 static int nameCmpr(const void *a, const void *b){
46 return fossil_strcmp(((const struct tktFieldInfo*)a)->zName,
47 ((const struct tktFieldInfo*)b)->zName);
48 }
49
50 /*
51 ** Return the index into aField[] of the given field name.
52 ** Return -1 if zFieldName is not in aField[].
53 */
54 static int fieldId(const char *zFieldName){
55 int i;
56 for(i=0; i<nField; i++){
57 if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
58 }
59 return -1;
60 }
61
62 /*
63 ** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them
64 ** in sorted order in aField[].
65 **
66 ** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
67 ** TICKETCHANGE tables exist, respectively.
68 */
69 static void getAllTicketFields(void){
70 Stmt q;
71 int i;
72 static int once = 0;
73 if( once ) return;
74 once = 1;
75 db_prepare(&q, "PRAGMA table_info(ticket)");
76 while( db_step(&q)==SQLITE_ROW ){
77 const char *zFieldName = db_column_text(&q, 1);
78 haveTicket = 1;
79 if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
80 if( nField%10==0 ){
81 aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
82 }
83 aField[nField].zName = mprintf("%s", zFieldName);
84 aField[nField].mUsed = USEDBY_TICKET;
85 nField++;
86 }
87 db_finalize(&q);
88 db_prepare(&q, "PRAGMA table_info(ticketchng)");
89 while( db_step(&q)==SQLITE_ROW ){
90 const char *zFieldName = db_column_text(&q, 1);
91 haveTicketChng = 1;
92 if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
93 if( (i = fieldId(zFieldName))>=0 ){
94 aField[i].mUsed |= USEDBY_TICKETCHNG;
95 continue;
96 }
97 if( nField%10==0 ){
98 aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
99 }
100 aField[nField].zName = mprintf("%s", zFieldName);
101 aField[nField].mUsed = USEDBY_TICKETCHNG;
102 nField++;
103 }
104 db_finalize(&q);
105 qsort(aField, nField, sizeof(aField[0]), nameCmpr);
106 for(i=0; i<nField; i++){
107 aField[i].zValue = "";
108 aField[i].zAppend = 0;
109 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110 }
111
112 /*
113 ** Query the database for all TICKET fields for the specific
114 ** ticket whose name is given by the "name" CGI parameter.
@@ -114,38 +141,24 @@
141 if( zVal==0 ){
142 zVal = "";
143 }else if( strncmp(zName, "private_", 8)==0 ){
144 zVal = zRevealed = db_reveal(zVal);
145 }
146 if( (j = fieldId(zName))>=0 ){
147 aField[j].zValue = mprintf("%s", zVal);
148 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
 
 
 
 
149 Th_Store(zName, zVal);
150 }
151 free(zRevealed);
152 }
153 }
154 db_finalize(&q);
155 for(i=0; i<nField; i++){
156 if( Th_Fetch(aField[i].zName, &size)==0 ){
157 Th_Store(aField[i].zName, aField[i].zValue);
158 }
159 }
 
 
 
 
 
 
 
 
 
 
160 }
161
162 /*
163 ** Transfer all CGI parameters to variables in the interpreter.
164 */
@@ -157,56 +170,73 @@
170 Th_Store(z, P(z));
171 }
172 }
173
174 /*
175 ** Update an entry of the TICKET and TICKETCHNG tables according to the
176 ** information in the ticket artifact given in p. Attempt to create
177 ** the appropriate TICKET table entry if tktid is zero. If tktid is nonzero
178 ** then it will be the ROWID of an existing TICKET entry.
179 **
180 ** Parameter rid is the recordID for the ticket artifact in the BLOB table.
181 **
182 ** Return the new rowid of the TICKET table entry.
 
183 */
184 static int ticket_insert(const Manifest *p, int rid, int tktid){
185 Blob sql1, sql2, sql3;
186 Stmt q;
187 int i, j;
 
188
189 if( tktid==0 ){
190 db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
 
191 "VALUES(%Q, 0)", p->zTicketUuid);
192 tktid = db_last_insert_rowid();
193 }
194 blob_zero(&sql1);
195 blob_zero(&sql2);
196 blob_zero(&sql3);
197 blob_appendf(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
198 for(i=0; i<p->nField; i++){
199 const char *zName = p->aField[i].zName;
200 if( zName[0]=='+' ){
201 zName++;
202 if( (j = fieldId(zName))<0 ) continue;
203 if( aField[j].mUsed & USEDBY_TICKET ){
204 blob_appendf(&sql1,", %s=coalesce(%s,'') || %Q",
205 zName, zName, p->aField[i].zValue);
206 }
207 }else{
208 if( (j = fieldId(zName))<0 ) continue;
209 if( aField[j].mUsed & USEDBY_TICKET ){
210 blob_appendf(&sql1,", %s=%Q", zName, p->aField[i].zValue);
211 }
212 }
213 if( aField[j].mUsed & USEDBY_TICKETCHNG ){
214 blob_appendf(&sql2, ",%s", zName);
215 blob_appendf(&sql3, ",%Q", p->aField[i].zValue);
216 }
217 if( rid>0 ){
218 wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
219 }
220 }
221 blob_appendf(&sql1, " WHERE tkt_id=%d", tktid);
222 db_prepare(&q, "%s", blob_str(&sql1));
 
223 db_bind_double(&q, ":mtime", p->rDate);
224 db_step(&q);
225 db_finalize(&q);
226 blob_reset(&sql1);
227 if( blob_size(&sql2)>0 ){
228 db_prepare(&q, "INSERT INTO ticketchng(tkt_id,tkt_mtime%s)"
229 "VALUES(%d,:mtime%s)",
230 blob_str(&sql2), tktid, blob_str(&sql3));
231 db_bind_double(&q, ":mtime", p->rDate);
232 db_step(&q);
233 db_finalize(&q);
234 }
235 blob_reset(&sql2);
236 blob_reset(&sql3);
237 return tktid;
238 }
239
240 /*
241 ** Rebuild an entire entry in the TICKET table
242 */
@@ -213,67 +243,78 @@
243 void ticket_rebuild_entry(const char *zTktUuid){
244 char *zTag = mprintf("tkt-%s", zTktUuid);
245 int tagid = tag_findid(zTag, 1);
246 Stmt q;
247 Manifest *pTicket;
248 int tktid;
249 int createFlag = 1;
250
251 fossil_free(zTag);
252 getAllTicketFields();
253 if( haveTicket==0 ) return;
254 tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid);
255 if( haveTicketChng ){
256 db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid);
257 }
258 db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid);
259 tktid = 0;
260 db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
261 while( db_step(&q)==SQLITE_ROW ){
262 int rid = db_column_int(&q, 0);
263 pTicket = manifest_get(rid, CFTYPE_TICKET);
264 if( pTicket ){
265 tktid = ticket_insert(pTicket, rid, tktid);
266 manifest_ticket_event(rid, pTicket, createFlag, tagid);
267 manifest_destroy(pTicket);
268 }
269 createFlag = 0;
270 }
271 db_finalize(&q);
272 }
273
274 /*
275 ** Create the TH1 interpreter and load the "common" code.
276 */
277 void ticket_init(void){
278 const char *zConfig;
279 Th_FossilInit(0, 0);
280 zConfig = ticket_common_code();
281 Th_Eval(g.interp, 0, zConfig, -1);
282 }
283
284 /*
285 ** Create the TH1 interpreter and load the "change" code.
286 */
287 int ticket_change(void){
288 const char *zConfig;
289 Th_FossilInit(0, 0);
290 zConfig = ticket_change_code();
291 return Th_Eval(g.interp, 0, zConfig, -1);
292 }
293
294 /*
295 ** Recreate the TICKET and TICKETCHNG tables.
296 */
297 void ticket_create_table(int separateConnection){
298 const char *zSql;
299
300 db_multi_exec(
301 "DROP TABLE IF EXISTS ticket;"
302 "DROP TABLE IF EXISTS ticketchng;"
303 );
304 zSql = ticket_table_schema();
305 if( separateConnection ){
306 db_end_transaction(0);
307 db_init_database(g.zRepositoryName, zSql, 0);
308 }else{
309 db_multi_exec("%s", zSql);
310 }
311 }
312
313 /*
314 ** Repopulate the TICKET and TICKETCHNG tables from scratch using all
315 ** available ticket artifacts.
316 */
317 void ticket_rebuild(void){
318 Stmt q;
319 ticket_create_table(1);
320 db_begin_transaction();
@@ -287,10 +328,30 @@
328 ticket_rebuild_entry(zName);
329 }
330 db_finalize(&q);
331 db_end_transaction(0);
332 }
333
334 /*
335 ** For trouble-shooting purposes, render a dump of the aField[] table to
336 ** the webpage currently under construction.
337 */
338 static void showAllFields(void){
339 int i;
340 @ <font color="blue">
341 @ <p>Database fields:</p><ul>
342 for(i=0; i<nField; i++){
343 @ <li>aField[%d(i)].zName = "%h(aField[i].zName)";
344 @ originally = "%h(aField[i].zValue)";
345 @ currently = "%h(PD(aField[i].zName,""))"";
346 if( aField[i].zAppend ){
347 @ zAppend = "%h(aField[i].zAppend)";
348 }
349 @ mUsed = %d(aField[i].mUsed);
350 }
351 @ </ul></font>
352 }
353
354 /*
355 ** WEBPAGE: tktview
356 ** URL: tktview?name=UUID
357 **
@@ -322,15 +383,24 @@
383 if( g.perm.ApndTkt && g.perm.Attach ){
384 style_submenu_element("Attach", "Add An Attachment",
385 "%s/attachadd?tkt=%T&from=%s/tktview/%t",
386 g.zTop, zUuid, g.zTop, zUuid);
387 }
388 if( P("plaintext") ){
389 style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid);
390 }else{
391 style_submenu_element("Plaintext", "Plaintext",
392 "%R/tktview/%S?plaintext", zUuid);
393 }
394 style_header("View Ticket");
395 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
396 ticket_init();
397 initializeVariablesFromCGI();
398 getAllTicketFields();
399 initializeVariablesFromDb();
400 zScript = ticket_viewpage_code();
401 if( P("showfields")!=0 ) showAllFields();
402 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
403 Th_Render(zScript);
404 if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
405
406 zFullName = db_text(0,
@@ -366,20 +436,20 @@
436 if( g.thTrace ){
437 Th_Trace("append_field %#h {%#h}<br />\n",
438 argl[1], argv[1], argl[2], argv[2]);
439 }
440 for(idx=0; idx<nField; idx++){
441 if( memcmp(aField[idx].zName, argv[1], argl[1])==0
442 && aField[idx].zName[argl[1]]==0 ){
443 break;
444 }
445 }
446 if( idx>=nField ){
447 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
448 return TH_ERROR;
449 }
450 aField[idx].zAppend = mprintf("%.*s", argl[2], argv[2]);
451 return TH_OK;
452 }
453
454 /*
455 ** Write a ticket into the repository.
@@ -440,29 +510,32 @@
510 blob_zero(&tktchng);
511 zDate = date_in_standard_format("now");
512 blob_appendf(&tktchng, "D %s\n", zDate);
513 free(zDate);
514 for(i=0; i<nField; i++){
515 if( aField[i].zAppend ){
516 blob_appendf(&tktchng, "J +%s %z\n", aField[i].zName,
517 fossilize(aField[i].zAppend, -1));
518 ++nJ;
519 }
520 }
521 for(i=0; i<nField; i++){
522 const char *zValue;
523 int nValue;
524 if( aField[i].zAppend ) continue;
525 zValue = Th_Fetch(aField[i].zName, &nValue);
526 if( zValue ){
527 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
528 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
529 || memcmp(zValue, aField[i].zValue, nValue)!=0
530 || strlen(aField[i].zValue)!=nValue
531 ){
532 if( memcmp(aField[i].zName, "private_", 8)==0 ){
533 zValue = db_conceal(zValue, nValue);
534 blob_appendf(&tktchng, "J %s %s\n", aField[i].zName, zValue);
535 }else{
536 blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
537 }
538 nJ++;
539 }
540 }
541 }
@@ -523,19 +596,19 @@
596 cgi_redirect("home");
597 }
598 style_header("New Ticket");
599 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
600 ticket_init();
601 initializeVariablesFromCGI();
602 getAllTicketFields();
603 initializeVariablesFromDb();
604 if( g.zPath[0]=='d' ) showAllFields();
605 form_begin(0, "%R/%s", g.zPath);
606 login_insert_csrf_secret();
607 if( P("date_override") && g.perm.Setup ){
608 @ <input type="hidden" name="date_override" value="%h(P("date_override"))">
609 }
 
610 zScript = ticket_newpage_code();
611 Th_Store("login", g.zLogin ? g.zLogin : "nobody");
612 Th_Store("date", db_text(0, "SELECT datetime('now')"));
613 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
614 (void*)&zNewUuid, 0);
@@ -596,14 +669,14 @@
669 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);
670 ticket_init();
671 getAllTicketFields();
672 initializeVariablesFromCGI();
673 initializeVariablesFromDb();
674 if( g.zPath[0]=='d' ) showAllFields();
675 form_begin(0, "%R/%s", g.zPath);
676 @ <input type="hidden" name="name" value="%s(zName)" />
677 login_insert_csrf_secret();
 
678 zScript = ticket_editpage_code();
679 Th_Store("login", g.zLogin ? g.zLogin : "nobody");
680 Th_Store("date", db_text(0, "SELECT datetime('now')"));
681 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
682 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
@@ -635,18 +708,23 @@
708 sqlite3_close(db);
709 return zErr;
710 }
711 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
712 0, 0, 0);
 
713 if( rc!=SQLITE_OK ){
714 zErr = mprintf("schema fails to define valid a TICKET "
715 "table containing all required fields");
716 }else{
717 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_mtime FROM ticketchng", 0,0,0);
718 if( rc!=SQLITE_OK ){
719 zErr = mprintf("schema fails to define valid a TICKETCHNG "
720 "table containing all required fields");
721 }
722 }
723 sqlite3_close(db);
724 }
725 return zErr;
726 }
727
728 /*
729 ** WEBPAGE: tkttimeline
730 ** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -734,10 +812,11 @@
812 void tkthistory_page(void){
813 Stmt q;
814 char *zTitle;
815 const char *zUuid;
816 int tagid;
817 int nChng = 0;
818
819 login_check_credentials();
820 if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
821 zUuid = PD("name","");
822 zTitle = mprintf("History Of Ticket %h", zUuid);
@@ -745,10 +824,17 @@
824 "%s/info/%s", g.zTop, zUuid);
825 style_submenu_element("Check-ins", "Check-ins",
826 "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
827 style_submenu_element("Timeline", "Timeline",
828 "%s/tkttimeline?name=%s", g.zTop, zUuid);
829 if( P("plaintext")!=0 ){
830 style_submenu_element("Formatted", "Formatted",
831 "%R/tkthistory/%S", zUuid);
832 }else{
833 style_submenu_element("Plaintext", "Plaintext",
834 "%R/tkthistory/%S?plaintext", zUuid);
835 }
836 style_header(zTitle);
837 free(zTitle);
838
839 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
840 if( tagid==0 ){
@@ -764,11 +850,11 @@
850 " UNION "
851 "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
852 " FROM attachment, blob"
853 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
854 " AND blob.rid=attachid"
855 " ORDER BY 1",
856 tagid, tagid
857 );
858 while( db_step(&q)==SQLITE_ROW ){
859 Manifest *pTicket;
860 char zShort[12];
@@ -776,40 +862,48 @@
862 int rid = db_column_int(&q, 1);
863 const char *zChngUuid = db_column_text(&q, 2);
864 const char *zFile = db_column_text(&q, 4);
865 memcpy(zShort, zChngUuid, 10);
866 zShort[10] = 0;
867 if( nChng==0 ){
868 @ <ol>
869 }
870 nChng++;
871 if( zFile!=0 ){
872 const char *zSrc = db_column_text(&q, 3);
873 const char *zUser = db_column_text(&q, 5);
874 if( zSrc==0 || zSrc[0]==0 ){
875 @
876 @ <li><p>Delete attachment "%h(zFile)"
877 }else{
878 @
879 @ <li><p>Add attachment
880 @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>"
881 }
882 @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
883 @ (rid %d(rid)) by
884 hyperlink_to_user(zUser,zDate," on");
885 hyperlink_to_date(zDate, ".</p>");
886 }else{
887 pTicket = manifest_get(rid, CFTYPE_TICKET);
888 if( pTicket ){
889 @
890 @ <li><p>Ticket change
891 @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
892 @ (rid %d(rid)) by
893 hyperlink_to_user(pTicket->zUser,zDate," on");
894 hyperlink_to_date(zDate, ":");
895 @ </p>
896 ticket_output_change_artifact(pTicket, "a");
897 }
898 manifest_destroy(pTicket);
899 }
900 }
901 db_finalize(&q);
902 if( nChng ){
903 @ </ol>
904 }
905 style_footer();
906 }
907
908 /*
909 ** Return TRUE if the given BLOB contains a newline character.
@@ -825,26 +919,35 @@
919
920 /*
921 ** The pTkt object is a ticket change artifact. Output a detailed
922 ** description of this object.
923 */
924 void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){
925 int i;
926 int wikiFlags = WIKI_NOBADLINKS;
927 const char *zBlock = "<blockquote>";
928 const char *zEnd = "</blockquote>";
929 if( P("plaintext")!=0 ){
930 wikiFlags |= WIKI_LINKSONLY;
931 zBlock = "<blockquote><pre class='verbatim'>";
932 zEnd = "</pre></blockquote>";
933 }
934 if( zListType==0 ) zListType = "1";
935 @ <ol type="%s(zListType)">
936 for(i=0; i<pTkt->nField; i++){
937 Blob val;
938 const char *z;
939 z = pTkt->aField[i].zName;
940 blob_set(&val, pTkt->aField[i].zValue);
941 if( z[0]=='+' ){
942 @ <li>Appended to %h(&z[1]):%s(zBlock)
943 wiki_convert(&val, 0, wikiFlags);
944 @ %s(zEnd)</li>
945 }else if( blob_size(&val)>50 || contains_newline(&val) ){
946 @ <li>Change %h(z) to:%s(zBlock)
947 wiki_convert(&val, 0, wikiFlags);
948 @ %s(zEnd)</li>
949 }else{
950 @ <li>Change %h(z) to "%h(blob_str(&val))"</li>
951 }
952 blob_reset(&val);
953 }
@@ -966,11 +1069,11 @@
1069 int i;
1070
1071 /* read all available ticket fields */
1072 getAllTicketFields();
1073 for(i=0; i<nField; i++){
1074 printf("%s\n",aField[i].zName);
1075 }
1076 }else if( !strncmp(g.argv[3],"reports",n) ){
1077 rpt_list_reports();
1078 }else{
1079 fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1041,21 +1144,23 @@
1144 int tagid;
1145
1146 if ( i != g.argc ){
1147 fossil_fatal("no other parameters expected to %s!",g.argv[2]);
1148 }
1149 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
1150 zTktUuid);
1151 if( tagid==0 ){
1152 fossil_fatal("no such ticket %h", zTktUuid);
1153 }
1154 db_prepare(&q,
1155 "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
1156 " FROM event, blob"
1157 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
1158 " AND blob.rid=event.objid"
1159 " UNION "
1160 "SELECT datetime(mtime,'localtime'), attachid, uuid, src, "
1161 " filename, user"
1162 " FROM attachment, blob"
1163 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
1164 " AND blob.rid=attachid"
1165 " ORDER BY 1 DESC",
1166 tagid, tagid
@@ -1071,21 +1176,22 @@
1176 zShort[10] = 0;
1177 if( zFile!=0 ){
1178 const char *zSrc = db_column_text(&q, 3);
1179 const char *zUser = db_column_text(&q, 5);
1180 if( zSrc==0 || zSrc[0]==0 ){
1181 fossil_print("Delete attachment %s\n", zFile);
1182 }else{
1183 fossil_print("Add attachment %s\n", zFile);
1184 }
1185 fossil_print(" by %s on %s\n", zUser, zDate);
1186 }else{
1187 pTicket = manifest_get(rid, CFTYPE_TICKET);
1188 if( pTicket ){
1189 int i;
1190
1191 fossil_print("Ticket Change by %s on %s:\n",
1192 pTicket->zUser, zDate);
1193 for(i=0; i<pTicket->nField; i++){
1194 Blob val;
1195 const char *z;
1196 z = pTicket->aField[i].zName;
1197 blob_set(&val, pTicket->aField[i].zValue);
@@ -1114,11 +1220,11 @@
1220 /* read all given ticket field/value pairs from command line */
1221 if( i==g.argc ){
1222 fossil_fatal("empty %s command aborted!",g.argv[2]);
1223 }
1224 getAllTicketFields();
1225 /* read commandline and assign fields in the aField[].zValue array */
1226 while( i<g.argc ){
1227 char *zFName;
1228 char *zFValue;
1229 int j;
1230 int append = 0;
@@ -1139,13 +1245,13 @@
1245 j = fieldId(zFName);
1246 if( j == -1 ){
1247 fossil_fatal("unknown field name '%s'!",zFName);
1248 }else{
1249 if (append) {
1250 aField[j].zAppend = zFValue;
1251 } else {
1252 aField[j].zValue = zFValue;
1253 }
1254 }
1255 }
1256
1257 /* now add the needed artifacts to the repository */
@@ -1155,25 +1261,25 @@
1261 /* append defined elements */
1262 for(i=0; i<nField; i++){
1263 char *zValue = 0;
1264 char *zPfx;
1265
1266 if (aField[i].zAppend && aField[i].zAppend[0] ){
1267 zPfx = " +";
1268 zValue = aField[i].zAppend;
1269 } else if( aField[i].zValue && aField[i].zValue[0] ){
1270 zPfx = " ";
1271 zValue = aField[i].zValue;
1272 } else {
1273 continue;
1274 }
1275 if( memcmp(aField[i].zName, "private_", 8)==0 ){
1276 zValue = db_conceal(zValue, strlen(zValue));
1277 blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue);
1278 }else{
1279 blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1280 aField[i].zName, strlen(zValue), zValue);
1281 }
1282 }
1283 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1284 blob_appendf(&tktchng, "U %F\n", zUser);
1285 md5sum_blob(&tktchng, &cksum);
1286
+206 -95
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
6767
@ CREATE TABLE ticket(
6868
@ -- Do not change any column that begins with tkt_
6969
@ tkt_id INTEGER PRIMARY KEY,
7070
@ tkt_uuid TEXT UNIQUE,
7171
@ tkt_mtime DATE,
72
-@ -- Add as many field as required below this line
72
+@ -- Add as many fields as required below this line
7373
@ type TEXT,
7474
@ status TEXT,
7575
@ subsystem TEXT,
7676
@ priority TEXT,
7777
@ severity TEXT,
@@ -79,10 +79,21 @@
7979
@ private_contact TEXT,
8080
@ resolution TEXT,
8181
@ title TEXT,
8282
@ comment TEXT
8383
@ );
84
+@ CREATE TABLE ticketchng(
85
+@ -- Do not change any column that begins with tkt_
86
+@ tkt_id INTEGER REFERENCES ticket,
87
+@ tkt_mtime DATE,
88
+@ -- Add as many fields as required below this line
89
+@ login TEXT,
90
+@ username TEXT,
91
+@ mimetype TEXT,
92
+@ icomment TEXT
93
+@ );
94
+@ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
8495
;
8596
8697
/*
8798
** Return the ticket table definition
8899
*/
@@ -120,11 +131,11 @@
120131
style_header("Edit %s", zTitle);
121132
if( P("clear")!=0 ){
122133
login_verify_csrf_secret();
123134
db_unset(zDbField, 0);
124135
if( xRebuild ) xRebuild();
125
- z = zDfltValue;
136
+ cgi_redirect("tktsetup");
126137
}else if( isSubmit ){
127138
char *zErr = 0;
128139
login_verify_csrf_secret();
129140
if( xText && (zErr = xText(z))!=0 ){
130141
@ <p class="tktsetupError">ERROR: %h(zErr)</p>
@@ -277,84 +288,117 @@
277288
);
278289
}
279290
280291
static const char zDefaultNew[] =
281292
@ <th1>
293
+@ if {![info exists mutype]} {set mutype {[links only]}}
282294
@ if {[info exists submit]} {
283295
@ set status Open
296
+@ if {$mutype eq "HTML"} {
297
+@ set mimetype "text/html"
298
+@ } elseif {$mutype eq "Wiki"} {
299
+@ set mimetype "text/x-fossil-wiki"
300
+@ } elseif {$mutype eq {[links only]}} {
301
+@ set mimetype "text/x-fossil-plain"
302
+@ } else {
303
+@ set mimetype "text/plain"
304
+@ }
284305
@ submit_ticket
285306
@ }
286307
@ </th1>
287308
@ <h1 style="text-align: center;">Enter A New Ticket</h1>
288309
@ <table cellpadding="5">
289310
@ <tr>
290
-@ <td colspan="2">
311
+@ <td colspan="3">
291312
@ Enter a one-line summary of the ticket:<br />
292313
@ <input type="text" name="title" size="60" value="$<title>" />
293314
@ </td>
294315
@ </tr>
295316
@
296317
@ <tr>
297
-@ <td style="text-align: center;">Type:
298
-@ <th1>combobox type $type_choices 1</th1>
299
-@ </td>
300
-@ <td>What type of ticket is this?</td>
318
+@ <td align="right">Type:</td>
319
+@ <td align="left"><th1>combobox type $type_choices 1</th1></td>
320
+@ <td align="left">What type of ticket is this?</td>
301321
@ </tr>
302322
@
303323
@ <tr>
304
-@ <td style="text-align: center;">Version:
324
+@ <td align="right">Version:</td>
325
+@ <td align="left">
305326
@ <input type="text" name="foundin" size="20" value="$<foundin>" />
306327
@ </td>
307
-@ <td>In what version or build number do you observe the problem?</td>
328
+@ <td align="left">In what version or build number do you observe
329
+@ the problem?</td>
308330
@ </tr>
309331
@
310332
@ <tr>
311
-@ <td style="text-align: center;">Severity:
312
-@ <th1>combobox severity $severity_choices 1</th1>
313
-@ </td>
314
-@ <td>How debilitating is the problem? How badly does the problem
333
+@ <td align="right">Severity:</td>
334
+@ <td align="left"><th1>combobox severity $severity_choices 1</th1></td>
335
+@ <td align="left">How debilitating is the problem? How badly does the problem
315336
@ affect the operation of the product?</td>
316337
@ </tr>
317338
@
318339
@ <tr>
319
-@ <td style="text-align: center;">EMail:
320
-@ <input type="text" name="private_contact" value="$<private_contact>" size="30" />
340
+@ <td align="right">EMail:</td>
341
+@ <td align="left">
342
+@ <input type="text" name="private_contact" value="$<private_contact>"
343
+@ size="30" />
321344
@ </td>
322
-@ <td><span style="text-decoration: underline;">Not publicly visible</span>.
345
+@ <td align="left"><u>Not publicly visible</u>
323346
@ Used by developers to contact you with questions.</td>
324347
@ </tr>
325348
@
326349
@ <tr>
327
-@ <td colspan="2">
350
+@ <td colspan="3">
328351
@ Enter a detailed description of the problem.
329352
@ For code defects, be sure to provide details on exactly how
330353
@ the problem can be reproduced. Provide as much detail as
331
-@ possible.
354
+@ possible. Format:
355
+@ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
332356
@ <br />
333357
@ <th1>set nline [linecount $comment 50 10]</th1>
334
-@ <textarea name="comment" cols="80" rows="$nline"
335
-@ wrap="virtual" class="wikiedit">$<comment></textarea><br />
336
-@ <input type="submit" name="preview" value="Preview" /></td>
358
+@ <textarea name="icomment" cols="80" rows="$nline"
359
+@ wrap="virtual" class="wikiedit">$<icomment></textarea><br />
337360
@ </tr>
338
-@
361
+@
339362
@ <th1>enable_output [info exists preview]</th1>
340
-@ <tr><td colspan="2">
363
+@ <tr><td colspan="3">
341364
@ Description Preview:<br /><hr />
342
-@ <th1>wiki $comment</th1>
343
-@ <hr />
344
-@ </td></tr>
365
+@ <th1>
366
+@ if {$mutype eq "Wiki"} {
367
+@ wiki $icomment
368
+@ } elseif {$mutype eq "Plain Text"} {
369
+@ set r [randhex]
370
+@ wiki "<verbatim-$r>[string trimright $icomment]\n</verbatim-$r>"
371
+@ } elseif {$mutype eq {[links only]}} {
372
+@ set r [randhex]
373
+@ wiki "<verbatim-$r links>[string trimright $icomment]\n</verbatim-$r>"
374
+@ } else {
375
+@ wiki "<nowiki>$icomment\n</nowiki>"
376
+@ }
377
+@ </th1>
378
+@ <hr /></td></tr>
379
+@ <th1>enable_output 1</th1>
380
+@
381
+@ <tr>
382
+@ <td><td align="left">
383
+@ <input type="submit" name="preview" value="Preview" />
384
+@ </td>
385
+@ <td align="left">See how the description will appear after formatting.</td>
386
+@ </tr>
387
+@
388
+@ <th1>enable_output [info exists preview]</th1>
389
+@ <tr>
390
+@ <td><td align="left">
391
+@ <input type="submit" name="submit" value="Submit" />
392
+@ </td>
393
+@ <td align="left">After filling in the information above, press this
394
+@ button to create the new ticket</td>
395
+@ </tr>
345396
@ <th1>enable_output 1</th1>
346397
@
347398
@ <tr>
348
-@ <td style="text-align: center;">
349
-@ <input type="submit" name="submit" value="Submit" />
350
-@ </td>
351
-@ <td>After filling in the information above, press this button to create
352
-@ the new ticket</td>
353
-@ </tr>
354
-@ <tr>
355
-@ <td style="text-align: center;">
399
+@ <td><td align="left">
356400
@ <input type="submit" name="cancel" value="Cancel" />
357401
@ </td>
358402
@ <td>Abandon and forget this ticket</td>
359403
@ </tr>
360404
@ </table>
@@ -387,14 +431,21 @@
387431
}
388432
389433
static const char zDefaultView[] =
390434
@ <table cellpadding="5">
391435
@ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
392
-@ <td class="tktDspValue" colspan="3">$<tkt_uuid></td></tr>
436
+@ <th1>
437
+@ if {[hascap s]} {
438
+@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
439
+@ html "($tkt_id)</td></tr>\n"
440
+@ } else {
441
+@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
442
+@ }
443
+@ </th1>
393444
@ <tr><td class="tktDspLabel">Title:</td>
394445
@ <td class="tktDspValue" colspan="3">
395
-@ <th1>wiki $title</th1>
446
+@ $<title>
396447
@ </td></tr>
397448
@ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue">
398449
@ $<status>
399450
@ </td>
400451
@ <td class="tktDspLabel">Type:</td><td class="tktDspValue">
@@ -423,14 +474,58 @@
423474
@ </tr>
424475
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
425476
@ <td colspan="3" valign="top" class="tktDspValue">
426477
@ $<foundin>
427478
@ </td></tr>
428
-@ <tr><td>Description &amp; Comments:</td></tr>
429
-@ <tr><td colspan="4" class="tktDspValue">
430
-@ <th1>wiki $comment</th1>
431
-@ </td></tr>
479
+@
480
+@ <th1>
481
+@ if {[info exists comment] && [string length $comment]>10} {
482
+@ html {
483
+@ <tr><td class="tktDspLabel">Description:</td></tr>
484
+@ <tr><td colspan="5" class="tktDspValue">
485
+@ }
486
+@ if {[info exists plaintext]} {
487
+@ set r [randhex]
488
+@ wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>"
489
+@ } else {
490
+@ wiki $comment
491
+@ }
492
+@ }
493
+@ set seenRow 0
494
+@ set alwaysPlaintext [info exists plaintext]
495
+@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
496
+@ mimetype as xmimetype, icomment AS xcomment,
497
+@ username AS xusername
498
+@ FROM ticketchng
499
+@ WHERE tkt_id=$tkt_id} {
500
+@ if {$seenRow} {
501
+@ html "<hr>\n"
502
+@ } else {
503
+@ html "<tr><td class='tktDspLabel'>User Comments:</td></tr>\n"
504
+@ html "<tr><td colspan='5' class='tktDspValue'>\n"
505
+@ set seenRow 1
506
+@ }
507
+@ html "[htmlize $xlogin]"
508
+@ if {$xlogin ne $xusername && [string length $xusername]>0} {
509
+@ html " (claiming to be [htmlize $xusername])"
510
+@ }
511
+@ html " added on $xdate:\n"
512
+@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
513
+@ set r [randhex]
514
+@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
515
+@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
516
+@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
517
+@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
518
+@ } elseif {$xmimetype eq "text/html"} {
519
+@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
520
+@ } else {
521
+@ set r [randhex]
522
+@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
523
+@ }
524
+@ }
525
+@ if {$seenRow} {html "</td></tr>\n"}
526
+@ </th1>
432527
@ </table>
433528
;
434529
435530
436531
/*
@@ -458,102 +553,118 @@
458553
);
459554
}
460555
461556
static const char zDefaultEdit[] =
462557
@ <th1>
558
+@ if {![info exists mutype]} {set mutype {[links only]}}
559
+@ if {![info exists icomment]} {set icomment {}}
463560
@ if {![info exists username]} {set username $login}
464561
@ if {[info exists submit]} {
465
-@ if {[info exists cmappnd]} {
466
-@ if {[string length $cmappnd]>0} {
467
-@ set ctxt "\n\n<hr /><i>[htmlize $login]"
468
-@ if {$username ne $login} {
469
-@ set ctxt "$ctxt claiming to be [htmlize $username]"
470
-@ }
471
-@ set ctxt "$ctxt added on [date] UTC:</i><br />\n$cmappnd"
472
-@ append_field comment $ctxt
473
-@ }
562
+@ if {$mutype eq "Wiki"} {
563
+@ set mimetype text/x-fossil-wiki
564
+@ } elseif {$mutype eq "HTML"} {
565
+@ set mimetype text/html
566
+@ } elseif {$mutype eq {[links only]}} {
567
+@ set mimetype text/x-fossil-plain
568
+@ } else {
569
+@ set mimetype text/plain
474570
@ }
475571
@ submit_ticket
476572
@ }
477573
@ </th1>
478574
@ <table cellpadding="5">
479575
@ <tr><td class="tktDspLabel">Title:</td><td>
480576
@ <input type="text" name="title" value="$<title>" size="60" />
481577
@ </td></tr>
578
+@
482579
@ <tr><td class="tktDspLabel">Status:</td><td>
483580
@ <th1>combobox status $status_choices 1</th1>
484581
@ </td></tr>
582
+@
485583
@ <tr><td class="tktDspLabel">Type:</td><td>
486584
@ <th1>combobox type $type_choices 1</th1>
487585
@ </td></tr>
586
+@
488587
@ <tr><td class="tktDspLabel">Severity:</td><td>
489588
@ <th1>combobox severity $severity_choices 1</th1>
490589
@ </td></tr>
590
+@
491591
@ <tr><td class="tktDspLabel">Priority:</td><td>
492592
@ <th1>combobox priority $priority_choices 1</th1>
493593
@ </td></tr>
594
+@
494595
@ <tr><td class="tktDspLabel">Resolution:</td><td>
495596
@ <th1>combobox resolution $resolution_choices 1</th1>
496597
@ </td></tr>
598
+@
497599
@ <tr><td class="tktDspLabel">Subsystem:</td><td>
498600
@ <th1>combobox subsystem $subsystem_choices 1</th1>
499601
@ </td></tr>
602
+@
500603
@ <th1>enable_output [hascap e]</th1>
501604
@ <tr><td class="tktDspLabel">Contact:</td><td>
502605
@ <input type="text" name="private_contact" size="40"
503606
@ value="$<private_contact>" />
504607
@ </td></tr>
505608
@ <th1>enable_output 1</th1>
609
+@
506610
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td><td>
507611
@ <input type="text" name="foundin" size="50" value="$<foundin>" />
508612
@ </td></tr>
509
-@ <tr><td colspan="2">
510
-@ <th1>
511
-@ if {![info exists eall]} {set eall 0}
512
-@ if {[info exists aonlybtn]} {set eall 0}
513
-@ if {[info exists eallbtn]} {set eall 1}
514
-@ if {![hascap w]} {set eall 0}
515
-@ if {![info exists cmappnd]} {set cmappnd {}}
516
-@ set nline [linecount $comment 15 10]
517
-@ enable_output $eall
518
-@ </th1>
519
-@ Description And Comments:<br />
520
-@ <textarea name="comment" cols="80" rows="$nline"
521
-@ wrap="virtual" class="wikiedit">$<comment></textarea><br />
522
-@ <input type="hidden" name="eall" value="1" />
523
-@ <input type="submit" name="aonlybtn" value="Append Remark" />
524
-@ <input type="submit" name="preview1btn" value="Preview" />
525
-@ <th1>enable_output [expr {!$eall}]</th1>
526
-@ Append Remark from
527
-@ <input type="text" name="username" value="$<username>" size="30" />:<br />
528
-@ <textarea name="cmappnd" cols="80" rows="15"
529
-@ wrap="virtual" class="wikiedit">$<cmappnd></textarea><br />
530
-@ <th1>enable_output [expr {[hascap w] && !$eall}]</th1>
531
-@ <input type="submit" name="eallbtn" value="Edit All" />
532
-@ <th1>enable_output [expr {!$eall}]</th1>
533
-@ <input type="submit" name="preview2btn" value="Preview" />
534
-@ <th1>enable_output 1</th1>
535
-@ </td></tr>
536
-@
537
-@ <th1>enable_output [info exists preview1btn]</th1>
538
-@ <tr><td colspan="2">
539
-@ Description Preview:<br /><hr />
540
-@ <th1>wiki $comment</th1>
541
-@ <hr />
542
-@ </td></tr>
543
-@ <th1>enable_output [info exists preview2btn]</th1>
544
-@ <tr><td colspan="2">
545
-@ Description Preview:<br /><hr />
546
-@ <th1>wiki $cmappnd</th1>
547
-@ <hr />
548
-@ </td></tr>
549
-@ <th1>enable_output 1</th1>
550
-@
551
-@ <tr><td align="right"></td><td>
552
-@ <input type="submit" name="submit" value="Submit Changes" />
553
-@ <input type="submit" name="cancel" value="Cancel" />
554
-@ </td></tr>
613
+@
614
+@ <tr><td colspan="2">
615
+@ Append Remark with format
616
+@ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
617
+@ from
618
+@ <input type="text" name="username" value="$<username>" size="30" />:<br />
619
+@ <textarea name="icomment" cols="80" rows="15"
620
+@ wrap="virtual" class="wikiedit">$<icomment></textarea>
621
+@ </td></tr>
622
+@
623
+@ <th1>enable_output [info exists preview]</th1>
624
+@ <tr><td colspan="2">
625
+@ Description Preview:<br><hr>
626
+@ <th1>
627
+@ if {$mutype eq "Wiki"} {
628
+@ wiki $icomment
629
+@ } elseif {$mutype eq "Plain Text"} {
630
+@ set r [randhex]
631
+@ wiki "<verbatim-$r>\n[string trimright $icomment]\n</verbatim-$r>"
632
+@ } elseif {$mutype eq {[links only]}} {
633
+@ set r [randhex]
634
+@ wiki "<verbatim-$r links>\n[string trimright $icomment]</verbatim-$r>"
635
+@ } else {
636
+@ wiki "<nowiki>\n[string trimright $icomment]\n</nowiki>"
637
+@ }
638
+@ </th1>
639
+@ <hr>
640
+@ </td></tr>
641
+@ <th1>enable_output 1</th1>
642
+@
643
+@ <tr>
644
+@ <td align="right">
645
+@ <input type="submit" name="preview" value="Preview" />
646
+@ </td>
647
+@ <td align="left">See how the description will appear after formatting.</td>
648
+@ </tr>
649
+@
650
+@ <th1>enable_output [info exists preview]</th1>
651
+@ <tr>
652
+@ <td align="right">
653
+@ <input type="submit" name="submit" value="Submit" />
654
+@ </td>
655
+@ <td align="left">Apply the changes shown above</td>
656
+@ </tr>
657
+@ <th1>enable_output 1</th1>
658
+@
659
+@ <tr>
660
+@ <td align="right">
661
+@ <input type="submit" name="cancel" value="Cancel" />
662
+@ </td>
663
+@ <td>Abandon this edit</td>
664
+@ </tr>
665
+@
555666
@ </table>
556667
;
557668
558669
/*
559670
** Return the code used to generate the edit ticket page
560671
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
67 @ CREATE TABLE ticket(
68 @ -- Do not change any column that begins with tkt_
69 @ tkt_id INTEGER PRIMARY KEY,
70 @ tkt_uuid TEXT UNIQUE,
71 @ tkt_mtime DATE,
72 @ -- Add as many field as required below this line
73 @ type TEXT,
74 @ status TEXT,
75 @ subsystem TEXT,
76 @ priority TEXT,
77 @ severity TEXT,
@@ -79,10 +79,21 @@
79 @ private_contact TEXT,
80 @ resolution TEXT,
81 @ title TEXT,
82 @ comment TEXT
83 @ );
 
 
 
 
 
 
 
 
 
 
 
84 ;
85
86 /*
87 ** Return the ticket table definition
88 */
@@ -120,11 +131,11 @@
120 style_header("Edit %s", zTitle);
121 if( P("clear")!=0 ){
122 login_verify_csrf_secret();
123 db_unset(zDbField, 0);
124 if( xRebuild ) xRebuild();
125 z = zDfltValue;
126 }else if( isSubmit ){
127 char *zErr = 0;
128 login_verify_csrf_secret();
129 if( xText && (zErr = xText(z))!=0 ){
130 @ <p class="tktsetupError">ERROR: %h(zErr)</p>
@@ -277,84 +288,117 @@
277 );
278 }
279
280 static const char zDefaultNew[] =
281 @ <th1>
 
282 @ if {[info exists submit]} {
283 @ set status Open
 
 
 
 
 
 
 
 
 
284 @ submit_ticket
285 @ }
286 @ </th1>
287 @ <h1 style="text-align: center;">Enter A New Ticket</h1>
288 @ <table cellpadding="5">
289 @ <tr>
290 @ <td colspan="2">
291 @ Enter a one-line summary of the ticket:<br />
292 @ <input type="text" name="title" size="60" value="$<title>" />
293 @ </td>
294 @ </tr>
295 @
296 @ <tr>
297 @ <td style="text-align: center;">Type:
298 @ <th1>combobox type $type_choices 1</th1>
299 @ </td>
300 @ <td>What type of ticket is this?</td>
301 @ </tr>
302 @
303 @ <tr>
304 @ <td style="text-align: center;">Version:
 
305 @ <input type="text" name="foundin" size="20" value="$<foundin>" />
306 @ </td>
307 @ <td>In what version or build number do you observe the problem?</td>
 
308 @ </tr>
309 @
310 @ <tr>
311 @ <td style="text-align: center;">Severity:
312 @ <th1>combobox severity $severity_choices 1</th1>
313 @ </td>
314 @ <td>How debilitating is the problem? How badly does the problem
315 @ affect the operation of the product?</td>
316 @ </tr>
317 @
318 @ <tr>
319 @ <td style="text-align: center;">EMail:
320 @ <input type="text" name="private_contact" value="$<private_contact>" size="30" />
 
 
321 @ </td>
322 @ <td><span style="text-decoration: underline;">Not publicly visible</span>.
323 @ Used by developers to contact you with questions.</td>
324 @ </tr>
325 @
326 @ <tr>
327 @ <td colspan="2">
328 @ Enter a detailed description of the problem.
329 @ For code defects, be sure to provide details on exactly how
330 @ the problem can be reproduced. Provide as much detail as
331 @ possible.
 
332 @ <br />
333 @ <th1>set nline [linecount $comment 50 10]</th1>
334 @ <textarea name="comment" cols="80" rows="$nline"
335 @ wrap="virtual" class="wikiedit">$<comment></textarea><br />
336 @ <input type="submit" name="preview" value="Preview" /></td>
337 @ </tr>
338 @
339 @ <th1>enable_output [info exists preview]</th1>
340 @ <tr><td colspan="2">
341 @ Description Preview:<br /><hr />
342 @ <th1>wiki $comment</th1>
343 @ <hr />
344 @ </td></tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345 @ <th1>enable_output 1</th1>
346 @
347 @ <tr>
348 @ <td style="text-align: center;">
349 @ <input type="submit" name="submit" value="Submit" />
350 @ </td>
351 @ <td>After filling in the information above, press this button to create
352 @ the new ticket</td>
353 @ </tr>
354 @ <tr>
355 @ <td style="text-align: center;">
356 @ <input type="submit" name="cancel" value="Cancel" />
357 @ </td>
358 @ <td>Abandon and forget this ticket</td>
359 @ </tr>
360 @ </table>
@@ -387,14 +431,21 @@
387 }
388
389 static const char zDefaultView[] =
390 @ <table cellpadding="5">
391 @ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
392 @ <td class="tktDspValue" colspan="3">$<tkt_uuid></td></tr>
 
 
 
 
 
 
 
393 @ <tr><td class="tktDspLabel">Title:</td>
394 @ <td class="tktDspValue" colspan="3">
395 @ <th1>wiki $title</th1>
396 @ </td></tr>
397 @ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue">
398 @ $<status>
399 @ </td>
400 @ <td class="tktDspLabel">Type:</td><td class="tktDspValue">
@@ -423,14 +474,58 @@
423 @ </tr>
424 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
425 @ <td colspan="3" valign="top" class="tktDspValue">
426 @ $<foundin>
427 @ </td></tr>
428 @ <tr><td>Description &amp; Comments:</td></tr>
429 @ <tr><td colspan="4" class="tktDspValue">
430 @ <th1>wiki $comment</th1>
431 @ </td></tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432 @ </table>
433 ;
434
435
436 /*
@@ -458,102 +553,118 @@
458 );
459 }
460
461 static const char zDefaultEdit[] =
462 @ <th1>
 
 
463 @ if {![info exists username]} {set username $login}
464 @ if {[info exists submit]} {
465 @ if {[info exists cmappnd]} {
466 @ if {[string length $cmappnd]>0} {
467 @ set ctxt "\n\n<hr /><i>[htmlize $login]"
468 @ if {$username ne $login} {
469 @ set ctxt "$ctxt claiming to be [htmlize $username]"
470 @ }
471 @ set ctxt "$ctxt added on [date] UTC:</i><br />\n$cmappnd"
472 @ append_field comment $ctxt
473 @ }
474 @ }
475 @ submit_ticket
476 @ }
477 @ </th1>
478 @ <table cellpadding="5">
479 @ <tr><td class="tktDspLabel">Title:</td><td>
480 @ <input type="text" name="title" value="$<title>" size="60" />
481 @ </td></tr>
 
482 @ <tr><td class="tktDspLabel">Status:</td><td>
483 @ <th1>combobox status $status_choices 1</th1>
484 @ </td></tr>
 
485 @ <tr><td class="tktDspLabel">Type:</td><td>
486 @ <th1>combobox type $type_choices 1</th1>
487 @ </td></tr>
 
488 @ <tr><td class="tktDspLabel">Severity:</td><td>
489 @ <th1>combobox severity $severity_choices 1</th1>
490 @ </td></tr>
 
491 @ <tr><td class="tktDspLabel">Priority:</td><td>
492 @ <th1>combobox priority $priority_choices 1</th1>
493 @ </td></tr>
 
494 @ <tr><td class="tktDspLabel">Resolution:</td><td>
495 @ <th1>combobox resolution $resolution_choices 1</th1>
496 @ </td></tr>
 
497 @ <tr><td class="tktDspLabel">Subsystem:</td><td>
498 @ <th1>combobox subsystem $subsystem_choices 1</th1>
499 @ </td></tr>
 
500 @ <th1>enable_output [hascap e]</th1>
501 @ <tr><td class="tktDspLabel">Contact:</td><td>
502 @ <input type="text" name="private_contact" size="40"
503 @ value="$<private_contact>" />
504 @ </td></tr>
505 @ <th1>enable_output 1</th1>
 
506 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td><td>
507 @ <input type="text" name="foundin" size="50" value="$<foundin>" />
508 @ </td></tr>
509 @ <tr><td colspan="2">
510 @ <th1>
511 @ if {![info exists eall]} {set eall 0}
512 @ if {[info exists aonlybtn]} {set eall 0}
513 @ if {[info exists eallbtn]} {set eall 1}
514 @ if {![hascap w]} {set eall 0}
515 @ if {![info exists cmappnd]} {set cmappnd {}}
516 @ set nline [linecount $comment 15 10]
517 @ enable_output $eall
518 @ </th1>
519 @ Description And Comments:<br />
520 @ <textarea name="comment" cols="80" rows="$nline"
521 @ wrap="virtual" class="wikiedit">$<comment></textarea><br />
522 @ <input type="hidden" name="eall" value="1" />
523 @ <input type="submit" name="aonlybtn" value="Append Remark" />
524 @ <input type="submit" name="preview1btn" value="Preview" />
525 @ <th1>enable_output [expr {!$eall}]</th1>
526 @ Append Remark from
527 @ <input type="text" name="username" value="$<username>" size="30" />:<br />
528 @ <textarea name="cmappnd" cols="80" rows="15"
529 @ wrap="virtual" class="wikiedit">$<cmappnd></textarea><br />
530 @ <th1>enable_output [expr {[hascap w] && !$eall}]</th1>
531 @ <input type="submit" name="eallbtn" value="Edit All" />
532 @ <th1>enable_output [expr {!$eall}]</th1>
533 @ <input type="submit" name="preview2btn" value="Preview" />
534 @ <th1>enable_output 1</th1>
535 @ </td></tr>
536 @
537 @ <th1>enable_output [info exists preview1btn]</th1>
538 @ <tr><td colspan="2">
539 @ Description Preview:<br /><hr />
540 @ <th1>wiki $comment</th1>
541 @ <hr />
542 @ </td></tr>
543 @ <th1>enable_output [info exists preview2btn]</th1>
544 @ <tr><td colspan="2">
545 @ Description Preview:<br /><hr />
546 @ <th1>wiki $cmappnd</th1>
547 @ <hr />
548 @ </td></tr>
549 @ <th1>enable_output 1</th1>
550 @
551 @ <tr><td align="right"></td><td>
552 @ <input type="submit" name="submit" value="Submit Changes" />
553 @ <input type="submit" name="cancel" value="Cancel" />
554 @ </td></tr>
 
 
 
 
 
 
 
555 @ </table>
556 ;
557
558 /*
559 ** Return the code used to generate the edit ticket page
560
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
67 @ CREATE TABLE ticket(
68 @ -- Do not change any column that begins with tkt_
69 @ tkt_id INTEGER PRIMARY KEY,
70 @ tkt_uuid TEXT UNIQUE,
71 @ tkt_mtime DATE,
72 @ -- Add as many fields as required below this line
73 @ type TEXT,
74 @ status TEXT,
75 @ subsystem TEXT,
76 @ priority TEXT,
77 @ severity TEXT,
@@ -79,10 +79,21 @@
79 @ private_contact TEXT,
80 @ resolution TEXT,
81 @ title TEXT,
82 @ comment TEXT
83 @ );
84 @ CREATE TABLE ticketchng(
85 @ -- Do not change any column that begins with tkt_
86 @ tkt_id INTEGER REFERENCES ticket,
87 @ tkt_mtime DATE,
88 @ -- Add as many fields as required below this line
89 @ login TEXT,
90 @ username TEXT,
91 @ mimetype TEXT,
92 @ icomment TEXT
93 @ );
94 @ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
95 ;
96
97 /*
98 ** Return the ticket table definition
99 */
@@ -120,11 +131,11 @@
131 style_header("Edit %s", zTitle);
132 if( P("clear")!=0 ){
133 login_verify_csrf_secret();
134 db_unset(zDbField, 0);
135 if( xRebuild ) xRebuild();
136 cgi_redirect("tktsetup");
137 }else if( isSubmit ){
138 char *zErr = 0;
139 login_verify_csrf_secret();
140 if( xText && (zErr = xText(z))!=0 ){
141 @ <p class="tktsetupError">ERROR: %h(zErr)</p>
@@ -277,84 +288,117 @@
288 );
289 }
290
291 static const char zDefaultNew[] =
292 @ <th1>
293 @ if {![info exists mutype]} {set mutype {[links only]}}
294 @ if {[info exists submit]} {
295 @ set status Open
296 @ if {$mutype eq "HTML"} {
297 @ set mimetype "text/html"
298 @ } elseif {$mutype eq "Wiki"} {
299 @ set mimetype "text/x-fossil-wiki"
300 @ } elseif {$mutype eq {[links only]}} {
301 @ set mimetype "text/x-fossil-plain"
302 @ } else {
303 @ set mimetype "text/plain"
304 @ }
305 @ submit_ticket
306 @ }
307 @ </th1>
308 @ <h1 style="text-align: center;">Enter A New Ticket</h1>
309 @ <table cellpadding="5">
310 @ <tr>
311 @ <td colspan="3">
312 @ Enter a one-line summary of the ticket:<br />
313 @ <input type="text" name="title" size="60" value="$<title>" />
314 @ </td>
315 @ </tr>
316 @
317 @ <tr>
318 @ <td align="right">Type:</td>
319 @ <td align="left"><th1>combobox type $type_choices 1</th1></td>
320 @ <td align="left">What type of ticket is this?</td>
 
321 @ </tr>
322 @
323 @ <tr>
324 @ <td align="right">Version:</td>
325 @ <td align="left">
326 @ <input type="text" name="foundin" size="20" value="$<foundin>" />
327 @ </td>
328 @ <td align="left">In what version or build number do you observe
329 @ the problem?</td>
330 @ </tr>
331 @
332 @ <tr>
333 @ <td align="right">Severity:</td>
334 @ <td align="left"><th1>combobox severity $severity_choices 1</th1></td>
335 @ <td align="left">How debilitating is the problem? How badly does the problem
 
336 @ affect the operation of the product?</td>
337 @ </tr>
338 @
339 @ <tr>
340 @ <td align="right">EMail:</td>
341 @ <td align="left">
342 @ <input type="text" name="private_contact" value="$<private_contact>"
343 @ size="30" />
344 @ </td>
345 @ <td align="left"><u>Not publicly visible</u>
346 @ Used by developers to contact you with questions.</td>
347 @ </tr>
348 @
349 @ <tr>
350 @ <td colspan="3">
351 @ Enter a detailed description of the problem.
352 @ For code defects, be sure to provide details on exactly how
353 @ the problem can be reproduced. Provide as much detail as
354 @ possible. Format:
355 @ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
356 @ <br />
357 @ <th1>set nline [linecount $comment 50 10]</th1>
358 @ <textarea name="icomment" cols="80" rows="$nline"
359 @ wrap="virtual" class="wikiedit">$<icomment></textarea><br />
 
360 @ </tr>
361 @
362 @ <th1>enable_output [info exists preview]</th1>
363 @ <tr><td colspan="3">
364 @ Description Preview:<br /><hr />
365 @ <th1>
366 @ if {$mutype eq "Wiki"} {
367 @ wiki $icomment
368 @ } elseif {$mutype eq "Plain Text"} {
369 @ set r [randhex]
370 @ wiki "<verbatim-$r>[string trimright $icomment]\n</verbatim-$r>"
371 @ } elseif {$mutype eq {[links only]}} {
372 @ set r [randhex]
373 @ wiki "<verbatim-$r links>[string trimright $icomment]\n</verbatim-$r>"
374 @ } else {
375 @ wiki "<nowiki>$icomment\n</nowiki>"
376 @ }
377 @ </th1>
378 @ <hr /></td></tr>
379 @ <th1>enable_output 1</th1>
380 @
381 @ <tr>
382 @ <td><td align="left">
383 @ <input type="submit" name="preview" value="Preview" />
384 @ </td>
385 @ <td align="left">See how the description will appear after formatting.</td>
386 @ </tr>
387 @
388 @ <th1>enable_output [info exists preview]</th1>
389 @ <tr>
390 @ <td><td align="left">
391 @ <input type="submit" name="submit" value="Submit" />
392 @ </td>
393 @ <td align="left">After filling in the information above, press this
394 @ button to create the new ticket</td>
395 @ </tr>
396 @ <th1>enable_output 1</th1>
397 @
398 @ <tr>
399 @ <td><td align="left">
 
 
 
 
 
 
 
400 @ <input type="submit" name="cancel" value="Cancel" />
401 @ </td>
402 @ <td>Abandon and forget this ticket</td>
403 @ </tr>
404 @ </table>
@@ -387,14 +431,21 @@
431 }
432
433 static const char zDefaultView[] =
434 @ <table cellpadding="5">
435 @ <tr><td class="tktDspLabel">Ticket&nbsp;UUID:</td>
436 @ <th1>
437 @ if {[hascap s]} {
438 @ html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
439 @ html "($tkt_id)</td></tr>\n"
440 @ } else {
441 @ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
442 @ }
443 @ </th1>
444 @ <tr><td class="tktDspLabel">Title:</td>
445 @ <td class="tktDspValue" colspan="3">
446 @ $<title>
447 @ </td></tr>
448 @ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue">
449 @ $<status>
450 @ </td>
451 @ <td class="tktDspLabel">Type:</td><td class="tktDspValue">
@@ -423,14 +474,58 @@
474 @ </tr>
475 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
476 @ <td colspan="3" valign="top" class="tktDspValue">
477 @ $<foundin>
478 @ </td></tr>
479 @
480 @ <th1>
481 @ if {[info exists comment] && [string length $comment]>10} {
482 @ html {
483 @ <tr><td class="tktDspLabel">Description:</td></tr>
484 @ <tr><td colspan="5" class="tktDspValue">
485 @ }
486 @ if {[info exists plaintext]} {
487 @ set r [randhex]
488 @ wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>"
489 @ } else {
490 @ wiki $comment
491 @ }
492 @ }
493 @ set seenRow 0
494 @ set alwaysPlaintext [info exists plaintext]
495 @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
496 @ mimetype as xmimetype, icomment AS xcomment,
497 @ username AS xusername
498 @ FROM ticketchng
499 @ WHERE tkt_id=$tkt_id} {
500 @ if {$seenRow} {
501 @ html "<hr>\n"
502 @ } else {
503 @ html "<tr><td class='tktDspLabel'>User Comments:</td></tr>\n"
504 @ html "<tr><td colspan='5' class='tktDspValue'>\n"
505 @ set seenRow 1
506 @ }
507 @ html "[htmlize $xlogin]"
508 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
509 @ html " (claiming to be [htmlize $xusername])"
510 @ }
511 @ html " added on $xdate:\n"
512 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
513 @ set r [randhex]
514 @ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
515 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
516 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
517 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
518 @ } elseif {$xmimetype eq "text/html"} {
519 @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
520 @ } else {
521 @ set r [randhex]
522 @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
523 @ }
524 @ }
525 @ if {$seenRow} {html "</td></tr>\n"}
526 @ </th1>
527 @ </table>
528 ;
529
530
531 /*
@@ -458,102 +553,118 @@
553 );
554 }
555
556 static const char zDefaultEdit[] =
557 @ <th1>
558 @ if {![info exists mutype]} {set mutype {[links only]}}
559 @ if {![info exists icomment]} {set icomment {}}
560 @ if {![info exists username]} {set username $login}
561 @ if {[info exists submit]} {
562 @ if {$mutype eq "Wiki"} {
563 @ set mimetype text/x-fossil-wiki
564 @ } elseif {$mutype eq "HTML"} {
565 @ set mimetype text/html
566 @ } elseif {$mutype eq {[links only]}} {
567 @ set mimetype text/x-fossil-plain
568 @ } else {
569 @ set mimetype text/plain
 
570 @ }
571 @ submit_ticket
572 @ }
573 @ </th1>
574 @ <table cellpadding="5">
575 @ <tr><td class="tktDspLabel">Title:</td><td>
576 @ <input type="text" name="title" value="$<title>" size="60" />
577 @ </td></tr>
578 @
579 @ <tr><td class="tktDspLabel">Status:</td><td>
580 @ <th1>combobox status $status_choices 1</th1>
581 @ </td></tr>
582 @
583 @ <tr><td class="tktDspLabel">Type:</td><td>
584 @ <th1>combobox type $type_choices 1</th1>
585 @ </td></tr>
586 @
587 @ <tr><td class="tktDspLabel">Severity:</td><td>
588 @ <th1>combobox severity $severity_choices 1</th1>
589 @ </td></tr>
590 @
591 @ <tr><td class="tktDspLabel">Priority:</td><td>
592 @ <th1>combobox priority $priority_choices 1</th1>
593 @ </td></tr>
594 @
595 @ <tr><td class="tktDspLabel">Resolution:</td><td>
596 @ <th1>combobox resolution $resolution_choices 1</th1>
597 @ </td></tr>
598 @
599 @ <tr><td class="tktDspLabel">Subsystem:</td><td>
600 @ <th1>combobox subsystem $subsystem_choices 1</th1>
601 @ </td></tr>
602 @
603 @ <th1>enable_output [hascap e]</th1>
604 @ <tr><td class="tktDspLabel">Contact:</td><td>
605 @ <input type="text" name="private_contact" size="40"
606 @ value="$<private_contact>" />
607 @ </td></tr>
608 @ <th1>enable_output 1</th1>
609 @
610 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td><td>
611 @ <input type="text" name="foundin" size="50" value="$<foundin>" />
612 @ </td></tr>
613 @
614 @ <tr><td colspan="2">
615 @ Append Remark with format
616 @ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
617 @ from
618 @ <input type="text" name="username" value="$<username>" size="30" />:<br />
619 @ <textarea name="icomment" cols="80" rows="15"
620 @ wrap="virtual" class="wikiedit">$<icomment></textarea>
621 @ </td></tr>
622 @
623 @ <th1>enable_output [info exists preview]</th1>
624 @ <tr><td colspan="2">
625 @ Description Preview:<br><hr>
626 @ <th1>
627 @ if {$mutype eq "Wiki"} {
628 @ wiki $icomment
629 @ } elseif {$mutype eq "Plain Text"} {
630 @ set r [randhex]
631 @ wiki "<verbatim-$r>\n[string trimright $icomment]\n</verbatim-$r>"
632 @ } elseif {$mutype eq {[links only]}} {
633 @ set r [randhex]
634 @ wiki "<verbatim-$r links>\n[string trimright $icomment]</verbatim-$r>"
635 @ } else {
636 @ wiki "<nowiki>\n[string trimright $icomment]\n</nowiki>"
637 @ }
638 @ </th1>
639 @ <hr>
640 @ </td></tr>
641 @ <th1>enable_output 1</th1>
642 @
643 @ <tr>
644 @ <td align="right">
645 @ <input type="submit" name="preview" value="Preview" />
646 @ </td>
647 @ <td align="left">See how the description will appear after formatting.</td>
648 @ </tr>
649 @
650 @ <th1>enable_output [info exists preview]</th1>
651 @ <tr>
652 @ <td align="right">
653 @ <input type="submit" name="submit" value="Submit" />
654 @ </td>
655 @ <td align="left">Apply the changes shown above</td>
656 @ </tr>
657 @ <th1>enable_output 1</th1>
658 @
659 @ <tr>
660 @ <td align="right">
661 @ <input type="submit" name="cancel" value="Cancel" />
662 @ </td>
663 @ <td>Abandon this edit</td>
664 @ </tr>
665 @
666 @ </table>
667 ;
668
669 /*
670 ** Return the code used to generate the edit ticket page
671
+54 -38
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
5151
#define ATTR_FACE 12
5252
#define ATTR_HEIGHT 13
5353
#define ATTR_HREF 14
5454
#define ATTR_HSPACE 15
5555
#define ATTR_ID 16
56
-#define ATTR_NAME 17
57
-#define ATTR_ROWSPAN 18
58
-#define ATTR_SIZE 19
59
-#define ATTR_SRC 20
60
-#define ATTR_START 21
61
-#define ATTR_STYLE 22
62
-#define ATTR_TARGET 23
63
-#define ATTR_TYPE 24
64
-#define ATTR_VALIGN 25
65
-#define ATTR_VALUE 26
66
-#define ATTR_VSPACE 27
67
-#define ATTR_WIDTH 28
56
+#define ATTR_LINKS 17
57
+#define ATTR_NAME 18
58
+#define ATTR_ROWSPAN 19
59
+#define ATTR_SIZE 20
60
+#define ATTR_SRC 21
61
+#define ATTR_START 22
62
+#define ATTR_STYLE 23
63
+#define ATTR_TARGET 24
64
+#define ATTR_TYPE 25
65
+#define ATTR_VALIGN 26
66
+#define ATTR_VALUE 27
67
+#define ATTR_VSPACE 28
68
+#define ATTR_WIDTH 29
6869
#define AMSK_ALIGN 0x00000001
6970
#define AMSK_ALT 0x00000002
7071
#define AMSK_BGCOLOR 0x00000004
7172
#define AMSK_BORDER 0x00000008
7273
#define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
7980
#define AMSK_FACE 0x00000800
8081
#define AMSK_HEIGHT 0x00001000
8182
#define AMSK_HREF 0x00002000
8283
#define AMSK_HSPACE 0x00004000
8384
#define AMSK_ID 0x00008000
84
-#define AMSK_NAME 0x00010000
85
-#define AMSK_ROWSPAN 0x00020000
86
-#define AMSK_SIZE 0x00040000
87
-#define AMSK_SRC 0x00080000
88
-#define AMSK_START 0x00100000
89
-#define AMSK_STYLE 0x00200000
90
-#define AMSK_TARGET 0x00400000
91
-#define AMSK_TYPE 0x00800000
92
-#define AMSK_VALIGN 0x01000000
93
-#define AMSK_VALUE 0x02000000
94
-#define AMSK_VSPACE 0x04000000
95
-#define AMSK_WIDTH 0x08000000
85
+#define AMSK_LINKS 0x00010000
86
+#define AMSK_NAME 0x00020000
87
+#define AMSK_ROWSPAN 0x00040000
88
+#define AMSK_SIZE 0x00080000
89
+#define AMSK_SRC 0x00100000
90
+#define AMSK_START 0x00200000
91
+#define AMSK_STYLE 0x00400000
92
+#define AMSK_TARGET 0x00800000
93
+#define AMSK_TYPE 0x01000000
94
+#define AMSK_VALIGN 0x02000000
95
+#define AMSK_VALUE 0x04000000
96
+#define AMSK_VSPACE 0x08000000
97
+#define AMSK_WIDTH 0x10000000
9698
9799
static const struct AllowedAttribute {
98100
const char *zName;
99101
unsigned int iMask;
100102
} aAttribute[] = {
@@ -113,10 +115,11 @@
113115
{ "face", AMSK_FACE, },
114116
{ "height", AMSK_HEIGHT, },
115117
{ "href", AMSK_HREF, },
116118
{ "hspace", AMSK_HSPACE, },
117119
{ "id", AMSK_ID, },
120
+ { "links", AMSK_LINKS, },
118121
{ "name", AMSK_NAME, },
119122
{ "rowspan", AMSK_ROWSPAN, },
120123
{ "size", AMSK_SIZE, },
121124
{ "src", AMSK_SRC, },
122125
{ "start", AMSK_START, },
@@ -438,11 +441,11 @@
438441
int n = 1;
439442
int inparen = 0;
440443
int c;
441444
if( z[n]=='/' ){ n++; }
442445
if( !fossil_isalpha(z[n]) ) return 0;
443
- while( fossil_isalnum(z[n]) ){ n++; }
446
+ while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; }
444447
c = z[n];
445448
if( c=='/' && z[n+1]=='>' ){ return n+2; }
446449
if( c!='>' && !fossil_isspace(c) ) return 0;
447450
while( (c = z[n])!=0 && (c!='>' || inparen) ){
448451
if( c==inparen ){
@@ -750,12 +753,23 @@
750753
}
751754
zTag[j] = 0;
752755
p->iCode = findTag(zTag);
753756
p->iType = aMarkup[p->iCode].iType;
754757
p->nAttr = 0;
758
+ c = 0;
759
+ if( z[i]=='-' ){
760
+ p->aAttr[0].iACode = iACode = ATTR_ID;
761
+ i++;
762
+ p->aAttr[0].zValue = &z[i];
763
+ while( fossil_isalnum(z[i]) ){ i++; }
764
+ p->aAttr[0].cTerm = c = z[i];
765
+ z[i++] = 0;
766
+ p->nAttr = 1;
767
+ if( c=='>' ) return;
768
+ }
755769
while( fossil_isspace(z[i]) ){ i++; }
756
- while( p->nAttr<8 && fossil_isalpha(z[i]) ){
770
+ while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
757771
int attrOk; /* True to preserver attribute. False to ignore it */
758772
j = 0;
759773
while( fossil_isalnum(z[i]) ){
760774
if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
761775
i++;
@@ -1161,11 +1175,15 @@
11611175
|| strncmp(zTarget, "mailto:", 7)==0
11621176
){
11631177
blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
11641178
}else if( zTarget[0]=='/' ){
11651179
blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1166
- }else if( zTarget[0]=='.' || zTarget[0]=='#' ){
1180
+ }else if( zTarget[0]=='.'
1181
+ && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1182
+ && (p->state & WIKI_LINKSONLY)==0 ){
1183
+ blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1184
+ }else if( zTarget[0]=='#' ){
11671185
blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
11681186
}else if( is_valid_uuid(zTarget) ){
11691187
int isClosed = 0;
11701188
if( is_ticket(zTarget, &isClosed) ){
11711189
/* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
14331451
const char *zId;
14341452
int iDiv;
14351453
parseMarkup(&markup, z);
14361454
14371455
/* Markup of the form </div id=ID> where there is a matching
1438
- ** ID somewhere on the stack. Exit the verbatim if were are in
1439
- ** it. Pop the stack up to the matching <div>. Discard the
1440
- ** </div>
1456
+ ** ID somewhere on the stack. Exit any contained verbatim.
1457
+ ** Pop the stack up to the matching <div>. Discard the </div>
14411458
*/
14421459
if( markup.iCode==MARKUP_DIV && markup.endTag &&
14431460
(zId = markupId(&markup))!=0 &&
14441461
(iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
14451462
){
@@ -1527,18 +1544,17 @@
15271544
p->preVerbState = p->state;
15281545
p->state &= ~ALLOW_WIKI;
15291546
for(ii=0; ii<markup.nAttr; ii++){
15301547
if( markup.aAttr[ii].iACode == ATTR_ID ){
15311548
p->zVerbatimId = markup.aAttr[ii].zValue;
1532
- }else if( markup.aAttr[ii].iACode == ATTR_TYPE ){
1533
- if( fossil_stricmp(markup.aAttr[ii].zValue, "allow-links")==0 ){
1534
- p->state |= ALLOW_LINKS;
1535
- }else{
1536
- blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1537
- markup.aAttr[ii].zValue);
1538
- vAttrDidAppend=1;
1539
- }
1549
+ }else if( markup.aAttr[ii].iACode==ATTR_TYPE ){
1550
+ blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1551
+ markup.aAttr[ii].zValue);
1552
+ vAttrDidAppend=1;
1553
+ }else if( markup.aAttr[ii].iACode==ATTR_LINKS
1554
+ && !is_false(markup.aAttr[ii].zValue) ){
1555
+ p->state |= ALLOW_LINKS;
15401556
}
15411557
}
15421558
if( !vAttrDidAppend ) {
15431559
endAutoParagraph(p);
15441560
blob_append(p->pOut, "<pre class='verbatim'>",-1);
15451561
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
51 #define ATTR_FACE 12
52 #define ATTR_HEIGHT 13
53 #define ATTR_HREF 14
54 #define ATTR_HSPACE 15
55 #define ATTR_ID 16
56 #define ATTR_NAME 17
57 #define ATTR_ROWSPAN 18
58 #define ATTR_SIZE 19
59 #define ATTR_SRC 20
60 #define ATTR_START 21
61 #define ATTR_STYLE 22
62 #define ATTR_TARGET 23
63 #define ATTR_TYPE 24
64 #define ATTR_VALIGN 25
65 #define ATTR_VALUE 26
66 #define ATTR_VSPACE 27
67 #define ATTR_WIDTH 28
 
68 #define AMSK_ALIGN 0x00000001
69 #define AMSK_ALT 0x00000002
70 #define AMSK_BGCOLOR 0x00000004
71 #define AMSK_BORDER 0x00000008
72 #define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
79 #define AMSK_FACE 0x00000800
80 #define AMSK_HEIGHT 0x00001000
81 #define AMSK_HREF 0x00002000
82 #define AMSK_HSPACE 0x00004000
83 #define AMSK_ID 0x00008000
84 #define AMSK_NAME 0x00010000
85 #define AMSK_ROWSPAN 0x00020000
86 #define AMSK_SIZE 0x00040000
87 #define AMSK_SRC 0x00080000
88 #define AMSK_START 0x00100000
89 #define AMSK_STYLE 0x00200000
90 #define AMSK_TARGET 0x00400000
91 #define AMSK_TYPE 0x00800000
92 #define AMSK_VALIGN 0x01000000
93 #define AMSK_VALUE 0x02000000
94 #define AMSK_VSPACE 0x04000000
95 #define AMSK_WIDTH 0x08000000
 
96
97 static const struct AllowedAttribute {
98 const char *zName;
99 unsigned int iMask;
100 } aAttribute[] = {
@@ -113,10 +115,11 @@
113 { "face", AMSK_FACE, },
114 { "height", AMSK_HEIGHT, },
115 { "href", AMSK_HREF, },
116 { "hspace", AMSK_HSPACE, },
117 { "id", AMSK_ID, },
 
118 { "name", AMSK_NAME, },
119 { "rowspan", AMSK_ROWSPAN, },
120 { "size", AMSK_SIZE, },
121 { "src", AMSK_SRC, },
122 { "start", AMSK_START, },
@@ -438,11 +441,11 @@
438 int n = 1;
439 int inparen = 0;
440 int c;
441 if( z[n]=='/' ){ n++; }
442 if( !fossil_isalpha(z[n]) ) return 0;
443 while( fossil_isalnum(z[n]) ){ n++; }
444 c = z[n];
445 if( c=='/' && z[n+1]=='>' ){ return n+2; }
446 if( c!='>' && !fossil_isspace(c) ) return 0;
447 while( (c = z[n])!=0 && (c!='>' || inparen) ){
448 if( c==inparen ){
@@ -750,12 +753,23 @@
750 }
751 zTag[j] = 0;
752 p->iCode = findTag(zTag);
753 p->iType = aMarkup[p->iCode].iType;
754 p->nAttr = 0;
 
 
 
 
 
 
 
 
 
 
 
755 while( fossil_isspace(z[i]) ){ i++; }
756 while( p->nAttr<8 && fossil_isalpha(z[i]) ){
757 int attrOk; /* True to preserver attribute. False to ignore it */
758 j = 0;
759 while( fossil_isalnum(z[i]) ){
760 if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
761 i++;
@@ -1161,11 +1175,15 @@
1161 || strncmp(zTarget, "mailto:", 7)==0
1162 ){
1163 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1164 }else if( zTarget[0]=='/' ){
1165 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1166 }else if( zTarget[0]=='.' || zTarget[0]=='#' ){
 
 
 
 
1167 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1168 }else if( is_valid_uuid(zTarget) ){
1169 int isClosed = 0;
1170 if( is_ticket(zTarget, &isClosed) ){
1171 /* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
1433 const char *zId;
1434 int iDiv;
1435 parseMarkup(&markup, z);
1436
1437 /* Markup of the form </div id=ID> where there is a matching
1438 ** ID somewhere on the stack. Exit the verbatim if were are in
1439 ** it. Pop the stack up to the matching <div>. Discard the
1440 ** </div>
1441 */
1442 if( markup.iCode==MARKUP_DIV && markup.endTag &&
1443 (zId = markupId(&markup))!=0 &&
1444 (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
1445 ){
@@ -1527,18 +1544,17 @@
1527 p->preVerbState = p->state;
1528 p->state &= ~ALLOW_WIKI;
1529 for(ii=0; ii<markup.nAttr; ii++){
1530 if( markup.aAttr[ii].iACode == ATTR_ID ){
1531 p->zVerbatimId = markup.aAttr[ii].zValue;
1532 }else if( markup.aAttr[ii].iACode == ATTR_TYPE ){
1533 if( fossil_stricmp(markup.aAttr[ii].zValue, "allow-links")==0 ){
1534 p->state |= ALLOW_LINKS;
1535 }else{
1536 blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1537 markup.aAttr[ii].zValue);
1538 vAttrDidAppend=1;
1539 }
1540 }
1541 }
1542 if( !vAttrDidAppend ) {
1543 endAutoParagraph(p);
1544 blob_append(p->pOut, "<pre class='verbatim'>",-1);
1545
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
51 #define ATTR_FACE 12
52 #define ATTR_HEIGHT 13
53 #define ATTR_HREF 14
54 #define ATTR_HSPACE 15
55 #define ATTR_ID 16
56 #define ATTR_LINKS 17
57 #define ATTR_NAME 18
58 #define ATTR_ROWSPAN 19
59 #define ATTR_SIZE 20
60 #define ATTR_SRC 21
61 #define ATTR_START 22
62 #define ATTR_STYLE 23
63 #define ATTR_TARGET 24
64 #define ATTR_TYPE 25
65 #define ATTR_VALIGN 26
66 #define ATTR_VALUE 27
67 #define ATTR_VSPACE 28
68 #define ATTR_WIDTH 29
69 #define AMSK_ALIGN 0x00000001
70 #define AMSK_ALT 0x00000002
71 #define AMSK_BGCOLOR 0x00000004
72 #define AMSK_BORDER 0x00000008
73 #define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
80 #define AMSK_FACE 0x00000800
81 #define AMSK_HEIGHT 0x00001000
82 #define AMSK_HREF 0x00002000
83 #define AMSK_HSPACE 0x00004000
84 #define AMSK_ID 0x00008000
85 #define AMSK_LINKS 0x00010000
86 #define AMSK_NAME 0x00020000
87 #define AMSK_ROWSPAN 0x00040000
88 #define AMSK_SIZE 0x00080000
89 #define AMSK_SRC 0x00100000
90 #define AMSK_START 0x00200000
91 #define AMSK_STYLE 0x00400000
92 #define AMSK_TARGET 0x00800000
93 #define AMSK_TYPE 0x01000000
94 #define AMSK_VALIGN 0x02000000
95 #define AMSK_VALUE 0x04000000
96 #define AMSK_VSPACE 0x08000000
97 #define AMSK_WIDTH 0x10000000
98
99 static const struct AllowedAttribute {
100 const char *zName;
101 unsigned int iMask;
102 } aAttribute[] = {
@@ -113,10 +115,11 @@
115 { "face", AMSK_FACE, },
116 { "height", AMSK_HEIGHT, },
117 { "href", AMSK_HREF, },
118 { "hspace", AMSK_HSPACE, },
119 { "id", AMSK_ID, },
120 { "links", AMSK_LINKS, },
121 { "name", AMSK_NAME, },
122 { "rowspan", AMSK_ROWSPAN, },
123 { "size", AMSK_SIZE, },
124 { "src", AMSK_SRC, },
125 { "start", AMSK_START, },
@@ -438,11 +441,11 @@
441 int n = 1;
442 int inparen = 0;
443 int c;
444 if( z[n]=='/' ){ n++; }
445 if( !fossil_isalpha(z[n]) ) return 0;
446 while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; }
447 c = z[n];
448 if( c=='/' && z[n+1]=='>' ){ return n+2; }
449 if( c!='>' && !fossil_isspace(c) ) return 0;
450 while( (c = z[n])!=0 && (c!='>' || inparen) ){
451 if( c==inparen ){
@@ -750,12 +753,23 @@
753 }
754 zTag[j] = 0;
755 p->iCode = findTag(zTag);
756 p->iType = aMarkup[p->iCode].iType;
757 p->nAttr = 0;
758 c = 0;
759 if( z[i]=='-' ){
760 p->aAttr[0].iACode = iACode = ATTR_ID;
761 i++;
762 p->aAttr[0].zValue = &z[i];
763 while( fossil_isalnum(z[i]) ){ i++; }
764 p->aAttr[0].cTerm = c = z[i];
765 z[i++] = 0;
766 p->nAttr = 1;
767 if( c=='>' ) return;
768 }
769 while( fossil_isspace(z[i]) ){ i++; }
770 while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
771 int attrOk; /* True to preserver attribute. False to ignore it */
772 j = 0;
773 while( fossil_isalnum(z[i]) ){
774 if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
775 i++;
@@ -1161,11 +1175,15 @@
1175 || strncmp(zTarget, "mailto:", 7)==0
1176 ){
1177 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1178 }else if( zTarget[0]=='/' ){
1179 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1180 }else if( zTarget[0]=='.'
1181 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1182 && (p->state & WIKI_LINKSONLY)==0 ){
1183 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1184 }else if( zTarget[0]=='#' ){
1185 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1186 }else if( is_valid_uuid(zTarget) ){
1187 int isClosed = 0;
1188 if( is_ticket(zTarget, &isClosed) ){
1189 /* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
1451 const char *zId;
1452 int iDiv;
1453 parseMarkup(&markup, z);
1454
1455 /* Markup of the form </div id=ID> where there is a matching
1456 ** ID somewhere on the stack. Exit any contained verbatim.
1457 ** Pop the stack up to the matching <div>. Discard the </div>
 
1458 */
1459 if( markup.iCode==MARKUP_DIV && markup.endTag &&
1460 (zId = markupId(&markup))!=0 &&
1461 (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
1462 ){
@@ -1527,18 +1544,17 @@
1544 p->preVerbState = p->state;
1545 p->state &= ~ALLOW_WIKI;
1546 for(ii=0; ii<markup.nAttr; ii++){
1547 if( markup.aAttr[ii].iACode == ATTR_ID ){
1548 p->zVerbatimId = markup.aAttr[ii].zValue;
1549 }else if( markup.aAttr[ii].iACode==ATTR_TYPE ){
1550 blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1551 markup.aAttr[ii].zValue);
1552 vAttrDidAppend=1;
1553 }else if( markup.aAttr[ii].iACode==ATTR_LINKS
1554 && !is_false(markup.aAttr[ii].zValue) ){
1555 p->state |= ALLOW_LINKS;
 
1556 }
1557 }
1558 if( !vAttrDidAppend ) {
1559 endAutoParagraph(p);
1560 blob_append(p->pOut, "<pre class='verbatim'>",-1);
1561
+54 -38
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
5151
#define ATTR_FACE 12
5252
#define ATTR_HEIGHT 13
5353
#define ATTR_HREF 14
5454
#define ATTR_HSPACE 15
5555
#define ATTR_ID 16
56
-#define ATTR_NAME 17
57
-#define ATTR_ROWSPAN 18
58
-#define ATTR_SIZE 19
59
-#define ATTR_SRC 20
60
-#define ATTR_START 21
61
-#define ATTR_STYLE 22
62
-#define ATTR_TARGET 23
63
-#define ATTR_TYPE 24
64
-#define ATTR_VALIGN 25
65
-#define ATTR_VALUE 26
66
-#define ATTR_VSPACE 27
67
-#define ATTR_WIDTH 28
56
+#define ATTR_LINKS 17
57
+#define ATTR_NAME 18
58
+#define ATTR_ROWSPAN 19
59
+#define ATTR_SIZE 20
60
+#define ATTR_SRC 21
61
+#define ATTR_START 22
62
+#define ATTR_STYLE 23
63
+#define ATTR_TARGET 24
64
+#define ATTR_TYPE 25
65
+#define ATTR_VALIGN 26
66
+#define ATTR_VALUE 27
67
+#define ATTR_VSPACE 28
68
+#define ATTR_WIDTH 29
6869
#define AMSK_ALIGN 0x00000001
6970
#define AMSK_ALT 0x00000002
7071
#define AMSK_BGCOLOR 0x00000004
7172
#define AMSK_BORDER 0x00000008
7273
#define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
7980
#define AMSK_FACE 0x00000800
8081
#define AMSK_HEIGHT 0x00001000
8182
#define AMSK_HREF 0x00002000
8283
#define AMSK_HSPACE 0x00004000
8384
#define AMSK_ID 0x00008000
84
-#define AMSK_NAME 0x00010000
85
-#define AMSK_ROWSPAN 0x00020000
86
-#define AMSK_SIZE 0x00040000
87
-#define AMSK_SRC 0x00080000
88
-#define AMSK_START 0x00100000
89
-#define AMSK_STYLE 0x00200000
90
-#define AMSK_TARGET 0x00400000
91
-#define AMSK_TYPE 0x00800000
92
-#define AMSK_VALIGN 0x01000000
93
-#define AMSK_VALUE 0x02000000
94
-#define AMSK_VSPACE 0x04000000
95
-#define AMSK_WIDTH 0x08000000
85
+#define AMSK_LINKS 0x00010000
86
+#define AMSK_NAME 0x00020000
87
+#define AMSK_ROWSPAN 0x00040000
88
+#define AMSK_SIZE 0x00080000
89
+#define AMSK_SRC 0x00100000
90
+#define AMSK_START 0x00200000
91
+#define AMSK_STYLE 0x00400000
92
+#define AMSK_TARGET 0x00800000
93
+#define AMSK_TYPE 0x01000000
94
+#define AMSK_VALIGN 0x02000000
95
+#define AMSK_VALUE 0x04000000
96
+#define AMSK_VSPACE 0x08000000
97
+#define AMSK_WIDTH 0x10000000
9698
9799
static const struct AllowedAttribute {
98100
const char *zName;
99101
unsigned int iMask;
100102
} aAttribute[] = {
@@ -113,10 +115,11 @@
113115
{ "face", AMSK_FACE, },
114116
{ "height", AMSK_HEIGHT, },
115117
{ "href", AMSK_HREF, },
116118
{ "hspace", AMSK_HSPACE, },
117119
{ "id", AMSK_ID, },
120
+ { "links", AMSK_LINKS, },
118121
{ "name", AMSK_NAME, },
119122
{ "rowspan", AMSK_ROWSPAN, },
120123
{ "size", AMSK_SIZE, },
121124
{ "src", AMSK_SRC, },
122125
{ "start", AMSK_START, },
@@ -438,11 +441,11 @@
438441
int n = 1;
439442
int inparen = 0;
440443
int c;
441444
if( z[n]=='/' ){ n++; }
442445
if( !fossil_isalpha(z[n]) ) return 0;
443
- while( fossil_isalnum(z[n]) ){ n++; }
446
+ while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; }
444447
c = z[n];
445448
if( c=='/' && z[n+1]=='>' ){ return n+2; }
446449
if( c!='>' && !fossil_isspace(c) ) return 0;
447450
while( (c = z[n])!=0 && (c!='>' || inparen) ){
448451
if( c==inparen ){
@@ -750,12 +753,23 @@
750753
}
751754
zTag[j] = 0;
752755
p->iCode = findTag(zTag);
753756
p->iType = aMarkup[p->iCode].iType;
754757
p->nAttr = 0;
758
+ c = 0;
759
+ if( z[i]=='-' ){
760
+ p->aAttr[0].iACode = iACode = ATTR_ID;
761
+ i++;
762
+ p->aAttr[0].zValue = &z[i];
763
+ while( fossil_isalnum(z[i]) ){ i++; }
764
+ p->aAttr[0].cTerm = c = z[i];
765
+ z[i++] = 0;
766
+ p->nAttr = 1;
767
+ if( c=='>' ) return;
768
+ }
755769
while( fossil_isspace(z[i]) ){ i++; }
756
- while( p->nAttr<8 && fossil_isalpha(z[i]) ){
770
+ while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
757771
int attrOk; /* True to preserver attribute. False to ignore it */
758772
j = 0;
759773
while( fossil_isalnum(z[i]) ){
760774
if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
761775
i++;
@@ -1161,11 +1175,15 @@
11611175
|| strncmp(zTarget, "mailto:", 7)==0
11621176
){
11631177
blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
11641178
}else if( zTarget[0]=='/' ){
11651179
blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1166
- }else if( zTarget[0]=='.' || zTarget[0]=='#' ){
1180
+ }else if( zTarget[0]=='.'
1181
+ && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1182
+ && (p->state & WIKI_LINKSONLY)==0 ){
1183
+ blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1184
+ }else if( zTarget[0]=='#' ){
11671185
blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
11681186
}else if( is_valid_uuid(zTarget) ){
11691187
int isClosed = 0;
11701188
if( is_ticket(zTarget, &isClosed) ){
11711189
/* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
14331451
const char *zId;
14341452
int iDiv;
14351453
parseMarkup(&markup, z);
14361454
14371455
/* Markup of the form </div id=ID> where there is a matching
1438
- ** ID somewhere on the stack. Exit the verbatim if were are in
1439
- ** it. Pop the stack up to the matching <div>. Discard the
1440
- ** </div>
1456
+ ** ID somewhere on the stack. Exit any contained verbatim.
1457
+ ** Pop the stack up to the matching <div>. Discard the </div>
14411458
*/
14421459
if( markup.iCode==MARKUP_DIV && markup.endTag &&
14431460
(zId = markupId(&markup))!=0 &&
14441461
(iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
14451462
){
@@ -1527,18 +1544,17 @@
15271544
p->preVerbState = p->state;
15281545
p->state &= ~ALLOW_WIKI;
15291546
for(ii=0; ii<markup.nAttr; ii++){
15301547
if( markup.aAttr[ii].iACode == ATTR_ID ){
15311548
p->zVerbatimId = markup.aAttr[ii].zValue;
1532
- }else if( markup.aAttr[ii].iACode == ATTR_TYPE ){
1533
- if( fossil_stricmp(markup.aAttr[ii].zValue, "allow-links")==0 ){
1534
- p->state |= ALLOW_LINKS;
1535
- }else{
1536
- blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1537
- markup.aAttr[ii].zValue);
1538
- vAttrDidAppend=1;
1539
- }
1549
+ }else if( markup.aAttr[ii].iACode==ATTR_TYPE ){
1550
+ blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1551
+ markup.aAttr[ii].zValue);
1552
+ vAttrDidAppend=1;
1553
+ }else if( markup.aAttr[ii].iACode==ATTR_LINKS
1554
+ && !is_false(markup.aAttr[ii].zValue) ){
1555
+ p->state |= ALLOW_LINKS;
15401556
}
15411557
}
15421558
if( !vAttrDidAppend ) {
15431559
endAutoParagraph(p);
15441560
blob_append(p->pOut, "<pre class='verbatim'>",-1);
15451561
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
51 #define ATTR_FACE 12
52 #define ATTR_HEIGHT 13
53 #define ATTR_HREF 14
54 #define ATTR_HSPACE 15
55 #define ATTR_ID 16
56 #define ATTR_NAME 17
57 #define ATTR_ROWSPAN 18
58 #define ATTR_SIZE 19
59 #define ATTR_SRC 20
60 #define ATTR_START 21
61 #define ATTR_STYLE 22
62 #define ATTR_TARGET 23
63 #define ATTR_TYPE 24
64 #define ATTR_VALIGN 25
65 #define ATTR_VALUE 26
66 #define ATTR_VSPACE 27
67 #define ATTR_WIDTH 28
 
68 #define AMSK_ALIGN 0x00000001
69 #define AMSK_ALT 0x00000002
70 #define AMSK_BGCOLOR 0x00000004
71 #define AMSK_BORDER 0x00000008
72 #define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
79 #define AMSK_FACE 0x00000800
80 #define AMSK_HEIGHT 0x00001000
81 #define AMSK_HREF 0x00002000
82 #define AMSK_HSPACE 0x00004000
83 #define AMSK_ID 0x00008000
84 #define AMSK_NAME 0x00010000
85 #define AMSK_ROWSPAN 0x00020000
86 #define AMSK_SIZE 0x00040000
87 #define AMSK_SRC 0x00080000
88 #define AMSK_START 0x00100000
89 #define AMSK_STYLE 0x00200000
90 #define AMSK_TARGET 0x00400000
91 #define AMSK_TYPE 0x00800000
92 #define AMSK_VALIGN 0x01000000
93 #define AMSK_VALUE 0x02000000
94 #define AMSK_VSPACE 0x04000000
95 #define AMSK_WIDTH 0x08000000
 
96
97 static const struct AllowedAttribute {
98 const char *zName;
99 unsigned int iMask;
100 } aAttribute[] = {
@@ -113,10 +115,11 @@
113 { "face", AMSK_FACE, },
114 { "height", AMSK_HEIGHT, },
115 { "href", AMSK_HREF, },
116 { "hspace", AMSK_HSPACE, },
117 { "id", AMSK_ID, },
 
118 { "name", AMSK_NAME, },
119 { "rowspan", AMSK_ROWSPAN, },
120 { "size", AMSK_SIZE, },
121 { "src", AMSK_SRC, },
122 { "start", AMSK_START, },
@@ -438,11 +441,11 @@
438 int n = 1;
439 int inparen = 0;
440 int c;
441 if( z[n]=='/' ){ n++; }
442 if( !fossil_isalpha(z[n]) ) return 0;
443 while( fossil_isalnum(z[n]) ){ n++; }
444 c = z[n];
445 if( c=='/' && z[n+1]=='>' ){ return n+2; }
446 if( c!='>' && !fossil_isspace(c) ) return 0;
447 while( (c = z[n])!=0 && (c!='>' || inparen) ){
448 if( c==inparen ){
@@ -750,12 +753,23 @@
750 }
751 zTag[j] = 0;
752 p->iCode = findTag(zTag);
753 p->iType = aMarkup[p->iCode].iType;
754 p->nAttr = 0;
 
 
 
 
 
 
 
 
 
 
 
755 while( fossil_isspace(z[i]) ){ i++; }
756 while( p->nAttr<8 && fossil_isalpha(z[i]) ){
757 int attrOk; /* True to preserver attribute. False to ignore it */
758 j = 0;
759 while( fossil_isalnum(z[i]) ){
760 if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
761 i++;
@@ -1161,11 +1175,15 @@
1161 || strncmp(zTarget, "mailto:", 7)==0
1162 ){
1163 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1164 }else if( zTarget[0]=='/' ){
1165 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1166 }else if( zTarget[0]=='.' || zTarget[0]=='#' ){
 
 
 
 
1167 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1168 }else if( is_valid_uuid(zTarget) ){
1169 int isClosed = 0;
1170 if( is_ticket(zTarget, &isClosed) ){
1171 /* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
1433 const char *zId;
1434 int iDiv;
1435 parseMarkup(&markup, z);
1436
1437 /* Markup of the form </div id=ID> where there is a matching
1438 ** ID somewhere on the stack. Exit the verbatim if were are in
1439 ** it. Pop the stack up to the matching <div>. Discard the
1440 ** </div>
1441 */
1442 if( markup.iCode==MARKUP_DIV && markup.endTag &&
1443 (zId = markupId(&markup))!=0 &&
1444 (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
1445 ){
@@ -1527,18 +1544,17 @@
1527 p->preVerbState = p->state;
1528 p->state &= ~ALLOW_WIKI;
1529 for(ii=0; ii<markup.nAttr; ii++){
1530 if( markup.aAttr[ii].iACode == ATTR_ID ){
1531 p->zVerbatimId = markup.aAttr[ii].zValue;
1532 }else if( markup.aAttr[ii].iACode == ATTR_TYPE ){
1533 if( fossil_stricmp(markup.aAttr[ii].zValue, "allow-links")==0 ){
1534 p->state |= ALLOW_LINKS;
1535 }else{
1536 blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1537 markup.aAttr[ii].zValue);
1538 vAttrDidAppend=1;
1539 }
1540 }
1541 }
1542 if( !vAttrDidAppend ) {
1543 endAutoParagraph(p);
1544 blob_append(p->pOut, "<pre class='verbatim'>",-1);
1545
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -51,22 +51,23 @@
51 #define ATTR_FACE 12
52 #define ATTR_HEIGHT 13
53 #define ATTR_HREF 14
54 #define ATTR_HSPACE 15
55 #define ATTR_ID 16
56 #define ATTR_LINKS 17
57 #define ATTR_NAME 18
58 #define ATTR_ROWSPAN 19
59 #define ATTR_SIZE 20
60 #define ATTR_SRC 21
61 #define ATTR_START 22
62 #define ATTR_STYLE 23
63 #define ATTR_TARGET 24
64 #define ATTR_TYPE 25
65 #define ATTR_VALIGN 26
66 #define ATTR_VALUE 27
67 #define ATTR_VSPACE 28
68 #define ATTR_WIDTH 29
69 #define AMSK_ALIGN 0x00000001
70 #define AMSK_ALT 0x00000002
71 #define AMSK_BGCOLOR 0x00000004
72 #define AMSK_BORDER 0x00000008
73 #define AMSK_CELLPADDING 0x00000010
@@ -79,22 +80,23 @@
80 #define AMSK_FACE 0x00000800
81 #define AMSK_HEIGHT 0x00001000
82 #define AMSK_HREF 0x00002000
83 #define AMSK_HSPACE 0x00004000
84 #define AMSK_ID 0x00008000
85 #define AMSK_LINKS 0x00010000
86 #define AMSK_NAME 0x00020000
87 #define AMSK_ROWSPAN 0x00040000
88 #define AMSK_SIZE 0x00080000
89 #define AMSK_SRC 0x00100000
90 #define AMSK_START 0x00200000
91 #define AMSK_STYLE 0x00400000
92 #define AMSK_TARGET 0x00800000
93 #define AMSK_TYPE 0x01000000
94 #define AMSK_VALIGN 0x02000000
95 #define AMSK_VALUE 0x04000000
96 #define AMSK_VSPACE 0x08000000
97 #define AMSK_WIDTH 0x10000000
98
99 static const struct AllowedAttribute {
100 const char *zName;
101 unsigned int iMask;
102 } aAttribute[] = {
@@ -113,10 +115,11 @@
115 { "face", AMSK_FACE, },
116 { "height", AMSK_HEIGHT, },
117 { "href", AMSK_HREF, },
118 { "hspace", AMSK_HSPACE, },
119 { "id", AMSK_ID, },
120 { "links", AMSK_LINKS, },
121 { "name", AMSK_NAME, },
122 { "rowspan", AMSK_ROWSPAN, },
123 { "size", AMSK_SIZE, },
124 { "src", AMSK_SRC, },
125 { "start", AMSK_START, },
@@ -438,11 +441,11 @@
441 int n = 1;
442 int inparen = 0;
443 int c;
444 if( z[n]=='/' ){ n++; }
445 if( !fossil_isalpha(z[n]) ) return 0;
446 while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; }
447 c = z[n];
448 if( c=='/' && z[n+1]=='>' ){ return n+2; }
449 if( c!='>' && !fossil_isspace(c) ) return 0;
450 while( (c = z[n])!=0 && (c!='>' || inparen) ){
451 if( c==inparen ){
@@ -750,12 +753,23 @@
753 }
754 zTag[j] = 0;
755 p->iCode = findTag(zTag);
756 p->iType = aMarkup[p->iCode].iType;
757 p->nAttr = 0;
758 c = 0;
759 if( z[i]=='-' ){
760 p->aAttr[0].iACode = iACode = ATTR_ID;
761 i++;
762 p->aAttr[0].zValue = &z[i];
763 while( fossil_isalnum(z[i]) ){ i++; }
764 p->aAttr[0].cTerm = c = z[i];
765 z[i++] = 0;
766 p->nAttr = 1;
767 if( c=='>' ) return;
768 }
769 while( fossil_isspace(z[i]) ){ i++; }
770 while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
771 int attrOk; /* True to preserver attribute. False to ignore it */
772 j = 0;
773 while( fossil_isalnum(z[i]) ){
774 if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
775 i++;
@@ -1161,11 +1175,15 @@
1175 || strncmp(zTarget, "mailto:", 7)==0
1176 ){
1177 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1178 }else if( zTarget[0]=='/' ){
1179 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1180 }else if( zTarget[0]=='.'
1181 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1182 && (p->state & WIKI_LINKSONLY)==0 ){
1183 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1184 }else if( zTarget[0]=='#' ){
1185 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1186 }else if( is_valid_uuid(zTarget) ){
1187 int isClosed = 0;
1188 if( is_ticket(zTarget, &isClosed) ){
1189 /* Special display processing for tickets. Display the hyperlink
@@ -1433,13 +1451,12 @@
1451 const char *zId;
1452 int iDiv;
1453 parseMarkup(&markup, z);
1454
1455 /* Markup of the form </div id=ID> where there is a matching
1456 ** ID somewhere on the stack. Exit any contained verbatim.
1457 ** Pop the stack up to the matching <div>. Discard the </div>
 
1458 */
1459 if( markup.iCode==MARKUP_DIV && markup.endTag &&
1460 (zId = markupId(&markup))!=0 &&
1461 (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
1462 ){
@@ -1527,18 +1544,17 @@
1544 p->preVerbState = p->state;
1545 p->state &= ~ALLOW_WIKI;
1546 for(ii=0; ii<markup.nAttr; ii++){
1547 if( markup.aAttr[ii].iACode == ATTR_ID ){
1548 p->zVerbatimId = markup.aAttr[ii].zValue;
1549 }else if( markup.aAttr[ii].iACode==ATTR_TYPE ){
1550 blob_appendf(p->pOut, "<pre name='code' class='%s'>",
1551 markup.aAttr[ii].zValue);
1552 vAttrDidAppend=1;
1553 }else if( markup.aAttr[ii].iACode==ATTR_LINKS
1554 && !is_false(markup.aAttr[ii].zValue) ){
1555 p->state |= ALLOW_LINKS;
 
1556 }
1557 }
1558 if( !vAttrDidAppend ) {
1559 endAutoParagraph(p);
1560 blob_append(p->pOut, "<pre class='verbatim'>",-1);
1561

Keyboard Shortcuts

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