| | @@ -26,28 +26,36 @@ |
| 26 | 26 | ** List attachments. |
| 27 | 27 | ** |
| 28 | 28 | ** tkt=TICKETUUID |
| 29 | 29 | ** page=WIKIPAGE |
| 30 | 30 | ** |
| 31 | | -** Either one of tkt= or page= are supplied or neither but not both. |
| 32 | | -** If neither are given, all attachments are listed. If one is given, |
| 33 | | -** only attachments for the designated ticket or wiki page are shown. |
| 34 | | -** TICKETUUID must be complete |
| 31 | +** At most one of technote=, tkt= or page= are supplied. |
| 32 | +** If none is given, all attachments are listed. If one is given, |
| 33 | +** only attachments for the designated technote, ticket or wiki page |
| 34 | +** are shown. TECHNOTEUUID and TICKETUUID may be just a prefix of the |
| 35 | +** relevant tech note or ticket, in which case all attachments of all |
| 36 | +** tech notes or tickets with the prefix will be listed. |
| 35 | 37 | */ |
| 36 | 38 | void attachlist_page(void){ |
| 37 | 39 | const char *zPage = P("page"); |
| 38 | 40 | const char *zTkt = P("tkt"); |
| 41 | + const char *zTechNote = P("technote"); |
| 39 | 42 | Blob sql; |
| 40 | 43 | Stmt q; |
| 41 | 44 | |
| 42 | 45 | if( zPage && zTkt ) zTkt = 0; |
| 43 | 46 | login_check_credentials(); |
| 44 | 47 | blob_zero(&sql); |
| 45 | 48 | blob_append_sql(&sql, |
| 46 | 49 | "SELECT datetime(mtime,toLocal()), src, target, filename," |
| 47 | 50 | " comment, user," |
| 48 | | - " (SELECT uuid FROM blob WHERE rid=attachid), attachid" |
| 51 | + " (SELECT uuid FROM blob WHERE rid=attachid), attachid," |
| 52 | + " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)" |
| 53 | + " THEN 1" |
| 54 | + " WHEN 'event-'||target IN (SELECT tagname FROM tag)" |
| 55 | + " THEN 2" |
| 56 | + " ELSE 0 END)" |
| 49 | 57 | " FROM attachment" |
| 50 | 58 | ); |
| 51 | 59 | if( zPage ){ |
| 52 | 60 | if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 53 | 61 | style_header("Attachments To %h", zPage); |
| | @@ -54,10 +62,15 @@ |
| 54 | 62 | blob_append_sql(&sql, " WHERE target=%Q", zPage); |
| 55 | 63 | }else if( zTkt ){ |
| 56 | 64 | if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } |
| 57 | 65 | style_header("Attachments To Ticket %S", zTkt); |
| 58 | 66 | blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt); |
| 67 | + }else if( zTechNote ){ |
| 68 | + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 69 | + style_header("Attachments to Tech Note %S", zTechNote); |
| 70 | + blob_append_sql(&sql, " WHERE target GLOB '%q*'", |
| 71 | + zTechNote); |
| 59 | 72 | }else{ |
| 60 | 73 | if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){ |
| 61 | 74 | login_needed(g.anon.RdTkt || g.anon.RdWiki); |
| 62 | 75 | return; |
| 63 | 76 | } |
| | @@ -73,21 +86,25 @@ |
| 73 | 86 | const char *zFilename = db_column_text(&q, 3); |
| 74 | 87 | const char *zComment = db_column_text(&q, 4); |
| 75 | 88 | const char *zUser = db_column_text(&q, 5); |
| 76 | 89 | const char *zUuid = db_column_text(&q, 6); |
| 77 | 90 | int attachid = db_column_int(&q, 7); |
| 91 | + // type 0 is a wiki page, 1 is a ticket, 2 is a tech note |
| 92 | + int type = db_column_int(&q, 8); |
| 78 | 93 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 79 | 94 | int i; |
| 80 | 95 | char *zUrlTail; |
| 81 | 96 | for(i=0; zFilename[i]; i++){ |
| 82 | 97 | if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ |
| 83 | 98 | zFilename = &zFilename[i+1]; |
| 84 | 99 | i = -1; |
| 85 | 100 | } |
| 86 | 101 | } |
| 87 | | - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ |
| 102 | + if( type==1 ){ |
| 88 | 103 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 104 | + }else if( type==2 ){ |
| 105 | + zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename); |
| 89 | 106 | }else{ |
| 90 | 107 | zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); |
| 91 | 108 | } |
| 92 | 109 | @ <li><p> |
| 93 | 110 | @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a> |
| | @@ -98,19 +115,22 @@ |
| 98 | 115 | @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> |
| 99 | 116 | if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; |
| 100 | 117 | if( zComment && zComment[0] ){ |
| 101 | 118 | @ %!W(zComment)<br /> |
| 102 | 119 | } |
| 103 | | - if( zPage==0 && zTkt==0 ){ |
| 120 | + if( zPage==0 && zTkt==0 && zTechNote==0 ){ |
| 104 | 121 | if( zSrc==0 || zSrc[0]==0 ){ |
| 105 | 122 | zSrc = "Deleted from"; |
| 106 | 123 | }else { |
| 107 | 124 | zSrc = "Added to"; |
| 108 | 125 | } |
| 109 | | - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ |
| 126 | + if( type==1 ){ |
| 110 | 127 | @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)"> |
| 111 | 128 | @ %S(zTarget)</a> |
| 129 | + }else if( type==2 ){ |
| 130 | + @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)"> |
| 131 | + @ %S(zTarget)</a> |
| 112 | 132 | }else{ |
| 113 | 133 | @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)"> |
| 114 | 134 | @ %h(zTarget)</a> |
| 115 | 135 | } |
| 116 | 136 | }else{ |
| | @@ -138,31 +158,35 @@ |
| 138 | 158 | ** Download or display an attachment. |
| 139 | 159 | ** Query parameters: |
| 140 | 160 | ** |
| 141 | 161 | ** tkt=TICKETUUID |
| 142 | 162 | ** page=WIKIPAGE |
| 163 | +** technote=TECHNOTEUUID |
| 143 | 164 | ** file=FILENAME |
| 144 | 165 | ** attachid=ID |
| 145 | 166 | ** |
| 146 | 167 | */ |
| 147 | 168 | void attachview_page(void){ |
| 148 | 169 | const char *zPage = P("page"); |
| 149 | 170 | const char *zTkt = P("tkt"); |
| 171 | + const char *zTechNote = P("technote"); |
| 150 | 172 | const char *zFile = P("file"); |
| 151 | 173 | const char *zTarget = 0; |
| 152 | 174 | int attachid = atoi(PD("attachid","0")); |
| 153 | 175 | char *zUUID; |
| 154 | 176 | |
| 155 | | - if( zPage && zTkt ) zTkt = 0; |
| 156 | 177 | if( zFile==0 ) fossil_redirect_home(); |
| 157 | 178 | login_check_credentials(); |
| 158 | 179 | if( zPage ){ |
| 159 | 180 | if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 160 | 181 | zTarget = zPage; |
| 161 | 182 | }else if( zTkt ){ |
| 162 | 183 | if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } |
| 163 | 184 | zTarget = zTkt; |
| 185 | + }else if( zTechNote ){ |
| 186 | + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 187 | + zTarget = zTechNote; |
| 164 | 188 | }else{ |
| 165 | 189 | fossil_redirect_home(); |
| 166 | 190 | } |
| 167 | 191 | if( attachid>0 ){ |
| 168 | 192 | zUUID = db_text(0, |
| | @@ -186,18 +210,19 @@ |
| 186 | 210 | }else if( zUUID[0]=='x' ){ |
| 187 | 211 | style_header("Missing"); |
| 188 | 212 | @ Attachment has been deleted |
| 189 | 213 | style_footer(); |
| 190 | 214 | return; |
| 191 | | - } |
| 192 | | - g.perm.Read = 1; |
| 193 | | - cgi_replace_parameter("name",zUUID); |
| 194 | | - if( fossil_strcmp(g.zPath,"attachview")==0 ){ |
| 195 | | - artifact_page(); |
| 196 | 215 | }else{ |
| 197 | | - cgi_replace_parameter("m", mimetype_from_name(zFile)); |
| 198 | | - rawartifact_page(); |
| 216 | + g.perm.Read = 1; |
| 217 | + cgi_replace_parameter("name",zUUID); |
| 218 | + if( fossil_strcmp(g.zPath,"attachview")==0 ){ |
| 219 | + artifact_page(); |
| 220 | + }else{ |
| 221 | + cgi_replace_parameter("m", mimetype_from_name(zFile)); |
| 222 | + rawartifact_page(); |
| 223 | + } |
| 199 | 224 | } |
| 200 | 225 | } |
| 201 | 226 | |
| 202 | 227 | /* |
| 203 | 228 | ** Save an attachment control artifact into the repository |
| | @@ -228,27 +253,34 @@ |
| 228 | 253 | ** WEBPAGE: attachadd |
| 229 | 254 | ** Add a new attachment. |
| 230 | 255 | ** |
| 231 | 256 | ** tkt=TICKETUUID |
| 232 | 257 | ** page=WIKIPAGE |
| 258 | +** technote=TECHNOTEUUID |
| 233 | 259 | ** from=URL |
| 234 | 260 | ** |
| 235 | 261 | */ |
| 236 | 262 | void attachadd_page(void){ |
| 237 | 263 | const char *zPage = P("page"); |
| 238 | 264 | const char *zTkt = P("tkt"); |
| 265 | + const char *zTechNote = P("technote"); |
| 239 | 266 | const char *zFrom = P("from"); |
| 240 | 267 | const char *aContent = P("f"); |
| 241 | 268 | const char *zName = PD("f:filename","unknown"); |
| 242 | 269 | const char *zTarget; |
| 243 | | - const char *zTargetType; |
| 270 | + char *zTargetType; |
| 244 | 271 | int szContent = atoi(PD("f:bytes","0")); |
| 245 | 272 | int goodCaptcha = 1; |
| 246 | 273 | |
| 247 | 274 | if( P("cancel") ) cgi_redirect(zFrom); |
| 248 | | - if( zPage && zTkt ) fossil_redirect_home(); |
| 249 | | - if( zPage==0 && zTkt==0 ) fossil_redirect_home(); |
| 275 | + if( (zPage && zTkt) |
| 276 | + || (zPage && zTechNote) |
| 277 | + || (zTkt && zTechNote) |
| 278 | + ){ |
| 279 | + fossil_redirect_home(); |
| 280 | + } |
| 281 | + if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home(); |
| 250 | 282 | login_check_credentials(); |
| 251 | 283 | if( zPage ){ |
| 252 | 284 | if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 253 | 285 | login_needed(g.anon.ApndWiki && g.anon.Attach); |
| 254 | 286 | return; |
| | @@ -257,10 +289,24 @@ |
| 257 | 289 | fossil_redirect_home(); |
| 258 | 290 | } |
| 259 | 291 | zTarget = zPage; |
| 260 | 292 | zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", |
| 261 | 293 | zPage, zPage); |
| 294 | + }else if ( zTechNote ){ |
| 295 | + if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 296 | + login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach); |
| 297 | + return; |
| 298 | + } |
| 299 | + if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){ |
| 300 | + zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag" |
| 301 | + " WHERE tagname GLOB 'event-%q*'", zTechNote); |
| 302 | + if( zTechNote==0) fossil_redirect_home(); |
| 303 | + } |
| 304 | + zTarget = zTechNote; |
| 305 | + zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>", |
| 306 | + zTechNote, zTechNote); |
| 307 | + |
| 262 | 308 | }else{ |
| 263 | 309 | if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ |
| 264 | 310 | login_needed(g.anon.ApndTkt && g.anon.Attach); |
| 265 | 311 | return; |
| 266 | 312 | } |
| | @@ -340,10 +386,12 @@ |
| 340 | 386 | @ <input type="file" name="f" size="60" /><br /> |
| 341 | 387 | @ Description:<br /> |
| 342 | 388 | @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br /> |
| 343 | 389 | if( zTkt ){ |
| 344 | 390 | @ <input type="hidden" name="tkt" value="%h(zTkt)" /> |
| 391 | + }else if( zTechNote ){ |
| 392 | + @ <input type="hidden" name="technote" value="%h(zTechNote)" /> |
| 345 | 393 | }else{ |
| 346 | 394 | @ <input type="hidden" name="page" value="%h(zPage)" /> |
| 347 | 395 | } |
| 348 | 396 | @ <input type="hidden" name="from" value="%h(zFrom)" /> |
| 349 | 397 | @ <input type="submit" name="ok" value="Add Attachment" /> |
| | @@ -350,10 +398,11 @@ |
| 350 | 398 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 351 | 399 | @ </div> |
| 352 | 400 | captcha_generate(0); |
| 353 | 401 | @ </form> |
| 354 | 402 | style_footer(); |
| 403 | + fossil_free(zTargetType); |
| 355 | 404 | } |
| 356 | 405 | |
| 357 | 406 | /* |
| 358 | 407 | ** WEBPAGE: ainfo |
| 359 | 408 | ** URL: /ainfo?name=ARTIFACTID |
| | @@ -364,15 +413,16 @@ |
| 364 | 413 | int rid; /* RID for the control artifact */ |
| 365 | 414 | int ridSrc; /* RID for the attached file */ |
| 366 | 415 | char *zDate; /* Date attached */ |
| 367 | 416 | const char *zUuid; /* UUID of the control artifact */ |
| 368 | 417 | Manifest *pAttach; /* Parse of the control artifact */ |
| 369 | | - const char *zTarget; /* Wiki or ticket attached to */ |
| 418 | + const char *zTarget; /* Wiki, ticket or tech note attached to */ |
| 370 | 419 | const char *zSrc; /* UUID of the attached file */ |
| 371 | 420 | const char *zName; /* Name of the attached file */ |
| 372 | 421 | const char *zDesc; /* Description of the attached file */ |
| 373 | 422 | const char *zWikiName = 0; /* Wiki page name when attached to Wiki */ |
| 423 | + const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */ |
| 374 | 424 | const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */ |
| 375 | 425 | int modPending; /* True if awaiting moderation */ |
| 376 | 426 | const char *zModAction; /* Moderation action or NULL */ |
| 377 | 427 | int isModerator; /* TRUE if user is the moderator */ |
| 378 | 428 | const char *zMime; /* MIME Type */ |
| | @@ -422,15 +472,23 @@ |
| 422 | 472 | zWikiName = zTarget; |
| 423 | 473 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 424 | 474 | if( g.perm.WrWiki ){ |
| 425 | 475 | style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); |
| 426 | 476 | } |
| 477 | + }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){ |
| 478 | + zTNUuid = zTarget; |
| 479 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 480 | + if( g.perm.Write && g.perm.WrWiki ){ |
| 481 | + style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); |
| 482 | + } |
| 427 | 483 | } |
| 428 | 484 | zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); |
| 429 | 485 | |
| 430 | 486 | if( P("confirm") |
| 431 | | - && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) |
| 487 | + && ((zTktUuid && g.perm.WrTkt) || |
| 488 | + (zWikiName && g.perm.WrWiki) || |
| 489 | + (zTNUuid && g.perm.Write && g.perm.WrWiki)) |
| 432 | 490 | ){ |
| 433 | 491 | int i, n, rid; |
| 434 | 492 | char *zDate; |
| 435 | 493 | Blob manifest; |
| 436 | 494 | Blob cksum; |
| | @@ -454,11 +512,13 @@ |
| 454 | 512 | db_end_transaction(0); |
| 455 | 513 | @ <p>The attachment below has been deleted.</p> |
| 456 | 514 | } |
| 457 | 515 | |
| 458 | 516 | if( P("del") |
| 459 | | - && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) |
| 517 | + && ((zTktUuid && g.perm.WrTkt) || |
| 518 | + (zWikiName && g.perm.WrWiki) || |
| 519 | + (zTNUuid && g.perm.Write && g.perm.WrWiki)) |
| 460 | 520 | ){ |
| 461 | 521 | form_begin(0, "%R/ainfo/%!S", zUuid); |
| 462 | 522 | @ <p>Confirm you want to delete the attachment shown below. |
| 463 | 523 | @ <input type="submit" name="confirm" value="Confirm"> |
| 464 | 524 | @ </form> |
| | @@ -502,10 +562,14 @@ |
| 502 | 562 | } |
| 503 | 563 | if( zTktUuid ){ |
| 504 | 564 | @ <tr><th>Ticket:</th> |
| 505 | 565 | @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr> |
| 506 | 566 | } |
| 567 | + if( zTNUuid ){ |
| 568 | + @ <tr><th>Tech Note:</th> |
| 569 | + @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr> |
| 570 | + } |
| 507 | 571 | if( zWikiName ){ |
| 508 | 572 | @ <tr><th>Wiki Page:</th> |
| 509 | 573 | @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr> |
| 510 | 574 | } |
| 511 | 575 | @ <tr><th>Date:</th><td> |
| 512 | 576 | |