| | @@ -291,17 +291,18 @@ |
| 291 | 291 | @ <hr> |
| 292 | 292 | |
| 293 | 293 | entry_attribute("Subscription Renewal Interval In Days", 8, |
| 294 | 294 | "email-renew-interval", "eri", "", 0); |
| 295 | 295 | @ <p> |
| 296 | | - @ If this value is a positive integer N, then email notification |
| 297 | | - @ subscriptions will be suspended N days after the last known |
| 296 | + @ If this value is a integer N greater than or equal to 14, then email |
| 297 | + @ notification subscriptions will be suspended N days after the last known |
| 298 | 298 | @ interaction with the user. This prevents sending notifications |
| 299 | | - @ to abandoned accounts. If a subscription gets close to expiring |
| 299 | + @ to abandoned accounts. If a subscription comes within 7 days of expiring, |
| 300 | 300 | @ a separate email goes out with the daily digest that prompts the |
| 301 | 301 | @ subscriber to click on a link to the "/renew" webpage in order to |
| 302 | | - @ extend their subscription. |
| 302 | + @ extend their subscription. Subscriptions never expire if this setting |
| 303 | + @ is less than 14 or is an empty string. |
| 303 | 304 | @ (Property: "email-renew-interval")</p> |
| 304 | 305 | @ <hr> |
| 305 | 306 | |
| 306 | 307 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 307 | 308 | "off", count(azSendMethods)/2, azSendMethods); |
| | @@ -968,10 +969,38 @@ |
| 968 | 969 | ** SETTING: email-subname width=16 |
| 969 | 970 | ** This is a short name used to identifies the repository in the Subject: |
| 970 | 971 | ** line of email alerts. Traditionally this name is included in square |
| 971 | 972 | ** brackets. Examples: "[fossil-src]", "[sqlite-src]". |
| 972 | 973 | */ |
| 974 | +/* |
| 975 | +** SETTING: email-renew-interval width=16 |
| 976 | +** If this setting as an integer N that is 14 or greater then email |
| 977 | +** notification is suspected for subscriptions that have a "last contact |
| 978 | +** time" of more than N days ago. The "last contact time" is recorded |
| 979 | +** in the SUBSCRIBER.LASTCONTACT entry of the database. Logging in, |
| 980 | +** sending a forum post, editing a wiki page, changing subscription settings |
| 981 | +** at /alerts, or visiting /renew all update the last contact time. |
| 982 | +** If this setting is not an integer value or is less than 14 or undefined, |
| 983 | +** then subscriptions never expire. |
| 984 | +*/ |
| 985 | +/* X-VARIABLE: email-renew-warning |
| 986 | +** X-VARIABLE: email-renew-cutoff |
| 987 | +** |
| 988 | +** These CONFIG table entries are not considered "settings" since their |
| 989 | +** values are computed and updated automatically. |
| 990 | +** |
| 991 | +** email-renew-cutoff is the lastContact cutoff for subscription. It |
| 992 | +** is measured in days since 1970-01-01. If The lastContact time for |
| 993 | +** a subscription is less than email-renew-cutoff, then now new emails |
| 994 | +** are sent to the subscriber. |
| 995 | +** |
| 996 | +** email-renew-warning is the time (in days since 1970-01-01) when the |
| 997 | +** last batch of "your subscription is about to expire" emails were |
| 998 | +** sent out. |
| 999 | +** |
| 1000 | +** email-renew-cutoff is normally 7 days behind email-renew-warning. |
| 1001 | +*/ |
| 973 | 1002 | /* |
| 974 | 1003 | ** SETTING: email-send-method width=5 default=off sensitive |
| 975 | 1004 | ** Determine the method used to send email. Allowed values are |
| 976 | 1005 | ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value |
| 977 | 1006 | ** means no email is ever sent. The "relay" value means emails are sent |
| | @@ -1032,29 +1061,32 @@ |
| 1032 | 1061 | ** a cron-job to make sure alerts are sent |
| 1033 | 1062 | ** in a timely manner. |
| 1034 | 1063 | ** Options: |
| 1035 | 1064 | ** |
| 1036 | 1065 | ** --digest Send digests |
| 1066 | +** --renewal Send subscription renewal |
| 1067 | +** notices |
| 1037 | 1068 | ** --test Write to standard output |
| 1038 | 1069 | ** |
| 1039 | 1070 | ** settings [NAME VALUE] With no arguments, list all email settings. |
| 1040 | 1071 | ** Or change the value of a single email setting. |
| 1041 | 1072 | ** |
| 1042 | 1073 | ** status Report on the status of the email alert |
| 1043 | 1074 | ** subsystem |
| 1044 | 1075 | ** |
| 1045 | | -** subscribers [PATTERN] List all subscribers matching PATTERN. |
| 1076 | +** subscribers [PATTERN] List all subscribers matching PATTERN. Either |
| 1077 | +** LIKE or GLOB wildcards can be used in PATTERN. |
| 1046 | 1078 | ** |
| 1047 | 1079 | ** test-message TO [OPTS] Send a single email message using whatever |
| 1048 | 1080 | ** email sending mechanism is currently configured. |
| 1049 | 1081 | ** Use this for testing the email notification |
| 1050 | 1082 | ** configuration. Options: |
| 1051 | 1083 | ** |
| 1052 | | -** --body FILENAME |
| 1053 | | -** --smtp-trace |
| 1054 | | -** --stdout |
| 1055 | | -** -S|--subject SUBJECT |
| 1084 | +** --body FILENAME Content from FILENAME |
| 1085 | +** --smtp-trace Trace SMTP processing |
| 1086 | +** --stdout Send msg to stdout |
| 1087 | +** -S|--subject SUBJECT Message "subject:" |
| 1056 | 1088 | ** |
| 1057 | 1089 | ** unsubscribe EMAIL Remove a single subscriber with the given EMAIL. |
| 1058 | 1090 | */ |
| 1059 | 1091 | void alert_cmd(void){ |
| 1060 | 1092 | const char *zCmd; |
| | @@ -1108,10 +1140,11 @@ |
| 1108 | 1140 | } |
| 1109 | 1141 | }else |
| 1110 | 1142 | if( strncmp(zCmd, "send", nCmd)==0 ){ |
| 1111 | 1143 | u32 eFlags = 0; |
| 1112 | 1144 | if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST; |
| 1145 | + if( find_option("renewal",0,0)!=0 ) eFlags |= SENDALERT_RENEWAL; |
| 1113 | 1146 | if( find_option("test",0,0)!=0 ){ |
| 1114 | 1147 | eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT; |
| 1115 | 1148 | } |
| 1116 | 1149 | verify_all_options(); |
| 1117 | 1150 | alert_send_alerts(eFlags); |
| | @@ -1137,10 +1170,12 @@ |
| 1137 | 1170 | if( strncmp(pSetting->name,"email-",6)!=0 ) continue; |
| 1138 | 1171 | print_setting(pSetting); |
| 1139 | 1172 | } |
| 1140 | 1173 | }else |
| 1141 | 1174 | if( strncmp(zCmd, "status", nCmd)==0 ){ |
| 1175 | + Stmt q; |
| 1176 | + int iCutoff; |
| 1142 | 1177 | int nSetting, n; |
| 1143 | 1178 | static const char *zFmt = "%-29s %d\n"; |
| 1144 | 1179 | const Setting *pSetting = setting_info(&nSetting); |
| 1145 | 1180 | db_open_config(1, 0); |
| 1146 | 1181 | verify_all_options(); |
| | @@ -1152,14 +1187,32 @@ |
| 1152 | 1187 | } |
| 1153 | 1188 | n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); |
| 1154 | 1189 | fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n); |
| 1155 | 1190 | n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest"); |
| 1156 | 1191 | fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n); |
| 1192 | + db_prepare(&q, |
| 1193 | + "SELECT" |
| 1194 | + " name," |
| 1195 | + " value," |
| 1196 | + " now()/86400-value," |
| 1197 | + " date(value*86400,'unixepoch')" |
| 1198 | + " FROM repository.config" |
| 1199 | + " WHERE name in ('email-renew-warning','email-renew-cutoff');"); |
| 1200 | + while( db_step(&q)==SQLITE_ROW ){ |
| 1201 | + fossil_print("%-29s %-6d (%d days ago on %s)\n", |
| 1202 | + db_column_text(&q, 0), |
| 1203 | + db_column_int(&q, 1), |
| 1204 | + db_column_int(&q, 2), |
| 1205 | + db_column_text(&q, 3)); |
| 1206 | + } |
| 1207 | + db_finalize(&q); |
| 1157 | 1208 | n = db_int(0,"SELECT count(*) FROM subscriber"); |
| 1158 | 1209 | fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n); |
| 1210 | + iCutoff = db_get_int("email-renew-cutoff", 0); |
| 1159 | 1211 | n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" |
| 1160 | | - " AND NOT sdonotcall AND length(ssub)>1"); |
| 1212 | + " AND NOT sdonotcall AND length(ssub)>1" |
| 1213 | + " AND lastContact>=%d", iCutoff); |
| 1161 | 1214 | fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n); |
| 1162 | 1215 | }else |
| 1163 | 1216 | if( strncmp(zCmd, "subscribers", nCmd)==0 ){ |
| 1164 | 1217 | Stmt q; |
| 1165 | 1218 | verify_all_options(); |
| | @@ -1790,11 +1843,13 @@ |
| 1790 | 1843 | " ssub," /* 4 */ |
| 1791 | 1844 | " smip," /* 5 */ |
| 1792 | 1845 | " suname," /* 6 */ |
| 1793 | 1846 | " datetime(mtime,'unixepoch')," /* 7 */ |
| 1794 | 1847 | " datetime(sctime,'unixepoch')," /* 8 */ |
| 1795 | | - " hex(subscriberCode)" /* 9 */ |
| 1848 | + " hex(subscriberCode)," /* 9 */ |
| 1849 | + " date(coalesce(lastContact*86400,mtime),'unixepoch')," /* 10 */ |
| 1850 | + " now()/86400 - coalesce(lastContact,mtime/86400)" /* 11 */ |
| 1796 | 1851 | " FROM subscriber WHERE subscriberId=%d", sid); |
| 1797 | 1852 | if( db_step(&q)!=SQLITE_ROW ){ |
| 1798 | 1853 | db_finalize(&q); |
| 1799 | 1854 | db_commit_transaction(); |
| 1800 | 1855 | cgi_redirect("subscribe"); |
| | @@ -1889,10 +1944,15 @@ |
| 1889 | 1944 | @ </tr> |
| 1890 | 1945 | @ <tr> |
| 1891 | 1946 | @ <td class='form_label'>Subscriber Code:</td> |
| 1892 | 1947 | @ <td>%h(db_column_text(&q,9))</td> |
| 1893 | 1948 | @ <tr> |
| 1949 | + @ <tr> |
| 1950 | + @ <td class='form_label'>Last Contact:</td> |
| 1951 | + @ <td>%h(db_column_text(&q,10)) ← \ |
| 1952 | + @ %,d(db_column_int(&q,11)) days ago</td> |
| 1953 | + @ </tr> |
| 1894 | 1954 | @ <td class="form_label">User:</td> |
| 1895 | 1955 | @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \ |
| 1896 | 1956 | @ size="30">\ |
| 1897 | 1957 | uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname); |
| 1898 | 1958 | if( uid ){ |
| | @@ -1963,14 +2023,16 @@ |
| 1963 | 2023 | } |
| 1964 | 2024 | |
| 1965 | 2025 | /* |
| 1966 | 2026 | ** WEBPAGE: renew |
| 1967 | 2027 | ** |
| 1968 | | -** Users visit this page to update the lastContact date on their |
| 1969 | | -** subscription. This prevents their subscriptions from expiring. |
| 1970 | | -** |
| 1971 | | -** A valid subscriber code is supplied in the name= query parameter. |
| 2028 | +** Users visit this page to update the last-contact date on their |
| 2029 | +** subscription. The last-contact date is the day that the subscriber |
| 2030 | +** last interacted with the repository. If the name= query parameter |
| 2031 | +** (or POST parameter) contains a valid subscriber code, then the last-contact |
| 2032 | +** subscription associated with that subscriber code is updated to be the |
| 2033 | +** current date. |
| 1972 | 2034 | */ |
| 1973 | 2035 | void renewal_page(void){ |
| 1974 | 2036 | const char *zName = P("name"); |
| 1975 | 2037 | int iInterval = db_get_int("email-renew-interval", 0); |
| 1976 | 2038 | Stmt s; |
| | @@ -2182,10 +2244,14 @@ |
| 2182 | 2244 | Stmt q; |
| 2183 | 2245 | sqlite3_int64 iNow; |
| 2184 | 2246 | int nTotal; |
| 2185 | 2247 | int nPending; |
| 2186 | 2248 | int nDel = 0; |
| 2249 | + int iCutoff = db_get_int("email-renew-cutoff",0); |
| 2250 | + int iWarning = db_get_int("email-renew-warning",0); |
| 2251 | + char zCutoffClr[8]; |
| 2252 | + char zWarnClr[8]; |
| 2187 | 2253 | if( alert_webpages_disabled() ) return; |
| 2188 | 2254 | login_check_credentials(); |
| 2189 | 2255 | if( !g.perm.Admin ){ |
| 2190 | 2256 | login_needed(0); |
| 2191 | 2257 | return; |
| | @@ -2240,10 +2306,12 @@ |
| 2240 | 2306 | style_submenu_element("Show All","%R/subscribers"); |
| 2241 | 2307 | } |
| 2242 | 2308 | blob_append_sql(&sql," ORDER BY mtime DESC"); |
| 2243 | 2309 | db_prepare_blob(&q, &sql); |
| 2244 | 2310 | iNow = time(0); |
| 2311 | + memcpy(zCutoffClr, hash_color("A"), sizeof(zCutoffClr)); |
| 2312 | + memcpy(zWarnClr, hash_color("HIJ"), sizeof(zWarnClr)); |
| 2245 | 2313 | @ <table border='1' class='sortable' \ |
| 2246 | 2314 | @ data-init-sort='6' data-column-types='tttttKKt'> |
| 2247 | 2315 | @ <thead> |
| 2248 | 2316 | @ <tr> |
| 2249 | 2317 | @ <th>Email |
| | @@ -2273,11 +2341,19 @@ |
| 2273 | 2341 | }else{ |
| 2274 | 2342 | @ <td>%h(zUname)</td> |
| 2275 | 2343 | } |
| 2276 | 2344 | @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td> |
| 2277 | 2345 | @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td> |
| 2278 | | - @ <td data-sortkey='%010llx(iContact)'>%z(human_readable_age(rContact))</td> |
| 2346 | + @ <td data-sortkey='%010llx(iContact)'>\ |
| 2347 | + if( iContact>iWarning ){ |
| 2348 | + @ <span>\ |
| 2349 | + }else if( iContact>iCutoff ){ |
| 2350 | + @ <span style='background-color:%s(zWarnClr);'>\ |
| 2351 | + }else{ |
| 2352 | + @ <span style='background-color:%s(zCutoffClr);'>\ |
| 2353 | + } |
| 2354 | + @ %z(human_readable_age(rContact))</td> |
| 2279 | 2355 | @ <td>%h(db_column_text(&q,7))</td> |
| 2280 | 2356 | @ </tr> |
| 2281 | 2357 | } |
| 2282 | 2358 | @ </tbody></table> |
| 2283 | 2359 | db_finalize(&q); |
| | @@ -2530,14 +2606,22 @@ |
| 2530 | 2606 | ** Usage: %fossil test-alert EVENTID ... |
| 2531 | 2607 | ** |
| 2532 | 2608 | ** Generate the text of an email alert for all of the EVENTIDs |
| 2533 | 2609 | ** listed on the command-line. Or if no events are listed on the |
| 2534 | 2610 | ** command line, generate text for all events named in the |
| 2535 | | -** pending_alert table. |
| 2611 | +** pending_alert table. The text of the email alerts appears on |
| 2612 | +** standard output. |
| 2613 | +** |
| 2614 | +** This command is intended for testing and debugging Fossil itself, |
| 2615 | +** for example when enhancing the email alert system or fixing bugs |
| 2616 | +** in the email alert system. If you are not making changes to the |
| 2617 | +** Fossil source code, this command is probably not useful to you. |
| 2536 | 2618 | ** |
| 2537 | | -** This command is intended for testing and debugging the logic |
| 2538 | | -** that generates email alert text. |
| 2619 | +** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' |
| 2620 | +** for check-in, forum, ticket, or wiki. The remaining text is a |
| 2621 | +** integer that references the EVENT.OBJID value for the event. |
| 2622 | +** Run /timeline?showid to see these OBJID values. |
| 2539 | 2623 | ** |
| 2540 | 2624 | ** Options: |
| 2541 | 2625 | ** |
| 2542 | 2626 | ** --digest Generate digest alert text |
| 2543 | 2627 | ** --needmod Assume all events are pending moderator approval |
| | @@ -2632,22 +2716,69 @@ |
| 2632 | 2716 | db_end_transaction(0); |
| 2633 | 2717 | if( doAuto ){ |
| 2634 | 2718 | alert_backoffice(SENDALERT_TRACE|mFlags); |
| 2635 | 2719 | } |
| 2636 | 2720 | } |
| 2721 | + |
| 2722 | +/* |
| 2723 | +** Construct the header and body for an email message that will alert |
| 2724 | +** a subscriber that their subscriptions are about to expire. |
| 2725 | +*/ |
| 2726 | +static void alert_renewal_msg( |
| 2727 | + Blob *pHdr, /* Write email header here */ |
| 2728 | + Blob *pBody, /* Write email body here */ |
| 2729 | + const char *zCode, /* The subscriber code */ |
| 2730 | + int lastContact, /* Last contact (days since 1970) */ |
| 2731 | + const char *zEAddr, /* Subscriber email address. Send to this. */ |
| 2732 | + const char *zSub, /* Subscription codes */ |
| 2733 | + const char *zRepoName, /* Name of the sending Fossil repostory */ |
| 2734 | + const char *zUrl /* URL for the sending Fossil repostory */ |
| 2735 | +){ |
| 2736 | + blob_appendf(pHdr,"To: <%s>\r\n", zEAddr); |
| 2737 | + blob_appendf(pHdr,"Subject: %s Subscription to %s expires soon\r\n", |
| 2738 | + zRepoName, zUrl); |
| 2739 | + blob_appendf(pBody, |
| 2740 | + "You are currently receiving email notification of the following kinds\n" |
| 2741 | + "of changes to the %s Fossil repository at %s:\n\n", |
| 2742 | + zRepoName, zUrl |
| 2743 | + ); |
| 2744 | + if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n"); |
| 2745 | + if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n"); |
| 2746 | + if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n"); |
| 2747 | + if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n"); |
| 2748 | + if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n"); |
| 2749 | + blob_appendf(pBody, |
| 2750 | + "\nTo continue receiving email notifications, click the following link\n" |
| 2751 | + "\n %s/renew/%s\n\n", |
| 2752 | + zUrl, zCode |
| 2753 | + ); |
| 2754 | + blob_appendf(pBody, |
| 2755 | + "If you take no action, your subscription will expire and you will be\n" |
| 2756 | + "unsubscribed in about a week. To make other changes or to unsubscribe\n" |
| 2757 | + "immediately, visit the following webpage:\n\n" |
| 2758 | + " %s/alerts/%s\n\n", |
| 2759 | + zUrl, zCode |
| 2760 | + ); |
| 2761 | +} |
| 2637 | 2762 | |
| 2638 | 2763 | #if INTERFACE |
| 2639 | 2764 | /* |
| 2640 | 2765 | ** Flags for alert_send_alerts() |
| 2641 | 2766 | */ |
| 2642 | 2767 | #define SENDALERT_DIGEST 0x0001 /* Send a digest */ |
| 2643 | 2768 | #define SENDALERT_PRESERVE 0x0002 /* Do not mark the task as done */ |
| 2644 | 2769 | #define SENDALERT_STDOUT 0x0004 /* Print emails instead of sending */ |
| 2645 | 2770 | #define SENDALERT_TRACE 0x0008 /* Trace operation for debugging */ |
| 2771 | +#define SENDALERT_RENEWAL 0x0010 /* Send renewal notices */ |
| 2646 | 2772 | |
| 2647 | 2773 | #endif /* INTERFACE */ |
| 2648 | 2774 | |
| 2775 | +/* |
| 2776 | +** Minimum number of days between renewal messages |
| 2777 | +*/ |
| 2778 | +#define ALERT_RENEWAL_MSG_FREQUENCY 7 /* Do renewals at most once/week */ |
| 2779 | + |
| 2649 | 2780 | /* |
| 2650 | 2781 | ** Send alert emails to subscribers. |
| 2651 | 2782 | ** |
| 2652 | 2783 | ** This procedure is run by either the backoffice, or in response to the |
| 2653 | 2784 | ** "fossil alerts send" command. Details of operation are controlled by |
| | @@ -2691,10 +2822,11 @@ |
| 2691 | 2822 | const char *zRepoName; |
| 2692 | 2823 | const char *zFrom; |
| 2693 | 2824 | const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; |
| 2694 | 2825 | AlertSender *pSender = 0; |
| 2695 | 2826 | u32 senderFlags = 0; |
| 2827 | + int iInterval = 0; /* Subscription renewal interval */ |
| 2696 | 2828 | |
| 2697 | 2829 | if( g.fSqlTrace ) fossil_trace("-- BEGIN alert_send_alerts(%u)\n", flags); |
| 2698 | 2830 | alert_schema(0); |
| 2699 | 2831 | if( !alert_enabled() && (flags & SENDALERT_STDOUT)==0 ) goto send_alert_done; |
| 2700 | 2832 | zUrl = db_get("email-url",0); |
| | @@ -2740,11 +2872,11 @@ |
| 2740 | 2872 | |
| 2741 | 2873 | /* Step 2: compute EmailEvent objects for every notification that |
| 2742 | 2874 | ** needs sending. |
| 2743 | 2875 | */ |
| 2744 | 2876 | pEvents = alert_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0); |
| 2745 | | - if( nEvent==0 ) goto send_alert_done; |
| 2877 | + if( nEvent==0 ) goto send_alert_expiration_warnings; |
| 2746 | 2878 | |
| 2747 | 2879 | /* Step 4a: Update the pending_alerts table to designate the |
| 2748 | 2880 | ** alerts as having all been sent. This is done *before* step (3) |
| 2749 | 2881 | ** so that a crash will not cause alerts to be sent multiple times. |
| 2750 | 2882 | ** Better a missed alert than being spammed with hundreds of alerts |
| | @@ -2775,13 +2907,16 @@ |
| 2775 | 2907 | " hex(subscriberCode)," /* 0 */ |
| 2776 | 2908 | " semail," /* 1 */ |
| 2777 | 2909 | " ssub," /* 2 */ |
| 2778 | 2910 | " fullcap(user.cap)" /* 3 */ |
| 2779 | 2911 | " FROM subscriber LEFT JOIN user ON (login=suname)" |
| 2780 | | - " WHERE sverified AND NOT sdonotcall" |
| 2781 | | - " AND sdigest IS %s", |
| 2782 | | - zDigest/*safe-for-%s*/ |
| 2912 | + " WHERE sverified" |
| 2913 | + " AND NOT sdonotcall" |
| 2914 | + " AND sdigest IS %s" |
| 2915 | + " AND coalesce(subscriber.lastContact,subscriber.mtime)>=%d", |
| 2916 | + zDigest/*safe-for-%s*/, |
| 2917 | + db_get_int("email-renew-cutoff",0) |
| 2783 | 2918 | ); |
| 2784 | 2919 | while( db_step(&q)==SQLITE_ROW ){ |
| 2785 | 2920 | const char *zCode = db_column_text(&q, 0); |
| 2786 | 2921 | const char *zSub = db_column_text(&q, 2); |
| 2787 | 2922 | const char *zEmail = db_column_text(&q, 1); |
| | @@ -2865,10 +3000,62 @@ |
| 2865 | 3000 | |
| 2866 | 3001 | /* Step 4b: Update the pending_alerts table to remove all of the |
| 2867 | 3002 | ** alerts that have been completely sent. |
| 2868 | 3003 | */ |
| 2869 | 3004 | db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep;"); |
| 3005 | + |
| 3006 | + /* Send renewal messages to subscribers whose subscriptions are about |
| 3007 | + ** to expire. Only do this if: |
| 3008 | + ** |
| 3009 | + ** (1) email-renew-interval is 14 or greater (or in other words if |
| 3010 | + ** subscription expiration is enabled). |
| 3011 | + ** |
| 3012 | + ** (2) The SENDALERT_RENEWAL flag is set |
| 3013 | + */ |
| 3014 | +send_alert_expiration_warnings: |
| 3015 | + if( (flags & SENDALERT_RENEWAL)!=0 |
| 3016 | + && (iInterval = db_get_int("email-renew-interval",0))>=14 |
| 3017 | + ){ |
| 3018 | + int iNow = (int)(time(0)/86400); |
| 3019 | + int iOldWarn = db_get_int("email-renew-warning",0); |
| 3020 | + int iNewWarn = iNow - iInterval + ALERT_RENEWAL_MSG_FREQUENCY; |
| 3021 | + if( iNewWarn >= iOldWarn + ALERT_RENEWAL_MSG_FREQUENCY ){ |
| 3022 | + db_prepare(&q, |
| 3023 | + "SELECT" |
| 3024 | + " hex(subscriberCode)," /* 0 */ |
| 3025 | + " lastContact," /* 1 */ |
| 3026 | + " semail," /* 2 */ |
| 3027 | + " ssub" /* 3 */ |
| 3028 | + " FROM subscriber" |
| 3029 | + " WHERE lastContact<=%d AND lastContact>%d" |
| 3030 | + " AND NOT sdonotcall" |
| 3031 | + " AND length(sdigest)>0", |
| 3032 | + iNewWarn, iOldWarn |
| 3033 | + ); |
| 3034 | + while( db_step(&q)==SQLITE_ROW ){ |
| 3035 | + Blob hdr, body; |
| 3036 | + blob_init(&hdr, 0, 0); |
| 3037 | + blob_init(&body, 0, 0); |
| 3038 | + alert_renewal_msg(&hdr, &body, |
| 3039 | + db_column_text(&q,0), |
| 3040 | + db_column_int(&q,1), |
| 3041 | + db_column_text(&q,2), |
| 3042 | + db_column_text(&q,3), |
| 3043 | + zRepoName, zUrl); |
| 3044 | + alert_send(pSender,&hdr,&body,0); |
| 3045 | + blob_reset(&hdr); |
| 3046 | + blob_reset(&body); |
| 3047 | + } |
| 3048 | + db_finalize(&q); |
| 3049 | + if( (flags & SENDALERT_PRESERVE)==0 ){ |
| 3050 | + if( iOldWarn>0 ){ |
| 3051 | + db_set_int("email-renew-cutoff", iOldWarn, 0); |
| 3052 | + } |
| 3053 | + db_set_int("email-renew-warning", iNewWarn, 0); |
| 3054 | + } |
| 3055 | + } |
| 3056 | + } |
| 2870 | 3057 | |
| 2871 | 3058 | send_alert_done: |
| 2872 | 3059 | alert_sender_free(pSender); |
| 2873 | 3060 | if( g.fSqlTrace ) fossil_trace("-- END alert_send_alerts(%u)\n", flags); |
| 2874 | 3061 | return nSent; |
| | @@ -2891,11 +3078,11 @@ |
| 2891 | 3078 | if( !alert_tables_exist() ) return 0; |
| 2892 | 3079 | nSent = alert_send_alerts(mFlags); |
| 2893 | 3080 | iJulianDay = db_int(0, "SELECT julianday('now')"); |
| 2894 | 3081 | if( iJulianDay>db_get_int("email-last-digest",0) ){ |
| 2895 | 3082 | db_set_int("email-last-digest",iJulianDay,0); |
| 2896 | | - nSent += alert_send_alerts(SENDALERT_DIGEST|mFlags); |
| 3083 | + nSent += alert_send_alerts(SENDALERT_DIGEST|SENDALERT_RENEWAL|mFlags); |
| 2897 | 3084 | } |
| 2898 | 3085 | return nSent; |
| 2899 | 3086 | } |
| 2900 | 3087 | |
| 2901 | 3088 | /* |
| 2902 | 3089 | |