Fossil SCM
Unify and regularize forum display code
Commit
6999639bbb87f642816887e2e743209ebb45d3302818f8e94535f070b98bc4de
Parent
adefa86c5e1fe13…
1 file changed
+352
-449
+352
-449
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -89,19 +89,17 @@ | ||
| 89 | 89 | fossil_free(pPost); |
| 90 | 90 | } |
| 91 | 91 | fossil_free(pThread); |
| 92 | 92 | } |
| 93 | 93 | |
| 94 | -#if 0 /* not used */ | |
| 95 | 94 | /* |
| 96 | 95 | ** Search a ForumPost list forwards looking for the post with fpid |
| 97 | 96 | */ |
| 98 | 97 | static ForumPost *forumpost_forward(ForumPost *p, int fpid){ |
| 99 | 98 | while( p && p->fpid!=fpid ) p = p->pNext; |
| 100 | 99 | return p; |
| 101 | 100 | } |
| 102 | -#endif | |
| 103 | 101 | |
| 104 | 102 | /* |
| 105 | 103 | ** Search backwards for a ForumPost |
| 106 | 104 | */ |
| 107 | 105 | static ForumPost *forumpost_backward(ForumPost *p, int fpid){ |
| @@ -358,28 +356,10 @@ | ||
| 358 | 356 | if( zClass ){ |
| 359 | 357 | @ </div> |
| 360 | 358 | } |
| 361 | 359 | } |
| 362 | 360 | |
| 363 | -/* | |
| 364 | -** Generate the buttons in the display that allow a forum supervisor to | |
| 365 | -** mark a user as trusted. Only do this if: | |
| 366 | -** | |
| 367 | -** (1) The poster is an individual, not a special user like "anonymous" | |
| 368 | -** (2) The current user has Forum Supervisor privilege | |
| 369 | -*/ | |
| 370 | -static void generateTrustControls(Manifest *pPost){ | |
| 371 | - if( !g.perm.AdminForum ) return; | |
| 372 | - if( login_is_special(pPost->zUser) ) return; | |
| 373 | - @ <br> | |
| 374 | - @ <label><input type="checkbox" name="trust"> | |
| 375 | - @ Trust user "%h(pPost->zUser)" | |
| 376 | - @ so that future posts by "%h(pPost->zUser)" do not require moderation. | |
| 377 | - @ </label> | |
| 378 | - @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)"> | |
| 379 | -} | |
| 380 | - | |
| 381 | 361 | /* |
| 382 | 362 | ** Compute a display name from a login name. |
| 383 | 363 | ** |
| 384 | 364 | ** If the input login is found in the USER table, then check the USER.INFO |
| 385 | 365 | ** field to see if it has display-name followed by an email address. |
| @@ -409,421 +389,324 @@ | ||
| 409 | 389 | db_reset(&q); |
| 410 | 390 | return zResult; |
| 411 | 391 | } |
| 412 | 392 | |
| 413 | 393 | /* |
| 414 | -** Display all posts in a forum thread in chronological order | |
| 415 | -*/ | |
| 416 | -static void forum_display_chronological(int froot, int target, int bRawMode){ | |
| 417 | - ForumThread *pThread = forumthread_create(froot, 0); | |
| 418 | - ForumPost *p; | |
| 419 | - int notAnon = login_is_individual(); | |
| 420 | - char cMode = bRawMode ? 'r' : 'c'; | |
| 421 | - for(p=pThread->pFirst; p; p=p->pNext){ | |
| 422 | - char *zDate; | |
| 423 | - Manifest *pPost; | |
| 424 | - int isPrivate; /* True for posts awaiting moderation */ | |
| 425 | - int sameUser; /* True if author is also the reader */ | |
| 426 | - const char *zUuid; | |
| 427 | - char *zDisplayName; /* The display name */ | |
| 428 | - | |
| 429 | - pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 430 | - if( pPost==0 ) continue; | |
| 431 | - if( p->fpid==target ){ | |
| 432 | - @ <div id="forum%d(p->fpid)" class="forumTime forumSel"> | |
| 433 | - }else if( p->pEditTail!=0 ){ | |
| 434 | - @ <div id="forum%d(p->fpid)" class="forumTime forumObs"> | |
| 435 | - }else{ | |
| 436 | - @ <div id="forum%d(p->fpid)" class="forumTime"> | |
| 437 | - } | |
| 438 | - if( pPost->zThreadTitle ){ | |
| 439 | - @ <h1>%h(pPost->zThreadTitle)</h1> | |
| 440 | - } | |
| 441 | - zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); | |
| 442 | - zDisplayName = display_name_from_login(pPost->zUser); | |
| 443 | - @ <h3 class='forumPostHdr'>(%d(p->sid)\ | |
| 444 | - if( p->nEdit ){ | |
| 445 | - @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ | |
| 446 | - } | |
| 447 | - @ ) By %h(zDisplayName) on %h(zDate) | |
| 448 | - fossil_free(zDisplayName); | |
| 449 | - fossil_free(zDate); | |
| 450 | - if( p->pEditPrev ){ | |
| 451 | - @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEditPrev->zUuid,cMode))\ | |
| 452 | - @ %d(p->pEditPrev->sid)\ | |
| 453 | - @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> | |
| 454 | - } | |
| 455 | - if( g.perm.Debug ){ | |
| 456 | - @ <span class="debug">\ | |
| 457 | - @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> | |
| 458 | - } | |
| 459 | - if( p->pIrt ){ | |
| 460 | - @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\ | |
| 461 | - @ %d(p->pIrt->sid)\ | |
| 462 | - if( p->pIrt->nEdit ){ | |
| 463 | - @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ | |
| 464 | - } | |
| 465 | - @ </a> | |
| 466 | - } | |
| 467 | - zUuid = p->zUuid; | |
| 468 | - if( p->pEditTail ){ | |
| 469 | - @ updated by %z(href("%R/forumpost/%S?t=%c",p->pEditTail->zUuid,cMode))\ | |
| 470 | - @ %d(p->pEditTail->sid)\ | |
| 471 | - @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a> | |
| 472 | - zUuid = p->pEditTail->zUuid; | |
| 473 | - } | |
| 474 | - if( p->fpid!=target ){ | |
| 475 | - @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a> | |
| 476 | - } | |
| 477 | - if( !bRawMode ){ | |
| 478 | - @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> | |
| 479 | - } | |
| 480 | - isPrivate = content_is_private(p->fpid); | |
| 481 | - sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 482 | - @ </h3> | |
| 483 | - if( isPrivate && !g.perm.ModForum && !sameUser ){ | |
| 484 | - @ <p><span class="modpending">Awaiting Moderator Approval</span></p> | |
| 485 | - }else{ | |
| 486 | - const char *zMimetype; | |
| 487 | - if( bRawMode ){ | |
| 488 | - zMimetype = "text/plain"; | |
| 489 | - }else if( p->pEditTail!=0 ){ | |
| 490 | - zMimetype = "text/plain"; | |
| 491 | - }else{ | |
| 492 | - zMimetype = pPost->zMimetype; | |
| 493 | - } | |
| 494 | - forum_render(0, zMimetype, pPost->zWiki, 0, 1); | |
| 495 | - } | |
| 496 | - if( g.perm.WrForum && p->pEditTail==0 ){ | |
| 497 | - int sameUser = login_is_individual() | |
| 498 | - && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 499 | - @ <div><form action="%R/forumedit" method="POST"> | |
| 500 | - @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> | |
| 501 | - if( !isPrivate ){ | |
| 502 | - /* Reply and Edit are only available if the post has already | |
| 503 | - ** been approved */ | |
| 504 | - @ <input type="submit" name="reply" value="Reply"> | |
| 505 | - if( g.perm.Admin || sameUser ){ | |
| 506 | - @ <input type="submit" name="edit" value="Edit"> | |
| 507 | - @ <input type="submit" name="nullout" value="Delete"> | |
| 508 | - } | |
| 509 | - }else if( g.perm.ModForum ){ | |
| 510 | - /* Provide moderators with moderation buttons for posts that | |
| 511 | - ** are pending moderation */ | |
| 512 | - @ <input type="submit" name="approve" value="Approve"> | |
| 513 | - @ <input type="submit" name="reject" value="Reject"> | |
| 514 | - generateTrustControls(pPost); | |
| 515 | - }else if( sameUser ){ | |
| 516 | - /* A post that is pending moderation can be deleted by the | |
| 517 | - ** person who originally submitted the post */ | |
| 518 | - @ <input type="submit" name="reject" value="Delete"> | |
| 519 | - } | |
| 520 | - @ </form></div> | |
| 521 | - } | |
| 522 | - manifest_destroy(pPost); | |
| 523 | - @ </div> | |
| 524 | - } | |
| 525 | - | |
| 526 | - /* Undocumented "threadtable" query parameter causes thread table | |
| 527 | - ** to be displayed for debugging purposes. | |
| 528 | - */ | |
| 529 | - if( PB("threadtable") ){ | |
| 530 | - @ <hr> | |
| 531 | - @ <table border="1" cellpadding="3" cellspacing="0"> | |
| 532 | - @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\ | |
| 533 | - @ <th>pEditNext<th>pEditPrev<th>hash | |
| 534 | - for(p=pThread->pFirst; p; p=p->pNext){ | |
| 535 | - @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\ | |
| 536 | - @ <td>%d(p->pIrt?p->pIrt->fpid:0)\ | |
| 537 | - @ <td>%d(p->pEditHead?p->pEditHead->fpid:0)\ | |
| 538 | - @ <td>%d(p->pEditTail?p->pEditTail->fpid:0)\ | |
| 539 | - @ <td>%d(p->pEditNext?p->pEditNext->fpid:0)\ | |
| 540 | - @ <td>%d(p->pEditPrev?p->pEditPrev->fpid:0)\ | |
| 541 | - @ <td>%S(p->zUuid)</tr> | |
| 542 | - } | |
| 543 | - @ </table> | |
| 544 | - } | |
| 545 | - | |
| 546 | - forumthread_delete(pThread); | |
| 547 | -} | |
| 548 | -/* | |
| 549 | -** Display all the edit history of post "target". | |
| 550 | -*/ | |
| 551 | -static void forum_display_history(int froot, int target, int bRawMode){ | |
| 552 | - ForumThread *pThread = forumthread_create(froot, 0); | |
| 553 | - ForumPost *p; | |
| 554 | - int notAnon = login_is_individual(); | |
| 555 | - char cMode = bRawMode ? 'r' : 'c'; | |
| 556 | - ForumPost *pEditTail = 0; | |
| 557 | - int cnt = 0; | |
| 558 | - for(p=pThread->pFirst; p; p=p->pNext){ | |
| 559 | - if( p->fpid==target ){ | |
| 560 | - pEditTail = p->pEditTail ? p->pEditTail : p; | |
| 561 | - break; | |
| 562 | - } | |
| 563 | - } | |
| 564 | - for(p=pThread->pFirst; p; p=p->pNext){ | |
| 565 | - char *zDate; | |
| 566 | - Manifest *pPost; | |
| 567 | - int isPrivate; /* True for posts awaiting moderation */ | |
| 568 | - int sameUser; /* True if author is also the reader */ | |
| 569 | - const char *zUuid; | |
| 570 | - char *zDisplayName; /* The display name */ | |
| 571 | - | |
| 572 | - if( p->fpid!=pEditTail->fpid && p->pEditTail!=pEditTail ) continue; | |
| 573 | - cnt++; | |
| 574 | - pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 575 | - if( pPost==0 ) continue; | |
| 576 | - @ <div id="forum%d(p->fpid)" class="forumTime"> | |
| 577 | - zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); | |
| 578 | - zDisplayName = display_name_from_login(pPost->zUser); | |
| 394 | +** Display a single post in a forum thread. | |
| 395 | +*/ | |
| 396 | +static void forum_display_post( | |
| 397 | + ForumPost *p, /* Forum post to display */ | |
| 398 | + int iIndentScale, /* Indent scale factor */ | |
| 399 | + int bRaw, /* True to omit the border */ | |
| 400 | + int bUnf, /* True to leave the post unformatted */ | |
| 401 | + int bHist, /* True if showing edit history */ | |
| 402 | + int bSelect, /* True if this is the selected post */ | |
| 403 | + char *zQuery /* Common query string */ | |
| 404 | +){ | |
| 405 | + char *zDisplayName; /* The display name */ | |
| 406 | + char *zDate; /* The time/date string */ | |
| 407 | + char *zHist; /* History query string */ | |
| 408 | + Manifest *pManifest; /* Manifest comprising the current post */ | |
| 409 | + int bPrivate; /* True for posts awaiting moderation */ | |
| 410 | + int bSameUser; /* True if author is also the reader */ | |
| 411 | + int iIndent; /* Indent level */ | |
| 412 | + const char *zMimetype;/* Formatting MIME type */ | |
| 413 | + | |
| 414 | + /* Get the manifest for the post. Abort if not found (e.g. shunned). */ | |
| 415 | + pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 416 | + if( !pManifest ) return; | |
| 417 | + | |
| 418 | + /* When not in raw mode, create the border around the post. */ | |
| 419 | + if( !bRaw ){ | |
| 420 | + /* Open the <div> enclosing the post. Set the class string to mark the post | |
| 421 | + ** as selected and/or obsolete. */ | |
| 422 | + iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; | |
| 423 | + @ <div id='forum%d(p->fpid)' class='forumTime\ | |
| 424 | + @ %s(bSelect ? " forumSel" : "")\ | |
| 425 | + @ %s(p->pEditTail ? " forumObs" : "")'\ | |
| 426 | + if( iIndent && iIndentScale ){ | |
| 427 | + @ style='margin-left: %d(iIndent*iIndentScale)ex' | |
| 428 | + } | |
| 429 | + @ > | |
| 430 | + | |
| 431 | + /* If this is the first post (or an edit thereof), emit the thread title. */ | |
| 432 | + if( pManifest->zThreadTitle ){ | |
| 433 | + @ <h1>%h(pManifest->zThreadTitle)</h1> | |
| 434 | + } | |
| 435 | + | |
| 436 | + /* Emit the serial number, revision number, author, and date. */ | |
| 437 | + zDisplayName = display_name_from_login(pManifest->zUser); | |
| 438 | + zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate); | |
| 579 | 439 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 580 | 440 | if( p->nEdit ){ |
| 581 | 441 | @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ |
| 582 | 442 | } |
| 583 | 443 | @ ) By %h(zDisplayName) on %h(zDate) |
| 584 | 444 | fossil_free(zDisplayName); |
| 585 | 445 | fossil_free(zDate); |
| 446 | + | |
| 447 | + /* If this is an edit, refer back to the old version. Be sure "hist" is in | |
| 448 | + ** the query string so the old version will actually be shown. */ | |
| 449 | + if( p->pEditPrev ){ | |
| 450 | + zHist = bHist ? "" : "&hist"; | |
| 451 | + @ edit of \ | |
| 452 | + @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ | |
| 453 | + @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> | |
| 454 | + } | |
| 455 | + | |
| 456 | + /* If debugging is enabled, link to the artifact page. */ | |
| 586 | 457 | if( g.perm.Debug ){ |
| 587 | 458 | @ <span class="debug">\ |
| 588 | 459 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 589 | 460 | } |
| 590 | - if( p->pIrt && cnt==1 ){ | |
| 591 | - @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\ | |
| 461 | + | |
| 462 | + /* If this is a reply, refer back to the parent post. */ | |
| 463 | + if( p->pIrt ){ | |
| 464 | + @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ | |
| 592 | 465 | @ %d(p->pIrt->sid)\ |
| 593 | 466 | if( p->pIrt->nEdit ){ |
| 594 | 467 | @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 595 | 468 | } |
| 596 | 469 | @ </a> |
| 597 | 470 | } |
| 598 | - zUuid = p->zUuid; | |
| 599 | - @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a> | |
| 600 | - if( !bRawMode ){ | |
| 601 | - @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> | |
| 602 | - } | |
| 603 | - isPrivate = content_is_private(p->fpid); | |
| 604 | - sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 605 | - @ </h3> | |
| 606 | - if( isPrivate && !g.perm.ModForum && !sameUser ){ | |
| 607 | - @ <p><span class="modpending">Awaiting Moderator Approval</span></p> | |
| 608 | - }else{ | |
| 609 | - forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, | |
| 610 | - 0, 1); | |
| 611 | - } | |
| 612 | - if( g.perm.WrForum && p->pEditTail==0 ){ | |
| 613 | - int sameUser = login_is_individual() | |
| 614 | - && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 615 | - @ <div><form action="%R/forumedit" method="POST"> | |
| 616 | - @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> | |
| 617 | - if( !isPrivate ){ | |
| 618 | - /* Reply and Edit are only available if the post has already | |
| 619 | - ** been approved */ | |
| 620 | - @ <input type="submit" name="reply" value="Reply"> | |
| 621 | - if( g.perm.Admin || sameUser ){ | |
| 622 | - @ <input type="submit" name="edit" value="Edit"> | |
| 623 | - @ <input type="submit" name="nullout" value="Delete"> | |
| 624 | - } | |
| 625 | - }else if( g.perm.ModForum ){ | |
| 626 | - /* Provide moderators with moderation buttons for posts that | |
| 627 | - ** are pending moderation */ | |
| 628 | - @ <input type="submit" name="approve" value="Approve"> | |
| 629 | - @ <input type="submit" name="reject" value="Reject"> | |
| 630 | - generateTrustControls(pPost); | |
| 631 | - }else if( sameUser ){ | |
| 632 | - /* A post that is pending moderation can be deleted by the | |
| 633 | - ** person who originally submitted the post */ | |
| 634 | - @ <input type="submit" name="reject" value="Delete"> | |
| 635 | - } | |
| 636 | - @ </form></div> | |
| 637 | - } | |
| 638 | - manifest_destroy(pPost); | |
| 639 | - @ </div> | |
| 640 | - } | |
| 641 | - forumthread_delete(pThread); | |
| 642 | -} | |
| 643 | - | |
| 644 | -/* | |
| 645 | -** Display all messages in a forumthread with indentation. | |
| 646 | -*/ | |
| 647 | -static int forum_display_hierarchical(int froot, int target){ | |
| 648 | - ForumThread *pThread; | |
| 649 | - ForumPost *p; | |
| 650 | - Manifest *pPost, *pOPost; | |
| 651 | - int fpid; | |
| 652 | - const char *zUuid; | |
| 653 | - char *zDate; | |
| 654 | - const char *zSel; | |
| 655 | - int notAnon = login_is_individual(); | |
| 656 | - int iIndentScale = 4; | |
| 657 | - | |
| 658 | - pThread = forumthread_create(froot, 1); | |
| 659 | - for(p=pThread->pFirst; p; p=p->pNext){ | |
| 660 | - if( p->fpid==target ){ | |
| 661 | - if( p->pEditHead ) p = p->pEditHead; | |
| 662 | - target = p->fpid; | |
| 663 | - break; | |
| 664 | - } | |
| 665 | - } | |
| 666 | - while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){ | |
| 667 | - iIndentScale--; | |
| 668 | - } | |
| 669 | - for(p=pThread->pDisplay; p; p=p->pDisplay){ | |
| 670 | - int isPrivate; /* True for posts awaiting moderation */ | |
| 671 | - int sameUser; /* True if reader is also the poster */ | |
| 672 | - char *zDisplayName; /* User name to be displayed */ | |
| 673 | - pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); | |
| 674 | - if( p->pEditTail ){ | |
| 675 | - fpid = p->pEditTail->fpid; | |
| 676 | - zUuid = p->pEditTail->zUuid; | |
| 677 | - pPost = manifest_get(fpid, CFTYPE_FORUM, 0); | |
| 678 | - }else{ | |
| 679 | - fpid = p->fpid; | |
| 680 | - zUuid = p->zUuid; | |
| 681 | - pPost = pOPost; | |
| 682 | - } | |
| 683 | - zSel = p->fpid==target ? " forumSel" : ""; | |
| 684 | - if( p->nIndent==1 ){ | |
| 685 | - @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'> | |
| 686 | - }else{ | |
| 687 | - @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \ | |
| 688 | - @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'> | |
| 689 | - } | |
| 690 | - if( pPost==0 ) continue; | |
| 691 | - if( pPost->zThreadTitle ){ | |
| 692 | - @ <h1>%h(pPost->zThreadTitle)</h1> | |
| 693 | - } | |
| 694 | - zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate); | |
| 695 | - zDisplayName = display_name_from_login(pOPost->zUser); | |
| 696 | - @ <h3 class='forumPostHdr'>\ | |
| 697 | - @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate) | |
| 698 | - fossil_free(zDisplayName); | |
| 699 | - fossil_free(zDate); | |
| 700 | - if( g.perm.Debug ){ | |
| 701 | - @ <span class="debug">\ | |
| 702 | - @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> | |
| 703 | - } | |
| 704 | - if( p->pEditTail ){ | |
| 705 | - zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); | |
| 706 | - if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){ | |
| 707 | - @ and edited on %h(zDate) | |
| 708 | - }else{ | |
| 709 | - @ as edited by %h(pPost->zUser) on %h(zDate) | |
| 710 | - } | |
| 711 | - fossil_free(zDate); | |
| 712 | - if( g.perm.Debug ){ | |
| 713 | - @ <span class="debug">\ | |
| 714 | - @ <a href="%R/artifact/%h(p->pEditTail->zUuid)">\ | |
| 715 | - @ (artifact-%d(p->pEditTail->fpid))</a></span> | |
| 716 | - } | |
| 717 | - @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a> | |
| 718 | - manifest_destroy(pOPost); | |
| 719 | - } | |
| 720 | - if( fpid!=target ){ | |
| 721 | - @ %z(href("%R/forumpost/%S",zUuid))[link]</a> | |
| 722 | - } | |
| 723 | - @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> | |
| 724 | - if( p->pIrt ){ | |
| 725 | - @ in reply to %z(href("%R/forumpost/%S?t=h",p->pIrt->zUuid))\ | |
| 726 | - @ %d(p->pIrt->sid)</a> | |
| 727 | - } | |
| 728 | - @ </h3> | |
| 729 | - isPrivate = content_is_private(fpid); | |
| 730 | - sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 731 | - if( isPrivate && !g.perm.ModForum && !sameUser ){ | |
| 732 | - @ <p><span class="modpending">Awaiting Moderator Approval</span></p> | |
| 733 | - }else{ | |
| 734 | - forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1); | |
| 735 | - } | |
| 736 | - if( g.perm.WrForum ){ | |
| 737 | - @ <div><form action="%R/forumedit" method="POST"> | |
| 738 | - @ <input type="hidden" name="fpid" value="%s(zUuid)"> | |
| 739 | - if( !isPrivate ){ | |
| 740 | - /* Reply and Edit are only available if the post has already | |
| 741 | - ** been approved */ | |
| 742 | - @ <input type="submit" name="reply" value="Reply"> | |
| 743 | - if( g.perm.Admin || sameUser ){ | |
| 744 | - @ <input type="submit" name="edit" value="Edit"> | |
| 745 | - @ <input type="submit" name="nullout" value="Delete"> | |
| 746 | - } | |
| 747 | - }else if( g.perm.ModForum ){ | |
| 748 | - /* Provide moderators with moderation buttons for posts that | |
| 749 | - ** are pending moderation */ | |
| 750 | - @ <input type="submit" name="approve" value="Approve"> | |
| 751 | - @ <input type="submit" name="reject" value="Reject"> | |
| 752 | - generateTrustControls(pPost); | |
| 753 | - }else if( sameUser ){ | |
| 754 | - /* A post that is pending moderation can be deleted by the | |
| 755 | - ** person who originally submitted the post */ | |
| 756 | - @ <input type="submit" name="reject" value="Delete"> | |
| 757 | - } | |
| 758 | - @ </form></div> | |
| 759 | - } | |
| 760 | - manifest_destroy(pPost); | |
| 761 | - @ </div> | |
| 762 | - } | |
| 763 | - forumthread_delete(pThread); | |
| 764 | - return target; | |
| 765 | -} | |
| 766 | - | |
| 767 | -/* | |
| 768 | -** Emits all JS code required by /forumpost. | |
| 769 | -*/ | |
| 770 | -static void forumpost_emit_page_js(){ | |
| 771 | - static int once = 0; | |
| 772 | - if(0==once){ | |
| 773 | - once = 1; | |
| 774 | - style_emit_script_fossil_bootstrap(1); | |
| 775 | - builtin_request_js("forum.js"); | |
| 776 | - builtin_request_js("fossil.dom.js"); | |
| 777 | - builtin_request_js("fossil.page.forumpost.js"); | |
| 778 | - } | |
| 471 | + | |
| 472 | + /* If this post was later edited, refer forward to the new version. */ | |
| 473 | + if( p->pEditTail ){ | |
| 474 | + @ updated by %z(href("%R/forumpost/%S?%s",p->pEditTail->zUuid,zQuery))\ | |
| 475 | + @ %d(p->pEditTail->sid)\ | |
| 476 | + @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a> | |
| 477 | + } | |
| 478 | + | |
| 479 | + /* Provide a link to select the individual post. */ | |
| 480 | + if( !bSelect ){ | |
| 481 | + @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a> | |
| 482 | + } | |
| 483 | + | |
| 484 | + /* Provide a link to the raw source code. */ | |
| 485 | + if( !bUnf ){ | |
| 486 | + @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a> | |
| 487 | + } | |
| 488 | + @ </h3> | |
| 489 | + } | |
| 490 | + | |
| 491 | + /* Check if this post is approved, also if it's by the current user. */ | |
| 492 | + bPrivate = content_is_private(p->fpid); | |
| 493 | + bSameUser = login_is_individual() | |
| 494 | + && fossil_strcmp(pManifest->zUser, g.zLogin)==0; | |
| 495 | + | |
| 496 | + /* Render the post if the user is able to see it. */ | |
| 497 | + if( bPrivate && !g.perm.ModForum && !bSameUser ){ | |
| 498 | + @ <p><span class="modpending">Awaiting Moderator Approval</span></p> | |
| 499 | + }else{ | |
| 500 | + if( bRaw || bUnf || p->pEditTail ){ | |
| 501 | + zMimetype = "text/plain"; | |
| 502 | + }else{ | |
| 503 | + zMimetype = pManifest->zMimetype; | |
| 504 | + } | |
| 505 | + forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); | |
| 506 | + } | |
| 507 | + | |
| 508 | + /* When not in raw mode, finish creating the border around the post. */ | |
| 509 | + if( !bRaw ){ | |
| 510 | + /* If the user is able to write to the forum and if this post has not been | |
| 511 | + ** edited, create a form with various interaction buttons. */ | |
| 512 | + if( g.perm.WrForum && !p->pEditTail ){ | |
| 513 | + @ <div><form action="%R/forumedit" method="POST"> | |
| 514 | + @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> | |
| 515 | + if( !bPrivate ){ | |
| 516 | + /* Reply and Edit are only available if the post has been approved. */ | |
| 517 | + @ <input type="submit" name="reply" value="Reply"> | |
| 518 | + if( g.perm.Admin || bSameUser ){ | |
| 519 | + @ <input type="submit" name="edit" value="Edit"> | |
| 520 | + @ <input type="submit" name="nullout" value="Delete"> | |
| 521 | + } | |
| 522 | + }else if( g.perm.ModForum ){ | |
| 523 | + /* Allow moderators to approve or reject pending posts. Also allow | |
| 524 | + ** forum supervisors to mark non-special users as trusted and therefore | |
| 525 | + ** able to post unmoderated. */ | |
| 526 | + @ <input type="submit" name="approve" value="Approve"> | |
| 527 | + @ <input type="submit" name="reject" value="Reject"> | |
| 528 | + if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){ | |
| 529 | + @ <br><label><input type="checkbox" name="trust"> | |
| 530 | + @ Trust user "%h(pManifest->zUser)" so that future posts by \ | |
| 531 | + @ "%h(pManifest->zUser)" do not require moderation. | |
| 532 | + @ </label> | |
| 533 | + @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)"> | |
| 534 | + } | |
| 535 | + }else if( bSameUser ){ | |
| 536 | + /* Allow users to delete (reject) their own pending posts. */ | |
| 537 | + @ <input type="submit" name="reject" value="Delete"> | |
| 538 | + } | |
| 539 | + @ </form></div> | |
| 540 | + } | |
| 541 | + @ </div> | |
| 542 | + } | |
| 543 | + | |
| 544 | + /* Clean up. */ | |
| 545 | + manifest_destroy(pManifest); | |
| 546 | +} | |
| 547 | + | |
| 548 | +/* | |
| 549 | +** Possible display modes for forum_display_thread(). | |
| 550 | +*/ | |
| 551 | +enum { | |
| 552 | + FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force | |
| 553 | + ** unformatted mode, and inhibit history mode */ | |
| 554 | + FD_SINGLE, /* Render a single post and (optionally) its edit history */ | |
| 555 | + FD_CHRONO, /* Render all posts in chronological order */ | |
| 556 | + FD_HIER, /* Render all posts in an indented hierarchy */ | |
| 557 | +}; | |
| 558 | + | |
| 559 | +/* | |
| 560 | +** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a | |
| 561 | +** single post from the thread and (optionally) its edit history. | |
| 562 | +*/ | |
| 563 | +static void forum_display_thread( | |
| 564 | + int froot, /* Forum thread root post ID */ | |
| 565 | + int fpid, /* Selected forum post ID, or 0 if none selected */ | |
| 566 | + int mode, /* Forum display mode, one of the FD_* enumerations */ | |
| 567 | + int bUnf, /* True if rendering unformatted */ | |
| 568 | + int bHist /* True if showing edit history, ignored for FD_RAW */ | |
| 569 | +){ | |
| 570 | + ForumThread *pThread; /* Thread structure */ | |
| 571 | + ForumPost *pSelect; /* Currently selected post, or NULL if none */ | |
| 572 | + ForumPost *p; /* Post iterator pointer */ | |
| 573 | + char *zQuery; /* Common query string */ | |
| 574 | + int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */ | |
| 575 | + | |
| 576 | + /* In raw mode, force unformatted display and disable history. */ | |
| 577 | + if( mode == FD_RAW ){ | |
| 578 | + bUnf = 1; | |
| 579 | + bHist = 0; | |
| 580 | + } | |
| 581 | + | |
| 582 | + /* Thread together the posts and (optionally) compute the hierarchy. */ | |
| 583 | + pThread = forumthread_create(froot, mode==FD_HIER); | |
| 584 | + | |
| 585 | + /* Compute the appropriate indent scaling. */ | |
| 586 | + if( mode==FD_HIER ){ | |
| 587 | + iIndentScale = 4; | |
| 588 | + while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){ | |
| 589 | + iIndentScale--; | |
| 590 | + } | |
| 591 | + }else{ | |
| 592 | + iIndentScale = 0; | |
| 593 | + } | |
| 594 | + | |
| 595 | + /* Find the selected post, or (if history not shown) its latest edit. */ | |
| 596 | + pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0; | |
| 597 | + if( !bHist && pSelect && pSelect->pEditTail ){ | |
| 598 | + pSelect = pSelect->pEditTail; | |
| 599 | + } | |
| 600 | + | |
| 601 | + /* When displaying only a single post, abort if no post was selected or the | |
| 602 | + ** selected forum post does not exist in the thread. Otherwise proceed to | |
| 603 | + ** display the entire thread without marking any posts as selected. */ | |
| 604 | + if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){ | |
| 605 | + return; | |
| 606 | + } | |
| 607 | + | |
| 608 | + /* Create the common query string to append to nearly all post links. */ | |
| 609 | + zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s", | |
| 610 | + mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h', | |
| 611 | + bUnf ? "&unf" : "", bHist ? "&hist" : ""); | |
| 612 | + | |
| 613 | + /* Identify which post to display first. If history is shown, start with the | |
| 614 | + ** original, unedited post. Otherwise advance to the post's latest edit. */ | |
| 615 | + if( mode==FD_RAW || mode==FD_SINGLE ){ | |
| 616 | + p = pSelect; | |
| 617 | + if( bHist && p->pEditHead ) p = p->pEditHead; | |
| 618 | + }else{ | |
| 619 | + p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay; | |
| 620 | + if( !bHist && p->pEditTail ) p = p->pEditTail; | |
| 621 | + } | |
| 622 | + | |
| 623 | + /* Display the appropriate subset of posts in sequence. */ | |
| 624 | + while( p ){ | |
| 625 | + /* Display the post. */ | |
| 626 | + forum_display_post(p, iIndentScale, mode==FD_RAW, | |
| 627 | + bUnf, bHist, p==pSelect, zQuery); | |
| 628 | + | |
| 629 | + /* Advance to the next post in the thread. */ | |
| 630 | + if( mode==FD_CHRONO ){ | |
| 631 | + /* Chronological mode: display posts (optionally including edits) in their | |
| 632 | + ** original commit order. */ | |
| 633 | + if( bHist ){ | |
| 634 | + p = p->pNext; | |
| 635 | + }else{ | |
| 636 | + if( p->pEditHead ) p = p->pEditHead; | |
| 637 | + do p = p->pNext; while( p && p->sid<=p->pPrev->sid ); | |
| 638 | + if( p && p->pEditTail ) p = p->pEditTail; | |
| 639 | + } | |
| 640 | + }else if( bHist && p->pEditNext ){ | |
| 641 | + /* Hierarchical and single mode: display each post's edits in sequence. */ | |
| 642 | + p = p->pEditNext; | |
| 643 | + }else if( mode==FD_HIER ){ | |
| 644 | + /* Hierarchical mode: after displaying with each post (optionally | |
| 645 | + ** including edits), go to the next post in computed display order. */ | |
| 646 | + p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay; | |
| 647 | + if( !bHist && p && p->pEditTail ) p = p->pEditTail; | |
| 648 | + }else{ | |
| 649 | + /* Single and raw mode: terminate after displaying the selected post and | |
| 650 | + ** (optionally) its edits. */ | |
| 651 | + break; | |
| 652 | + } | |
| 653 | + } | |
| 654 | + | |
| 655 | + /* Undocumented "threadtable" query parameter causes thread table to be | |
| 656 | + ** displayed for debugging purposes. */ | |
| 657 | + if( PB("threadtable") ){ | |
| 658 | + @ <hr> | |
| 659 | + @ <table border="1" cellpadding="3" cellspacing="0"> | |
| 660 | + @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\ | |
| 661 | + @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash | |
| 662 | + for(p=pThread->pFirst; p; p=p->pNext){ | |
| 663 | + @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\ | |
| 664 | + @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\ | |
| 665 | + @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\ | |
| 666 | + @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\ | |
| 667 | + @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\ | |
| 668 | + @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\ | |
| 669 | + @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\ | |
| 670 | + @ <td>%S(p->zUuid)</tr> | |
| 671 | + } | |
| 672 | + @ </table> | |
| 673 | + } | |
| 674 | + | |
| 675 | + /* Clean up. */ | |
| 676 | + forumthread_delete(pThread); | |
| 677 | + fossil_free(zQuery); | |
| 779 | 678 | } |
| 780 | 679 | |
| 781 | 680 | /* |
| 782 | 681 | ** WEBPAGE: forumpost |
| 783 | 682 | ** |
| 784 | 683 | ** Show a single forum posting. The posting is shown in context with |
| 785 | -** it's entire thread. The selected posting is enclosed within | |
| 684 | +** its entire thread. The selected posting is enclosed within | |
| 786 | 685 | ** <div class='forumSel'>...</div>. Javascript is used to move the |
| 787 | 686 | ** selected posting into view after the page loads. |
| 788 | 687 | ** |
| 789 | 688 | ** Query parameters: |
| 790 | 689 | ** |
| 791 | -** name=X REQUIRED. The hash of the post to display | |
| 792 | -** t=MODE Display mode. | |
| 793 | -** 'c' for chronological | |
| 794 | -** 'h' for hierarchical | |
| 795 | -** 'a' for automatic | |
| 796 | -** 'r' for raw | |
| 797 | -** 'y' for history of post X only | |
| 798 | -** raw If present, show only the post specified and | |
| 799 | -** show its original unformatted source text. | |
| 690 | +** name=X REQUIRED. The hash of the post to display. | |
| 691 | +** t=a Automatic display mode, i.e. hierarchical for | |
| 692 | +** desktop and chronological for mobile. This is the | |
| 693 | +** default if the "t" query parameter is omitted. | |
| 694 | +** t=c Show posts in the order they were written. | |
| 695 | +** t=h Show posts usin hierarchical indenting. | |
| 696 | +** t=s Show only the post specified by "name=X". | |
| 697 | +** t=r Alias for "t=c&unf&hist". | |
| 698 | +** t=y Alias for "t=s&unf&hist". | |
| 699 | +** raw Alias for "t=s&unf". Additionally, omit the border | |
| 700 | +** around the post, and ignore "t" and "hist". | |
| 701 | +** unf Show the original, unformatted source text. | |
| 702 | +** hist Show edit history in addition to current posts. | |
| 800 | 703 | */ |
| 801 | 704 | void forumpost_page(void){ |
| 802 | 705 | forumthread_page(); |
| 803 | 706 | } |
| 804 | 707 | |
| 805 | -/* | |
| 806 | -** Add an appropriate style_header() to include title of the | |
| 807 | -** given forum post. | |
| 808 | -*/ | |
| 809 | -static int forumthread_page_header(int froot, int fpid){ | |
| 810 | - char *zThreadTitle = 0; | |
| 811 | - | |
| 812 | - zThreadTitle = db_text("", | |
| 813 | - "SELECT" | |
| 814 | - " substr(event.comment,instr(event.comment,':')+2)" | |
| 815 | - " FROM forumpost, event" | |
| 816 | - " WHERE event.objid=forumpost.fpid" | |
| 817 | - " AND forumpost.fpid=%d;", | |
| 818 | - fpid | |
| 819 | - ); | |
| 820 | - style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum"); | |
| 821 | - fossil_free(zThreadTitle); | |
| 822 | - return 0; | |
| 823 | -} | |
| 824 | - | |
| 825 | 708 | /* |
| 826 | 709 | ** WEBPAGE: forumthread |
| 827 | 710 | ** |
| 828 | 711 | ** Show all forum messages associated with a particular message thread. |
| 829 | 712 | ** The result is basically the same as /forumpost except that none of |
| @@ -830,24 +713,28 @@ | ||
| 830 | 713 | ** the postings in the thread are selected. |
| 831 | 714 | ** |
| 832 | 715 | ** Query parameters: |
| 833 | 716 | ** |
| 834 | 717 | ** name=X REQUIRED. The hash of any post of the thread. |
| 835 | -** t=MODE Display mode. MODE is... | |
| 836 | -** 'c' for chronological, or | |
| 837 | -** 'h' for hierarchical, or | |
| 838 | -** 'a' for automatic, or | |
| 839 | -** 'r' for raw. | |
| 840 | -** raw Show only the post given by name= and show it unformatted | |
| 841 | -** hist Show only the edit history for the name= post | |
| 718 | +** t=a Automatic display mode, i.e. hierarchical for | |
| 719 | +** desktop and chronological for mobile. This is the | |
| 720 | +** default if the "t" query parameter is omitted. | |
| 721 | +** t=c Show posts in the order they were written. | |
| 722 | +** t=h Show posts using hierarchical indenting. | |
| 723 | +** unf Show the original, unformatted source text. | |
| 724 | +** hist Show edit history in addition to current posts. | |
| 842 | 725 | */ |
| 843 | 726 | void forumthread_page(void){ |
| 844 | 727 | int fpid; |
| 845 | 728 | int froot; |
| 729 | + char *zThreadTitle; | |
| 846 | 730 | const char *zName = P("name"); |
| 847 | 731 | const char *zMode = PD("t","a"); |
| 848 | 732 | int bRaw = PB("raw"); |
| 733 | + int bUnf = PB("unf"); | |
| 734 | + int bHist = PB("hist"); | |
| 735 | + int mode; | |
| 849 | 736 | login_check_credentials(); |
| 850 | 737 | if( !g.perm.RdForum ){ |
| 851 | 738 | login_needed(g.anon.RdForum); |
| 852 | 739 | return; |
| 853 | 740 | } |
| @@ -861,54 +748,70 @@ | ||
| 861 | 748 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 862 | 749 | if( froot==0 ){ |
| 863 | 750 | webpage_error("Not a forum post: \"%s\"", zName); |
| 864 | 751 | } |
| 865 | 752 | if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0; |
| 866 | - if( zMode[0]=='a' ){ | |
| 867 | - if( cgi_from_mobile() ){ | |
| 868 | - zMode = "c"; /* Default to chronological on mobile */ | |
| 869 | - }else{ | |
| 870 | - zMode = "h"; | |
| 871 | - } | |
| 872 | - } | |
| 873 | - if( zMode[0]!='y' ){ | |
| 874 | - forumthread_page_header(froot, fpid); | |
| 875 | - } | |
| 876 | - if( bRaw && fpid ){ | |
| 877 | - Manifest *pPost; | |
| 878 | - pPost = manifest_get(fpid, CFTYPE_FORUM, 0); | |
| 879 | - if( pPost==0 ){ | |
| 880 | - @ <p>No such forum post: %h(zName) | |
| 881 | - }else{ | |
| 882 | - int isPrivate = content_is_private(fpid); | |
| 883 | - int notAnon = login_is_individual(); | |
| 884 | - int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; | |
| 885 | - if( isPrivate && !g.perm.ModForum && !sameUser ){ | |
| 886 | - @ <p><span class="modpending">Awaiting Moderator Approval</span></p> | |
| 887 | - }else{ | |
| 888 | - forum_render(0, "text/plain", pPost->zWiki, 0, 0); | |
| 889 | - } | |
| 890 | - manifest_destroy(pPost); | |
| 891 | - } | |
| 892 | - }else if( zMode[0]=='c' ){ | |
| 893 | - style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); | |
| 894 | - style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); | |
| 895 | - forum_display_chronological(froot, fpid, 0); | |
| 896 | - }else if( zMode[0]=='r' ){ | |
| 897 | - style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); | |
| 898 | - style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); | |
| 899 | - forum_display_chronological(froot, fpid, 1); | |
| 900 | - }else if( zMode[0]=='y' ){ | |
| 901 | - style_header("Edit History Of A Forum Post"); | |
| 902 | - style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName); | |
| 903 | - forum_display_history(froot, fpid, 1); | |
| 904 | - }else{ | |
| 905 | - style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); | |
| 906 | - style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); | |
| 907 | - forum_display_hierarchical(froot, fpid); | |
| 908 | - } | |
| 909 | - forumpost_emit_page_js(); | |
| 753 | + | |
| 754 | + /* Decode the mode parameters. */ | |
| 755 | + if( bRaw ){ | |
| 756 | + mode = FD_RAW; | |
| 757 | + bUnf = 1; | |
| 758 | + bHist = 0; | |
| 759 | + cgi_replace_query_parameter("unf", "on"); | |
| 760 | + cgi_delete_query_parameter("hist"); | |
| 761 | + cgi_delete_query_parameter("raw"); | |
| 762 | + }else{ | |
| 763 | + switch( *zMode ){ | |
| 764 | + case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break; | |
| 765 | + case 'c': mode = FD_CHRONO; break; | |
| 766 | + case 'h': mode = FD_HIER; break; | |
| 767 | + case 's': mode = FD_SINGLE; break; | |
| 768 | + case 'r': mode = FD_CHRONO; break; | |
| 769 | + case 'y': mode = FD_SINGLE; break; | |
| 770 | + default: webpage_error("Invalid thread mode: \"%s\"", zMode); | |
| 771 | + } | |
| 772 | + if( *zMode=='r' || *zMode=='y') { | |
| 773 | + bUnf = 1; | |
| 774 | + bHist = 1; | |
| 775 | + cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s"); | |
| 776 | + cgi_replace_query_parameter("unf", "on"); | |
| 777 | + cgi_replace_query_parameter("hist", "on"); | |
| 778 | + } | |
| 779 | + } | |
| 780 | + | |
| 781 | + /* Define the page header. */ | |
| 782 | + zThreadTitle = db_text("", | |
| 783 | + "SELECT" | |
| 784 | + " substr(event.comment,instr(event.comment,':')+2)" | |
| 785 | + " FROM forumpost, event" | |
| 786 | + " WHERE event.objid=forumpost.fpid" | |
| 787 | + " AND forumpost.fpid=%d;", | |
| 788 | + fpid | |
| 789 | + ); | |
| 790 | + style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum"); | |
| 791 | + fossil_free(zThreadTitle); | |
| 792 | + if( mode!=FD_CHRONO ){ | |
| 793 | + style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName, | |
| 794 | + bUnf ? "&unf" : "", bHist ? "&hist" : ""); | |
| 795 | + } | |
| 796 | + if( mode!=FD_HIER ){ | |
| 797 | + style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName, | |
| 798 | + bUnf ? "&unf" : "", bHist ? "&hist" : ""); | |
| 799 | + } | |
| 800 | + style_submenu_checkbox("unf", "Unformatted", 0, 0); | |
| 801 | + style_submenu_checkbox("hist", "History", 0, 0); | |
| 802 | + | |
| 803 | + /* Display the thread. */ | |
| 804 | + forum_display_thread(froot, fpid, mode, bUnf, bHist); | |
| 805 | + | |
| 806 | + /* Emit Forum Javascript. */ | |
| 807 | + style_emit_script_fossil_bootstrap(1); | |
| 808 | + builtin_request_js("forum.js"); | |
| 809 | + builtin_request_js("fossil.dom.js"); | |
| 810 | + builtin_request_js("fossil.page.forumpost.js"); | |
| 811 | + | |
| 812 | + /* Emit the page style. */ | |
| 910 | 813 | style_footer(); |
| 911 | 814 | } |
| 912 | 815 | |
| 913 | 816 | /* |
| 914 | 817 | ** Return true if a forum post should be moderated. |
| 915 | 818 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -89,19 +89,17 @@ | |
| 89 | fossil_free(pPost); |
| 90 | } |
| 91 | fossil_free(pThread); |
| 92 | } |
| 93 | |
| 94 | #if 0 /* not used */ |
| 95 | /* |
| 96 | ** Search a ForumPost list forwards looking for the post with fpid |
| 97 | */ |
| 98 | static ForumPost *forumpost_forward(ForumPost *p, int fpid){ |
| 99 | while( p && p->fpid!=fpid ) p = p->pNext; |
| 100 | return p; |
| 101 | } |
| 102 | #endif |
| 103 | |
| 104 | /* |
| 105 | ** Search backwards for a ForumPost |
| 106 | */ |
| 107 | static ForumPost *forumpost_backward(ForumPost *p, int fpid){ |
| @@ -358,28 +356,10 @@ | |
| 358 | if( zClass ){ |
| 359 | @ </div> |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | /* |
| 364 | ** Generate the buttons in the display that allow a forum supervisor to |
| 365 | ** mark a user as trusted. Only do this if: |
| 366 | ** |
| 367 | ** (1) The poster is an individual, not a special user like "anonymous" |
| 368 | ** (2) The current user has Forum Supervisor privilege |
| 369 | */ |
| 370 | static void generateTrustControls(Manifest *pPost){ |
| 371 | if( !g.perm.AdminForum ) return; |
| 372 | if( login_is_special(pPost->zUser) ) return; |
| 373 | @ <br> |
| 374 | @ <label><input type="checkbox" name="trust"> |
| 375 | @ Trust user "%h(pPost->zUser)" |
| 376 | @ so that future posts by "%h(pPost->zUser)" do not require moderation. |
| 377 | @ </label> |
| 378 | @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)"> |
| 379 | } |
| 380 | |
| 381 | /* |
| 382 | ** Compute a display name from a login name. |
| 383 | ** |
| 384 | ** If the input login is found in the USER table, then check the USER.INFO |
| 385 | ** field to see if it has display-name followed by an email address. |
| @@ -409,421 +389,324 @@ | |
| 409 | db_reset(&q); |
| 410 | return zResult; |
| 411 | } |
| 412 | |
| 413 | /* |
| 414 | ** Display all posts in a forum thread in chronological order |
| 415 | */ |
| 416 | static void forum_display_chronological(int froot, int target, int bRawMode){ |
| 417 | ForumThread *pThread = forumthread_create(froot, 0); |
| 418 | ForumPost *p; |
| 419 | int notAnon = login_is_individual(); |
| 420 | char cMode = bRawMode ? 'r' : 'c'; |
| 421 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 422 | char *zDate; |
| 423 | Manifest *pPost; |
| 424 | int isPrivate; /* True for posts awaiting moderation */ |
| 425 | int sameUser; /* True if author is also the reader */ |
| 426 | const char *zUuid; |
| 427 | char *zDisplayName; /* The display name */ |
| 428 | |
| 429 | pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 430 | if( pPost==0 ) continue; |
| 431 | if( p->fpid==target ){ |
| 432 | @ <div id="forum%d(p->fpid)" class="forumTime forumSel"> |
| 433 | }else if( p->pEditTail!=0 ){ |
| 434 | @ <div id="forum%d(p->fpid)" class="forumTime forumObs"> |
| 435 | }else{ |
| 436 | @ <div id="forum%d(p->fpid)" class="forumTime"> |
| 437 | } |
| 438 | if( pPost->zThreadTitle ){ |
| 439 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 440 | } |
| 441 | zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 442 | zDisplayName = display_name_from_login(pPost->zUser); |
| 443 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 444 | if( p->nEdit ){ |
| 445 | @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ |
| 446 | } |
| 447 | @ ) By %h(zDisplayName) on %h(zDate) |
| 448 | fossil_free(zDisplayName); |
| 449 | fossil_free(zDate); |
| 450 | if( p->pEditPrev ){ |
| 451 | @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEditPrev->zUuid,cMode))\ |
| 452 | @ %d(p->pEditPrev->sid)\ |
| 453 | @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 454 | } |
| 455 | if( g.perm.Debug ){ |
| 456 | @ <span class="debug">\ |
| 457 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 458 | } |
| 459 | if( p->pIrt ){ |
| 460 | @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\ |
| 461 | @ %d(p->pIrt->sid)\ |
| 462 | if( p->pIrt->nEdit ){ |
| 463 | @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 464 | } |
| 465 | @ </a> |
| 466 | } |
| 467 | zUuid = p->zUuid; |
| 468 | if( p->pEditTail ){ |
| 469 | @ updated by %z(href("%R/forumpost/%S?t=%c",p->pEditTail->zUuid,cMode))\ |
| 470 | @ %d(p->pEditTail->sid)\ |
| 471 | @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a> |
| 472 | zUuid = p->pEditTail->zUuid; |
| 473 | } |
| 474 | if( p->fpid!=target ){ |
| 475 | @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a> |
| 476 | } |
| 477 | if( !bRawMode ){ |
| 478 | @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> |
| 479 | } |
| 480 | isPrivate = content_is_private(p->fpid); |
| 481 | sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 482 | @ </h3> |
| 483 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 484 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 485 | }else{ |
| 486 | const char *zMimetype; |
| 487 | if( bRawMode ){ |
| 488 | zMimetype = "text/plain"; |
| 489 | }else if( p->pEditTail!=0 ){ |
| 490 | zMimetype = "text/plain"; |
| 491 | }else{ |
| 492 | zMimetype = pPost->zMimetype; |
| 493 | } |
| 494 | forum_render(0, zMimetype, pPost->zWiki, 0, 1); |
| 495 | } |
| 496 | if( g.perm.WrForum && p->pEditTail==0 ){ |
| 497 | int sameUser = login_is_individual() |
| 498 | && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 499 | @ <div><form action="%R/forumedit" method="POST"> |
| 500 | @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> |
| 501 | if( !isPrivate ){ |
| 502 | /* Reply and Edit are only available if the post has already |
| 503 | ** been approved */ |
| 504 | @ <input type="submit" name="reply" value="Reply"> |
| 505 | if( g.perm.Admin || sameUser ){ |
| 506 | @ <input type="submit" name="edit" value="Edit"> |
| 507 | @ <input type="submit" name="nullout" value="Delete"> |
| 508 | } |
| 509 | }else if( g.perm.ModForum ){ |
| 510 | /* Provide moderators with moderation buttons for posts that |
| 511 | ** are pending moderation */ |
| 512 | @ <input type="submit" name="approve" value="Approve"> |
| 513 | @ <input type="submit" name="reject" value="Reject"> |
| 514 | generateTrustControls(pPost); |
| 515 | }else if( sameUser ){ |
| 516 | /* A post that is pending moderation can be deleted by the |
| 517 | ** person who originally submitted the post */ |
| 518 | @ <input type="submit" name="reject" value="Delete"> |
| 519 | } |
| 520 | @ </form></div> |
| 521 | } |
| 522 | manifest_destroy(pPost); |
| 523 | @ </div> |
| 524 | } |
| 525 | |
| 526 | /* Undocumented "threadtable" query parameter causes thread table |
| 527 | ** to be displayed for debugging purposes. |
| 528 | */ |
| 529 | if( PB("threadtable") ){ |
| 530 | @ <hr> |
| 531 | @ <table border="1" cellpadding="3" cellspacing="0"> |
| 532 | @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\ |
| 533 | @ <th>pEditNext<th>pEditPrev<th>hash |
| 534 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 535 | @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\ |
| 536 | @ <td>%d(p->pIrt?p->pIrt->fpid:0)\ |
| 537 | @ <td>%d(p->pEditHead?p->pEditHead->fpid:0)\ |
| 538 | @ <td>%d(p->pEditTail?p->pEditTail->fpid:0)\ |
| 539 | @ <td>%d(p->pEditNext?p->pEditNext->fpid:0)\ |
| 540 | @ <td>%d(p->pEditPrev?p->pEditPrev->fpid:0)\ |
| 541 | @ <td>%S(p->zUuid)</tr> |
| 542 | } |
| 543 | @ </table> |
| 544 | } |
| 545 | |
| 546 | forumthread_delete(pThread); |
| 547 | } |
| 548 | /* |
| 549 | ** Display all the edit history of post "target". |
| 550 | */ |
| 551 | static void forum_display_history(int froot, int target, int bRawMode){ |
| 552 | ForumThread *pThread = forumthread_create(froot, 0); |
| 553 | ForumPost *p; |
| 554 | int notAnon = login_is_individual(); |
| 555 | char cMode = bRawMode ? 'r' : 'c'; |
| 556 | ForumPost *pEditTail = 0; |
| 557 | int cnt = 0; |
| 558 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 559 | if( p->fpid==target ){ |
| 560 | pEditTail = p->pEditTail ? p->pEditTail : p; |
| 561 | break; |
| 562 | } |
| 563 | } |
| 564 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 565 | char *zDate; |
| 566 | Manifest *pPost; |
| 567 | int isPrivate; /* True for posts awaiting moderation */ |
| 568 | int sameUser; /* True if author is also the reader */ |
| 569 | const char *zUuid; |
| 570 | char *zDisplayName; /* The display name */ |
| 571 | |
| 572 | if( p->fpid!=pEditTail->fpid && p->pEditTail!=pEditTail ) continue; |
| 573 | cnt++; |
| 574 | pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 575 | if( pPost==0 ) continue; |
| 576 | @ <div id="forum%d(p->fpid)" class="forumTime"> |
| 577 | zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 578 | zDisplayName = display_name_from_login(pPost->zUser); |
| 579 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 580 | if( p->nEdit ){ |
| 581 | @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ |
| 582 | } |
| 583 | @ ) By %h(zDisplayName) on %h(zDate) |
| 584 | fossil_free(zDisplayName); |
| 585 | fossil_free(zDate); |
| 586 | if( g.perm.Debug ){ |
| 587 | @ <span class="debug">\ |
| 588 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 589 | } |
| 590 | if( p->pIrt && cnt==1 ){ |
| 591 | @ in reply to %z(href("%R/forumpost/%S?t=%c",p->pIrt->zUuid,cMode))\ |
| 592 | @ %d(p->pIrt->sid)\ |
| 593 | if( p->pIrt->nEdit ){ |
| 594 | @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 595 | } |
| 596 | @ </a> |
| 597 | } |
| 598 | zUuid = p->zUuid; |
| 599 | @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a> |
| 600 | if( !bRawMode ){ |
| 601 | @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> |
| 602 | } |
| 603 | isPrivate = content_is_private(p->fpid); |
| 604 | sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 605 | @ </h3> |
| 606 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 607 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 608 | }else{ |
| 609 | forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, |
| 610 | 0, 1); |
| 611 | } |
| 612 | if( g.perm.WrForum && p->pEditTail==0 ){ |
| 613 | int sameUser = login_is_individual() |
| 614 | && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 615 | @ <div><form action="%R/forumedit" method="POST"> |
| 616 | @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> |
| 617 | if( !isPrivate ){ |
| 618 | /* Reply and Edit are only available if the post has already |
| 619 | ** been approved */ |
| 620 | @ <input type="submit" name="reply" value="Reply"> |
| 621 | if( g.perm.Admin || sameUser ){ |
| 622 | @ <input type="submit" name="edit" value="Edit"> |
| 623 | @ <input type="submit" name="nullout" value="Delete"> |
| 624 | } |
| 625 | }else if( g.perm.ModForum ){ |
| 626 | /* Provide moderators with moderation buttons for posts that |
| 627 | ** are pending moderation */ |
| 628 | @ <input type="submit" name="approve" value="Approve"> |
| 629 | @ <input type="submit" name="reject" value="Reject"> |
| 630 | generateTrustControls(pPost); |
| 631 | }else if( sameUser ){ |
| 632 | /* A post that is pending moderation can be deleted by the |
| 633 | ** person who originally submitted the post */ |
| 634 | @ <input type="submit" name="reject" value="Delete"> |
| 635 | } |
| 636 | @ </form></div> |
| 637 | } |
| 638 | manifest_destroy(pPost); |
| 639 | @ </div> |
| 640 | } |
| 641 | forumthread_delete(pThread); |
| 642 | } |
| 643 | |
| 644 | /* |
| 645 | ** Display all messages in a forumthread with indentation. |
| 646 | */ |
| 647 | static int forum_display_hierarchical(int froot, int target){ |
| 648 | ForumThread *pThread; |
| 649 | ForumPost *p; |
| 650 | Manifest *pPost, *pOPost; |
| 651 | int fpid; |
| 652 | const char *zUuid; |
| 653 | char *zDate; |
| 654 | const char *zSel; |
| 655 | int notAnon = login_is_individual(); |
| 656 | int iIndentScale = 4; |
| 657 | |
| 658 | pThread = forumthread_create(froot, 1); |
| 659 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 660 | if( p->fpid==target ){ |
| 661 | if( p->pEditHead ) p = p->pEditHead; |
| 662 | target = p->fpid; |
| 663 | break; |
| 664 | } |
| 665 | } |
| 666 | while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){ |
| 667 | iIndentScale--; |
| 668 | } |
| 669 | for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 670 | int isPrivate; /* True for posts awaiting moderation */ |
| 671 | int sameUser; /* True if reader is also the poster */ |
| 672 | char *zDisplayName; /* User name to be displayed */ |
| 673 | pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 674 | if( p->pEditTail ){ |
| 675 | fpid = p->pEditTail->fpid; |
| 676 | zUuid = p->pEditTail->zUuid; |
| 677 | pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 678 | }else{ |
| 679 | fpid = p->fpid; |
| 680 | zUuid = p->zUuid; |
| 681 | pPost = pOPost; |
| 682 | } |
| 683 | zSel = p->fpid==target ? " forumSel" : ""; |
| 684 | if( p->nIndent==1 ){ |
| 685 | @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'> |
| 686 | }else{ |
| 687 | @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \ |
| 688 | @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'> |
| 689 | } |
| 690 | if( pPost==0 ) continue; |
| 691 | if( pPost->zThreadTitle ){ |
| 692 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 693 | } |
| 694 | zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate); |
| 695 | zDisplayName = display_name_from_login(pOPost->zUser); |
| 696 | @ <h3 class='forumPostHdr'>\ |
| 697 | @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate) |
| 698 | fossil_free(zDisplayName); |
| 699 | fossil_free(zDate); |
| 700 | if( g.perm.Debug ){ |
| 701 | @ <span class="debug">\ |
| 702 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 703 | } |
| 704 | if( p->pEditTail ){ |
| 705 | zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate); |
| 706 | if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){ |
| 707 | @ and edited on %h(zDate) |
| 708 | }else{ |
| 709 | @ as edited by %h(pPost->zUser) on %h(zDate) |
| 710 | } |
| 711 | fossil_free(zDate); |
| 712 | if( g.perm.Debug ){ |
| 713 | @ <span class="debug">\ |
| 714 | @ <a href="%R/artifact/%h(p->pEditTail->zUuid)">\ |
| 715 | @ (artifact-%d(p->pEditTail->fpid))</a></span> |
| 716 | } |
| 717 | @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a> |
| 718 | manifest_destroy(pOPost); |
| 719 | } |
| 720 | if( fpid!=target ){ |
| 721 | @ %z(href("%R/forumpost/%S",zUuid))[link]</a> |
| 722 | } |
| 723 | @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> |
| 724 | if( p->pIrt ){ |
| 725 | @ in reply to %z(href("%R/forumpost/%S?t=h",p->pIrt->zUuid))\ |
| 726 | @ %d(p->pIrt->sid)</a> |
| 727 | } |
| 728 | @ </h3> |
| 729 | isPrivate = content_is_private(fpid); |
| 730 | sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 731 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 732 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 733 | }else{ |
| 734 | forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1); |
| 735 | } |
| 736 | if( g.perm.WrForum ){ |
| 737 | @ <div><form action="%R/forumedit" method="POST"> |
| 738 | @ <input type="hidden" name="fpid" value="%s(zUuid)"> |
| 739 | if( !isPrivate ){ |
| 740 | /* Reply and Edit are only available if the post has already |
| 741 | ** been approved */ |
| 742 | @ <input type="submit" name="reply" value="Reply"> |
| 743 | if( g.perm.Admin || sameUser ){ |
| 744 | @ <input type="submit" name="edit" value="Edit"> |
| 745 | @ <input type="submit" name="nullout" value="Delete"> |
| 746 | } |
| 747 | }else if( g.perm.ModForum ){ |
| 748 | /* Provide moderators with moderation buttons for posts that |
| 749 | ** are pending moderation */ |
| 750 | @ <input type="submit" name="approve" value="Approve"> |
| 751 | @ <input type="submit" name="reject" value="Reject"> |
| 752 | generateTrustControls(pPost); |
| 753 | }else if( sameUser ){ |
| 754 | /* A post that is pending moderation can be deleted by the |
| 755 | ** person who originally submitted the post */ |
| 756 | @ <input type="submit" name="reject" value="Delete"> |
| 757 | } |
| 758 | @ </form></div> |
| 759 | } |
| 760 | manifest_destroy(pPost); |
| 761 | @ </div> |
| 762 | } |
| 763 | forumthread_delete(pThread); |
| 764 | return target; |
| 765 | } |
| 766 | |
| 767 | /* |
| 768 | ** Emits all JS code required by /forumpost. |
| 769 | */ |
| 770 | static void forumpost_emit_page_js(){ |
| 771 | static int once = 0; |
| 772 | if(0==once){ |
| 773 | once = 1; |
| 774 | style_emit_script_fossil_bootstrap(1); |
| 775 | builtin_request_js("forum.js"); |
| 776 | builtin_request_js("fossil.dom.js"); |
| 777 | builtin_request_js("fossil.page.forumpost.js"); |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | /* |
| 782 | ** WEBPAGE: forumpost |
| 783 | ** |
| 784 | ** Show a single forum posting. The posting is shown in context with |
| 785 | ** it's entire thread. The selected posting is enclosed within |
| 786 | ** <div class='forumSel'>...</div>. Javascript is used to move the |
| 787 | ** selected posting into view after the page loads. |
| 788 | ** |
| 789 | ** Query parameters: |
| 790 | ** |
| 791 | ** name=X REQUIRED. The hash of the post to display |
| 792 | ** t=MODE Display mode. |
| 793 | ** 'c' for chronological |
| 794 | ** 'h' for hierarchical |
| 795 | ** 'a' for automatic |
| 796 | ** 'r' for raw |
| 797 | ** 'y' for history of post X only |
| 798 | ** raw If present, show only the post specified and |
| 799 | ** show its original unformatted source text. |
| 800 | */ |
| 801 | void forumpost_page(void){ |
| 802 | forumthread_page(); |
| 803 | } |
| 804 | |
| 805 | /* |
| 806 | ** Add an appropriate style_header() to include title of the |
| 807 | ** given forum post. |
| 808 | */ |
| 809 | static int forumthread_page_header(int froot, int fpid){ |
| 810 | char *zThreadTitle = 0; |
| 811 | |
| 812 | zThreadTitle = db_text("", |
| 813 | "SELECT" |
| 814 | " substr(event.comment,instr(event.comment,':')+2)" |
| 815 | " FROM forumpost, event" |
| 816 | " WHERE event.objid=forumpost.fpid" |
| 817 | " AND forumpost.fpid=%d;", |
| 818 | fpid |
| 819 | ); |
| 820 | style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum"); |
| 821 | fossil_free(zThreadTitle); |
| 822 | return 0; |
| 823 | } |
| 824 | |
| 825 | /* |
| 826 | ** WEBPAGE: forumthread |
| 827 | ** |
| 828 | ** Show all forum messages associated with a particular message thread. |
| 829 | ** The result is basically the same as /forumpost except that none of |
| @@ -830,24 +713,28 @@ | |
| 830 | ** the postings in the thread are selected. |
| 831 | ** |
| 832 | ** Query parameters: |
| 833 | ** |
| 834 | ** name=X REQUIRED. The hash of any post of the thread. |
| 835 | ** t=MODE Display mode. MODE is... |
| 836 | ** 'c' for chronological, or |
| 837 | ** 'h' for hierarchical, or |
| 838 | ** 'a' for automatic, or |
| 839 | ** 'r' for raw. |
| 840 | ** raw Show only the post given by name= and show it unformatted |
| 841 | ** hist Show only the edit history for the name= post |
| 842 | */ |
| 843 | void forumthread_page(void){ |
| 844 | int fpid; |
| 845 | int froot; |
| 846 | const char *zName = P("name"); |
| 847 | const char *zMode = PD("t","a"); |
| 848 | int bRaw = PB("raw"); |
| 849 | login_check_credentials(); |
| 850 | if( !g.perm.RdForum ){ |
| 851 | login_needed(g.anon.RdForum); |
| 852 | return; |
| 853 | } |
| @@ -861,54 +748,70 @@ | |
| 861 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 862 | if( froot==0 ){ |
| 863 | webpage_error("Not a forum post: \"%s\"", zName); |
| 864 | } |
| 865 | if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0; |
| 866 | if( zMode[0]=='a' ){ |
| 867 | if( cgi_from_mobile() ){ |
| 868 | zMode = "c"; /* Default to chronological on mobile */ |
| 869 | }else{ |
| 870 | zMode = "h"; |
| 871 | } |
| 872 | } |
| 873 | if( zMode[0]!='y' ){ |
| 874 | forumthread_page_header(froot, fpid); |
| 875 | } |
| 876 | if( bRaw && fpid ){ |
| 877 | Manifest *pPost; |
| 878 | pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 879 | if( pPost==0 ){ |
| 880 | @ <p>No such forum post: %h(zName) |
| 881 | }else{ |
| 882 | int isPrivate = content_is_private(fpid); |
| 883 | int notAnon = login_is_individual(); |
| 884 | int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 885 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 886 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 887 | }else{ |
| 888 | forum_render(0, "text/plain", pPost->zWiki, 0, 0); |
| 889 | } |
| 890 | manifest_destroy(pPost); |
| 891 | } |
| 892 | }else if( zMode[0]=='c' ){ |
| 893 | style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); |
| 894 | style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); |
| 895 | forum_display_chronological(froot, fpid, 0); |
| 896 | }else if( zMode[0]=='r' ){ |
| 897 | style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); |
| 898 | style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName); |
| 899 | forum_display_chronological(froot, fpid, 1); |
| 900 | }else if( zMode[0]=='y' ){ |
| 901 | style_header("Edit History Of A Forum Post"); |
| 902 | style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName); |
| 903 | forum_display_history(froot, fpid, 1); |
| 904 | }else{ |
| 905 | style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName); |
| 906 | style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName); |
| 907 | forum_display_hierarchical(froot, fpid); |
| 908 | } |
| 909 | forumpost_emit_page_js(); |
| 910 | style_footer(); |
| 911 | } |
| 912 | |
| 913 | /* |
| 914 | ** Return true if a forum post should be moderated. |
| 915 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -89,19 +89,17 @@ | |
| 89 | fossil_free(pPost); |
| 90 | } |
| 91 | fossil_free(pThread); |
| 92 | } |
| 93 | |
| 94 | /* |
| 95 | ** Search a ForumPost list forwards looking for the post with fpid |
| 96 | */ |
| 97 | static ForumPost *forumpost_forward(ForumPost *p, int fpid){ |
| 98 | while( p && p->fpid!=fpid ) p = p->pNext; |
| 99 | return p; |
| 100 | } |
| 101 | |
| 102 | /* |
| 103 | ** Search backwards for a ForumPost |
| 104 | */ |
| 105 | static ForumPost *forumpost_backward(ForumPost *p, int fpid){ |
| @@ -358,28 +356,10 @@ | |
| 356 | if( zClass ){ |
| 357 | @ </div> |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | /* |
| 362 | ** Compute a display name from a login name. |
| 363 | ** |
| 364 | ** If the input login is found in the USER table, then check the USER.INFO |
| 365 | ** field to see if it has display-name followed by an email address. |
| @@ -409,421 +389,324 @@ | |
| 389 | db_reset(&q); |
| 390 | return zResult; |
| 391 | } |
| 392 | |
| 393 | /* |
| 394 | ** Display a single post in a forum thread. |
| 395 | */ |
| 396 | static void forum_display_post( |
| 397 | ForumPost *p, /* Forum post to display */ |
| 398 | int iIndentScale, /* Indent scale factor */ |
| 399 | int bRaw, /* True to omit the border */ |
| 400 | int bUnf, /* True to leave the post unformatted */ |
| 401 | int bHist, /* True if showing edit history */ |
| 402 | int bSelect, /* True if this is the selected post */ |
| 403 | char *zQuery /* Common query string */ |
| 404 | ){ |
| 405 | char *zDisplayName; /* The display name */ |
| 406 | char *zDate; /* The time/date string */ |
| 407 | char *zHist; /* History query string */ |
| 408 | Manifest *pManifest; /* Manifest comprising the current post */ |
| 409 | int bPrivate; /* True for posts awaiting moderation */ |
| 410 | int bSameUser; /* True if author is also the reader */ |
| 411 | int iIndent; /* Indent level */ |
| 412 | const char *zMimetype;/* Formatting MIME type */ |
| 413 | |
| 414 | /* Get the manifest for the post. Abort if not found (e.g. shunned). */ |
| 415 | pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 416 | if( !pManifest ) return; |
| 417 | |
| 418 | /* When not in raw mode, create the border around the post. */ |
| 419 | if( !bRaw ){ |
| 420 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 421 | ** as selected and/or obsolete. */ |
| 422 | iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; |
| 423 | @ <div id='forum%d(p->fpid)' class='forumTime\ |
| 424 | @ %s(bSelect ? " forumSel" : "")\ |
| 425 | @ %s(p->pEditTail ? " forumObs" : "")'\ |
| 426 | if( iIndent && iIndentScale ){ |
| 427 | @ style='margin-left: %d(iIndent*iIndentScale)ex' |
| 428 | } |
| 429 | @ > |
| 430 | |
| 431 | /* If this is the first post (or an edit thereof), emit the thread title. */ |
| 432 | if( pManifest->zThreadTitle ){ |
| 433 | @ <h1>%h(pManifest->zThreadTitle)</h1> |
| 434 | } |
| 435 | |
| 436 | /* Emit the serial number, revision number, author, and date. */ |
| 437 | zDisplayName = display_name_from_login(pManifest->zUser); |
| 438 | zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate); |
| 439 | @ <h3 class='forumPostHdr'>(%d(p->sid)\ |
| 440 | if( p->nEdit ){ |
| 441 | @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\ |
| 442 | } |
| 443 | @ ) By %h(zDisplayName) on %h(zDate) |
| 444 | fossil_free(zDisplayName); |
| 445 | fossil_free(zDate); |
| 446 | |
| 447 | /* If this is an edit, refer back to the old version. Be sure "hist" is in |
| 448 | ** the query string so the old version will actually be shown. */ |
| 449 | if( p->pEditPrev ){ |
| 450 | zHist = bHist ? "" : "&hist"; |
| 451 | @ edit of \ |
| 452 | @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ |
| 453 | @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a> |
| 454 | } |
| 455 | |
| 456 | /* If debugging is enabled, link to the artifact page. */ |
| 457 | if( g.perm.Debug ){ |
| 458 | @ <span class="debug">\ |
| 459 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 460 | } |
| 461 | |
| 462 | /* If this is a reply, refer back to the parent post. */ |
| 463 | if( p->pIrt ){ |
| 464 | @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ |
| 465 | @ %d(p->pIrt->sid)\ |
| 466 | if( p->pIrt->nEdit ){ |
| 467 | @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ |
| 468 | } |
| 469 | @ </a> |
| 470 | } |
| 471 | |
| 472 | /* If this post was later edited, refer forward to the new version. */ |
| 473 | if( p->pEditTail ){ |
| 474 | @ updated by %z(href("%R/forumpost/%S?%s",p->pEditTail->zUuid,zQuery))\ |
| 475 | @ %d(p->pEditTail->sid)\ |
| 476 | @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditTail->rev)</a> |
| 477 | } |
| 478 | |
| 479 | /* Provide a link to select the individual post. */ |
| 480 | if( !bSelect ){ |
| 481 | @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a> |
| 482 | } |
| 483 | |
| 484 | /* Provide a link to the raw source code. */ |
| 485 | if( !bUnf ){ |
| 486 | @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a> |
| 487 | } |
| 488 | @ </h3> |
| 489 | } |
| 490 | |
| 491 | /* Check if this post is approved, also if it's by the current user. */ |
| 492 | bPrivate = content_is_private(p->fpid); |
| 493 | bSameUser = login_is_individual() |
| 494 | && fossil_strcmp(pManifest->zUser, g.zLogin)==0; |
| 495 | |
| 496 | /* Render the post if the user is able to see it. */ |
| 497 | if( bPrivate && !g.perm.ModForum && !bSameUser ){ |
| 498 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 499 | }else{ |
| 500 | if( bRaw || bUnf || p->pEditTail ){ |
| 501 | zMimetype = "text/plain"; |
| 502 | }else{ |
| 503 | zMimetype = pManifest->zMimetype; |
| 504 | } |
| 505 | forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); |
| 506 | } |
| 507 | |
| 508 | /* When not in raw mode, finish creating the border around the post. */ |
| 509 | if( !bRaw ){ |
| 510 | /* If the user is able to write to the forum and if this post has not been |
| 511 | ** edited, create a form with various interaction buttons. */ |
| 512 | if( g.perm.WrForum && !p->pEditTail ){ |
| 513 | @ <div><form action="%R/forumedit" method="POST"> |
| 514 | @ <input type="hidden" name="fpid" value="%s(p->zUuid)"> |
| 515 | if( !bPrivate ){ |
| 516 | /* Reply and Edit are only available if the post has been approved. */ |
| 517 | @ <input type="submit" name="reply" value="Reply"> |
| 518 | if( g.perm.Admin || bSameUser ){ |
| 519 | @ <input type="submit" name="edit" value="Edit"> |
| 520 | @ <input type="submit" name="nullout" value="Delete"> |
| 521 | } |
| 522 | }else if( g.perm.ModForum ){ |
| 523 | /* Allow moderators to approve or reject pending posts. Also allow |
| 524 | ** forum supervisors to mark non-special users as trusted and therefore |
| 525 | ** able to post unmoderated. */ |
| 526 | @ <input type="submit" name="approve" value="Approve"> |
| 527 | @ <input type="submit" name="reject" value="Reject"> |
| 528 | if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){ |
| 529 | @ <br><label><input type="checkbox" name="trust"> |
| 530 | @ Trust user "%h(pManifest->zUser)" so that future posts by \ |
| 531 | @ "%h(pManifest->zUser)" do not require moderation. |
| 532 | @ </label> |
| 533 | @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)"> |
| 534 | } |
| 535 | }else if( bSameUser ){ |
| 536 | /* Allow users to delete (reject) their own pending posts. */ |
| 537 | @ <input type="submit" name="reject" value="Delete"> |
| 538 | } |
| 539 | @ </form></div> |
| 540 | } |
| 541 | @ </div> |
| 542 | } |
| 543 | |
| 544 | /* Clean up. */ |
| 545 | manifest_destroy(pManifest); |
| 546 | } |
| 547 | |
| 548 | /* |
| 549 | ** Possible display modes for forum_display_thread(). |
| 550 | */ |
| 551 | enum { |
| 552 | FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force |
| 553 | ** unformatted mode, and inhibit history mode */ |
| 554 | FD_SINGLE, /* Render a single post and (optionally) its edit history */ |
| 555 | FD_CHRONO, /* Render all posts in chronological order */ |
| 556 | FD_HIER, /* Render all posts in an indented hierarchy */ |
| 557 | }; |
| 558 | |
| 559 | /* |
| 560 | ** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a |
| 561 | ** single post from the thread and (optionally) its edit history. |
| 562 | */ |
| 563 | static void forum_display_thread( |
| 564 | int froot, /* Forum thread root post ID */ |
| 565 | int fpid, /* Selected forum post ID, or 0 if none selected */ |
| 566 | int mode, /* Forum display mode, one of the FD_* enumerations */ |
| 567 | int bUnf, /* True if rendering unformatted */ |
| 568 | int bHist /* True if showing edit history, ignored for FD_RAW */ |
| 569 | ){ |
| 570 | ForumThread *pThread; /* Thread structure */ |
| 571 | ForumPost *pSelect; /* Currently selected post, or NULL if none */ |
| 572 | ForumPost *p; /* Post iterator pointer */ |
| 573 | char *zQuery; /* Common query string */ |
| 574 | int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */ |
| 575 | |
| 576 | /* In raw mode, force unformatted display and disable history. */ |
| 577 | if( mode == FD_RAW ){ |
| 578 | bUnf = 1; |
| 579 | bHist = 0; |
| 580 | } |
| 581 | |
| 582 | /* Thread together the posts and (optionally) compute the hierarchy. */ |
| 583 | pThread = forumthread_create(froot, mode==FD_HIER); |
| 584 | |
| 585 | /* Compute the appropriate indent scaling. */ |
| 586 | if( mode==FD_HIER ){ |
| 587 | iIndentScale = 4; |
| 588 | while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){ |
| 589 | iIndentScale--; |
| 590 | } |
| 591 | }else{ |
| 592 | iIndentScale = 0; |
| 593 | } |
| 594 | |
| 595 | /* Find the selected post, or (if history not shown) its latest edit. */ |
| 596 | pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0; |
| 597 | if( !bHist && pSelect && pSelect->pEditTail ){ |
| 598 | pSelect = pSelect->pEditTail; |
| 599 | } |
| 600 | |
| 601 | /* When displaying only a single post, abort if no post was selected or the |
| 602 | ** selected forum post does not exist in the thread. Otherwise proceed to |
| 603 | ** display the entire thread without marking any posts as selected. */ |
| 604 | if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){ |
| 605 | return; |
| 606 | } |
| 607 | |
| 608 | /* Create the common query string to append to nearly all post links. */ |
| 609 | zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s", |
| 610 | mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h', |
| 611 | bUnf ? "&unf" : "", bHist ? "&hist" : ""); |
| 612 | |
| 613 | /* Identify which post to display first. If history is shown, start with the |
| 614 | ** original, unedited post. Otherwise advance to the post's latest edit. */ |
| 615 | if( mode==FD_RAW || mode==FD_SINGLE ){ |
| 616 | p = pSelect; |
| 617 | if( bHist && p->pEditHead ) p = p->pEditHead; |
| 618 | }else{ |
| 619 | p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay; |
| 620 | if( !bHist && p->pEditTail ) p = p->pEditTail; |
| 621 | } |
| 622 | |
| 623 | /* Display the appropriate subset of posts in sequence. */ |
| 624 | while( p ){ |
| 625 | /* Display the post. */ |
| 626 | forum_display_post(p, iIndentScale, mode==FD_RAW, |
| 627 | bUnf, bHist, p==pSelect, zQuery); |
| 628 | |
| 629 | /* Advance to the next post in the thread. */ |
| 630 | if( mode==FD_CHRONO ){ |
| 631 | /* Chronological mode: display posts (optionally including edits) in their |
| 632 | ** original commit order. */ |
| 633 | if( bHist ){ |
| 634 | p = p->pNext; |
| 635 | }else{ |
| 636 | if( p->pEditHead ) p = p->pEditHead; |
| 637 | do p = p->pNext; while( p && p->sid<=p->pPrev->sid ); |
| 638 | if( p && p->pEditTail ) p = p->pEditTail; |
| 639 | } |
| 640 | }else if( bHist && p->pEditNext ){ |
| 641 | /* Hierarchical and single mode: display each post's edits in sequence. */ |
| 642 | p = p->pEditNext; |
| 643 | }else if( mode==FD_HIER ){ |
| 644 | /* Hierarchical mode: after displaying with each post (optionally |
| 645 | ** including edits), go to the next post in computed display order. */ |
| 646 | p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay; |
| 647 | if( !bHist && p && p->pEditTail ) p = p->pEditTail; |
| 648 | }else{ |
| 649 | /* Single and raw mode: terminate after displaying the selected post and |
| 650 | ** (optionally) its edits. */ |
| 651 | break; |
| 652 | } |
| 653 | } |
| 654 | |
| 655 | /* Undocumented "threadtable" query parameter causes thread table to be |
| 656 | ** displayed for debugging purposes. */ |
| 657 | if( PB("threadtable") ){ |
| 658 | @ <hr> |
| 659 | @ <table border="1" cellpadding="3" cellspacing="0"> |
| 660 | @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\ |
| 661 | @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash |
| 662 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 663 | @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\ |
| 664 | @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\ |
| 665 | @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\ |
| 666 | @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\ |
| 667 | @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\ |
| 668 | @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\ |
| 669 | @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\ |
| 670 | @ <td>%S(p->zUuid)</tr> |
| 671 | } |
| 672 | @ </table> |
| 673 | } |
| 674 | |
| 675 | /* Clean up. */ |
| 676 | forumthread_delete(pThread); |
| 677 | fossil_free(zQuery); |
| 678 | } |
| 679 | |
| 680 | /* |
| 681 | ** WEBPAGE: forumpost |
| 682 | ** |
| 683 | ** Show a single forum posting. The posting is shown in context with |
| 684 | ** its entire thread. The selected posting is enclosed within |
| 685 | ** <div class='forumSel'>...</div>. Javascript is used to move the |
| 686 | ** selected posting into view after the page loads. |
| 687 | ** |
| 688 | ** Query parameters: |
| 689 | ** |
| 690 | ** name=X REQUIRED. The hash of the post to display. |
| 691 | ** t=a Automatic display mode, i.e. hierarchical for |
| 692 | ** desktop and chronological for mobile. This is the |
| 693 | ** default if the "t" query parameter is omitted. |
| 694 | ** t=c Show posts in the order they were written. |
| 695 | ** t=h Show posts usin hierarchical indenting. |
| 696 | ** t=s Show only the post specified by "name=X". |
| 697 | ** t=r Alias for "t=c&unf&hist". |
| 698 | ** t=y Alias for "t=s&unf&hist". |
| 699 | ** raw Alias for "t=s&unf". Additionally, omit the border |
| 700 | ** around the post, and ignore "t" and "hist". |
| 701 | ** unf Show the original, unformatted source text. |
| 702 | ** hist Show edit history in addition to current posts. |
| 703 | */ |
| 704 | void forumpost_page(void){ |
| 705 | forumthread_page(); |
| 706 | } |
| 707 | |
| 708 | /* |
| 709 | ** WEBPAGE: forumthread |
| 710 | ** |
| 711 | ** Show all forum messages associated with a particular message thread. |
| 712 | ** The result is basically the same as /forumpost except that none of |
| @@ -830,24 +713,28 @@ | |
| 713 | ** the postings in the thread are selected. |
| 714 | ** |
| 715 | ** Query parameters: |
| 716 | ** |
| 717 | ** name=X REQUIRED. The hash of any post of the thread. |
| 718 | ** t=a Automatic display mode, i.e. hierarchical for |
| 719 | ** desktop and chronological for mobile. This is the |
| 720 | ** default if the "t" query parameter is omitted. |
| 721 | ** t=c Show posts in the order they were written. |
| 722 | ** t=h Show posts using hierarchical indenting. |
| 723 | ** unf Show the original, unformatted source text. |
| 724 | ** hist Show edit history in addition to current posts. |
| 725 | */ |
| 726 | void forumthread_page(void){ |
| 727 | int fpid; |
| 728 | int froot; |
| 729 | char *zThreadTitle; |
| 730 | const char *zName = P("name"); |
| 731 | const char *zMode = PD("t","a"); |
| 732 | int bRaw = PB("raw"); |
| 733 | int bUnf = PB("unf"); |
| 734 | int bHist = PB("hist"); |
| 735 | int mode; |
| 736 | login_check_credentials(); |
| 737 | if( !g.perm.RdForum ){ |
| 738 | login_needed(g.anon.RdForum); |
| 739 | return; |
| 740 | } |
| @@ -861,54 +748,70 @@ | |
| 748 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 749 | if( froot==0 ){ |
| 750 | webpage_error("Not a forum post: \"%s\"", zName); |
| 751 | } |
| 752 | if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0; |
| 753 | |
| 754 | /* Decode the mode parameters. */ |
| 755 | if( bRaw ){ |
| 756 | mode = FD_RAW; |
| 757 | bUnf = 1; |
| 758 | bHist = 0; |
| 759 | cgi_replace_query_parameter("unf", "on"); |
| 760 | cgi_delete_query_parameter("hist"); |
| 761 | cgi_delete_query_parameter("raw"); |
| 762 | }else{ |
| 763 | switch( *zMode ){ |
| 764 | case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break; |
| 765 | case 'c': mode = FD_CHRONO; break; |
| 766 | case 'h': mode = FD_HIER; break; |
| 767 | case 's': mode = FD_SINGLE; break; |
| 768 | case 'r': mode = FD_CHRONO; break; |
| 769 | case 'y': mode = FD_SINGLE; break; |
| 770 | default: webpage_error("Invalid thread mode: \"%s\"", zMode); |
| 771 | } |
| 772 | if( *zMode=='r' || *zMode=='y') { |
| 773 | bUnf = 1; |
| 774 | bHist = 1; |
| 775 | cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s"); |
| 776 | cgi_replace_query_parameter("unf", "on"); |
| 777 | cgi_replace_query_parameter("hist", "on"); |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | /* Define the page header. */ |
| 782 | zThreadTitle = db_text("", |
| 783 | "SELECT" |
| 784 | " substr(event.comment,instr(event.comment,':')+2)" |
| 785 | " FROM forumpost, event" |
| 786 | " WHERE event.objid=forumpost.fpid" |
| 787 | " AND forumpost.fpid=%d;", |
| 788 | fpid |
| 789 | ); |
| 790 | style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum"); |
| 791 | fossil_free(zThreadTitle); |
| 792 | if( mode!=FD_CHRONO ){ |
| 793 | style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName, |
| 794 | bUnf ? "&unf" : "", bHist ? "&hist" : ""); |
| 795 | } |
| 796 | if( mode!=FD_HIER ){ |
| 797 | style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName, |
| 798 | bUnf ? "&unf" : "", bHist ? "&hist" : ""); |
| 799 | } |
| 800 | style_submenu_checkbox("unf", "Unformatted", 0, 0); |
| 801 | style_submenu_checkbox("hist", "History", 0, 0); |
| 802 | |
| 803 | /* Display the thread. */ |
| 804 | forum_display_thread(froot, fpid, mode, bUnf, bHist); |
| 805 | |
| 806 | /* Emit Forum Javascript. */ |
| 807 | style_emit_script_fossil_bootstrap(1); |
| 808 | builtin_request_js("forum.js"); |
| 809 | builtin_request_js("fossil.dom.js"); |
| 810 | builtin_request_js("fossil.page.forumpost.js"); |
| 811 | |
| 812 | /* Emit the page style. */ |
| 813 | style_footer(); |
| 814 | } |
| 815 | |
| 816 | /* |
| 817 | ** Return true if a forum post should be moderated. |
| 818 |