| | @@ -440,11 +440,12 @@ |
| 440 | 440 | ** |
| 441 | 441 | ** - The applied tag is propagating so so that "closed" tags can |
| 442 | 442 | ** account for how edits of posts are handled. This differs from |
| 443 | 443 | ** closure of a branch, where a non-propagating tag is used. |
| 444 | 444 | */ |
| 445 | | -static int forumpost_tag(int frid, const char *zTagName, int addTag, |
| 445 | +static int forumpost_tag(int frid, int addTag, |
| 446 | + const char *zTagName, |
| 446 | 447 | const char *zValue){ |
| 447 | 448 | Blob artifact = BLOB_INITIALIZER; /* Output artifact */ |
| 448 | 449 | Blob cksum = BLOB_INITIALIZER; /* Z-card */ |
| 449 | 450 | int iTagged; /* true if frid is already tagged */ |
| 450 | 451 | int trid; /* RID of new control artifact */ |
| | @@ -1657,10 +1658,11 @@ |
| 1657 | 1658 | return z[0]==0; |
| 1658 | 1659 | } |
| 1659 | 1660 | |
| 1660 | 1661 | /* Flags for use with forum_post() */ |
| 1661 | 1662 | #define FPOST_NO_ALERT 1 /* do not send any alerts */ |
| 1663 | +#define FPOST_DRY_RUN 2 /* do not save the artifact */ |
| 1662 | 1664 | |
| 1663 | 1665 | /* |
| 1664 | 1666 | ** Return a flags value for use with the final argument to |
| 1665 | 1667 | ** forum_post(), extracted from the CGI environment. |
| 1666 | 1668 | */ |
| | @@ -1667,10 +1669,13 @@ |
| 1667 | 1669 | static int forum_post_flags(void){ |
| 1668 | 1670 | int iPostFlags = 0; |
| 1669 | 1671 | if( g.perm.Debug && P("fpsilent")!=0 ){ |
| 1670 | 1672 | iPostFlags |= FPOST_NO_ALERT; |
| 1671 | 1673 | } |
| 1674 | + if( P("dryrun")!=0 ){ |
| 1675 | + iPostFlags |= FPOST_DRY_RUN; |
| 1676 | + } |
| 1672 | 1677 | return iPostFlags; |
| 1673 | 1678 | } |
| 1674 | 1679 | |
| 1675 | 1680 | /* |
| 1676 | 1681 | ** Add a new Forum Post artifact to the repository. |
| | @@ -1723,17 +1728,19 @@ |
| 1723 | 1728 | fossil_free(zG); |
| 1724 | 1729 | } |
| 1725 | 1730 | if( zTitle ){ |
| 1726 | 1731 | blob_appendf(&x, "H %F\n", zTitle); |
| 1727 | 1732 | } |
| 1728 | | - zI = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iInReplyTo); |
| 1733 | + zI = rid_to_uuid(iInReplyTo); |
| 1729 | 1734 | if( zI ){ |
| 1730 | 1735 | blob_appendf(&x, "I %s\n", zI); |
| 1731 | 1736 | fossil_free(zI); |
| 1732 | 1737 | } |
| 1733 | | - if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){ |
| 1734 | | - blob_appendf(&x, "N %s\n", zMimetype); |
| 1738 | + if( zMimetype!=0 |
| 1739 | + && zMimetype[0]!=0 |
| 1740 | + && fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){ |
| 1741 | + blob_appendf(&x, "N %F\n", zMimetype); |
| 1735 | 1742 | } |
| 1736 | 1743 | if( iEdit>0 ){ |
| 1737 | 1744 | char *zP = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iEdit); |
| 1738 | 1745 | if( zP==0 ) webpage_error("missing edit artifact %d", iEdit); |
| 1739 | 1746 | blob_appendf(&x, "P %s\n", zP); |
| | @@ -1761,11 +1768,11 @@ |
| 1761 | 1768 | webpage_error("malformed forum post artifact - %s", blob_str(&errMsg)); |
| 1762 | 1769 | } |
| 1763 | 1770 | webpage_assert( pPost->type==CFTYPE_FORUM ); |
| 1764 | 1771 | manifest_destroy(pPost); |
| 1765 | 1772 | |
| 1766 | | - if( P("dryrun") ){ |
| 1773 | + if( (iFlags & FPOST_DRY_RUN)!=0 ){ |
| 1767 | 1774 | @ <div class='debug'> |
| 1768 | 1775 | @ This is the artifact that would have been generated: |
| 1769 | 1776 | @ <pre>%h(blob_str(&x))</pre> |
| 1770 | 1777 | @ </div> |
| 1771 | 1778 | blob_reset(&x); |
| | @@ -1824,11 +1831,11 @@ |
| 1824 | 1831 | int addTag, int validFpid){ |
| 1825 | 1832 | if( !cgi_csrf_safe(2) ){ |
| 1826 | 1833 | webpage_error("CSRF validation failed"); |
| 1827 | 1834 | }else{ |
| 1828 | 1835 | const int fpid = validFpid>0 ? validFpid : forum_validate_fpid_param(); |
| 1829 | | - forumpost_tag(fpid, zTag, addTag, zVal); |
| 1836 | + forumpost_tag(fpid, addTag, zTag, zVal); |
| 1830 | 1837 | cgi_redirectf("%R/forumpost/%S",P("fpid")); |
| 1831 | 1838 | } |
| 1832 | 1839 | } |
| 1833 | 1840 | |
| 1834 | 1841 | /* |
| | @@ -2788,10 +2795,153 @@ |
| 2788 | 2795 | forum_emit_js(); |
| 2789 | 2796 | } |
| 2790 | 2797 | style_finish_page(); |
| 2791 | 2798 | } |
| 2792 | 2799 | |
| 2800 | +/* |
| 2801 | +** The AJAX counterpart of forum_post(). |
| 2802 | +** |
| 2803 | +** Returns the new artifact's RID on success, 0 if no changes were |
| 2804 | +** necessary (e.g. an empty new post or dry-run mode), and a negative |
| 2805 | +** value on error. If it returns a negative value then it will have |
| 2806 | +** populated the ajax response state with an error object. |
| 2807 | +** |
| 2808 | +** The caller must have started a transaction and must roll it back if |
| 2809 | +** this call returns <=0, noting that only the negative-value case is |
| 2810 | +** an error. |
| 2811 | +*/ |
| 2812 | +int forum_post_ajax( |
| 2813 | + const char *zTitle, /* Title. NULL for replies */ |
| 2814 | + int iInReplyTo, /* Post replying to. 0 for new threads */ |
| 2815 | + int iEdit, /* Post being edited, or zero for a new post */ |
| 2816 | + const char *zUser, /* Username. NULL means use login name */ |
| 2817 | + const char *zMimetype, /* Mimetype of content. */ |
| 2818 | + const char *zContent, /* Content */ |
| 2819 | + int iFlags /* FPOST_xyz flag values */ |
| 2820 | +){ |
| 2821 | + char *zI; |
| 2822 | + char *zG; |
| 2823 | + int iBasis; |
| 2824 | + Blob x = BLOB_INITIALIZER, |
| 2825 | + cksum = BLOB_INITIALIZER, |
| 2826 | + formatCheck = BLOB_INITIALIZER, |
| 2827 | + errMsg = BLOB_INITIALIZER; |
| 2828 | + Manifest *pPost = 0; |
| 2829 | + int nContent = zContent ? (int)strlen(zContent) : 0; |
| 2830 | + int rc = 0; |
| 2831 | + |
| 2832 | + assert( db_transaction_nesting_depth()>0 ); |
| 2833 | + schema_forum(); |
| 2834 | + if( iEdit==0 && whitespace_only(zContent) ){ |
| 2835 | + return 0; |
| 2836 | + } |
| 2837 | + if( !g.perm.Admin && (iEdit || iInReplyTo) |
| 2838 | + && forum_rid_is_tagged(iEdit ? iEdit : iInReplyTo, "closed", 1) ){ |
| 2839 | + return -ajax_route_error(400, "Thread is closed."); |
| 2840 | + } |
| 2841 | + if( 0==iInReplyTo && whitespace_only(zTitle) ){ |
| 2842 | + return -ajax_route_error(400, "Empty title is not permitted."); |
| 2843 | + } |
| 2844 | + |
| 2845 | + if( zUser==0 ){ |
| 2846 | + if( login_is_nobody() ){ |
| 2847 | + zUser = "anonymous"; |
| 2848 | + }else{ |
| 2849 | + zUser = login_name(); |
| 2850 | + } |
| 2851 | + } |
| 2852 | + if( iEdit>0 |
| 2853 | + && !g.perm.Admin |
| 2854 | + && !forumpost_is_owner(iEdit, zUser) ){ |
| 2855 | + return -ajax_route_error( |
| 2856 | + 403, "Only admins may edit other peoples' posts." |
| 2857 | + ); |
| 2858 | + } |
| 2859 | + if( iInReplyTo==0 && iEdit>0 ){ |
| 2860 | + iBasis = iEdit; |
| 2861 | + iInReplyTo = db_int(0, "SELECT firt FROM forumpost WHERE fpid=%d", |
| 2862 | + iEdit); |
| 2863 | + }else{ |
| 2864 | + iBasis = iInReplyTo; |
| 2865 | + } |
| 2866 | + webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 ); |
| 2867 | + blob_init(&x, 0, 0); |
| 2868 | + blob_appendf(&x, "D %z\n", date_in_standard_format("now")); |
| 2869 | + zG = db_text( |
| 2870 | + 0, |
| 2871 | + "SELECT uuid FROM blob, forumpost" |
| 2872 | + " WHERE blob.rid==forumpost.froot" |
| 2873 | + " AND forumpost.fpid=%d", |
| 2874 | + iBasis |
| 2875 | + ); |
| 2876 | + if( zG ){ |
| 2877 | + blob_appendf(&x, "G %z\n", zG); |
| 2878 | + } |
| 2879 | + if( zTitle ){ |
| 2880 | + blob_appendf(&x, "H %F\n", zTitle); |
| 2881 | + } |
| 2882 | + if( iInReplyTo>0 ){ |
| 2883 | + zI = rid_to_uuid(iInReplyTo); |
| 2884 | + if( 0==zI ){ |
| 2885 | + rc = -ajax_route_error(404, "Missing in-reply-to artifact %d", |
| 2886 | + iInReplyTo); |
| 2887 | + goto post_ajax_end; |
| 2888 | + } |
| 2889 | + blob_appendf(&x, "I %z\n", zI); |
| 2890 | + } |
| 2891 | + if( zMimetype!=0 |
| 2892 | + && zMimetype[0]!=0 |
| 2893 | + && fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){ |
| 2894 | + blob_appendf(&x, "N %F\n", zMimetype); |
| 2895 | + } |
| 2896 | + if( iEdit>0 ){ |
| 2897 | + char *zP = rid_to_uuid(iEdit); |
| 2898 | + if( zP==0 ){ |
| 2899 | + rc = -ajax_route_error(404, "Missing edit artifact %d", iEdit); |
| 2900 | + goto post_ajax_end; |
| 2901 | + } |
| 2902 | + blob_appendf(&x, "P %z\n", zP); |
| 2903 | + } |
| 2904 | + |
| 2905 | + blob_appendf(&x, "U %F\n", zUser); |
| 2906 | + blob_appendf(&x, "W %d\n%s\n", nContent, zContent); |
| 2907 | + md5sum_blob(&x, &cksum); |
| 2908 | + blob_appendf(&x, "Z %b\n", &cksum); |
| 2909 | + blob_reset(&cksum); |
| 2910 | + |
| 2911 | + /* Verify that the artifact we are creating is well-formed */ |
| 2912 | + blob_init(&formatCheck, 0, 0); |
| 2913 | + blob_init(&errMsg, 0, 0); |
| 2914 | + blob_copy(&formatCheck, &x); |
| 2915 | + pPost = manifest_parse(&formatCheck, 0, &errMsg); |
| 2916 | + if( pPost==0 ){ |
| 2917 | + ajax_route_error(500, "Malformed forum post artifact: %b", &errMsg); |
| 2918 | + rc = -500; |
| 2919 | + goto post_ajax_end; |
| 2920 | + } |
| 2921 | + webpage_assert( pPost->type==CFTYPE_FORUM ); |
| 2922 | + |
| 2923 | + if( (iFlags & FPOST_DRY_RUN)!=0 ){ |
| 2924 | + rc = 0; |
| 2925 | + }else{ |
| 2926 | + int nrid; |
| 2927 | + db_begin_transaction(); |
| 2928 | + nrid = wiki_put(&x, iEdit>0 ? iEdit : 0, forum_need_moderation()); |
| 2929 | + blob_reset(&x); |
| 2930 | + if( (iFlags & FPOST_NO_ALERT)!=0 ){ |
| 2931 | + alert_unqueue('f', nrid); |
| 2932 | + } |
| 2933 | + rc = nrid; |
| 2934 | + db_end_transaction(0); |
| 2935 | + } |
| 2936 | +post_ajax_end: |
| 2937 | + manifest_destroy(pPost); |
| 2938 | + blob_reset(&x); |
| 2939 | + blob_reset(&cksum); |
| 2940 | + blob_reset(&formatCheck); |
| 2941 | + return rc; |
| 2942 | +} |
| 2793 | 2943 | /* |
| 2794 | 2944 | ** WEBPAGE: forumajax_save hidden |
| 2795 | 2945 | ** |
| 2796 | 2946 | ** WIP |
| 2797 | 2947 | ** |
| | @@ -2805,13 +2955,18 @@ |
| 2805 | 2955 | const char *zIrt; |
| 2806 | 2956 | const char *zMimetype; |
| 2807 | 2957 | const char *zContent; |
| 2808 | 2958 | const char *zStatus; |
| 2809 | 2959 | const int bHasAttachment = P("file1")!=0; |
| 2960 | + char *zNewUuid = 0; |
| 2810 | 2961 | int bNeedsModeration = 0; |
| 2811 | 2962 | int goodCaptcha = 1; |
| 2812 | | - int bRollback = 0; |
| 2963 | + int bRollback = PB("rollback"); /* True if we should roll back. */ |
| 2964 | + int iIrt = 0; /* In-reply-to rid or 0 */ |
| 2965 | + int iEditRid = 0; /* Post rid being edited or 0 */ |
| 2966 | + int rc = 0; |
| 2967 | + int nrid = 0; |
| 2813 | 2968 | |
| 2814 | 2969 | if( !ajax_route_bootstrap(0, 1) ){ |
| 2815 | 2970 | return; |
| 2816 | 2971 | }else if( !g.perm.WrForum |
| 2817 | 2972 | || (bHasAttachment && !g.perm.AttachForum) ){ |
| | @@ -2822,10 +2977,16 @@ |
| 2822 | 2977 | return; |
| 2823 | 2978 | }else if( 0==(goodCaptcha = captcha_is_correct(0)) ){ |
| 2824 | 2979 | ajax_route_error_captcha(); |
| 2825 | 2980 | return; |
| 2826 | 2981 | } |
| 2982 | + |
| 2983 | + zTitle = P("title"); |
| 2984 | + zIrt = P("firt"); |
| 2985 | + zMimetype = P("mimetype"); |
| 2986 | + zContent = P("content"); |
| 2987 | + zStatus = P("status"); |
| 2827 | 2988 | db_begin_transaction(); |
| 2828 | 2989 | /* |
| 2829 | 2990 | ** TODOs include: |
| 2830 | 2991 | ** |
| 2831 | 2992 | ** - Permissions and sanity checks, of course. |
| | @@ -2844,14 +3005,47 @@ |
| 2844 | 3005 | ** - Allow status change only if permissions allow. |
| 2845 | 3006 | */ |
| 2846 | 3007 | |
| 2847 | 3008 | (void)bNeedsModeration; |
| 2848 | 3009 | (void)zUuid; |
| 2849 | | - (void)zTitle; |
| 2850 | 3010 | (void)zIrt; |
| 2851 | 3011 | (void)zMimetype; |
| 2852 | 3012 | (void)zContent; |
| 2853 | 3013 | (void)zStatus; |
| 2854 | 3014 | |
| 2855 | | - ajax_route_error(400, "Save is TODO"); |
| 3015 | + if( 1 ){ |
| 3016 | + bRollback = 1; |
| 3017 | + ajax_route_error(400, "Save is TODO"); |
| 3018 | + goto ajax_save_end; |
| 3019 | + } |
| 3020 | + |
| 3021 | + nrid = forum_post_ajax(zTitle, iIrt, iEditRid, 0, zMimetype, |
| 3022 | + zContent, forum_post_flags()); |
| 3023 | + if( nrid==0 ){ |
| 3024 | + CX("{\"message\": \"No changes needed saving.\"}\n"); |
| 3025 | + goto ajax_save_end; |
| 3026 | + } |
| 3027 | + if( zStatus!=0 && zStatus[0]!=0 ){ |
| 3028 | + forumpost_tag(nrid, 1, "status", zStatus) |
| 3029 | + /* FIXME: ^^^ fails fatally on error */; |
| 3030 | + } |
| 3031 | + zNewUuid = rid_to_uuid(nrid); |
| 3032 | + if( 0!=P("file1") ){ |
| 3033 | + /* Attachments */ |
| 3034 | + const int atRc = |
| 3035 | + attachments_ajax_from_POST(zNewUuid, bNeedsModeration); |
| 3036 | + if( atRc<0 ){ |
| 3037 | + rc = atRc; |
| 3038 | + goto ajax_save_end; |
| 3039 | + } |
| 3040 | + } |
| 3041 | + |
| 3042 | + |
| 3043 | + if( 0==rc ){ |
| 3044 | + CX("{\"uuid\": %!j}\n", zNewUuid); |
| 3045 | + } |
| 3046 | + |
| 3047 | +ajax_save_end: |
| 3048 | + fossil_free(zNewUuid); |
| 3049 | + if( 0!=rc ) bRollback = 1; |
| 2856 | 3050 | db_end_transaction(bRollback); |
| 2857 | 3051 | } |
| 2858 | 3052 | |