Fossil SCM
Add logic to generate the text of email alert messages.
Commit
bb30d02efdb768b424b05efa50649bfba11b0e447ce78f36bd21b099a017c912
Parent
d51ca5f567349b4…
1 file changed
+151
-8
+151
-8
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -166,16 +166,46 @@ | ||
| 166 | 166 | email_subscriber_list_link(); |
| 167 | 167 | style_header("Email Notification Setup"); |
| 168 | 168 | @ <form action="%R/setup_email" method="post"><div> |
| 169 | 169 | @ <input type="submit" name="submit" value="Apply Changes" /><hr> |
| 170 | 170 | login_insert_csrf_secret(); |
| 171 | + | |
| 172 | + entry_attribute("Canonical Server URL", 40, "email-url", | |
| 173 | + "eurl", "", 0); | |
| 174 | + @ <p><b>Required.</b> | |
| 175 | + @ This is URL used as the basename for hyperlinks included in | |
| 176 | + @ email alert text. Omit the trailing "/". | |
| 177 | + @ Suggested value: "%h(g.zBaseURL)" | |
| 178 | + @ (Property: "email-url")</p> | |
| 179 | + @ <hr> | |
| 180 | + | |
| 181 | + entry_attribute("\"From\" email address", 20, "email-self", | |
| 182 | + "eself", "", 0); | |
| 183 | + @ <p><b>Required.</b> | |
| 184 | + @ This is the email from which email notifications are sent. The | |
| 185 | + @ system administrator should arrange for emails sent to this address | |
| 186 | + @ to be handed off to the "fossil email incoming" command so that Fossil | |
| 187 | + @ can handle bounces. (Property: "email-self")</p> | |
| 188 | + @ <hr> | |
| 189 | + | |
| 190 | + entry_attribute("Repository Nickname", 16, "email-subname", | |
| 191 | + "enn", "", 0); | |
| 192 | + @ <p><b>Required.</b> | |
| 193 | + @ This is short name used to identifies the repository in the | |
| 194 | + @ Subject: line of email alerts. Traditionally this name is | |
| 195 | + @ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]". | |
| 196 | + @ (Property: "email-subname")</p> | |
| 197 | + @ <hr> | |
| 198 | + | |
| 171 | 199 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 172 | 200 | "off", count(azSendMethods)/2, azSendMethods); |
| 173 | 201 | @ <p>How to send email. The "Pipe to a command" |
| 174 | 202 | @ method is the usual choice in production. |
| 175 | 203 | @ (Property: "email-send-method")</p> |
| 176 | 204 | @ <hr> |
| 205 | + | |
| 206 | + | |
| 177 | 207 | entry_attribute("Command To Pipe Email To", 80, "email-send-command", |
| 178 | 208 | "ecmd", "sendmail -t", 0); |
| 179 | 209 | @ <p>When the send method is "pipe to a command", this is the command |
| 180 | 210 | @ that is run. Email messages are piped into the standard input of this |
| 181 | 211 | @ command. The command is expected to extract the sender address, |
| @@ -193,18 +223,10 @@ | ||
| 193 | 223 | @ <p>When the send method is "store in a directory", each email message is |
| 194 | 224 | @ stored as a separate file in the directory shown here. |
| 195 | 225 | @ (Property: "email-send-dir")</p> |
| 196 | 226 | @ <hr> |
| 197 | 227 | |
| 198 | - entry_attribute("\"From\" email address", 40, "email-self", | |
| 199 | - "eself", "", 0); | |
| 200 | - @ <p>This is the email from which email notifications are sent. The | |
| 201 | - @ system administrator should arrange for emails sent to this address | |
| 202 | - @ to be handed off to the "fossil email incoming" command so that Fossil | |
| 203 | - @ can handle bounces. (Property: "email-self")</p> | |
| 204 | - @ <hr> | |
| 205 | - | |
| 206 | 228 | entry_attribute("Administrator email address", 40, "email-admin", |
| 207 | 229 | "eadmin", "", 0); |
| 208 | 230 | @ <p>This is the email for the human administrator for the system. |
| 209 | 231 | @ Abuse and trouble reports are send here. |
| 210 | 232 | @ (Property: "email-admin")</p> |
| @@ -1213,5 +1235,126 @@ | ||
| 1213 | 1235 | } |
| 1214 | 1236 | @ </table> |
| 1215 | 1237 | db_finalize(&q); |
| 1216 | 1238 | style_footer(); |
| 1217 | 1239 | } |
| 1240 | + | |
| 1241 | +#if LOCAL_INTERFACE | |
| 1242 | +/* Allowed values for the mAlert flags parameter to email_alert_text | |
| 1243 | +*/ | |
| 1244 | +#define ALERT_HTML 0x01 /* Generate HTML instead of plain text */ | |
| 1245 | +#endif | |
| 1246 | + | |
| 1247 | +/* | |
| 1248 | +** Append the text for a single alert to the end of pOut | |
| 1249 | +*/ | |
| 1250 | +void email_one_alert(const char *zEvent, u32 mAlert, Blob *pOut){ | |
| 1251 | + static Stmt q; | |
| 1252 | + int id; | |
| 1253 | + const char *zType = ""; | |
| 1254 | + db_static_prepare(&q, | |
| 1255 | + "SELECT" | |
| 1256 | + " blob.uuid," /* 0 */ | |
| 1257 | + " datetime(event.mtime)," /* 1 */ | |
| 1258 | + " coalesce(ecomment,comment)" | |
| 1259 | + " || ' (user: ' || coalesce(euser,user,'?')" | |
| 1260 | + " || (SELECT case when length(x)>0 then ' tags: ' || x else '' end" | |
| 1261 | + " FROM (SELECT group_concat(substr(tagname,5), ', ') AS x" | |
| 1262 | + " FROM tag, tagxref" | |
| 1263 | + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" | |
| 1264 | + " AND tagxref.rid=blob.rid AND tagxref.tagtype>0))" | |
| 1265 | + " || ')' as comment," /* 2 */ | |
| 1266 | + " tagxref.value AS branch" /* 3 */ | |
| 1267 | + " FROM tag CROSS JOIN event CROSS JOIN blob" | |
| 1268 | + " LEFT JOIN tagxref ON tagxref.tagid=tag.tagid" | |
| 1269 | + " AND tagxref.tagtype>0" | |
| 1270 | + " AND tagxref.rid=blob.rid" | |
| 1271 | + " WHERE blob.rid=event.objid" | |
| 1272 | + " AND tag.tagname='branch'" | |
| 1273 | + " AND event.objid=:objid" | |
| 1274 | + ); | |
| 1275 | + switch( zEvent[0] ){ | |
| 1276 | + case 'c': zType = "Check-In"; break; | |
| 1277 | + case 't': zType = "Wiki Edit"; break; | |
| 1278 | + case 'w': zType = "Ticket Change"; break; | |
| 1279 | + default: return; | |
| 1280 | + } | |
| 1281 | + id = atoi(zEvent+1); | |
| 1282 | + if( id<=0 ) return; | |
| 1283 | + db_bind_int(&q, ":objid", id); | |
| 1284 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 1285 | + blob_appendf(pOut,"\n== %s %s ==\n%s\n%s/info/%.20s\n", | |
| 1286 | + db_column_text(&q,1), | |
| 1287 | + zType, | |
| 1288 | + db_column_text(&q,2), | |
| 1289 | + db_get("email-url","http://localhost:8080"), | |
| 1290 | + db_column_text(&q,0) | |
| 1291 | + ); | |
| 1292 | + } | |
| 1293 | + db_reset(&q); | |
| 1294 | +} | |
| 1295 | + | |
| 1296 | +/* | |
| 1297 | +** Put a header on an alert email | |
| 1298 | +*/ | |
| 1299 | +void email_header(u32 mAlert, Blob *pOut){ | |
| 1300 | + blob_appendf(pOut, | |
| 1301 | + "This is an automated email reporting changes " | |
| 1302 | + "on Fossil repository %s (%s/timeline)\n", | |
| 1303 | + db_get("email-subname","(unknown)"), | |
| 1304 | + db_get("email-url","http://localhost:8080")); | |
| 1305 | +} | |
| 1306 | + | |
| 1307 | +/* | |
| 1308 | +** Append the "unsubscribe" notification and other footer text to | |
| 1309 | +** the end of an email alert being assemblied in pOut. | |
| 1310 | +*/ | |
| 1311 | +void email_footer(u32 mAlert, Blob *pOut){ | |
| 1312 | + blob_appendf(pOut, "\n%.72c\nTo unsubscribe: %s/unsubscribe\n", | |
| 1313 | + '-', db_get("email-url","http://localhost:8080")); | |
| 1314 | +} | |
| 1315 | + | |
| 1316 | +/* | |
| 1317 | +** COMMAND: test-generate-alert | |
| 1318 | +** | |
| 1319 | +** Usage: %fossil test-generate-alert [--html] [--actual] EVENTID ... | |
| 1320 | +** | |
| 1321 | +** Generate the text of an email alert for all of the EVENTIDs | |
| 1322 | +** listed on the command-line. Write that text to standard | |
| 1323 | +** output. If the --actual flag is present, then the EVENTIDs are | |
| 1324 | +** the actual event-ids in the pending_alert table. | |
| 1325 | +** | |
| 1326 | +** This command is intended for testing and debugging the logic | |
| 1327 | +** that generates email alert text. | |
| 1328 | +** | |
| 1329 | +** The mimetype is text/plain by default. Use the --html option | |
| 1330 | +** to generate text/html alert text. | |
| 1331 | +*/ | |
| 1332 | +void test_generate_alert_cmd(void){ | |
| 1333 | + u32 mAlert = 0; | |
| 1334 | + int bActual = find_option("actual",0,0)!=0; | |
| 1335 | + Blob out; | |
| 1336 | + int i; | |
| 1337 | + | |
| 1338 | + if( find_option("html",0,0)!=0 ) mAlert |= ALERT_HTML; | |
| 1339 | + db_find_and_open_repository(0, 0); | |
| 1340 | + verify_all_options(); | |
| 1341 | + email_schema(); | |
| 1342 | + blob_init(&out, 0, 0); | |
| 1343 | + email_header(mAlert, &out); | |
| 1344 | + if( bActual ){ | |
| 1345 | + Stmt q; | |
| 1346 | + db_prepare(&q, "SELECT eventid FROM pending_alert"); | |
| 1347 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1348 | + email_one_alert(db_column_text(&q,0), mAlert, &out); | |
| 1349 | + } | |
| 1350 | + db_finalize(&q); | |
| 1351 | + }else{ | |
| 1352 | + int i; | |
| 1353 | + for(i=2; i<g.argc; i++){ | |
| 1354 | + email_one_alert(g.argv[i], mAlert, &out); | |
| 1355 | + } | |
| 1356 | + } | |
| 1357 | + email_footer(mAlert, &out); | |
| 1358 | + fossil_print("%s", blob_str(&out)); | |
| 1359 | + blob_zero(&out); | |
| 1360 | +} | |
| 1218 | 1361 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -166,16 +166,46 @@ | |
| 166 | email_subscriber_list_link(); |
| 167 | style_header("Email Notification Setup"); |
| 168 | @ <form action="%R/setup_email" method="post"><div> |
| 169 | @ <input type="submit" name="submit" value="Apply Changes" /><hr> |
| 170 | login_insert_csrf_secret(); |
| 171 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 172 | "off", count(azSendMethods)/2, azSendMethods); |
| 173 | @ <p>How to send email. The "Pipe to a command" |
| 174 | @ method is the usual choice in production. |
| 175 | @ (Property: "email-send-method")</p> |
| 176 | @ <hr> |
| 177 | entry_attribute("Command To Pipe Email To", 80, "email-send-command", |
| 178 | "ecmd", "sendmail -t", 0); |
| 179 | @ <p>When the send method is "pipe to a command", this is the command |
| 180 | @ that is run. Email messages are piped into the standard input of this |
| 181 | @ command. The command is expected to extract the sender address, |
| @@ -193,18 +223,10 @@ | |
| 193 | @ <p>When the send method is "store in a directory", each email message is |
| 194 | @ stored as a separate file in the directory shown here. |
| 195 | @ (Property: "email-send-dir")</p> |
| 196 | @ <hr> |
| 197 | |
| 198 | entry_attribute("\"From\" email address", 40, "email-self", |
| 199 | "eself", "", 0); |
| 200 | @ <p>This is the email from which email notifications are sent. The |
| 201 | @ system administrator should arrange for emails sent to this address |
| 202 | @ to be handed off to the "fossil email incoming" command so that Fossil |
| 203 | @ can handle bounces. (Property: "email-self")</p> |
| 204 | @ <hr> |
| 205 | |
| 206 | entry_attribute("Administrator email address", 40, "email-admin", |
| 207 | "eadmin", "", 0); |
| 208 | @ <p>This is the email for the human administrator for the system. |
| 209 | @ Abuse and trouble reports are send here. |
| 210 | @ (Property: "email-admin")</p> |
| @@ -1213,5 +1235,126 @@ | |
| 1213 | } |
| 1214 | @ </table> |
| 1215 | db_finalize(&q); |
| 1216 | style_footer(); |
| 1217 | } |
| 1218 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -166,16 +166,46 @@ | |
| 166 | email_subscriber_list_link(); |
| 167 | style_header("Email Notification Setup"); |
| 168 | @ <form action="%R/setup_email" method="post"><div> |
| 169 | @ <input type="submit" name="submit" value="Apply Changes" /><hr> |
| 170 | login_insert_csrf_secret(); |
| 171 | |
| 172 | entry_attribute("Canonical Server URL", 40, "email-url", |
| 173 | "eurl", "", 0); |
| 174 | @ <p><b>Required.</b> |
| 175 | @ This is URL used as the basename for hyperlinks included in |
| 176 | @ email alert text. Omit the trailing "/". |
| 177 | @ Suggested value: "%h(g.zBaseURL)" |
| 178 | @ (Property: "email-url")</p> |
| 179 | @ <hr> |
| 180 | |
| 181 | entry_attribute("\"From\" email address", 20, "email-self", |
| 182 | "eself", "", 0); |
| 183 | @ <p><b>Required.</b> |
| 184 | @ This is the email from which email notifications are sent. The |
| 185 | @ system administrator should arrange for emails sent to this address |
| 186 | @ to be handed off to the "fossil email incoming" command so that Fossil |
| 187 | @ can handle bounces. (Property: "email-self")</p> |
| 188 | @ <hr> |
| 189 | |
| 190 | entry_attribute("Repository Nickname", 16, "email-subname", |
| 191 | "enn", "", 0); |
| 192 | @ <p><b>Required.</b> |
| 193 | @ This is short name used to identifies the repository in the |
| 194 | @ Subject: line of email alerts. Traditionally this name is |
| 195 | @ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]". |
| 196 | @ (Property: "email-subname")</p> |
| 197 | @ <hr> |
| 198 | |
| 199 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 200 | "off", count(azSendMethods)/2, azSendMethods); |
| 201 | @ <p>How to send email. The "Pipe to a command" |
| 202 | @ method is the usual choice in production. |
| 203 | @ (Property: "email-send-method")</p> |
| 204 | @ <hr> |
| 205 | |
| 206 | |
| 207 | entry_attribute("Command To Pipe Email To", 80, "email-send-command", |
| 208 | "ecmd", "sendmail -t", 0); |
| 209 | @ <p>When the send method is "pipe to a command", this is the command |
| 210 | @ that is run. Email messages are piped into the standard input of this |
| 211 | @ command. The command is expected to extract the sender address, |
| @@ -193,18 +223,10 @@ | |
| 223 | @ <p>When the send method is "store in a directory", each email message is |
| 224 | @ stored as a separate file in the directory shown here. |
| 225 | @ (Property: "email-send-dir")</p> |
| 226 | @ <hr> |
| 227 | |
| 228 | entry_attribute("Administrator email address", 40, "email-admin", |
| 229 | "eadmin", "", 0); |
| 230 | @ <p>This is the email for the human administrator for the system. |
| 231 | @ Abuse and trouble reports are send here. |
| 232 | @ (Property: "email-admin")</p> |
| @@ -1213,5 +1235,126 @@ | |
| 1235 | } |
| 1236 | @ </table> |
| 1237 | db_finalize(&q); |
| 1238 | style_footer(); |
| 1239 | } |
| 1240 | |
| 1241 | #if LOCAL_INTERFACE |
| 1242 | /* Allowed values for the mAlert flags parameter to email_alert_text |
| 1243 | */ |
| 1244 | #define ALERT_HTML 0x01 /* Generate HTML instead of plain text */ |
| 1245 | #endif |
| 1246 | |
| 1247 | /* |
| 1248 | ** Append the text for a single alert to the end of pOut |
| 1249 | */ |
| 1250 | void email_one_alert(const char *zEvent, u32 mAlert, Blob *pOut){ |
| 1251 | static Stmt q; |
| 1252 | int id; |
| 1253 | const char *zType = ""; |
| 1254 | db_static_prepare(&q, |
| 1255 | "SELECT" |
| 1256 | " blob.uuid," /* 0 */ |
| 1257 | " datetime(event.mtime)," /* 1 */ |
| 1258 | " coalesce(ecomment,comment)" |
| 1259 | " || ' (user: ' || coalesce(euser,user,'?')" |
| 1260 | " || (SELECT case when length(x)>0 then ' tags: ' || x else '' end" |
| 1261 | " FROM (SELECT group_concat(substr(tagname,5), ', ') AS x" |
| 1262 | " FROM tag, tagxref" |
| 1263 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 1264 | " AND tagxref.rid=blob.rid AND tagxref.tagtype>0))" |
| 1265 | " || ')' as comment," /* 2 */ |
| 1266 | " tagxref.value AS branch" /* 3 */ |
| 1267 | " FROM tag CROSS JOIN event CROSS JOIN blob" |
| 1268 | " LEFT JOIN tagxref ON tagxref.tagid=tag.tagid" |
| 1269 | " AND tagxref.tagtype>0" |
| 1270 | " AND tagxref.rid=blob.rid" |
| 1271 | " WHERE blob.rid=event.objid" |
| 1272 | " AND tag.tagname='branch'" |
| 1273 | " AND event.objid=:objid" |
| 1274 | ); |
| 1275 | switch( zEvent[0] ){ |
| 1276 | case 'c': zType = "Check-In"; break; |
| 1277 | case 't': zType = "Wiki Edit"; break; |
| 1278 | case 'w': zType = "Ticket Change"; break; |
| 1279 | default: return; |
| 1280 | } |
| 1281 | id = atoi(zEvent+1); |
| 1282 | if( id<=0 ) return; |
| 1283 | db_bind_int(&q, ":objid", id); |
| 1284 | if( db_step(&q)==SQLITE_ROW ){ |
| 1285 | blob_appendf(pOut,"\n== %s %s ==\n%s\n%s/info/%.20s\n", |
| 1286 | db_column_text(&q,1), |
| 1287 | zType, |
| 1288 | db_column_text(&q,2), |
| 1289 | db_get("email-url","http://localhost:8080"), |
| 1290 | db_column_text(&q,0) |
| 1291 | ); |
| 1292 | } |
| 1293 | db_reset(&q); |
| 1294 | } |
| 1295 | |
| 1296 | /* |
| 1297 | ** Put a header on an alert email |
| 1298 | */ |
| 1299 | void email_header(u32 mAlert, Blob *pOut){ |
| 1300 | blob_appendf(pOut, |
| 1301 | "This is an automated email reporting changes " |
| 1302 | "on Fossil repository %s (%s/timeline)\n", |
| 1303 | db_get("email-subname","(unknown)"), |
| 1304 | db_get("email-url","http://localhost:8080")); |
| 1305 | } |
| 1306 | |
| 1307 | /* |
| 1308 | ** Append the "unsubscribe" notification and other footer text to |
| 1309 | ** the end of an email alert being assemblied in pOut. |
| 1310 | */ |
| 1311 | void email_footer(u32 mAlert, Blob *pOut){ |
| 1312 | blob_appendf(pOut, "\n%.72c\nTo unsubscribe: %s/unsubscribe\n", |
| 1313 | '-', db_get("email-url","http://localhost:8080")); |
| 1314 | } |
| 1315 | |
| 1316 | /* |
| 1317 | ** COMMAND: test-generate-alert |
| 1318 | ** |
| 1319 | ** Usage: %fossil test-generate-alert [--html] [--actual] EVENTID ... |
| 1320 | ** |
| 1321 | ** Generate the text of an email alert for all of the EVENTIDs |
| 1322 | ** listed on the command-line. Write that text to standard |
| 1323 | ** output. If the --actual flag is present, then the EVENTIDs are |
| 1324 | ** the actual event-ids in the pending_alert table. |
| 1325 | ** |
| 1326 | ** This command is intended for testing and debugging the logic |
| 1327 | ** that generates email alert text. |
| 1328 | ** |
| 1329 | ** The mimetype is text/plain by default. Use the --html option |
| 1330 | ** to generate text/html alert text. |
| 1331 | */ |
| 1332 | void test_generate_alert_cmd(void){ |
| 1333 | u32 mAlert = 0; |
| 1334 | int bActual = find_option("actual",0,0)!=0; |
| 1335 | Blob out; |
| 1336 | int i; |
| 1337 | |
| 1338 | if( find_option("html",0,0)!=0 ) mAlert |= ALERT_HTML; |
| 1339 | db_find_and_open_repository(0, 0); |
| 1340 | verify_all_options(); |
| 1341 | email_schema(); |
| 1342 | blob_init(&out, 0, 0); |
| 1343 | email_header(mAlert, &out); |
| 1344 | if( bActual ){ |
| 1345 | Stmt q; |
| 1346 | db_prepare(&q, "SELECT eventid FROM pending_alert"); |
| 1347 | while( db_step(&q)==SQLITE_ROW ){ |
| 1348 | email_one_alert(db_column_text(&q,0), mAlert, &out); |
| 1349 | } |
| 1350 | db_finalize(&q); |
| 1351 | }else{ |
| 1352 | int i; |
| 1353 | for(i=2; i<g.argc; i++){ |
| 1354 | email_one_alert(g.argv[i], mAlert, &out); |
| 1355 | } |
| 1356 | } |
| 1357 | email_footer(mAlert, &out); |
| 1358 | fossil_print("%s", blob_str(&out)); |
| 1359 | blob_zero(&out); |
| 1360 | } |
| 1361 |