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.
Commit
4f8c8975bc4d1303a604da339f869c32eb0da960
Parent
14cf3f3c9ddafad…
21 files changed
+2
-1
+2
-1
+32
-1
+3
+3
+11
-3
+11
-3
+5
-1
+5
-1
+8
-6
+32
-6
+11
+7
+1
+33
-2
+92
-3
+5
-1
+251
-145
+206
-95
+54
-38
+54
-38
~
src/attach.c
~
src/attach.c
~
src/cgi.c
~
src/db.c
~
src/db.c
~
src/info.c
~
src/info.c
~
src/main.c
~
src/main.c
~
src/manifest.c
~
src/report.c
~
src/schema.c
~
src/th.c
~
src/th.h
~
src/th_lang.c
~
src/th_main.c
~
src/timeline.c
~
src/tkt.c
~
src/tktsetup.c
~
src/wikiformat.c
~
src/wikiformat.c
+2
-1
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -530,11 +530,12 @@ | ||
| 530 | 530 | @ <pre> |
| 531 | 531 | @ %h(z) |
| 532 | 532 | @ </pre> |
| 533 | 533 | } |
| 534 | 534 | }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); | |
| 536 | 537 | }else{ |
| 537 | 538 | int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); |
| 538 | 539 | @ <i>(file is %d(sz) bytes of binary data)</i> |
| 539 | 540 | } |
| 540 | 541 | @ </blockquote> |
| 541 | 542 |
| --- 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 @@ | ||
| 530 | 530 | @ <pre> |
| 531 | 531 | @ %h(z) |
| 532 | 532 | @ </pre> |
| 533 | 533 | } |
| 534 | 534 | }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); | |
| 536 | 537 | }else{ |
| 537 | 538 | int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); |
| 538 | 539 | @ <i>(file is %d(sz) bytes of binary data)</i> |
| 539 | 540 | } |
| 540 | 541 | @ </blockquote> |
| 541 | 542 |
| --- 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 @@ | ||
| 783 | 783 | } |
| 784 | 784 | fossil_exit( g.isHTTP ? 0 : 1); |
| 785 | 785 | } |
| 786 | 786 | #endif /* FOSSIL_ENABLE_JSON */ |
| 787 | 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 | + | |
| 788 | 816 | |
| 789 | 817 | /* |
| 790 | 818 | ** Initialize the query parameter database. Information is pulled from |
| 791 | 819 | ** the QUERY_STRING environment variable (if it exists), from standard |
| 792 | 820 | ** input if there is POST data, and from HTTP_COOKIE. |
| @@ -825,10 +853,11 @@ | ||
| 825 | 853 | if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 |
| 826 | 854 | || strncmp(zType,"multipart/form-data",19)==0 ){ |
| 827 | 855 | z = fossil_malloc( len+1 ); |
| 828 | 856 | len = fread(z, 1, len, g.httpIn); |
| 829 | 857 | z[len] = 0; |
| 858 | + cgi_trace(z); | |
| 830 | 859 | if( zType[0]=='a' ){ |
| 831 | 860 | add_param_list(z, '&'); |
| 832 | 861 | }else{ |
| 833 | 862 | process_multipart_form_data(z, len); |
| 834 | 863 | } |
| @@ -1145,10 +1174,11 @@ | ||
| 1145 | 1174 | char zLine[2000]; /* A single line of input. */ |
| 1146 | 1175 | g.fullHttpReply = 1; |
| 1147 | 1176 | if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
| 1148 | 1177 | malformed_request(); |
| 1149 | 1178 | } |
| 1179 | + cgi_trace(zLine); | |
| 1150 | 1180 | zToken = extract_token(zLine, &z); |
| 1151 | 1181 | if( zToken==0 ){ |
| 1152 | 1182 | malformed_request(); |
| 1153 | 1183 | } |
| 1154 | 1184 | if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 |
| @@ -1181,10 +1211,11 @@ | ||
| 1181 | 1211 | */ |
| 1182 | 1212 | while( fgets(zLine,sizeof(zLine),g.httpIn) ){ |
| 1183 | 1213 | char *zFieldName; |
| 1184 | 1214 | char *zVal; |
| 1185 | 1215 | |
| 1216 | + cgi_trace(zLine); | |
| 1186 | 1217 | zFieldName = extract_token(zLine,&zVal); |
| 1187 | 1218 | if( zFieldName==0 || *zFieldName==0 ) break; |
| 1188 | 1219 | while( fossil_isspace(*zVal) ){ zVal++; } |
| 1189 | 1220 | i = strlen(zVal); |
| 1190 | 1221 | while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } |
| @@ -1212,12 +1243,12 @@ | ||
| 1212 | 1243 | #endif |
| 1213 | 1244 | }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
| 1214 | 1245 | cgi_setenv("HTTP_USER_AGENT", zVal); |
| 1215 | 1246 | } |
| 1216 | 1247 | } |
| 1217 | - | |
| 1218 | 1248 | cgi_init(); |
| 1249 | + cgi_trace(0); | |
| 1219 | 1250 | } |
| 1220 | 1251 | |
| 1221 | 1252 | #if INTERFACE |
| 1222 | 1253 | /* |
| 1223 | 1254 | ** Bitmap values for the flags parameter to cgi_http_server(). |
| 1224 | 1255 |
| --- 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 |
M
src/db.c
+3
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1312,10 +1312,13 @@ | ||
| 1312 | 1312 | " VALUES('project-code', lower(hex(randomblob(20))),now());" |
| 1313 | 1313 | ); |
| 1314 | 1314 | } |
| 1315 | 1315 | if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0); |
| 1316 | 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 | + } | |
| 1317 | 1320 | db_create_default_users(0, zDefaultUser); |
| 1318 | 1321 | if( zDefaultUser ) g.zLogin = zDefaultUser; |
| 1319 | 1322 | user_select(); |
| 1320 | 1323 | |
| 1321 | 1324 | if( zTemplate ){ |
| 1322 | 1325 |
| --- 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 |
M
src/db.c
+3
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1312,10 +1312,13 @@ | ||
| 1312 | 1312 | " VALUES('project-code', lower(hex(randomblob(20))),now());" |
| 1313 | 1313 | ); |
| 1314 | 1314 | } |
| 1315 | 1315 | if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0); |
| 1316 | 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 | + } | |
| 1317 | 1320 | db_create_default_users(0, zDefaultUser); |
| 1318 | 1321 | if( zDefaultUser ) g.zLogin = zDefaultUser; |
| 1319 | 1322 | user_select(); |
| 1320 | 1323 | |
| 1321 | 1324 | if( zTemplate ){ |
| 1322 | 1325 |
| --- 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 @@ | ||
| 1111 | 1111 | cnt++; |
| 1112 | 1112 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 1113 | 1113 | blob_append(pDownloadName, zName, -1); |
| 1114 | 1114 | } |
| 1115 | 1115 | } |
| 1116 | - @ </ul></ul> | |
| 1116 | + @ </ul> | |
| 1117 | 1117 | free(prevName); |
| 1118 | 1118 | db_finalize(&q); |
| 1119 | 1119 | db_prepare(&q, |
| 1120 | 1120 | "SELECT substr(tagname, 6, 10000), datetime(event.mtime)," |
| 1121 | 1121 | " coalesce(event.euser, event.user)" |
| @@ -1654,11 +1654,13 @@ | ||
| 1654 | 1654 | @ <pre> |
| 1655 | 1655 | @ %h(z) |
| 1656 | 1656 | @ </pre> |
| 1657 | 1657 | } |
| 1658 | 1658 | }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); | |
| 1660 | 1662 | }else{ |
| 1661 | 1663 | @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> |
| 1662 | 1664 | } |
| 1663 | 1665 | @ </blockquote> |
| 1664 | 1666 | } |
| @@ -1711,10 +1713,16 @@ | ||
| 1711 | 1713 | style_header("Ticket Change Details"); |
| 1712 | 1714 | style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); |
| 1713 | 1715 | style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); |
| 1714 | 1716 | style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); |
| 1715 | 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 | + } | |
| 1716 | 1724 | |
| 1717 | 1725 | @ <div class="section">Overview</div> |
| 1718 | 1726 | @ <p><table class="label-value"> |
| 1719 | 1727 | @ <tr><th>Artifact ID:</th> |
| 1720 | 1728 | @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> |
| @@ -1747,11 +1755,11 @@ | ||
| 1747 | 1755 | @ </blockquote> |
| 1748 | 1756 | } |
| 1749 | 1757 | |
| 1750 | 1758 | @ <div class="section">Changes</div> |
| 1751 | 1759 | @ <p> |
| 1752 | - ticket_output_change_artifact(pTktChng); | |
| 1760 | + ticket_output_change_artifact(pTktChng, 0); | |
| 1753 | 1761 | manifest_destroy(pTktChng); |
| 1754 | 1762 | style_footer(); |
| 1755 | 1763 | } |
| 1756 | 1764 | |
| 1757 | 1765 | |
| 1758 | 1766 |
| --- 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 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 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 @@ | ||
| 1111 | 1111 | cnt++; |
| 1112 | 1112 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 1113 | 1113 | blob_append(pDownloadName, zName, -1); |
| 1114 | 1114 | } |
| 1115 | 1115 | } |
| 1116 | - @ </ul></ul> | |
| 1116 | + @ </ul> | |
| 1117 | 1117 | free(prevName); |
| 1118 | 1118 | db_finalize(&q); |
| 1119 | 1119 | db_prepare(&q, |
| 1120 | 1120 | "SELECT substr(tagname, 6, 10000), datetime(event.mtime)," |
| 1121 | 1121 | " coalesce(event.euser, event.user)" |
| @@ -1654,11 +1654,13 @@ | ||
| 1654 | 1654 | @ <pre> |
| 1655 | 1655 | @ %h(z) |
| 1656 | 1656 | @ </pre> |
| 1657 | 1657 | } |
| 1658 | 1658 | }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); | |
| 1660 | 1662 | }else{ |
| 1661 | 1663 | @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> |
| 1662 | 1664 | } |
| 1663 | 1665 | @ </blockquote> |
| 1664 | 1666 | } |
| @@ -1711,10 +1713,16 @@ | ||
| 1711 | 1713 | style_header("Ticket Change Details"); |
| 1712 | 1714 | style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); |
| 1713 | 1715 | style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); |
| 1714 | 1716 | style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); |
| 1715 | 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 | + } | |
| 1716 | 1724 | |
| 1717 | 1725 | @ <div class="section">Overview</div> |
| 1718 | 1726 | @ <p><table class="label-value"> |
| 1719 | 1727 | @ <tr><th>Artifact ID:</th> |
| 1720 | 1728 | @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> |
| @@ -1747,11 +1755,11 @@ | ||
| 1747 | 1755 | @ </blockquote> |
| 1748 | 1756 | } |
| 1749 | 1757 | |
| 1750 | 1758 | @ <div class="section">Changes</div> |
| 1751 | 1759 | @ <p> |
| 1752 | - ticket_output_change_artifact(pTktChng); | |
| 1760 | + ticket_output_change_artifact(pTktChng, 0); | |
| 1753 | 1761 | manifest_destroy(pTktChng); |
| 1754 | 1762 | style_footer(); |
| 1755 | 1763 | } |
| 1756 | 1764 | |
| 1757 | 1765 | |
| 1758 | 1766 |
| --- 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 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 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 @@ | ||
| 1632 | 1632 | blob_read_from_file(&config, zFile); |
| 1633 | 1633 | while( blob_line(&config, &line) ){ |
| 1634 | 1634 | if( !blob_token(&line, &key) ) continue; |
| 1635 | 1635 | if( blob_buffer(&key)[0]=='#' ) continue; |
| 1636 | 1636 | 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"); | |
| 1638 | 1638 | blob_reset(&value); |
| 1639 | 1639 | continue; |
| 1640 | 1640 | } |
| 1641 | 1641 | if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ |
| 1642 | 1642 | cgi_setenv("HOME", blob_str(&value)); |
| @@ -1850,10 +1850,14 @@ | ||
| 1850 | 1850 | ** |
| 1851 | 1851 | ** COMMAND: test-http |
| 1852 | 1852 | ** Works like the http command but gives setup permission to all users. |
| 1853 | 1853 | */ |
| 1854 | 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 | + } | |
| 1855 | 1859 | login_set_capabilities("sx", 0); |
| 1856 | 1860 | g.useLocalauth = 1; |
| 1857 | 1861 | cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); |
| 1858 | 1862 | g.httpIn = stdin; |
| 1859 | 1863 | g.httpOut = stdout; |
| 1860 | 1864 |
| --- 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 @@ | ||
| 1632 | 1632 | blob_read_from_file(&config, zFile); |
| 1633 | 1633 | while( blob_line(&config, &line) ){ |
| 1634 | 1634 | if( !blob_token(&line, &key) ) continue; |
| 1635 | 1635 | if( blob_buffer(&key)[0]=='#' ) continue; |
| 1636 | 1636 | 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"); | |
| 1638 | 1638 | blob_reset(&value); |
| 1639 | 1639 | continue; |
| 1640 | 1640 | } |
| 1641 | 1641 | if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ |
| 1642 | 1642 | cgi_setenv("HOME", blob_str(&value)); |
| @@ -1850,10 +1850,14 @@ | ||
| 1850 | 1850 | ** |
| 1851 | 1851 | ** COMMAND: test-http |
| 1852 | 1852 | ** Works like the http command but gives setup permission to all users. |
| 1853 | 1853 | */ |
| 1854 | 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 | + } | |
| 1855 | 1859 | login_set_capabilities("sx", 0); |
| 1856 | 1860 | g.useLocalauth = 1; |
| 1857 | 1861 | cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); |
| 1858 | 1862 | g.httpIn = stdin; |
| 1859 | 1863 | g.httpOut = stdout; |
| 1860 | 1864 |
| --- 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 @@ | ||
| 1552 | 1552 | if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ |
| 1553 | 1553 | zNewStatus = pManifest->aField[i].zValue; |
| 1554 | 1554 | } |
| 1555 | 1555 | } |
| 1556 | 1556 | if( zNewStatus ){ |
| 1557 | - blob_appendf(&comment, "%h ticket [%.10s]: <i>%s</i>", | |
| 1557 | + blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>", | |
| 1558 | 1558 | zNewStatus, pManifest->zTicketUuid, zTitle |
| 1559 | 1559 | ); |
| 1560 | 1560 | if( pManifest->nField>1 ){ |
| 1561 | 1561 | blob_appendf(&comment, " plus %d other change%s", |
| 1562 | 1562 | pManifest->nField-1, pManifest->nField==2 ? "" : "s"); |
| @@ -1566,11 +1566,11 @@ | ||
| 1566 | 1566 | }else{ |
| 1567 | 1567 | zNewStatus = db_text("unknown", |
| 1568 | 1568 | "SELECT %s FROM ticket WHERE tkt_uuid='%s'", |
| 1569 | 1569 | zStatusColumn, pManifest->zTicketUuid |
| 1570 | 1570 | ); |
| 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 " | |
| 1572 | 1572 | "%d other change%s", |
| 1573 | 1573 | pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField, |
| 1574 | 1574 | pManifest->nField==1 ? "" : "s" |
| 1575 | 1575 | ); |
| 1576 | 1576 | free(zNewStatus); |
| @@ -1868,12 +1868,13 @@ | ||
| 1868 | 1868 | if( strlen(p->zAttachTarget)!=UUID_SIZE |
| 1869 | 1869 | || !validate16(p->zAttachTarget, UUID_SIZE) |
| 1870 | 1870 | ){ |
| 1871 | 1871 | char *zComment; |
| 1872 | 1872 | 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); | |
| 1875 | 1876 | }else{ |
| 1876 | 1877 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 1877 | 1878 | p->zAttachName, p->zAttachTarget); |
| 1878 | 1879 | } |
| 1879 | 1880 | db_multi_exec( |
| @@ -1883,12 +1884,13 @@ | ||
| 1883 | 1884 | ); |
| 1884 | 1885 | free(zComment); |
| 1885 | 1886 | }else{ |
| 1886 | 1887 | char *zComment; |
| 1887 | 1888 | 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); | |
| 1890 | 1892 | }else{ |
| 1891 | 1893 | zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", |
| 1892 | 1894 | p->zAttachName, p->zAttachTarget); |
| 1893 | 1895 | } |
| 1894 | 1896 | db_multi_exec( |
| 1895 | 1897 |
| --- 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 @@ | ||
| 488 | 488 | @ is assumed to hold a ticket number. A hyperlink will be created from |
| 489 | 489 | @ that column to a detailed view of the ticket.</p></li> |
| 490 | 490 | @ |
| 491 | 491 | @ <li><p>If a column of the result set is named "bgcolor" then the content |
| 492 | 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. | |
| 493 | 498 | @ |
| 494 | 499 | @ <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. | |
| 497 | 504 | @ </p></li> |
| 498 | 505 | @ |
| 499 | 506 | @ <li><p>The query can join other tables in the database besides TICKET. |
| 500 | 507 | @ </p></li> |
| 501 | 508 | @ </ul> |
| @@ -589,12 +596,12 @@ | ||
| 589 | 596 | @ sdate(changetime) AS 'Changed', |
| 590 | 597 | @ assignedto AS 'Assigned', |
| 591 | 598 | @ severity AS 'Svr', |
| 592 | 599 | @ priority AS 'Pri', |
| 593 | 600 | @ 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 | |
| 596 | 603 | @ FROM ticket |
| 597 | 604 | @ </pre></blockquote> |
| 598 | 605 | @ |
| 599 | 606 | @ <p>Or, to see part of the description on the same row, use the |
| 600 | 607 | @ <b>wiki()</b> function with some string manipulation. Using the |
| @@ -619,10 +626,13 @@ | ||
| 619 | 626 | int nCount; /* Row number */ |
| 620 | 627 | int nCol; /* Number of columns */ |
| 621 | 628 | int isMultirow; /* True if multiple table rows per query result row */ |
| 622 | 629 | int iNewRow; /* Index of first column that goes on separate row */ |
| 623 | 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 */ | |
| 624 | 634 | }; |
| 625 | 635 | |
| 626 | 636 | /* |
| 627 | 637 | ** The callback function for db_query |
| 628 | 638 | */ |
| @@ -663,10 +673,23 @@ | ||
| 663 | 673 | } |
| 664 | 674 | if( !pState->isMultirow ){ |
| 665 | 675 | if( azName[i][0]=='_' ){ |
| 666 | 676 | pState->isMultirow = 1; |
| 667 | 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 | + } | |
| 668 | 691 | }else{ |
| 669 | 692 | pState->nCol++; |
| 670 | 693 | } |
| 671 | 694 | } |
| 672 | 695 | } |
| @@ -728,14 +751,17 @@ | ||
| 728 | 751 | @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td> |
| 729 | 752 | zTid = 0; |
| 730 | 753 | } |
| 731 | 754 | if( zData[0] ){ |
| 732 | 755 | 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) | |
| 734 | 759 | blob_init(&content, zData, -1); |
| 735 | - wiki_convert(&content, 0, WIKI_NOBADLINKS); | |
| 760 | + wiki_convert(&content, 0, pState->wikiFlags); | |
| 736 | 761 | blob_reset(&content); |
| 762 | + @ %s(pState->zWikiEnd) | |
| 737 | 763 | } |
| 738 | 764 | }else if( azName[i][0]=='#' ){ |
| 739 | 765 | zTid = zData; |
| 740 | 766 | @ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td> |
| 741 | 767 | }else if( zData[0]==0 ){ |
| 742 | 768 |
| --- 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 @@ | ||
| 397 | 397 | @ private_contact TEXT, |
| 398 | 398 | @ resolution TEXT, |
| 399 | 399 | @ title TEXT, |
| 400 | 400 | @ comment TEXT |
| 401 | 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); | |
| 402 | 413 | ; |
| 403 | 414 | |
| 404 | 415 | /* |
| 405 | 416 | ** Predefined tagid values |
| 406 | 417 | */ |
| 407 | 418 |
| --- 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 |
M
src/th.c
+7
| --- src/th.c | ||
| +++ src/th.c | ||
| @@ -1148,10 +1148,17 @@ | ||
| 1148 | 1148 | return TH_ERROR; |
| 1149 | 1149 | } |
| 1150 | 1150 | |
| 1151 | 1151 | return Th_SetResult(interp, pValue->zData, pValue->nData); |
| 1152 | 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 | +} | |
| 1153 | 1160 | |
| 1154 | 1161 | /* |
| 1155 | 1162 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1156 | 1163 | ** array member. If the variable does not exist it is created. The |
| 1157 | 1164 | ** variable is set to the value supplied in string (zValue, nValue). |
| 1158 | 1165 |
| --- 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 |
M
src/th.h
+1
| --- src/th.h | ||
| +++ src/th.h | ||
| @@ -49,10 +49,11 @@ | ||
| 49 | 49 | |
| 50 | 50 | /* |
| 51 | 51 | ** Access TH variables in the current stack frame. If the variable name |
| 52 | 52 | ** begins with "::", the lookup is in the top level (global) frame. |
| 53 | 53 | */ |
| 54 | +int Th_ExistsVar(Th_Interp *, const char *, int); | |
| 54 | 55 | int Th_GetVar(Th_Interp *, const char *, int); |
| 55 | 56 | int Th_SetVar(Th_Interp *, const char *, int, const char *, int); |
| 56 | 57 | int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); |
| 57 | 58 | int Th_UnsetVar(Th_Interp *, const char *, int); |
| 58 | 59 | |
| 59 | 60 |
| --- 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 @@ | ||
| 817 | 817 | return TH_OK; |
| 818 | 818 | } |
| 819 | 819 | |
| 820 | 820 | /* |
| 821 | 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: | |
| 822 | 850 | ** |
| 823 | 851 | ** info exists VAR |
| 824 | 852 | */ |
| 825 | 853 | static int info_exists_command( |
| 826 | 854 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| @@ -828,12 +856,12 @@ | ||
| 828 | 856 | int rc; |
| 829 | 857 | |
| 830 | 858 | if( argc!=3 ){ |
| 831 | 859 | return Th_WrongNumArgs(interp, "info exists var"); |
| 832 | 860 | } |
| 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); | |
| 835 | 863 | return TH_OK; |
| 836 | 864 | } |
| 837 | 865 | |
| 838 | 866 | /* |
| 839 | 867 | ** TH Syntax: |
| @@ -897,10 +925,13 @@ | ||
| 897 | 925 | { "is", string_is_command }, |
| 898 | 926 | { "last", string_last_command }, |
| 899 | 927 | { "length", string_length_command }, |
| 900 | 928 | { "range", string_range_command }, |
| 901 | 929 | { "repeat", string_repeat_command }, |
| 930 | + { "trim", string_trim_command }, | |
| 931 | + { "trimleft", string_trim_command }, | |
| 932 | + { "trimright", string_trim_command }, | |
| 902 | 933 | { 0, 0 } |
| 903 | 934 | }; |
| 904 | 935 | return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub); |
| 905 | 936 | } |
| 906 | 937 | |
| 907 | 938 |
| --- 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 @@ | ||
| 18 | 18 | ** This file contains an interface between the TH scripting language |
| 19 | 19 | ** (an independent project) and fossil. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "th_main.h" |
| 23 | +#include "sqlite3.h" | |
| 23 | 24 | |
| 24 | 25 | /* |
| 25 | 26 | ** Global variable counting the number of outstanding calls to malloc() |
| 26 | 27 | ** made by the th1 implementation. This is used to catch memory leaks |
| 27 | 28 | ** in the interpreter. Obviously, it also means th1 is not threadsafe. |
| @@ -72,14 +73,19 @@ | ||
| 72 | 73 | void *p, |
| 73 | 74 | int argc, |
| 74 | 75 | const char **argv, |
| 75 | 76 | int *argl |
| 76 | 77 | ){ |
| 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); | |
| 79 | 85 | } |
| 80 | - return Th_ToInt(interp, argv[1], argl[1], &enableOutput); | |
| 86 | + return rc; | |
| 81 | 87 | } |
| 82 | 88 | |
| 83 | 89 | /* |
| 84 | 90 | ** Return a name for a TH1 return code. |
| 85 | 91 | */ |
| @@ -119,16 +125,19 @@ | ||
| 119 | 125 | if( encode ) free((char*)z); |
| 120 | 126 | } |
| 121 | 127 | } |
| 122 | 128 | |
| 123 | 129 | static void sendError(const char *z, int n, int forceCgi){ |
| 130 | + int savedEnable = enableOutput; | |
| 131 | + enableOutput = 1; | |
| 124 | 132 | if( forceCgi || g.cgiOutput ){ |
| 125 | 133 | sendText("<hr><p class=\"thmainError\">", -1, 0); |
| 126 | 134 | } |
| 127 | 135 | sendText("ERROR: ", -1, 0); |
| 128 | 136 | sendText((char*)z, n, 1); |
| 129 | 137 | sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0); |
| 138 | + enableOutput = savedEnable; | |
| 130 | 139 | } |
| 131 | 140 | |
| 132 | 141 | /* |
| 133 | 142 | ** TH command: puts STRING |
| 134 | 143 | ** TH command: html STRING |
| @@ -567,10 +576,86 @@ | ||
| 567 | 576 | encode16(aRand, zOut, n); |
| 568 | 577 | Th_SetResult(interp, (const char *)zOut, -1); |
| 569 | 578 | return TH_OK; |
| 570 | 579 | } |
| 571 | 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 | +} | |
| 572 | 657 | |
| 573 | 658 | /* |
| 574 | 659 | ** Make sure the interpreter has been initialized. Initialize it if |
| 575 | 660 | ** it has not been already. |
| 576 | 661 | ** |
| @@ -593,10 +678,11 @@ | ||
| 593 | 678 | {"hasfeature", hasfeatureCmd, 0}, |
| 594 | 679 | {"html", putsCmd, (void*)&aFlags[0]}, |
| 595 | 680 | {"htmlize", htmlizeCmd, 0}, |
| 596 | 681 | {"linecount", linecntCmd, 0}, |
| 597 | 682 | {"puts", putsCmd, (void*)&aFlags[1]}, |
| 683 | + {"query", queryCmd, 0}, | |
| 598 | 684 | {"randhex", randhexCmd, 0}, |
| 599 | 685 | {"repository", repositoryCmd, 0}, |
| 600 | 686 | {"stime", stimeCmd, 0}, |
| 601 | 687 | {"utime", utimeCmd, 0}, |
| 602 | 688 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -801,10 +887,13 @@ | ||
| 801 | 887 | sendText((char*)zResult, n, encode); |
| 802 | 888 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 803 | 889 | sendText(z, i, 0); |
| 804 | 890 | z += i+5; |
| 805 | 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 | + } | |
| 806 | 895 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 807 | 896 | if( rc!=TH_OK ) break; |
| 808 | 897 | z += i; |
| 809 | 898 | if( z[0] ){ z += 6; } |
| 810 | 899 | i = 0; |
| 811 | 900 |
| --- 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 @@ | ||
| 344 | 344 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 345 | 345 | }else if( (tmFlags & TIMELINE_ARTID)!=0 ){ |
| 346 | 346 | hyperlink_to_uuid(zUuid); |
| 347 | 347 | } |
| 348 | 348 | 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 ){ | |
| 350 | 354 | Blob truncated; |
| 351 | 355 | blob_zero(&truncated); |
| 352 | 356 | blob_append(&truncated, blob_buffer(&comment), mxWikiLen); |
| 353 | 357 | blob_append(&truncated, "...", 3); |
| 354 | 358 | @ %w(blob_str(&truncated)) |
| 355 | 359 |
| --- 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 @@ | ||
| 26 | 26 | ** The list of database user-defined fields in the TICKET table. |
| 27 | 27 | ** The real table also contains some addition fields for internal |
| 28 | 28 | ** used. The internal-use fields begin with "tkt_". |
| 29 | 29 | */ |
| 30 | 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 */ | |
| 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 */ | |
| 34 | 41 | |
| 35 | 42 | /* |
| 36 | -** Compare two entries in azField for sorting purposes | |
| 43 | +** Compare two entries in aField[] for sorting purposes | |
| 37 | 44 | */ |
| 38 | 45 | 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; | |
| 40 | 60 | } |
| 41 | 61 | |
| 42 | 62 | /* |
| 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[]. | |
| 45 | 65 | ** |
| 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. | |
| 48 | 68 | */ |
| 49 | 69 | static void getAllTicketFields(void){ |
| 50 | 70 | Stmt q; |
| 51 | 71 | int i; |
| 52 | - if( nField>0 ) return; | |
| 72 | + static int once = 0; | |
| 73 | + if( once ) return; | |
| 74 | + once = 1; | |
| 53 | 75 | db_prepare(&q, "PRAGMA table_info(ticket)"); |
| 54 | 76 | 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 | + } | |
| 57 | 97 | if( nField%10==0 ){ |
| 58 | - azField = fossil_realloc(azField, sizeof(azField)*3*(nField+10) ); | |
| 98 | + aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) ); | |
| 59 | 99 | } |
| 60 | - azField[nField] = mprintf("%s", zField); | |
| 100 | + aField[nField].zName = mprintf("%s", zFieldName); | |
| 101 | + aField[nField].mUsed = USEDBY_TICKETCHNG; | |
| 61 | 102 | nField++; |
| 62 | 103 | } |
| 63 | 104 | 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 | + } | |
| 83 | 110 | } |
| 84 | 111 | |
| 85 | 112 | /* |
| 86 | 113 | ** Query the database for all TICKET fields for the specific |
| 87 | 114 | ** ticket whose name is given by the "name" CGI parameter. |
| @@ -114,38 +141,24 @@ | ||
| 114 | 141 | if( zVal==0 ){ |
| 115 | 142 | zVal = ""; |
| 116 | 143 | }else if( strncmp(zName, "private_", 8)==0 ){ |
| 117 | 144 | zVal = zRevealed = db_reveal(zVal); |
| 118 | 145 | } |
| 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 ){ | |
| 126 | 149 | Th_Store(zName, zVal); |
| 127 | 150 | } |
| 128 | 151 | free(zRevealed); |
| 129 | 152 | } |
| 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 | + } | |
| 147 | 160 | } |
| 148 | 161 | |
| 149 | 162 | /* |
| 150 | 163 | ** Transfer all CGI parameters to variables in the interpreter. |
| 151 | 164 | */ |
| @@ -157,56 +170,73 @@ | ||
| 157 | 170 | Th_Store(z, P(z)); |
| 158 | 171 | } |
| 159 | 172 | } |
| 160 | 173 | |
| 161 | 174 | /* |
| 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. | |
| 167 | 181 | ** |
| 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. | |
| 170 | 183 | */ |
| 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; | |
| 173 | 186 | Stmt q; |
| 174 | - int i; | |
| 175 | - int rc = 0; | |
| 187 | + int i, j; | |
| 176 | 188 | |
| 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) " | |
| 180 | 191 | "VALUES(%Q, 0)", p->zTicketUuid); |
| 181 | - rc = db_changes(); | |
| 192 | + tktid = db_last_insert_rowid(); | |
| 182 | 193 | } |
| 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"); | |
| 185 | 198 | for(i=0; i<p->nField; i++){ |
| 186 | 199 | const char *zName = p->aField[i].zName; |
| 187 | 200 | if( zName[0]=='+' ){ |
| 188 | 201 | 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 | + } | |
| 192 | 207 | }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); | |
| 195 | 216 | } |
| 196 | 217 | if( rid>0 ){ |
| 197 | 218 | wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0); |
| 198 | 219 | } |
| 199 | 220 | } |
| 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)); | |
| 203 | 223 | db_bind_double(&q, ":mtime", p->rDate); |
| 204 | 224 | db_step(&q); |
| 205 | 225 | 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; | |
| 208 | 238 | } |
| 209 | 239 | |
| 210 | 240 | /* |
| 211 | 241 | ** Rebuild an entire entry in the TICKET table |
| 212 | 242 | */ |
| @@ -213,67 +243,78 @@ | ||
| 213 | 243 | void ticket_rebuild_entry(const char *zTktUuid){ |
| 214 | 244 | char *zTag = mprintf("tkt-%s", zTktUuid); |
| 215 | 245 | int tagid = tag_findid(zTag, 1); |
| 216 | 246 | Stmt q; |
| 217 | 247 | Manifest *pTicket; |
| 248 | + int tktid; | |
| 218 | 249 | int createFlag = 1; |
| 219 | 250 | |
| 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; | |
| 224 | 260 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 225 | 261 | while( db_step(&q)==SQLITE_ROW ){ |
| 226 | 262 | int rid = db_column_int(&q, 0); |
| 227 | 263 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 228 | 264 | if( pTicket ){ |
| 229 | - ticket_insert(pTicket, createFlag, rid); | |
| 265 | + tktid = ticket_insert(pTicket, rid, tktid); | |
| 230 | 266 | manifest_ticket_event(rid, pTicket, createFlag, tagid); |
| 231 | 267 | manifest_destroy(pTicket); |
| 232 | 268 | } |
| 233 | 269 | createFlag = 0; |
| 234 | 270 | } |
| 235 | 271 | db_finalize(&q); |
| 236 | 272 | } |
| 237 | 273 | |
| 238 | 274 | /* |
| 239 | -** Create the subscript interpreter and load the "common" code. | |
| 275 | +** Create the TH1 interpreter and load the "common" code. | |
| 240 | 276 | */ |
| 241 | 277 | void ticket_init(void){ |
| 242 | 278 | const char *zConfig; |
| 243 | 279 | Th_FossilInit(0, 0); |
| 244 | 280 | zConfig = ticket_common_code(); |
| 245 | 281 | Th_Eval(g.interp, 0, zConfig, -1); |
| 246 | 282 | } |
| 247 | 283 | |
| 248 | 284 | /* |
| 249 | -** Create the subscript interpreter and load the "change" code. | |
| 285 | +** Create the TH1 interpreter and load the "change" code. | |
| 250 | 286 | */ |
| 251 | 287 | int ticket_change(void){ |
| 252 | 288 | const char *zConfig; |
| 253 | 289 | Th_FossilInit(0, 0); |
| 254 | 290 | zConfig = ticket_change_code(); |
| 255 | 291 | return Th_Eval(g.interp, 0, zConfig, -1); |
| 256 | 292 | } |
| 257 | 293 | |
| 258 | 294 | /* |
| 259 | -** Recreate the ticket table. | |
| 295 | +** Recreate the TICKET and TICKETCHNG tables. | |
| 260 | 296 | */ |
| 261 | 297 | void ticket_create_table(int separateConnection){ |
| 262 | 298 | const char *zSql; |
| 263 | 299 | |
| 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 | + ); | |
| 265 | 304 | zSql = ticket_table_schema(); |
| 266 | 305 | if( separateConnection ){ |
| 306 | + db_end_transaction(0); | |
| 267 | 307 | db_init_database(g.zRepositoryName, zSql, 0); |
| 268 | 308 | }else{ |
| 269 | 309 | db_multi_exec("%s", zSql); |
| 270 | 310 | } |
| 271 | 311 | } |
| 272 | 312 | |
| 273 | 313 | /* |
| 274 | -** Repopulate the ticket table | |
| 314 | +** Repopulate the TICKET and TICKETCHNG tables from scratch using all | |
| 315 | +** available ticket artifacts. | |
| 275 | 316 | */ |
| 276 | 317 | void ticket_rebuild(void){ |
| 277 | 318 | Stmt q; |
| 278 | 319 | ticket_create_table(1); |
| 279 | 320 | db_begin_transaction(); |
| @@ -287,10 +328,30 @@ | ||
| 287 | 328 | ticket_rebuild_entry(zName); |
| 288 | 329 | } |
| 289 | 330 | db_finalize(&q); |
| 290 | 331 | db_end_transaction(0); |
| 291 | 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 | +} | |
| 292 | 353 | |
| 293 | 354 | /* |
| 294 | 355 | ** WEBPAGE: tktview |
| 295 | 356 | ** URL: tktview?name=UUID |
| 296 | 357 | ** |
| @@ -322,15 +383,24 @@ | ||
| 322 | 383 | if( g.perm.ApndTkt && g.perm.Attach ){ |
| 323 | 384 | style_submenu_element("Attach", "Add An Attachment", |
| 324 | 385 | "%s/attachadd?tkt=%T&from=%s/tktview/%t", |
| 325 | 386 | g.zTop, zUuid, g.zTop, zUuid); |
| 326 | 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 | + } | |
| 327 | 394 | style_header("View Ticket"); |
| 328 | 395 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); |
| 329 | 396 | ticket_init(); |
| 397 | + initializeVariablesFromCGI(); | |
| 398 | + getAllTicketFields(); | |
| 330 | 399 | initializeVariablesFromDb(); |
| 331 | 400 | zScript = ticket_viewpage_code(); |
| 401 | + if( P("showfields")!=0 ) showAllFields(); | |
| 332 | 402 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); |
| 333 | 403 | Th_Render(zScript); |
| 334 | 404 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); |
| 335 | 405 | |
| 336 | 406 | zFullName = db_text(0, |
| @@ -366,20 +436,20 @@ | ||
| 366 | 436 | if( g.thTrace ){ |
| 367 | 437 | Th_Trace("append_field %#h {%#h}<br />\n", |
| 368 | 438 | argl[1], argv[1], argl[2], argv[2]); |
| 369 | 439 | } |
| 370 | 440 | 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 ){ | |
| 373 | 443 | break; |
| 374 | 444 | } |
| 375 | 445 | } |
| 376 | 446 | if( idx>=nField ){ |
| 377 | 447 | Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); |
| 378 | 448 | return TH_ERROR; |
| 379 | 449 | } |
| 380 | - azAppend[idx] = mprintf("%.*s", argl[2], argv[2]); | |
| 450 | + aField[idx].zAppend = mprintf("%.*s", argl[2], argv[2]); | |
| 381 | 451 | return TH_OK; |
| 382 | 452 | } |
| 383 | 453 | |
| 384 | 454 | /* |
| 385 | 455 | ** Write a ticket into the repository. |
| @@ -440,29 +510,32 @@ | ||
| 440 | 510 | blob_zero(&tktchng); |
| 441 | 511 | zDate = date_in_standard_format("now"); |
| 442 | 512 | blob_appendf(&tktchng, "D %s\n", zDate); |
| 443 | 513 | free(zDate); |
| 444 | 514 | 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)); | |
| 448 | 518 | ++nJ; |
| 449 | 519 | } |
| 450 | 520 | } |
| 451 | 521 | for(i=0; i<nField; i++){ |
| 452 | 522 | const char *zValue; |
| 453 | 523 | 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); | |
| 456 | 526 | if( zValue ){ |
| 457 | 527 | 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 ){ | |
| 460 | 533 | 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); | |
| 462 | 535 | }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); | |
| 464 | 537 | } |
| 465 | 538 | nJ++; |
| 466 | 539 | } |
| 467 | 540 | } |
| 468 | 541 | } |
| @@ -523,19 +596,19 @@ | ||
| 523 | 596 | cgi_redirect("home"); |
| 524 | 597 | } |
| 525 | 598 | style_header("New Ticket"); |
| 526 | 599 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); |
| 527 | 600 | ticket_init(); |
| 601 | + initializeVariablesFromCGI(); | |
| 528 | 602 | getAllTicketFields(); |
| 529 | 603 | initializeVariablesFromDb(); |
| 530 | - initializeVariablesFromCGI(); | |
| 604 | + if( g.zPath[0]=='d' ) showAllFields(); | |
| 531 | 605 | form_begin(0, "%R/%s", g.zPath); |
| 532 | 606 | login_insert_csrf_secret(); |
| 533 | 607 | if( P("date_override") && g.perm.Setup ){ |
| 534 | 608 | @ <input type="hidden" name="date_override" value="%h(P("date_override"))"> |
| 535 | 609 | } |
| 536 | - @ </p> | |
| 537 | 610 | zScript = ticket_newpage_code(); |
| 538 | 611 | Th_Store("login", g.zLogin ? g.zLogin : "nobody"); |
| 539 | 612 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 540 | 613 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, |
| 541 | 614 | (void*)&zNewUuid, 0); |
| @@ -596,14 +669,14 @@ | ||
| 596 | 669 | if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); |
| 597 | 670 | ticket_init(); |
| 598 | 671 | getAllTicketFields(); |
| 599 | 672 | initializeVariablesFromCGI(); |
| 600 | 673 | initializeVariablesFromDb(); |
| 674 | + if( g.zPath[0]=='d' ) showAllFields(); | |
| 601 | 675 | form_begin(0, "%R/%s", g.zPath); |
| 602 | 676 | @ <input type="hidden" name="name" value="%s(zName)" /> |
| 603 | 677 | login_insert_csrf_secret(); |
| 604 | - @ </p> | |
| 605 | 678 | zScript = ticket_editpage_code(); |
| 606 | 679 | Th_Store("login", g.zLogin ? g.zLogin : "nobody"); |
| 607 | 680 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 608 | 681 | Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); |
| 609 | 682 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); |
| @@ -635,18 +708,23 @@ | ||
| 635 | 708 | sqlite3_close(db); |
| 636 | 709 | return zErr; |
| 637 | 710 | } |
| 638 | 711 | rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket", |
| 639 | 712 | 0, 0, 0); |
| 640 | - sqlite3_close(db); | |
| 641 | 713 | 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 | + } | |
| 645 | 722 | } |
| 723 | + sqlite3_close(db); | |
| 646 | 724 | } |
| 647 | - return 0; | |
| 725 | + return zErr; | |
| 648 | 726 | } |
| 649 | 727 | |
| 650 | 728 | /* |
| 651 | 729 | ** WEBPAGE: tkttimeline |
| 652 | 730 | ** URL: /tkttimeline?name=TICKETUUID&y=TYPE |
| @@ -734,10 +812,11 @@ | ||
| 734 | 812 | void tkthistory_page(void){ |
| 735 | 813 | Stmt q; |
| 736 | 814 | char *zTitle; |
| 737 | 815 | const char *zUuid; |
| 738 | 816 | int tagid; |
| 817 | + int nChng = 0; | |
| 739 | 818 | |
| 740 | 819 | login_check_credentials(); |
| 741 | 820 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } |
| 742 | 821 | zUuid = PD("name",""); |
| 743 | 822 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| @@ -745,10 +824,17 @@ | ||
| 745 | 824 | "%s/info/%s", g.zTop, zUuid); |
| 746 | 825 | style_submenu_element("Check-ins", "Check-ins", |
| 747 | 826 | "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); |
| 748 | 827 | style_submenu_element("Timeline", "Timeline", |
| 749 | 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 | + } | |
| 750 | 836 | style_header(zTitle); |
| 751 | 837 | free(zTitle); |
| 752 | 838 | |
| 753 | 839 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); |
| 754 | 840 | if( tagid==0 ){ |
| @@ -764,11 +850,11 @@ | ||
| 764 | 850 | " UNION " |
| 765 | 851 | "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" |
| 766 | 852 | " FROM attachment, blob" |
| 767 | 853 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 768 | 854 | " AND blob.rid=attachid" |
| 769 | - " ORDER BY 1 DESC", | |
| 855 | + " ORDER BY 1", | |
| 770 | 856 | tagid, tagid |
| 771 | 857 | ); |
| 772 | 858 | while( db_step(&q)==SQLITE_ROW ){ |
| 773 | 859 | Manifest *pTicket; |
| 774 | 860 | char zShort[12]; |
| @@ -776,40 +862,48 @@ | ||
| 776 | 862 | int rid = db_column_int(&q, 1); |
| 777 | 863 | const char *zChngUuid = db_column_text(&q, 2); |
| 778 | 864 | const char *zFile = db_column_text(&q, 4); |
| 779 | 865 | memcpy(zShort, zChngUuid, 10); |
| 780 | 866 | zShort[10] = 0; |
| 867 | + if( nChng==0 ){ | |
| 868 | + @ <ol> | |
| 869 | + } | |
| 870 | + nChng++; | |
| 781 | 871 | if( zFile!=0 ){ |
| 782 | 872 | const char *zSrc = db_column_text(&q, 3); |
| 783 | 873 | const char *zUser = db_column_text(&q, 5); |
| 784 | 874 | if( zSrc==0 || zSrc[0]==0 ){ |
| 785 | 875 | @ |
| 786 | - @ <p>Delete attachment "%h(zFile)" | |
| 876 | + @ <li><p>Delete attachment "%h(zFile)" | |
| 787 | 877 | }else{ |
| 788 | 878 | @ |
| 789 | - @ <p>Add attachment "%h(zFile)" | |
| 879 | + @ <li><p>Add attachment | |
| 880 | + @ "%z(href("%R/artifact/%S",zSrc))%h(zFile)</a>" | |
| 790 | 881 | } |
| 791 | 882 | @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] |
| 792 | 883 | @ (rid %d(rid)) by |
| 793 | 884 | hyperlink_to_user(zUser,zDate," on"); |
| 794 | 885 | hyperlink_to_date(zDate, ".</p>"); |
| 795 | 886 | }else{ |
| 796 | 887 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 797 | 888 | if( pTicket ){ |
| 798 | 889 | @ |
| 799 | - @ <p>Ticket change | |
| 890 | + @ <li><p>Ticket change | |
| 800 | 891 | @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] |
| 801 | 892 | @ (rid %d(rid)) by |
| 802 | 893 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 803 | 894 | hyperlink_to_date(zDate, ":"); |
| 804 | 895 | @ </p> |
| 805 | - ticket_output_change_artifact(pTicket); | |
| 896 | + ticket_output_change_artifact(pTicket, "a"); | |
| 806 | 897 | } |
| 807 | 898 | manifest_destroy(pTicket); |
| 808 | 899 | } |
| 809 | 900 | } |
| 810 | 901 | db_finalize(&q); |
| 902 | + if( nChng ){ | |
| 903 | + @ </ol> | |
| 904 | + } | |
| 811 | 905 | style_footer(); |
| 812 | 906 | } |
| 813 | 907 | |
| 814 | 908 | /* |
| 815 | 909 | ** Return TRUE if the given BLOB contains a newline character. |
| @@ -825,26 +919,35 @@ | ||
| 825 | 919 | |
| 826 | 920 | /* |
| 827 | 921 | ** The pTkt object is a ticket change artifact. Output a detailed |
| 828 | 922 | ** description of this object. |
| 829 | 923 | */ |
| 830 | -void ticket_output_change_artifact(Manifest *pTkt){ | |
| 924 | +void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){ | |
| 831 | 925 | 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)"> | |
| 833 | 936 | for(i=0; i<pTkt->nField; i++){ |
| 834 | 937 | Blob val; |
| 835 | 938 | const char *z; |
| 836 | 939 | z = pTkt->aField[i].zName; |
| 837 | 940 | blob_set(&val, pTkt->aField[i].zValue); |
| 838 | 941 | 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> | |
| 846 | 949 | }else{ |
| 847 | 950 | @ <li>Change %h(z) to "%h(blob_str(&val))"</li> |
| 848 | 951 | } |
| 849 | 952 | blob_reset(&val); |
| 850 | 953 | } |
| @@ -966,11 +1069,11 @@ | ||
| 966 | 1069 | int i; |
| 967 | 1070 | |
| 968 | 1071 | /* read all available ticket fields */ |
| 969 | 1072 | getAllTicketFields(); |
| 970 | 1073 | for(i=0; i<nField; i++){ |
| 971 | - printf("%s\n",azField[i]); | |
| 1074 | + printf("%s\n",aField[i].zName); | |
| 972 | 1075 | } |
| 973 | 1076 | }else if( !strncmp(g.argv[3],"reports",n) ){ |
| 974 | 1077 | rpt_list_reports(); |
| 975 | 1078 | }else{ |
| 976 | 1079 | fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); |
| @@ -1041,21 +1144,23 @@ | ||
| 1041 | 1144 | int tagid; |
| 1042 | 1145 | |
| 1043 | 1146 | if ( i != g.argc ){ |
| 1044 | 1147 | fossil_fatal("no other parameters expected to %s!",g.argv[2]); |
| 1045 | 1148 | } |
| 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); | |
| 1047 | 1151 | if( tagid==0 ){ |
| 1048 | 1152 | fossil_fatal("no such ticket %h", zTktUuid); |
| 1049 | 1153 | } |
| 1050 | 1154 | db_prepare(&q, |
| 1051 | 1155 | "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" |
| 1052 | 1156 | " FROM event, blob" |
| 1053 | 1157 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 1054 | 1158 | " AND blob.rid=event.objid" |
| 1055 | 1159 | " UNION " |
| 1056 | - "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" | |
| 1160 | + "SELECT datetime(mtime,'localtime'), attachid, uuid, src, " | |
| 1161 | + " filename, user" | |
| 1057 | 1162 | " FROM attachment, blob" |
| 1058 | 1163 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 1059 | 1164 | " AND blob.rid=attachid" |
| 1060 | 1165 | " ORDER BY 1 DESC", |
| 1061 | 1166 | tagid, tagid |
| @@ -1071,21 +1176,22 @@ | ||
| 1071 | 1176 | zShort[10] = 0; |
| 1072 | 1177 | if( zFile!=0 ){ |
| 1073 | 1178 | const char *zSrc = db_column_text(&q, 3); |
| 1074 | 1179 | const char *zUser = db_column_text(&q, 5); |
| 1075 | 1180 | if( zSrc==0 || zSrc[0]==0 ){ |
| 1076 | - fossil_print("Delete attachment %h\n", zFile); | |
| 1181 | + fossil_print("Delete attachment %s\n", zFile); | |
| 1077 | 1182 | }else{ |
| 1078 | - fossil_print("Add attachment %h\n", zFile); | |
| 1183 | + fossil_print("Add attachment %s\n", zFile); | |
| 1079 | 1184 | } |
| 1080 | - fossil_print(" by %h on %h\n", zUser, zDate); | |
| 1185 | + fossil_print(" by %s on %s\n", zUser, zDate); | |
| 1081 | 1186 | }else{ |
| 1082 | 1187 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 1083 | 1188 | if( pTicket ){ |
| 1084 | 1189 | int i; |
| 1085 | 1190 | |
| 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); | |
| 1087 | 1193 | for(i=0; i<pTicket->nField; i++){ |
| 1088 | 1194 | Blob val; |
| 1089 | 1195 | const char *z; |
| 1090 | 1196 | z = pTicket->aField[i].zName; |
| 1091 | 1197 | blob_set(&val, pTicket->aField[i].zValue); |
| @@ -1114,11 +1220,11 @@ | ||
| 1114 | 1220 | /* read all given ticket field/value pairs from command line */ |
| 1115 | 1221 | if( i==g.argc ){ |
| 1116 | 1222 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1117 | 1223 | } |
| 1118 | 1224 | getAllTicketFields(); |
| 1119 | - /* read commandline and assign fields in the azValue array */ | |
| 1225 | + /* read commandline and assign fields in the aField[].zValue array */ | |
| 1120 | 1226 | while( i<g.argc ){ |
| 1121 | 1227 | char *zFName; |
| 1122 | 1228 | char *zFValue; |
| 1123 | 1229 | int j; |
| 1124 | 1230 | int append = 0; |
| @@ -1139,13 +1245,13 @@ | ||
| 1139 | 1245 | j = fieldId(zFName); |
| 1140 | 1246 | if( j == -1 ){ |
| 1141 | 1247 | fossil_fatal("unknown field name '%s'!",zFName); |
| 1142 | 1248 | }else{ |
| 1143 | 1249 | if (append) { |
| 1144 | - azAppend[j] = zFValue; | |
| 1250 | + aField[j].zAppend = zFValue; | |
| 1145 | 1251 | } else { |
| 1146 | - azValue[j] = zFValue; | |
| 1252 | + aField[j].zValue = zFValue; | |
| 1147 | 1253 | } |
| 1148 | 1254 | } |
| 1149 | 1255 | } |
| 1150 | 1256 | |
| 1151 | 1257 | /* now add the needed artifacts to the repository */ |
| @@ -1155,25 +1261,25 @@ | ||
| 1155 | 1261 | /* append defined elements */ |
| 1156 | 1262 | for(i=0; i<nField; i++){ |
| 1157 | 1263 | char *zValue = 0; |
| 1158 | 1264 | char *zPfx; |
| 1159 | 1265 | |
| 1160 | - if (azAppend[i] && azAppend[i][0] ){ | |
| 1266 | + if (aField[i].zAppend && aField[i].zAppend[0] ){ | |
| 1161 | 1267 | 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] ){ | |
| 1164 | 1270 | zPfx = " "; |
| 1165 | - zValue = azValue[i]; | |
| 1271 | + zValue = aField[i].zValue; | |
| 1166 | 1272 | } else { |
| 1167 | 1273 | continue; |
| 1168 | 1274 | } |
| 1169 | - if( strncmp(azField[i], "private_", 8)==0 ){ | |
| 1275 | + if( memcmp(aField[i].zName, "private_", 8)==0 ){ | |
| 1170 | 1276 | 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); | |
| 1172 | 1278 | }else{ |
| 1173 | 1279 | blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, |
| 1174 | - azField[i], strlen(zValue), zValue); | |
| 1280 | + aField[i].zName, strlen(zValue), zValue); | |
| 1175 | 1281 | } |
| 1176 | 1282 | } |
| 1177 | 1283 | blob_appendf(&tktchng, "K %s\n", zTktUuid); |
| 1178 | 1284 | blob_appendf(&tktchng, "U %F\n", zUser); |
| 1179 | 1285 | md5sum_blob(&tktchng, &cksum); |
| 1180 | 1286 |
| --- 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 @@ | ||
| 67 | 67 | @ CREATE TABLE ticket( |
| 68 | 68 | @ -- Do not change any column that begins with tkt_ |
| 69 | 69 | @ tkt_id INTEGER PRIMARY KEY, |
| 70 | 70 | @ tkt_uuid TEXT UNIQUE, |
| 71 | 71 | @ tkt_mtime DATE, |
| 72 | -@ -- Add as many field as required below this line | |
| 72 | +@ -- Add as many fields as required below this line | |
| 73 | 73 | @ type TEXT, |
| 74 | 74 | @ status TEXT, |
| 75 | 75 | @ subsystem TEXT, |
| 76 | 76 | @ priority TEXT, |
| 77 | 77 | @ severity TEXT, |
| @@ -79,10 +79,21 @@ | ||
| 79 | 79 | @ private_contact TEXT, |
| 80 | 80 | @ resolution TEXT, |
| 81 | 81 | @ title TEXT, |
| 82 | 82 | @ comment TEXT |
| 83 | 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); | |
| 84 | 95 | ; |
| 85 | 96 | |
| 86 | 97 | /* |
| 87 | 98 | ** Return the ticket table definition |
| 88 | 99 | */ |
| @@ -120,11 +131,11 @@ | ||
| 120 | 131 | style_header("Edit %s", zTitle); |
| 121 | 132 | if( P("clear")!=0 ){ |
| 122 | 133 | login_verify_csrf_secret(); |
| 123 | 134 | db_unset(zDbField, 0); |
| 124 | 135 | if( xRebuild ) xRebuild(); |
| 125 | - z = zDfltValue; | |
| 136 | + cgi_redirect("tktsetup"); | |
| 126 | 137 | }else if( isSubmit ){ |
| 127 | 138 | char *zErr = 0; |
| 128 | 139 | login_verify_csrf_secret(); |
| 129 | 140 | if( xText && (zErr = xText(z))!=0 ){ |
| 130 | 141 | @ <p class="tktsetupError">ERROR: %h(zErr)</p> |
| @@ -277,84 +288,117 @@ | ||
| 277 | 288 | ); |
| 278 | 289 | } |
| 279 | 290 | |
| 280 | 291 | static const char zDefaultNew[] = |
| 281 | 292 | @ <th1> |
| 293 | +@ if {![info exists mutype]} {set mutype {[links only]}} | |
| 282 | 294 | @ if {[info exists submit]} { |
| 283 | 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 | +@ } | |
| 284 | 305 | @ submit_ticket |
| 285 | 306 | @ } |
| 286 | 307 | @ </th1> |
| 287 | 308 | @ <h1 style="text-align: center;">Enter A New Ticket</h1> |
| 288 | 309 | @ <table cellpadding="5"> |
| 289 | 310 | @ <tr> |
| 290 | -@ <td colspan="2"> | |
| 311 | +@ <td colspan="3"> | |
| 291 | 312 | @ Enter a one-line summary of the ticket:<br /> |
| 292 | 313 | @ <input type="text" name="title" size="60" value="$<title>" /> |
| 293 | 314 | @ </td> |
| 294 | 315 | @ </tr> |
| 295 | 316 | @ |
| 296 | 317 | @ <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> | |
| 301 | 321 | @ </tr> |
| 302 | 322 | @ |
| 303 | 323 | @ <tr> |
| 304 | -@ <td style="text-align: center;">Version: | |
| 324 | +@ <td align="right">Version:</td> | |
| 325 | +@ <td align="left"> | |
| 305 | 326 | @ <input type="text" name="foundin" size="20" value="$<foundin>" /> |
| 306 | 327 | @ </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> | |
| 308 | 330 | @ </tr> |
| 309 | 331 | @ |
| 310 | 332 | @ <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 | |
| 315 | 336 | @ affect the operation of the product?</td> |
| 316 | 337 | @ </tr> |
| 317 | 338 | @ |
| 318 | 339 | @ <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" /> | |
| 321 | 344 | @ </td> |
| 322 | -@ <td><span style="text-decoration: underline;">Not publicly visible</span>. | |
| 345 | +@ <td align="left"><u>Not publicly visible</u> | |
| 323 | 346 | @ Used by developers to contact you with questions.</td> |
| 324 | 347 | @ </tr> |
| 325 | 348 | @ |
| 326 | 349 | @ <tr> |
| 327 | -@ <td colspan="2"> | |
| 350 | +@ <td colspan="3"> | |
| 328 | 351 | @ Enter a detailed description of the problem. |
| 329 | 352 | @ For code defects, be sure to provide details on exactly how |
| 330 | 353 | @ 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> | |
| 332 | 356 | @ <br /> |
| 333 | 357 | @ <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 /> | |
| 337 | 360 | @ </tr> |
| 338 | -@ | |
| 361 | +@ | |
| 339 | 362 | @ <th1>enable_output [info exists preview]</th1> |
| 340 | -@ <tr><td colspan="2"> | |
| 363 | +@ <tr><td colspan="3"> | |
| 341 | 364 | @ 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> | |
| 345 | 396 | @ <th1>enable_output 1</th1> |
| 346 | 397 | @ |
| 347 | 398 | @ <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"> | |
| 356 | 400 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 357 | 401 | @ </td> |
| 358 | 402 | @ <td>Abandon and forget this ticket</td> |
| 359 | 403 | @ </tr> |
| 360 | 404 | @ </table> |
| @@ -387,14 +431,21 @@ | ||
| 387 | 431 | } |
| 388 | 432 | |
| 389 | 433 | static const char zDefaultView[] = |
| 390 | 434 | @ <table cellpadding="5"> |
| 391 | 435 | @ <tr><td class="tktDspLabel">Ticket 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> | |
| 393 | 444 | @ <tr><td class="tktDspLabel">Title:</td> |
| 394 | 445 | @ <td class="tktDspValue" colspan="3"> |
| 395 | -@ <th1>wiki $title</th1> | |
| 446 | +@ $<title> | |
| 396 | 447 | @ </td></tr> |
| 397 | 448 | @ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue"> |
| 398 | 449 | @ $<status> |
| 399 | 450 | @ </td> |
| 400 | 451 | @ <td class="tktDspLabel">Type:</td><td class="tktDspValue"> |
| @@ -423,14 +474,58 @@ | ||
| 423 | 474 | @ </tr> |
| 424 | 475 | @ <tr><td class="tktDspLabel">Version Found In:</td> |
| 425 | 476 | @ <td colspan="3" valign="top" class="tktDspValue"> |
| 426 | 477 | @ $<foundin> |
| 427 | 478 | @ </td></tr> |
| 428 | -@ <tr><td>Description & 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> | |
| 432 | 527 | @ </table> |
| 433 | 528 | ; |
| 434 | 529 | |
| 435 | 530 | |
| 436 | 531 | /* |
| @@ -458,102 +553,118 @@ | ||
| 458 | 553 | ); |
| 459 | 554 | } |
| 460 | 555 | |
| 461 | 556 | static const char zDefaultEdit[] = |
| 462 | 557 | @ <th1> |
| 558 | +@ if {![info exists mutype]} {set mutype {[links only]}} | |
| 559 | +@ if {![info exists icomment]} {set icomment {}} | |
| 463 | 560 | @ if {![info exists username]} {set username $login} |
| 464 | 561 | @ 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 | |
| 474 | 570 | @ } |
| 475 | 571 | @ submit_ticket |
| 476 | 572 | @ } |
| 477 | 573 | @ </th1> |
| 478 | 574 | @ <table cellpadding="5"> |
| 479 | 575 | @ <tr><td class="tktDspLabel">Title:</td><td> |
| 480 | 576 | @ <input type="text" name="title" value="$<title>" size="60" /> |
| 481 | 577 | @ </td></tr> |
| 578 | +@ | |
| 482 | 579 | @ <tr><td class="tktDspLabel">Status:</td><td> |
| 483 | 580 | @ <th1>combobox status $status_choices 1</th1> |
| 484 | 581 | @ </td></tr> |
| 582 | +@ | |
| 485 | 583 | @ <tr><td class="tktDspLabel">Type:</td><td> |
| 486 | 584 | @ <th1>combobox type $type_choices 1</th1> |
| 487 | 585 | @ </td></tr> |
| 586 | +@ | |
| 488 | 587 | @ <tr><td class="tktDspLabel">Severity:</td><td> |
| 489 | 588 | @ <th1>combobox severity $severity_choices 1</th1> |
| 490 | 589 | @ </td></tr> |
| 590 | +@ | |
| 491 | 591 | @ <tr><td class="tktDspLabel">Priority:</td><td> |
| 492 | 592 | @ <th1>combobox priority $priority_choices 1</th1> |
| 493 | 593 | @ </td></tr> |
| 594 | +@ | |
| 494 | 595 | @ <tr><td class="tktDspLabel">Resolution:</td><td> |
| 495 | 596 | @ <th1>combobox resolution $resolution_choices 1</th1> |
| 496 | 597 | @ </td></tr> |
| 598 | +@ | |
| 497 | 599 | @ <tr><td class="tktDspLabel">Subsystem:</td><td> |
| 498 | 600 | @ <th1>combobox subsystem $subsystem_choices 1</th1> |
| 499 | 601 | @ </td></tr> |
| 602 | +@ | |
| 500 | 603 | @ <th1>enable_output [hascap e]</th1> |
| 501 | 604 | @ <tr><td class="tktDspLabel">Contact:</td><td> |
| 502 | 605 | @ <input type="text" name="private_contact" size="40" |
| 503 | 606 | @ value="$<private_contact>" /> |
| 504 | 607 | @ </td></tr> |
| 505 | 608 | @ <th1>enable_output 1</th1> |
| 609 | +@ | |
| 506 | 610 | @ <tr><td class="tktDspLabel">Version Found In:</td><td> |
| 507 | 611 | @ <input type="text" name="foundin" size="50" value="$<foundin>" /> |
| 508 | 612 | @ </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 | +@ | |
| 555 | 666 | @ </table> |
| 556 | 667 | ; |
| 557 | 668 | |
| 558 | 669 | /* |
| 559 | 670 | ** Return the code used to generate the edit ticket page |
| 560 | 671 |
| --- 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 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 Found In:</td> |
| 425 | @ <td colspan="3" valign="top" class="tktDspValue"> |
| 426 | @ $<foundin> |
| 427 | @ </td></tr> |
| 428 | @ <tr><td>Description & 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 Found 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 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 Found 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 Found 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 @@ | ||
| 51 | 51 | #define ATTR_FACE 12 |
| 52 | 52 | #define ATTR_HEIGHT 13 |
| 53 | 53 | #define ATTR_HREF 14 |
| 54 | 54 | #define ATTR_HSPACE 15 |
| 55 | 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 | |
| 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 | |
| 68 | 69 | #define AMSK_ALIGN 0x00000001 |
| 69 | 70 | #define AMSK_ALT 0x00000002 |
| 70 | 71 | #define AMSK_BGCOLOR 0x00000004 |
| 71 | 72 | #define AMSK_BORDER 0x00000008 |
| 72 | 73 | #define AMSK_CELLPADDING 0x00000010 |
| @@ -79,22 +80,23 @@ | ||
| 79 | 80 | #define AMSK_FACE 0x00000800 |
| 80 | 81 | #define AMSK_HEIGHT 0x00001000 |
| 81 | 82 | #define AMSK_HREF 0x00002000 |
| 82 | 83 | #define AMSK_HSPACE 0x00004000 |
| 83 | 84 | #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 | |
| 96 | 98 | |
| 97 | 99 | static const struct AllowedAttribute { |
| 98 | 100 | const char *zName; |
| 99 | 101 | unsigned int iMask; |
| 100 | 102 | } aAttribute[] = { |
| @@ -113,10 +115,11 @@ | ||
| 113 | 115 | { "face", AMSK_FACE, }, |
| 114 | 116 | { "height", AMSK_HEIGHT, }, |
| 115 | 117 | { "href", AMSK_HREF, }, |
| 116 | 118 | { "hspace", AMSK_HSPACE, }, |
| 117 | 119 | { "id", AMSK_ID, }, |
| 120 | + { "links", AMSK_LINKS, }, | |
| 118 | 121 | { "name", AMSK_NAME, }, |
| 119 | 122 | { "rowspan", AMSK_ROWSPAN, }, |
| 120 | 123 | { "size", AMSK_SIZE, }, |
| 121 | 124 | { "src", AMSK_SRC, }, |
| 122 | 125 | { "start", AMSK_START, }, |
| @@ -438,11 +441,11 @@ | ||
| 438 | 441 | int n = 1; |
| 439 | 442 | int inparen = 0; |
| 440 | 443 | int c; |
| 441 | 444 | if( z[n]=='/' ){ n++; } |
| 442 | 445 | if( !fossil_isalpha(z[n]) ) return 0; |
| 443 | - while( fossil_isalnum(z[n]) ){ n++; } | |
| 446 | + while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; } | |
| 444 | 447 | c = z[n]; |
| 445 | 448 | if( c=='/' && z[n+1]=='>' ){ return n+2; } |
| 446 | 449 | if( c!='>' && !fossil_isspace(c) ) return 0; |
| 447 | 450 | while( (c = z[n])!=0 && (c!='>' || inparen) ){ |
| 448 | 451 | if( c==inparen ){ |
| @@ -750,12 +753,23 @@ | ||
| 750 | 753 | } |
| 751 | 754 | zTag[j] = 0; |
| 752 | 755 | p->iCode = findTag(zTag); |
| 753 | 756 | p->iType = aMarkup[p->iCode].iType; |
| 754 | 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 | + } | |
| 755 | 769 | 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]) ){ | |
| 757 | 771 | int attrOk; /* True to preserver attribute. False to ignore it */ |
| 758 | 772 | j = 0; |
| 759 | 773 | while( fossil_isalnum(z[i]) ){ |
| 760 | 774 | if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]); |
| 761 | 775 | i++; |
| @@ -1161,11 +1175,15 @@ | ||
| 1161 | 1175 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1162 | 1176 | ){ |
| 1163 | 1177 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1164 | 1178 | }else if( zTarget[0]=='/' ){ |
| 1165 | 1179 | 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]=='#' ){ | |
| 1167 | 1185 | blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); |
| 1168 | 1186 | }else if( is_valid_uuid(zTarget) ){ |
| 1169 | 1187 | int isClosed = 0; |
| 1170 | 1188 | if( is_ticket(zTarget, &isClosed) ){ |
| 1171 | 1189 | /* Special display processing for tickets. Display the hyperlink |
| @@ -1433,13 +1451,12 @@ | ||
| 1433 | 1451 | const char *zId; |
| 1434 | 1452 | int iDiv; |
| 1435 | 1453 | parseMarkup(&markup, z); |
| 1436 | 1454 | |
| 1437 | 1455 | /* 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> | |
| 1441 | 1458 | */ |
| 1442 | 1459 | if( markup.iCode==MARKUP_DIV && markup.endTag && |
| 1443 | 1460 | (zId = markupId(&markup))!=0 && |
| 1444 | 1461 | (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0 |
| 1445 | 1462 | ){ |
| @@ -1527,18 +1544,17 @@ | ||
| 1527 | 1544 | p->preVerbState = p->state; |
| 1528 | 1545 | p->state &= ~ALLOW_WIKI; |
| 1529 | 1546 | for(ii=0; ii<markup.nAttr; ii++){ |
| 1530 | 1547 | if( markup.aAttr[ii].iACode == ATTR_ID ){ |
| 1531 | 1548 | 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; | |
| 1540 | 1556 | } |
| 1541 | 1557 | } |
| 1542 | 1558 | if( !vAttrDidAppend ) { |
| 1543 | 1559 | endAutoParagraph(p); |
| 1544 | 1560 | blob_append(p->pOut, "<pre class='verbatim'>",-1); |
| 1545 | 1561 |
| --- 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 @@ | ||
| 51 | 51 | #define ATTR_FACE 12 |
| 52 | 52 | #define ATTR_HEIGHT 13 |
| 53 | 53 | #define ATTR_HREF 14 |
| 54 | 54 | #define ATTR_HSPACE 15 |
| 55 | 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 | |
| 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 | |
| 68 | 69 | #define AMSK_ALIGN 0x00000001 |
| 69 | 70 | #define AMSK_ALT 0x00000002 |
| 70 | 71 | #define AMSK_BGCOLOR 0x00000004 |
| 71 | 72 | #define AMSK_BORDER 0x00000008 |
| 72 | 73 | #define AMSK_CELLPADDING 0x00000010 |
| @@ -79,22 +80,23 @@ | ||
| 79 | 80 | #define AMSK_FACE 0x00000800 |
| 80 | 81 | #define AMSK_HEIGHT 0x00001000 |
| 81 | 82 | #define AMSK_HREF 0x00002000 |
| 82 | 83 | #define AMSK_HSPACE 0x00004000 |
| 83 | 84 | #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 | |
| 96 | 98 | |
| 97 | 99 | static const struct AllowedAttribute { |
| 98 | 100 | const char *zName; |
| 99 | 101 | unsigned int iMask; |
| 100 | 102 | } aAttribute[] = { |
| @@ -113,10 +115,11 @@ | ||
| 113 | 115 | { "face", AMSK_FACE, }, |
| 114 | 116 | { "height", AMSK_HEIGHT, }, |
| 115 | 117 | { "href", AMSK_HREF, }, |
| 116 | 118 | { "hspace", AMSK_HSPACE, }, |
| 117 | 119 | { "id", AMSK_ID, }, |
| 120 | + { "links", AMSK_LINKS, }, | |
| 118 | 121 | { "name", AMSK_NAME, }, |
| 119 | 122 | { "rowspan", AMSK_ROWSPAN, }, |
| 120 | 123 | { "size", AMSK_SIZE, }, |
| 121 | 124 | { "src", AMSK_SRC, }, |
| 122 | 125 | { "start", AMSK_START, }, |
| @@ -438,11 +441,11 @@ | ||
| 438 | 441 | int n = 1; |
| 439 | 442 | int inparen = 0; |
| 440 | 443 | int c; |
| 441 | 444 | if( z[n]=='/' ){ n++; } |
| 442 | 445 | if( !fossil_isalpha(z[n]) ) return 0; |
| 443 | - while( fossil_isalnum(z[n]) ){ n++; } | |
| 446 | + while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; } | |
| 444 | 447 | c = z[n]; |
| 445 | 448 | if( c=='/' && z[n+1]=='>' ){ return n+2; } |
| 446 | 449 | if( c!='>' && !fossil_isspace(c) ) return 0; |
| 447 | 450 | while( (c = z[n])!=0 && (c!='>' || inparen) ){ |
| 448 | 451 | if( c==inparen ){ |
| @@ -750,12 +753,23 @@ | ||
| 750 | 753 | } |
| 751 | 754 | zTag[j] = 0; |
| 752 | 755 | p->iCode = findTag(zTag); |
| 753 | 756 | p->iType = aMarkup[p->iCode].iType; |
| 754 | 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 | + } | |
| 755 | 769 | 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]) ){ | |
| 757 | 771 | int attrOk; /* True to preserver attribute. False to ignore it */ |
| 758 | 772 | j = 0; |
| 759 | 773 | while( fossil_isalnum(z[i]) ){ |
| 760 | 774 | if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]); |
| 761 | 775 | i++; |
| @@ -1161,11 +1175,15 @@ | ||
| 1161 | 1175 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1162 | 1176 | ){ |
| 1163 | 1177 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1164 | 1178 | }else if( zTarget[0]=='/' ){ |
| 1165 | 1179 | 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]=='#' ){ | |
| 1167 | 1185 | blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); |
| 1168 | 1186 | }else if( is_valid_uuid(zTarget) ){ |
| 1169 | 1187 | int isClosed = 0; |
| 1170 | 1188 | if( is_ticket(zTarget, &isClosed) ){ |
| 1171 | 1189 | /* Special display processing for tickets. Display the hyperlink |
| @@ -1433,13 +1451,12 @@ | ||
| 1433 | 1451 | const char *zId; |
| 1434 | 1452 | int iDiv; |
| 1435 | 1453 | parseMarkup(&markup, z); |
| 1436 | 1454 | |
| 1437 | 1455 | /* 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> | |
| 1441 | 1458 | */ |
| 1442 | 1459 | if( markup.iCode==MARKUP_DIV && markup.endTag && |
| 1443 | 1460 | (zId = markupId(&markup))!=0 && |
| 1444 | 1461 | (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0 |
| 1445 | 1462 | ){ |
| @@ -1527,18 +1544,17 @@ | ||
| 1527 | 1544 | p->preVerbState = p->state; |
| 1528 | 1545 | p->state &= ~ALLOW_WIKI; |
| 1529 | 1546 | for(ii=0; ii<markup.nAttr; ii++){ |
| 1530 | 1547 | if( markup.aAttr[ii].iACode == ATTR_ID ){ |
| 1531 | 1548 | 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; | |
| 1540 | 1556 | } |
| 1541 | 1557 | } |
| 1542 | 1558 | if( !vAttrDidAppend ) { |
| 1543 | 1559 | endAutoParagraph(p); |
| 1544 | 1560 | blob_append(p->pOut, "<pre class='verbatim'>",-1); |
| 1545 | 1561 |
| --- 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 |