Fossil SCM
Two new notification options: "n" means to be notified for new forum threads only and "r" means to be notified for forum posts that are a reply to a post made by the user.
Commit
d4361f6a94725aa36f30f4d1444963da2732dcc86bd5261f26dd495021a5e2b1
Parent
a7e9dd53ef60097…
1 file changed
+94
-10
+94
-10
| --- src/alerts.c | ||
| +++ src/alerts.c | ||
| @@ -47,12 +47,15 @@ | ||
| 47 | 47 | @ -- The ssub field is a string where each character indicates a particular |
| 48 | 48 | @ -- type of event to subscribe to. Choices: |
| 49 | 49 | @ -- a - Announcements |
| 50 | 50 | @ -- c - Check-ins |
| 51 | 51 | @ -- f - Forum posts |
| 52 | +@ -- n - New forum threads | |
| 53 | +@ -- r - Replies to my own forum posts | |
| 52 | 54 | @ -- t - Ticket changes |
| 53 | 55 | @ -- w - Wiki changes |
| 56 | +@ -- x - Edits to forum posts | |
| 54 | 57 | @ -- Probably different codes will be added in the future. In the future |
| 55 | 58 | @ -- we might also add a separate table that allows subscribing to email |
| 56 | 59 | @ -- notifications for specific branches or tags or tickets. |
| 57 | 60 | @ -- |
| 58 | 61 | @ CREATE TABLE repository.subscriber( |
| @@ -1556,10 +1559,12 @@ | ||
| 1556 | 1559 | if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin; |
| 1557 | 1560 | if( suname && suname[0]==0 ) suname = 0; |
| 1558 | 1561 | if( PB("sa") ) ssub[nsub++] = 'a'; |
| 1559 | 1562 | if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; |
| 1560 | 1563 | if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; |
| 1564 | + if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n'; | |
| 1565 | + if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r'; | |
| 1561 | 1566 | if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; |
| 1562 | 1567 | if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; |
| 1563 | 1568 | if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; |
| 1564 | 1569 | ssub[nsub] = 0; |
| 1565 | 1570 | zCode = db_text(0, |
| @@ -1618,10 +1623,12 @@ | ||
| 1618 | 1623 | ** come from a prior Submit of the form) then default all of the |
| 1619 | 1624 | ** subscription options to "on" */ |
| 1620 | 1625 | cgi_set_parameter_nocopy("sa","1",1); |
| 1621 | 1626 | if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1); |
| 1622 | 1627 | if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1); |
| 1628 | + if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1); | |
| 1629 | + if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1); | |
| 1623 | 1630 | if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1); |
| 1624 | 1631 | if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1); |
| 1625 | 1632 | } |
| 1626 | 1633 | @ <p>To receive email notifications for changes to this |
| 1627 | 1634 | @ repository, fill out the form below and press the "Submit" button.</p> |
| @@ -1675,13 +1682,17 @@ | ||
| 1675 | 1682 | @ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \ |
| 1676 | 1683 | @ Check-ins</label><br> |
| 1677 | 1684 | } |
| 1678 | 1685 | if( g.perm.RdForum ){ |
| 1679 | 1686 | @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ |
| 1680 | - @ Forum Posts</label><br> | |
| 1687 | + @ All Forum Posts</label><br> | |
| 1688 | + @ <label><input type="checkbox" name="sn" %s(PCK("sn"))> \ | |
| 1689 | + @ New Forum Threads</label><br> | |
| 1690 | + @ <label><input type="checkbox" name="sr" %s(PCK("sr"))> \ | |
| 1691 | + @ Replies To My Forum Posts</label><br> | |
| 1681 | 1692 | @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ |
| 1682 | - @ Forum Edits</label><br> | |
| 1693 | + @ Edits To Forum Posts</label><br> | |
| 1683 | 1694 | } |
| 1684 | 1695 | if( g.perm.RdTkt ){ |
| 1685 | 1696 | @ <label><input type="checkbox" name="st" %s(PCK("st"))> \ |
| 1686 | 1697 | @ Ticket changes</label><br> |
| 1687 | 1698 | } |
| @@ -1799,10 +1810,11 @@ | ||
| 1799 | 1810 | */ |
| 1800 | 1811 | void alert_page(void){ |
| 1801 | 1812 | const char *zName = 0; /* Value of the name= query parameter */ |
| 1802 | 1813 | Stmt q; /* For querying the database */ |
| 1803 | 1814 | int sa, sc, sf, st, sw, sx; /* Types of notifications requested */ |
| 1815 | + int sn, sr; | |
| 1804 | 1816 | int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */ |
| 1805 | 1817 | int isLogin; /* True if logged in as an individual */ |
| 1806 | 1818 | const char *ssub = 0; /* Subscription flags */ |
| 1807 | 1819 | const char *semail = 0; /* Email address */ |
| 1808 | 1820 | const char *smip; /* */ |
| @@ -1855,10 +1867,12 @@ | ||
| 1855 | 1867 | sdigest = PB("sdigest"); |
| 1856 | 1868 | semail = P("semail"); |
| 1857 | 1869 | if( PB("sa") ) newSsub[nsub++] = 'a'; |
| 1858 | 1870 | if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c'; |
| 1859 | 1871 | if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f'; |
| 1872 | + if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n'; | |
| 1873 | + if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r'; | |
| 1860 | 1874 | if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't'; |
| 1861 | 1875 | if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w'; |
| 1862 | 1876 | if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x'; |
| 1863 | 1877 | newSsub[nsub] = 0; |
| 1864 | 1878 | ssub = newSsub; |
| @@ -1951,10 +1965,12 @@ | ||
| 1951 | 1965 | sverified = db_column_int(&q, 1); |
| 1952 | 1966 | } |
| 1953 | 1967 | sa = strchr(ssub,'a')!=0; |
| 1954 | 1968 | sc = strchr(ssub,'c')!=0; |
| 1955 | 1969 | sf = strchr(ssub,'f')!=0; |
| 1970 | + sn = strchr(ssub,'n')!=0; | |
| 1971 | + sr = strchr(ssub,'r')!=0; | |
| 1956 | 1972 | st = strchr(ssub,'t')!=0; |
| 1957 | 1973 | sw = strchr(ssub,'w')!=0; |
| 1958 | 1974 | sx = strchr(ssub,'x')!=0; |
| 1959 | 1975 | smip = db_column_text(&q, 5); |
| 1960 | 1976 | mtime = db_column_text(&q, 7); |
| @@ -2056,13 +2072,17 @@ | ||
| 2056 | 2072 | @ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\ |
| 2057 | 2073 | @ Check-ins</label><br> |
| 2058 | 2074 | } |
| 2059 | 2075 | if( g.perm.RdForum ){ |
| 2060 | 2076 | @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ |
| 2061 | - @ Forum Posts</label><br> | |
| 2077 | + @ All Forum Posts</label><br> | |
| 2078 | + @ <label><input type="checkbox" name="sn" %s(sn?"checked":"")>\ | |
| 2079 | + @ New Forum Threads</label><br> | |
| 2080 | + @ <label><input type="checkbox" name="sr" %s(sr?"checked":"")>\ | |
| 2081 | + @ Replies To My Posts</label><br> | |
| 2062 | 2082 | @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ |
| 2063 | - @ Forum Edits</label><br> | |
| 2083 | + @ Edits To Forum Posts</label><br> | |
| 2064 | 2084 | } |
| 2065 | 2085 | if( g.perm.RdTkt ){ |
| 2066 | 2086 | @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ |
| 2067 | 2087 | @ Ticket changes</label><br> |
| 2068 | 2088 | } |
| @@ -2484,20 +2504,24 @@ | ||
| 2484 | 2504 | ** |
| 2485 | 2505 | ** type values: |
| 2486 | 2506 | ** |
| 2487 | 2507 | ** c A new check-in |
| 2488 | 2508 | ** f An original forum post |
| 2509 | +** n New forum threads | |
| 2510 | +** r Replies to my forum posts | |
| 2489 | 2511 | ** x An edit to a prior forum post |
| 2490 | 2512 | ** t A new ticket or a change to an existing ticket |
| 2491 | 2513 | ** w A change to a wiki page |
| 2514 | +** x Edits to forum posts | |
| 2492 | 2515 | */ |
| 2493 | 2516 | struct EmailEvent { |
| 2494 | - int type; /* 'c', 'f', 't', 'w', 'x' */ | |
| 2517 | + int type; /* 'c', 'f', 'n', 'r', 't', 'w', 'x' */ | |
| 2495 | 2518 | int needMod; /* Pending moderator approval */ |
| 2496 | 2519 | Blob hdr; /* Header content, for forum entries */ |
| 2497 | 2520 | Blob txt; /* Text description to appear in an alert */ |
| 2498 | 2521 | char *zFromName; /* Human name of the sender */ |
| 2522 | + char *zPriors; /* Upthread sender IDs for forum posts */ | |
| 2499 | 2523 | EmailEvent *pNext; /* Next in chronological order */ |
| 2500 | 2524 | }; |
| 2501 | 2525 | #endif |
| 2502 | 2526 | |
| 2503 | 2527 | /* |
| @@ -2507,14 +2531,44 @@ | ||
| 2507 | 2531 | while( p ){ |
| 2508 | 2532 | EmailEvent *pNext = p->pNext; |
| 2509 | 2533 | blob_reset(&p->txt); |
| 2510 | 2534 | blob_reset(&p->hdr); |
| 2511 | 2535 | fossil_free(p->zFromName); |
| 2536 | + fossil_free(p->zPriors); | |
| 2512 | 2537 | fossil_free(p); |
| 2513 | 2538 | p = pNext; |
| 2514 | 2539 | } |
| 2515 | 2540 | } |
| 2541 | + | |
| 2542 | +/* | |
| 2543 | +** Compute a string that is appropriate for the EmailEvent.zPriors field | |
| 2544 | +** for a particular forum post. | |
| 2545 | +** | |
| 2546 | +** This string is an encode list of sender names and rids for all ancestors | |
| 2547 | +** of the fpdi post - the post that fpid answer, the post that that parent | |
| 2548 | +** post answers, and so forth back up to the root post. Duplicates sender | |
| 2549 | +** names are omitted. | |
| 2550 | +** | |
| 2551 | +** The EmailEvent.zPriors field is used to screen events for people who | |
| 2552 | +** only want to see replies to their own posts or to specific posts. | |
| 2553 | +*/ | |
| 2554 | +static char *alert_compute_priors(int fpid){ | |
| 2555 | + return db_text(0, | |
| 2556 | + "WITH priors(rid,who) AS (" | |
| 2557 | + " SELECT firt, coalesce(euser,user)" | |
| 2558 | + " FROM forumpost LEFT JOIN event ON fpid=objid" | |
| 2559 | + " WHERE fpid=%d" | |
| 2560 | + " UNION ALL" | |
| 2561 | + " SELECT firt, coalesce(euser,user)" | |
| 2562 | + " FROM priors, forumpost LEFT JOIN event ON fpid=objid" | |
| 2563 | + " WHERE fpid=rid" | |
| 2564 | + ")" | |
| 2565 | + "SELECT ','||group_concat(DISTINCT 'u'||who)||" | |
| 2566 | + "','||group_concat(rid) FROM priors;", | |
| 2567 | + fpid | |
| 2568 | + ); | |
| 2569 | +} | |
| 2516 | 2570 | |
| 2517 | 2571 | /* |
| 2518 | 2572 | ** Compute and return a linked list of EmailEvent objects |
| 2519 | 2573 | ** corresponding to the current content of the temp.wantalert |
| 2520 | 2574 | ** table which should be defined as follows: |
| @@ -2644,11 +2698,12 @@ | ||
| 2644 | 2698 | " ORDER BY event.mtime" |
| 2645 | 2699 | ); |
| 2646 | 2700 | zFrom = db_get("email-self",0); |
| 2647 | 2701 | zSub = db_get("email-subname",""); |
| 2648 | 2702 | while( db_step(&q)==SQLITE_ROW ){ |
| 2649 | - Manifest *pPost = manifest_get(db_column_int(&q,0), CFTYPE_FORUM, 0); | |
| 2703 | + int fpid = db_column_int(&q,0); | |
| 2704 | + Manifest *pPost = manifest_get(fpid, CFTYPE_FORUM, 0); | |
| 2650 | 2705 | const char *zIrt; |
| 2651 | 2706 | const char *zUuid; |
| 2652 | 2707 | const char *zTitle; |
| 2653 | 2708 | const char *z; |
| 2654 | 2709 | if( pPost==0 ) continue; |
| @@ -2657,10 +2712,11 @@ | ||
| 2657 | 2712 | pLast = p; |
| 2658 | 2713 | p->type = db_column_int(&q,7) ? 'f' : 'x'; |
| 2659 | 2714 | p->needMod = db_column_int(&q, 5); |
| 2660 | 2715 | z = db_column_text(&q,6); |
| 2661 | 2716 | p->zFromName = z && z[0] ? fossil_strdup(z) : 0; |
| 2717 | + p->zPriors = alert_compute_priors(fpid); | |
| 2662 | 2718 | p->pNext = 0; |
| 2663 | 2719 | blob_init(&p->hdr, 0, 0); |
| 2664 | 2720 | zUuid = db_column_text(&q, 1); |
| 2665 | 2721 | zTitle = db_column_text(&q, 3); |
| 2666 | 2722 | if( p->needMod ){ |
| @@ -2864,10 +2920,25 @@ | ||
| 2864 | 2920 | "immediately, visit the following webpage:\n\n" |
| 2865 | 2921 | " %s/alerts/%s\n\n", |
| 2866 | 2922 | ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode |
| 2867 | 2923 | ); |
| 2868 | 2924 | } |
| 2925 | + | |
| 2926 | +/* | |
| 2927 | +** If zUser is a sender of one of the ancestors of a forum post | |
| 2928 | +** (if zUser appears in zPriors) then return true. | |
| 2929 | +*/ | |
| 2930 | +static int alert_in_priors(const char *zUser, const char *zPriors){ | |
| 2931 | + int n = (int)strlen(zUser); | |
| 2932 | + char zBuf[200]; | |
| 2933 | + if( n>195 ) return 0; | |
| 2934 | + if( zPriors==0 || zPriors[0]==0 ) return 0; | |
| 2935 | + zBuf[0] = ','; | |
| 2936 | + zBuf[1] = 'u'; | |
| 2937 | + memcpy(zBuf+2, zUser, n+1); | |
| 2938 | + return strstr(zPriors, zBuf)!=0; | |
| 2939 | +} | |
| 2869 | 2940 | |
| 2870 | 2941 | #if INTERFACE |
| 2871 | 2942 | /* |
| 2872 | 2943 | ** Flags for alert_send_alerts() |
| 2873 | 2944 | */ |
| @@ -3012,11 +3083,12 @@ | ||
| 3012 | 3083 | db_prepare(&q, |
| 3013 | 3084 | "SELECT" |
| 3014 | 3085 | " hex(subscriberCode)," /* 0 */ |
| 3015 | 3086 | " semail," /* 1 */ |
| 3016 | 3087 | " ssub," /* 2 */ |
| 3017 | - " fullcap(user.cap)" /* 3 */ | |
| 3088 | + " fullcap(user.cap)," /* 3 */ | |
| 3089 | + " suname" /* 4 */ | |
| 3018 | 3090 | " FROM subscriber LEFT JOIN user ON (login=suname)" |
| 3019 | 3091 | " WHERE sverified" |
| 3020 | 3092 | " AND NOT sdonotcall" |
| 3021 | 3093 | " AND sdigest IS %s" |
| 3022 | 3094 | " AND coalesce(subscriber.lastContact,subscriber.mtime)>=%d", |
| @@ -3028,19 +3100,30 @@ | ||
| 3028 | 3100 | const char *zSub = db_column_text(&q, 2); |
| 3029 | 3101 | const char *zEmail = db_column_text(&q, 1); |
| 3030 | 3102 | const char *zCap = db_column_text(&q, 3); |
| 3031 | 3103 | int nHit = 0; |
| 3032 | 3104 | for(p=pEvents; p; p=p->pNext){ |
| 3033 | - if( strchr(zSub,p->type)==0 ) continue; | |
| 3105 | + if( strchr(zSub,p->type)==0 ){ | |
| 3106 | + if( p->type!='f' ) continue; | |
| 3107 | + if( strchr(zSub,'n')!=0 && (p->zPriors==0 || p->zPriors[0]==0) ){ | |
| 3108 | + /* New post: accepted */ | |
| 3109 | + }else if( strchr(zSub,'r')!=0 | |
| 3110 | + && alert_in_priors(db_column_text(&q,4), p->zPriors) ){ | |
| 3111 | + /* A follow-up to a post written by the user: accept */ | |
| 3112 | + }else{ | |
| 3113 | + continue; | |
| 3114 | + } | |
| 3115 | + } | |
| 3034 | 3116 | if( p->needMod ){ |
| 3035 | 3117 | /* For events that require moderator approval, only send an alert |
| 3036 | 3118 | ** if the recipient is a moderator for that type of event. Setup |
| 3037 | 3119 | ** and Admin users always get notified. */ |
| 3038 | 3120 | char xType = '*'; |
| 3039 | 3121 | if( strpbrk(zCap,"as")==0 ){ |
| 3040 | 3122 | switch( p->type ){ |
| 3041 | - case 'x': case 'f': xType = '5'; break; | |
| 3123 | + case 'x': case 'f': | |
| 3124 | + case 'n': case 'r': xType = '5'; break; | |
| 3042 | 3125 | case 't': xType = 'q'; break; |
| 3043 | 3126 | case 'w': xType = 'l'; break; |
| 3044 | 3127 | } |
| 3045 | 3128 | if( strchr(zCap,xType)==0 ) continue; |
| 3046 | 3129 | } |
| @@ -3051,11 +3134,12 @@ | ||
| 3051 | 3134 | /* Other users only see the alert if they have sufficient |
| 3052 | 3135 | ** privilege to view the event itself */ |
| 3053 | 3136 | char xType = '*'; |
| 3054 | 3137 | switch( p->type ){ |
| 3055 | 3138 | case 'c': xType = 'o'; break; |
| 3056 | - case 'x': case 'f': xType = '2'; break; | |
| 3139 | + case 'x': case 'f': | |
| 3140 | + case 'n': case 'r': xType = '2'; break; | |
| 3057 | 3141 | case 't': xType = 'r'; break; |
| 3058 | 3142 | case 'w': xType = 'j'; break; |
| 3059 | 3143 | } |
| 3060 | 3144 | if( strchr(zCap,xType)==0 ) continue; |
| 3061 | 3145 | } |
| 3062 | 3146 |
| --- src/alerts.c | |
| +++ src/alerts.c | |
| @@ -47,12 +47,15 @@ | |
| 47 | @ -- The ssub field is a string where each character indicates a particular |
| 48 | @ -- type of event to subscribe to. Choices: |
| 49 | @ -- a - Announcements |
| 50 | @ -- c - Check-ins |
| 51 | @ -- f - Forum posts |
| 52 | @ -- t - Ticket changes |
| 53 | @ -- w - Wiki changes |
| 54 | @ -- Probably different codes will be added in the future. In the future |
| 55 | @ -- we might also add a separate table that allows subscribing to email |
| 56 | @ -- notifications for specific branches or tags or tickets. |
| 57 | @ -- |
| 58 | @ CREATE TABLE repository.subscriber( |
| @@ -1556,10 +1559,12 @@ | |
| 1556 | if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin; |
| 1557 | if( suname && suname[0]==0 ) suname = 0; |
| 1558 | if( PB("sa") ) ssub[nsub++] = 'a'; |
| 1559 | if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; |
| 1560 | if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; |
| 1561 | if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; |
| 1562 | if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; |
| 1563 | if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; |
| 1564 | ssub[nsub] = 0; |
| 1565 | zCode = db_text(0, |
| @@ -1618,10 +1623,12 @@ | |
| 1618 | ** come from a prior Submit of the form) then default all of the |
| 1619 | ** subscription options to "on" */ |
| 1620 | cgi_set_parameter_nocopy("sa","1",1); |
| 1621 | if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1); |
| 1622 | if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1); |
| 1623 | if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1); |
| 1624 | if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1); |
| 1625 | } |
| 1626 | @ <p>To receive email notifications for changes to this |
| 1627 | @ repository, fill out the form below and press the "Submit" button.</p> |
| @@ -1675,13 +1682,17 @@ | |
| 1675 | @ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \ |
| 1676 | @ Check-ins</label><br> |
| 1677 | } |
| 1678 | if( g.perm.RdForum ){ |
| 1679 | @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ |
| 1680 | @ Forum Posts</label><br> |
| 1681 | @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ |
| 1682 | @ Forum Edits</label><br> |
| 1683 | } |
| 1684 | if( g.perm.RdTkt ){ |
| 1685 | @ <label><input type="checkbox" name="st" %s(PCK("st"))> \ |
| 1686 | @ Ticket changes</label><br> |
| 1687 | } |
| @@ -1799,10 +1810,11 @@ | |
| 1799 | */ |
| 1800 | void alert_page(void){ |
| 1801 | const char *zName = 0; /* Value of the name= query parameter */ |
| 1802 | Stmt q; /* For querying the database */ |
| 1803 | int sa, sc, sf, st, sw, sx; /* Types of notifications requested */ |
| 1804 | int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */ |
| 1805 | int isLogin; /* True if logged in as an individual */ |
| 1806 | const char *ssub = 0; /* Subscription flags */ |
| 1807 | const char *semail = 0; /* Email address */ |
| 1808 | const char *smip; /* */ |
| @@ -1855,10 +1867,12 @@ | |
| 1855 | sdigest = PB("sdigest"); |
| 1856 | semail = P("semail"); |
| 1857 | if( PB("sa") ) newSsub[nsub++] = 'a'; |
| 1858 | if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c'; |
| 1859 | if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f'; |
| 1860 | if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't'; |
| 1861 | if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w'; |
| 1862 | if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x'; |
| 1863 | newSsub[nsub] = 0; |
| 1864 | ssub = newSsub; |
| @@ -1951,10 +1965,12 @@ | |
| 1951 | sverified = db_column_int(&q, 1); |
| 1952 | } |
| 1953 | sa = strchr(ssub,'a')!=0; |
| 1954 | sc = strchr(ssub,'c')!=0; |
| 1955 | sf = strchr(ssub,'f')!=0; |
| 1956 | st = strchr(ssub,'t')!=0; |
| 1957 | sw = strchr(ssub,'w')!=0; |
| 1958 | sx = strchr(ssub,'x')!=0; |
| 1959 | smip = db_column_text(&q, 5); |
| 1960 | mtime = db_column_text(&q, 7); |
| @@ -2056,13 +2072,17 @@ | |
| 2056 | @ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\ |
| 2057 | @ Check-ins</label><br> |
| 2058 | } |
| 2059 | if( g.perm.RdForum ){ |
| 2060 | @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ |
| 2061 | @ Forum Posts</label><br> |
| 2062 | @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ |
| 2063 | @ Forum Edits</label><br> |
| 2064 | } |
| 2065 | if( g.perm.RdTkt ){ |
| 2066 | @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ |
| 2067 | @ Ticket changes</label><br> |
| 2068 | } |
| @@ -2484,20 +2504,24 @@ | |
| 2484 | ** |
| 2485 | ** type values: |
| 2486 | ** |
| 2487 | ** c A new check-in |
| 2488 | ** f An original forum post |
| 2489 | ** x An edit to a prior forum post |
| 2490 | ** t A new ticket or a change to an existing ticket |
| 2491 | ** w A change to a wiki page |
| 2492 | */ |
| 2493 | struct EmailEvent { |
| 2494 | int type; /* 'c', 'f', 't', 'w', 'x' */ |
| 2495 | int needMod; /* Pending moderator approval */ |
| 2496 | Blob hdr; /* Header content, for forum entries */ |
| 2497 | Blob txt; /* Text description to appear in an alert */ |
| 2498 | char *zFromName; /* Human name of the sender */ |
| 2499 | EmailEvent *pNext; /* Next in chronological order */ |
| 2500 | }; |
| 2501 | #endif |
| 2502 | |
| 2503 | /* |
| @@ -2507,14 +2531,44 @@ | |
| 2507 | while( p ){ |
| 2508 | EmailEvent *pNext = p->pNext; |
| 2509 | blob_reset(&p->txt); |
| 2510 | blob_reset(&p->hdr); |
| 2511 | fossil_free(p->zFromName); |
| 2512 | fossil_free(p); |
| 2513 | p = pNext; |
| 2514 | } |
| 2515 | } |
| 2516 | |
| 2517 | /* |
| 2518 | ** Compute and return a linked list of EmailEvent objects |
| 2519 | ** corresponding to the current content of the temp.wantalert |
| 2520 | ** table which should be defined as follows: |
| @@ -2644,11 +2698,12 @@ | |
| 2644 | " ORDER BY event.mtime" |
| 2645 | ); |
| 2646 | zFrom = db_get("email-self",0); |
| 2647 | zSub = db_get("email-subname",""); |
| 2648 | while( db_step(&q)==SQLITE_ROW ){ |
| 2649 | Manifest *pPost = manifest_get(db_column_int(&q,0), CFTYPE_FORUM, 0); |
| 2650 | const char *zIrt; |
| 2651 | const char *zUuid; |
| 2652 | const char *zTitle; |
| 2653 | const char *z; |
| 2654 | if( pPost==0 ) continue; |
| @@ -2657,10 +2712,11 @@ | |
| 2657 | pLast = p; |
| 2658 | p->type = db_column_int(&q,7) ? 'f' : 'x'; |
| 2659 | p->needMod = db_column_int(&q, 5); |
| 2660 | z = db_column_text(&q,6); |
| 2661 | p->zFromName = z && z[0] ? fossil_strdup(z) : 0; |
| 2662 | p->pNext = 0; |
| 2663 | blob_init(&p->hdr, 0, 0); |
| 2664 | zUuid = db_column_text(&q, 1); |
| 2665 | zTitle = db_column_text(&q, 3); |
| 2666 | if( p->needMod ){ |
| @@ -2864,10 +2920,25 @@ | |
| 2864 | "immediately, visit the following webpage:\n\n" |
| 2865 | " %s/alerts/%s\n\n", |
| 2866 | ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode |
| 2867 | ); |
| 2868 | } |
| 2869 | |
| 2870 | #if INTERFACE |
| 2871 | /* |
| 2872 | ** Flags for alert_send_alerts() |
| 2873 | */ |
| @@ -3012,11 +3083,12 @@ | |
| 3012 | db_prepare(&q, |
| 3013 | "SELECT" |
| 3014 | " hex(subscriberCode)," /* 0 */ |
| 3015 | " semail," /* 1 */ |
| 3016 | " ssub," /* 2 */ |
| 3017 | " fullcap(user.cap)" /* 3 */ |
| 3018 | " FROM subscriber LEFT JOIN user ON (login=suname)" |
| 3019 | " WHERE sverified" |
| 3020 | " AND NOT sdonotcall" |
| 3021 | " AND sdigest IS %s" |
| 3022 | " AND coalesce(subscriber.lastContact,subscriber.mtime)>=%d", |
| @@ -3028,19 +3100,30 @@ | |
| 3028 | const char *zSub = db_column_text(&q, 2); |
| 3029 | const char *zEmail = db_column_text(&q, 1); |
| 3030 | const char *zCap = db_column_text(&q, 3); |
| 3031 | int nHit = 0; |
| 3032 | for(p=pEvents; p; p=p->pNext){ |
| 3033 | if( strchr(zSub,p->type)==0 ) continue; |
| 3034 | if( p->needMod ){ |
| 3035 | /* For events that require moderator approval, only send an alert |
| 3036 | ** if the recipient is a moderator for that type of event. Setup |
| 3037 | ** and Admin users always get notified. */ |
| 3038 | char xType = '*'; |
| 3039 | if( strpbrk(zCap,"as")==0 ){ |
| 3040 | switch( p->type ){ |
| 3041 | case 'x': case 'f': xType = '5'; break; |
| 3042 | case 't': xType = 'q'; break; |
| 3043 | case 'w': xType = 'l'; break; |
| 3044 | } |
| 3045 | if( strchr(zCap,xType)==0 ) continue; |
| 3046 | } |
| @@ -3051,11 +3134,12 @@ | |
| 3051 | /* Other users only see the alert if they have sufficient |
| 3052 | ** privilege to view the event itself */ |
| 3053 | char xType = '*'; |
| 3054 | switch( p->type ){ |
| 3055 | case 'c': xType = 'o'; break; |
| 3056 | case 'x': case 'f': xType = '2'; break; |
| 3057 | case 't': xType = 'r'; break; |
| 3058 | case 'w': xType = 'j'; break; |
| 3059 | } |
| 3060 | if( strchr(zCap,xType)==0 ) continue; |
| 3061 | } |
| 3062 |
| --- src/alerts.c | |
| +++ src/alerts.c | |
| @@ -47,12 +47,15 @@ | |
| 47 | @ -- The ssub field is a string where each character indicates a particular |
| 48 | @ -- type of event to subscribe to. Choices: |
| 49 | @ -- a - Announcements |
| 50 | @ -- c - Check-ins |
| 51 | @ -- f - Forum posts |
| 52 | @ -- n - New forum threads |
| 53 | @ -- r - Replies to my own forum posts |
| 54 | @ -- t - Ticket changes |
| 55 | @ -- w - Wiki changes |
| 56 | @ -- x - Edits to forum posts |
| 57 | @ -- Probably different codes will be added in the future. In the future |
| 58 | @ -- we might also add a separate table that allows subscribing to email |
| 59 | @ -- notifications for specific branches or tags or tickets. |
| 60 | @ -- |
| 61 | @ CREATE TABLE repository.subscriber( |
| @@ -1556,10 +1559,12 @@ | |
| 1559 | if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin; |
| 1560 | if( suname && suname[0]==0 ) suname = 0; |
| 1561 | if( PB("sa") ) ssub[nsub++] = 'a'; |
| 1562 | if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; |
| 1563 | if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; |
| 1564 | if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n'; |
| 1565 | if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r'; |
| 1566 | if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; |
| 1567 | if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; |
| 1568 | if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; |
| 1569 | ssub[nsub] = 0; |
| 1570 | zCode = db_text(0, |
| @@ -1618,10 +1623,12 @@ | |
| 1623 | ** come from a prior Submit of the form) then default all of the |
| 1624 | ** subscription options to "on" */ |
| 1625 | cgi_set_parameter_nocopy("sa","1",1); |
| 1626 | if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1); |
| 1627 | if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1); |
| 1628 | if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1); |
| 1629 | if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1); |
| 1630 | if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1); |
| 1631 | if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1); |
| 1632 | } |
| 1633 | @ <p>To receive email notifications for changes to this |
| 1634 | @ repository, fill out the form below and press the "Submit" button.</p> |
| @@ -1675,13 +1682,17 @@ | |
| 1682 | @ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \ |
| 1683 | @ Check-ins</label><br> |
| 1684 | } |
| 1685 | if( g.perm.RdForum ){ |
| 1686 | @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ |
| 1687 | @ All Forum Posts</label><br> |
| 1688 | @ <label><input type="checkbox" name="sn" %s(PCK("sn"))> \ |
| 1689 | @ New Forum Threads</label><br> |
| 1690 | @ <label><input type="checkbox" name="sr" %s(PCK("sr"))> \ |
| 1691 | @ Replies To My Forum Posts</label><br> |
| 1692 | @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ |
| 1693 | @ Edits To Forum Posts</label><br> |
| 1694 | } |
| 1695 | if( g.perm.RdTkt ){ |
| 1696 | @ <label><input type="checkbox" name="st" %s(PCK("st"))> \ |
| 1697 | @ Ticket changes</label><br> |
| 1698 | } |
| @@ -1799,10 +1810,11 @@ | |
| 1810 | */ |
| 1811 | void alert_page(void){ |
| 1812 | const char *zName = 0; /* Value of the name= query parameter */ |
| 1813 | Stmt q; /* For querying the database */ |
| 1814 | int sa, sc, sf, st, sw, sx; /* Types of notifications requested */ |
| 1815 | int sn, sr; |
| 1816 | int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */ |
| 1817 | int isLogin; /* True if logged in as an individual */ |
| 1818 | const char *ssub = 0; /* Subscription flags */ |
| 1819 | const char *semail = 0; /* Email address */ |
| 1820 | const char *smip; /* */ |
| @@ -1855,10 +1867,12 @@ | |
| 1867 | sdigest = PB("sdigest"); |
| 1868 | semail = P("semail"); |
| 1869 | if( PB("sa") ) newSsub[nsub++] = 'a'; |
| 1870 | if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c'; |
| 1871 | if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f'; |
| 1872 | if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n'; |
| 1873 | if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r'; |
| 1874 | if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't'; |
| 1875 | if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w'; |
| 1876 | if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x'; |
| 1877 | newSsub[nsub] = 0; |
| 1878 | ssub = newSsub; |
| @@ -1951,10 +1965,12 @@ | |
| 1965 | sverified = db_column_int(&q, 1); |
| 1966 | } |
| 1967 | sa = strchr(ssub,'a')!=0; |
| 1968 | sc = strchr(ssub,'c')!=0; |
| 1969 | sf = strchr(ssub,'f')!=0; |
| 1970 | sn = strchr(ssub,'n')!=0; |
| 1971 | sr = strchr(ssub,'r')!=0; |
| 1972 | st = strchr(ssub,'t')!=0; |
| 1973 | sw = strchr(ssub,'w')!=0; |
| 1974 | sx = strchr(ssub,'x')!=0; |
| 1975 | smip = db_column_text(&q, 5); |
| 1976 | mtime = db_column_text(&q, 7); |
| @@ -2056,13 +2072,17 @@ | |
| 2072 | @ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\ |
| 2073 | @ Check-ins</label><br> |
| 2074 | } |
| 2075 | if( g.perm.RdForum ){ |
| 2076 | @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ |
| 2077 | @ All Forum Posts</label><br> |
| 2078 | @ <label><input type="checkbox" name="sn" %s(sn?"checked":"")>\ |
| 2079 | @ New Forum Threads</label><br> |
| 2080 | @ <label><input type="checkbox" name="sr" %s(sr?"checked":"")>\ |
| 2081 | @ Replies To My Posts</label><br> |
| 2082 | @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ |
| 2083 | @ Edits To Forum Posts</label><br> |
| 2084 | } |
| 2085 | if( g.perm.RdTkt ){ |
| 2086 | @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ |
| 2087 | @ Ticket changes</label><br> |
| 2088 | } |
| @@ -2484,20 +2504,24 @@ | |
| 2504 | ** |
| 2505 | ** type values: |
| 2506 | ** |
| 2507 | ** c A new check-in |
| 2508 | ** f An original forum post |
| 2509 | ** n New forum threads |
| 2510 | ** r Replies to my forum posts |
| 2511 | ** x An edit to a prior forum post |
| 2512 | ** t A new ticket or a change to an existing ticket |
| 2513 | ** w A change to a wiki page |
| 2514 | ** x Edits to forum posts |
| 2515 | */ |
| 2516 | struct EmailEvent { |
| 2517 | int type; /* 'c', 'f', 'n', 'r', 't', 'w', 'x' */ |
| 2518 | int needMod; /* Pending moderator approval */ |
| 2519 | Blob hdr; /* Header content, for forum entries */ |
| 2520 | Blob txt; /* Text description to appear in an alert */ |
| 2521 | char *zFromName; /* Human name of the sender */ |
| 2522 | char *zPriors; /* Upthread sender IDs for forum posts */ |
| 2523 | EmailEvent *pNext; /* Next in chronological order */ |
| 2524 | }; |
| 2525 | #endif |
| 2526 | |
| 2527 | /* |
| @@ -2507,14 +2531,44 @@ | |
| 2531 | while( p ){ |
| 2532 | EmailEvent *pNext = p->pNext; |
| 2533 | blob_reset(&p->txt); |
| 2534 | blob_reset(&p->hdr); |
| 2535 | fossil_free(p->zFromName); |
| 2536 | fossil_free(p->zPriors); |
| 2537 | fossil_free(p); |
| 2538 | p = pNext; |
| 2539 | } |
| 2540 | } |
| 2541 | |
| 2542 | /* |
| 2543 | ** Compute a string that is appropriate for the EmailEvent.zPriors field |
| 2544 | ** for a particular forum post. |
| 2545 | ** |
| 2546 | ** This string is an encode list of sender names and rids for all ancestors |
| 2547 | ** of the fpdi post - the post that fpid answer, the post that that parent |
| 2548 | ** post answers, and so forth back up to the root post. Duplicates sender |
| 2549 | ** names are omitted. |
| 2550 | ** |
| 2551 | ** The EmailEvent.zPriors field is used to screen events for people who |
| 2552 | ** only want to see replies to their own posts or to specific posts. |
| 2553 | */ |
| 2554 | static char *alert_compute_priors(int fpid){ |
| 2555 | return db_text(0, |
| 2556 | "WITH priors(rid,who) AS (" |
| 2557 | " SELECT firt, coalesce(euser,user)" |
| 2558 | " FROM forumpost LEFT JOIN event ON fpid=objid" |
| 2559 | " WHERE fpid=%d" |
| 2560 | " UNION ALL" |
| 2561 | " SELECT firt, coalesce(euser,user)" |
| 2562 | " FROM priors, forumpost LEFT JOIN event ON fpid=objid" |
| 2563 | " WHERE fpid=rid" |
| 2564 | ")" |
| 2565 | "SELECT ','||group_concat(DISTINCT 'u'||who)||" |
| 2566 | "','||group_concat(rid) FROM priors;", |
| 2567 | fpid |
| 2568 | ); |
| 2569 | } |
| 2570 | |
| 2571 | /* |
| 2572 | ** Compute and return a linked list of EmailEvent objects |
| 2573 | ** corresponding to the current content of the temp.wantalert |
| 2574 | ** table which should be defined as follows: |
| @@ -2644,11 +2698,12 @@ | |
| 2698 | " ORDER BY event.mtime" |
| 2699 | ); |
| 2700 | zFrom = db_get("email-self",0); |
| 2701 | zSub = db_get("email-subname",""); |
| 2702 | while( db_step(&q)==SQLITE_ROW ){ |
| 2703 | int fpid = db_column_int(&q,0); |
| 2704 | Manifest *pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| 2705 | const char *zIrt; |
| 2706 | const char *zUuid; |
| 2707 | const char *zTitle; |
| 2708 | const char *z; |
| 2709 | if( pPost==0 ) continue; |
| @@ -2657,10 +2712,11 @@ | |
| 2712 | pLast = p; |
| 2713 | p->type = db_column_int(&q,7) ? 'f' : 'x'; |
| 2714 | p->needMod = db_column_int(&q, 5); |
| 2715 | z = db_column_text(&q,6); |
| 2716 | p->zFromName = z && z[0] ? fossil_strdup(z) : 0; |
| 2717 | p->zPriors = alert_compute_priors(fpid); |
| 2718 | p->pNext = 0; |
| 2719 | blob_init(&p->hdr, 0, 0); |
| 2720 | zUuid = db_column_text(&q, 1); |
| 2721 | zTitle = db_column_text(&q, 3); |
| 2722 | if( p->needMod ){ |
| @@ -2864,10 +2920,25 @@ | |
| 2920 | "immediately, visit the following webpage:\n\n" |
| 2921 | " %s/alerts/%s\n\n", |
| 2922 | ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode |
| 2923 | ); |
| 2924 | } |
| 2925 | |
| 2926 | /* |
| 2927 | ** If zUser is a sender of one of the ancestors of a forum post |
| 2928 | ** (if zUser appears in zPriors) then return true. |
| 2929 | */ |
| 2930 | static int alert_in_priors(const char *zUser, const char *zPriors){ |
| 2931 | int n = (int)strlen(zUser); |
| 2932 | char zBuf[200]; |
| 2933 | if( n>195 ) return 0; |
| 2934 | if( zPriors==0 || zPriors[0]==0 ) return 0; |
| 2935 | zBuf[0] = ','; |
| 2936 | zBuf[1] = 'u'; |
| 2937 | memcpy(zBuf+2, zUser, n+1); |
| 2938 | return strstr(zPriors, zBuf)!=0; |
| 2939 | } |
| 2940 | |
| 2941 | #if INTERFACE |
| 2942 | /* |
| 2943 | ** Flags for alert_send_alerts() |
| 2944 | */ |
| @@ -3012,11 +3083,12 @@ | |
| 3083 | db_prepare(&q, |
| 3084 | "SELECT" |
| 3085 | " hex(subscriberCode)," /* 0 */ |
| 3086 | " semail," /* 1 */ |
| 3087 | " ssub," /* 2 */ |
| 3088 | " fullcap(user.cap)," /* 3 */ |
| 3089 | " suname" /* 4 */ |
| 3090 | " FROM subscriber LEFT JOIN user ON (login=suname)" |
| 3091 | " WHERE sverified" |
| 3092 | " AND NOT sdonotcall" |
| 3093 | " AND sdigest IS %s" |
| 3094 | " AND coalesce(subscriber.lastContact,subscriber.mtime)>=%d", |
| @@ -3028,19 +3100,30 @@ | |
| 3100 | const char *zSub = db_column_text(&q, 2); |
| 3101 | const char *zEmail = db_column_text(&q, 1); |
| 3102 | const char *zCap = db_column_text(&q, 3); |
| 3103 | int nHit = 0; |
| 3104 | for(p=pEvents; p; p=p->pNext){ |
| 3105 | if( strchr(zSub,p->type)==0 ){ |
| 3106 | if( p->type!='f' ) continue; |
| 3107 | if( strchr(zSub,'n')!=0 && (p->zPriors==0 || p->zPriors[0]==0) ){ |
| 3108 | /* New post: accepted */ |
| 3109 | }else if( strchr(zSub,'r')!=0 |
| 3110 | && alert_in_priors(db_column_text(&q,4), p->zPriors) ){ |
| 3111 | /* A follow-up to a post written by the user: accept */ |
| 3112 | }else{ |
| 3113 | continue; |
| 3114 | } |
| 3115 | } |
| 3116 | if( p->needMod ){ |
| 3117 | /* For events that require moderator approval, only send an alert |
| 3118 | ** if the recipient is a moderator for that type of event. Setup |
| 3119 | ** and Admin users always get notified. */ |
| 3120 | char xType = '*'; |
| 3121 | if( strpbrk(zCap,"as")==0 ){ |
| 3122 | switch( p->type ){ |
| 3123 | case 'x': case 'f': |
| 3124 | case 'n': case 'r': xType = '5'; break; |
| 3125 | case 't': xType = 'q'; break; |
| 3126 | case 'w': xType = 'l'; break; |
| 3127 | } |
| 3128 | if( strchr(zCap,xType)==0 ) continue; |
| 3129 | } |
| @@ -3051,11 +3134,12 @@ | |
| 3134 | /* Other users only see the alert if they have sufficient |
| 3135 | ** privilege to view the event itself */ |
| 3136 | char xType = '*'; |
| 3137 | switch( p->type ){ |
| 3138 | case 'c': xType = 'o'; break; |
| 3139 | case 'x': case 'f': |
| 3140 | case 'n': case 'r': xType = '2'; break; |
| 3141 | case 't': xType = 'r'; break; |
| 3142 | case 'w': xType = 'j'; break; |
| 3143 | } |
| 3144 | if( strchr(zCap,xType)==0 ) continue; |
| 3145 | } |
| 3146 |