Fossil SCM

Merged in trunk.

stephan 2021-05-31 04:11 plink-for-non-checkins merge
Commit 24ccc48160bdc45206412ae4c1d0df9b04cc2f0a15041c5e562932030eefc4c7
+114 -22
--- src/alerts.c
+++ src/alerts.c
@@ -64,11 +64,12 @@
6464
@ sdonotcall BOOLEAN, -- true for Do Not Call
6565
@ sdigest BOOLEAN, -- true for daily digests only
6666
@ ssub TEXT, -- baseline subscriptions
6767
@ sctime INTDATE, -- When this entry was created. unixtime
6868
@ mtime INTDATE, -- Last change. unixtime
69
-@ smip TEXT -- IP address of last change
69
+@ smip TEXT, -- IP address of last change
70
+@ lastContact INT -- Last contact. days since 1970
7071
@ );
7172
@ CREATE INDEX repository.subscriberUname
7273
@ ON subscriber(suname) WHERE suname IS NOT NULL;
7374
@
7475
@ DROP TABLE IF EXISTS repository.pending_alert;
@@ -83,28 +84,33 @@
8384
@ sentSep BOOLEAN DEFAULT false, -- individual alert sent
8485
@ sentDigest BOOLEAN DEFAULT false, -- digest alert sent
8586
@ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent
8687
@ ) WITHOUT ROWID;
8788
@
89
+@ -- Obsolete table. No longer used.
8890
@ DROP TABLE IF EXISTS repository.alert_bounce;
89
-@ -- Record bounced emails. If too many bounces are received within
90
-@ -- some defined time range, then cancel the subscription. Older
91
-@ -- entries are periodically purged.
92
-@ --
93
-@ CREATE TABLE repository.alert_bounce(
94
-@ subscriberId INTEGER, -- to whom the email was sent.
95
-@ sendTime INTEGER, -- seconds since 1970 when email was sent
96
-@ rcvdTime INTEGER -- seconds since 1970 when bounce was received
97
-@ );
9891
;
9992
10093
/*
10194
** Return true if the email notification tables exist.
10295
*/
10396
int alert_tables_exist(void){
10497
return db_table_exists("repository", "subscriber");
10598
}
99
+
100
+/*
101
+** Record the fact that user zUser has made contact with the repository.
102
+** This resets the subscription timeout on that user.
103
+*/
104
+void alert_user_contact(const char *zUser){
105
+ if( db_table_has_column("repository","subscriber","lastContact") ){
106
+ db_multi_exec(
107
+ "UPDATE subscriber SET lastContact=now()/86400 WHERE suname=%Q",
108
+ zUser
109
+ );
110
+ }
111
+}
106112
107113
/*
108114
** Make sure the table needed for email notification exist in the repository.
109115
**
110116
** If the bOnlyIfEnabled option is true, then tables are only created
@@ -116,16 +122,27 @@
116122
&& fossil_strcmp(db_get("email-send-method",0),"off")==0
117123
){
118124
return; /* Don't create table for disabled email */
119125
}
120126
db_exec_sql(zAlertInit);
121
- }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
122
- db_multi_exec(
123
- "ALTER TABLE repository.pending_alert"
124
- " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
125
- );
127
+ return;
126128
}
129
+ if( db_table_has_column("repository","subscriber","lastContact") ){
130
+ return;
131
+ }
132
+ db_multi_exec(
133
+ "DROP TABLE IF EXISTS repository.alert_bounde;\n"
134
+ "ALTER TABLE repository.subscriber ADD COLUMN lastContact INT;\n"
135
+ "UPDATE subscriber SET lastContact=mtime/86400;"
136
+ );
137
+ if( db_table_has_column("repository","pending_alert","sentMod") ){
138
+ return;
139
+ }
140
+ db_multi_exec(
141
+ "ALTER TABLE repository.pending_alert"
142
+ " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
143
+ );
127144
}
128145
129146
/*
130147
** Enable triggers that automatically populate the pending_alert
131148
** table.
@@ -270,10 +287,23 @@
270287
@ This is short name used to identifies the repository in the
271288
@ Subject: line of email alerts. Traditionally this name is
272289
@ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]".
273290
@ (Property: "email-subname")</p>
274291
@ <hr>
292
+
293
+ entry_attribute("Subscription Renewal Interval In Days", 8,
294
+ "email-renew-interval", "eri", "", 0);
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
298
+ @ interaction with the user. This prevents sending notifications
299
+ @ to abandoned accounts. If a subscription gets close to expiring
300
+ @ a separate email goes out with the daily digest that prompts the
301
+ @ subscriber to click on a link to the "/renew" webpage in order to
302
+ @ extend their subscription.
303
+ @ (Property: "email-renew-interval")</p>
304
+ @ <hr>
275305
276306
multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
277307
"off", count(azSendMethods)/2, azSendMethods);
278308
@ <p>How to send email. Requires auxiliary information from the fields
279309
@ that follow. Hint: Use the <a href="%R/announce">/announce</a> page
@@ -1391,12 +1421,12 @@
13911421
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
13921422
if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
13931423
ssub[nsub] = 0;
13941424
zCode = db_text(0,
13951425
"INSERT INTO subscriber(semail,suname,"
1396
- " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1397
- "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
1426
+ " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)"
1427
+ "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)"
13981428
"RETURNING hex(subscriberCode);",
13991429
/* semail */ zEAddr,
14001430
/* suname */ suname,
14011431
/* sverified */ needCaptcha==0,
14021432
/* sdigest */ PB("di"),
@@ -1641,10 +1671,11 @@
16411671
int eErr = 0; /* Type of error */
16421672
char *zErr = 0; /* Error message text */
16431673
int sid = 0; /* Subscriber ID */
16441674
int nName; /* Length of zName in bytes */
16451675
char *zHalfCode; /* prefix of subscriberCode */
1676
+ int keepAlive = 0; /* True to update the last contact time */
16461677
16471678
db_begin_transaction();
16481679
if( alert_webpages_disabled() ){
16491680
db_commit_transaction();
16501681
return;
@@ -1665,10 +1696,11 @@
16651696
sid = db_int(0,
16661697
"SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
16671698
" THEN subscriberId ELSE 0 END"
16681699
" FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
16691700
" LIMIT 1", zName, zName);
1701
+ if( sid ) keepAlive = 1;
16701702
}
16711703
if( sid==0 && isLogin ){
16721704
sid = db_int(0, "SELECT subscriberId FROM subscriber"
16731705
" WHERE suname=%Q", g.zLogin);
16741706
}
@@ -1697,11 +1729,12 @@
16971729
blob_init(&update, "UPDATE subscriber SET", -1);
16981730
blob_append_sql(&update,
16991731
" sdonotcall=%d,"
17001732
" sdigest=%d,"
17011733
" ssub=%Q,"
1702
- " mtime=strftime('%%s','now'),"
1734
+ " mtime=now(),"
1735
+ " lastContact=now()/86400,"
17031736
" smip=%Q",
17041737
sdonotcall,
17051738
sdigest,
17061739
ssub,
17071740
g.zIpAddr
@@ -1727,10 +1760,15 @@
17271760
if( eErr==0 ){
17281761
db_exec_sql(blob_str(&update));
17291762
ssub = 0;
17301763
}
17311764
blob_reset(&update);
1765
+ }else if( keepAlive ){
1766
+ db_multi_exec(
1767
+ "UPDATE subscriber SET lastContact=now()/86400"
1768
+ " WHERE subscriberId=%d", sid
1769
+ );
17321770
}
17331771
if( P("delete")!=0 && cgi_csrf_safe(1) ){
17341772
if( !PB("dodelete") ){
17351773
eErr = 9;
17361774
zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1921,10 +1959,59 @@
19211959
db_finalize(&q);
19221960
style_finish_page();
19231961
db_commit_transaction();
19241962
return;
19251963
}
1964
+
1965
+/*
1966
+** WEBPAGE: renew
1967
+**
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.
1972
+*/
1973
+void renewal_page(void){
1974
+ const char *zName = P("name");
1975
+ int iInterval = db_get_int("email-renew-interval", 0);
1976
+ Stmt s;
1977
+ int rc;
1978
+
1979
+ style_header("Subscription Renewal");
1980
+ if( zName==0 || strlen(zName)<4 ){
1981
+ @ <p>No subscription specified</p>
1982
+ style_finish_page();
1983
+ return;
1984
+ }
1985
+
1986
+ if( !db_table_has_column("repository","subscriber","lastContact")
1987
+ || iInterval<1
1988
+ ){
1989
+ @ <p>This repository does not expire email notification subscriptions.
1990
+ @ No renewals are necessary.</p>
1991
+ style_finish_page();
1992
+ return;
1993
+ }
1994
+
1995
+ db_prepare(&s,
1996
+ "UPDATE subscriber"
1997
+ " SET lastContact=now()/86400"
1998
+ " WHERE subscriberCode=hextoblob(%Q)"
1999
+ " RETURNING semail, date('now','+%d days');",
2000
+ zName, iInterval+1
2001
+ );
2002
+ rc = db_step(&s);
2003
+ if( rc==SQLITE_ROW ){
2004
+ @ <p>The email notification subscription for %h(db_column_text(&s,0))
2005
+ @ has been extended until %h(db_column_text(&s,1)) UTC.
2006
+ }else{
2007
+ @ <p>No such subscriber-id: %h(zName)</p>
2008
+ }
2009
+ db_finalize(&s);
2010
+ style_finish_page();
2011
+}
2012
+
19262013
19272014
/* This is the message that gets sent to describe how to change
19282015
** or modify a subscription
19292016
*/
19302017
static const char zUnsubMsg[] =
@@ -2111,11 +2198,11 @@
21112198
nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
21122199
if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
21132200
int nNewPending;
21142201
db_multi_exec(
21152202
"DELETE FROM subscriber"
2116
- " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')"
2203
+ " WHERE NOT sverified AND mtime<now()-86400"
21172204
);
21182205
nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
21192206
" WHERE NOT sverified");
21202207
nDel = nPending - nNewPending;
21212208
nPending = nNewPending;
@@ -2122,11 +2209,11 @@
21222209
nTotal -= nDel;
21232210
}
21242211
if( nPending>0 ){
21252212
@ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
21262213
if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
2127
- " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')")
2214
+ " WHERE NOT sverified AND mtime<now()-86400")
21282215
){
21292216
style_submenu_element("Purge Pending","subscribers?purge");
21302217
}
21312218
}else{
21322219
@ <h1>%,d(nTotal) Subscribers</h1>
@@ -2142,11 +2229,12 @@
21422229
" suname," /* 3 */
21432230
" sverified," /* 4 */
21442231
" sdigest," /* 5 */
21452232
" mtime," /* 6 */
21462233
" date(sctime,'unixepoch')," /* 7 */
2147
- " (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */
2234
+ " (SELECT uid FROM user WHERE login=subscriber.suname)," /* 8 */
2235
+ " coalesce(lastContact,mtime/86400)" /* 9 */
21482236
" FROM subscriber"
21492237
);
21502238
if( P("only")!=0 ){
21512239
blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
21522240
style_submenu_element("Show All","%R/subscribers");
@@ -2153,27 +2241,30 @@
21532241
}
21542242
blob_append_sql(&sql," ORDER BY mtime DESC");
21552243
db_prepare_blob(&q, &sql);
21562244
iNow = time(0);
21572245
@ <table border='1' class='sortable' \
2158
- @ data-init-sort='6' data-column-types='tttttKt'>
2246
+ @ data-init-sort='6' data-column-types='tttttKKt'>
21592247
@ <thead>
21602248
@ <tr>
21612249
@ <th>Email
21622250
@ <th>Events
21632251
@ <th>Digest-Only?
21642252
@ <th>User
21652253
@ <th>Verified?
21662254
@ <th>Last change
2255
+ @ <th>Last contact
21672256
@ <th>Created
21682257
@ </tr>
21692258
@ </thead><tbody>
21702259
while( db_step(&q)==SQLITE_ROW ){
21712260
sqlite3_int64 iMtime = db_column_int64(&q, 6);
21722261
double rAge = (iNow - iMtime)/86400.0;
21732262
int uid = db_column_int(&q, 8);
21742263
const char *zUname = db_column_text(&q, 3);
2264
+ sqlite3_int64 iContact = db_column_int64(&q, 9);
2265
+ double rContact = (iNow/86400) - iContact;
21752266
@ <tr>
21762267
@ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
21772268
@ %h(db_column_text(&q,1))</a></td>
21782269
@ <td>%h(db_column_text(&q,2))</td>
21792270
@ <td>%s(db_column_int(&q,5)?"digest":"")</td>
@@ -2182,10 +2273,11 @@
21822273
}else{
21832274
@ <td>%h(zUname)</td>
21842275
}
21852276
@ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
21862277
@ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
2278
+ @ <td data-sortkey='%010llx(iContact)'>%z(human_readable_age(rContact))</td>
21872279
@ <td>%h(db_column_text(&q,7))</td>
21882280
@ </tr>
21892281
}
21902282
@ </tbody></table>
21912283
db_finalize(&q);
21922284
--- src/alerts.c
+++ src/alerts.c
@@ -64,11 +64,12 @@
64 @ sdonotcall BOOLEAN, -- true for Do Not Call
65 @ sdigest BOOLEAN, -- true for daily digests only
66 @ ssub TEXT, -- baseline subscriptions
67 @ sctime INTDATE, -- When this entry was created. unixtime
68 @ mtime INTDATE, -- Last change. unixtime
69 @ smip TEXT -- IP address of last change
 
70 @ );
71 @ CREATE INDEX repository.subscriberUname
72 @ ON subscriber(suname) WHERE suname IS NOT NULL;
73 @
74 @ DROP TABLE IF EXISTS repository.pending_alert;
@@ -83,28 +84,33 @@
83 @ sentSep BOOLEAN DEFAULT false, -- individual alert sent
84 @ sentDigest BOOLEAN DEFAULT false, -- digest alert sent
85 @ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent
86 @ ) WITHOUT ROWID;
87 @
 
88 @ DROP TABLE IF EXISTS repository.alert_bounce;
89 @ -- Record bounced emails. If too many bounces are received within
90 @ -- some defined time range, then cancel the subscription. Older
91 @ -- entries are periodically purged.
92 @ --
93 @ CREATE TABLE repository.alert_bounce(
94 @ subscriberId INTEGER, -- to whom the email was sent.
95 @ sendTime INTEGER, -- seconds since 1970 when email was sent
96 @ rcvdTime INTEGER -- seconds since 1970 when bounce was received
97 @ );
98 ;
99
100 /*
101 ** Return true if the email notification tables exist.
102 */
103 int alert_tables_exist(void){
104 return db_table_exists("repository", "subscriber");
105 }
 
 
 
 
 
 
 
 
 
 
 
 
 
106
107 /*
108 ** Make sure the table needed for email notification exist in the repository.
109 **
110 ** If the bOnlyIfEnabled option is true, then tables are only created
@@ -116,16 +122,27 @@
116 && fossil_strcmp(db_get("email-send-method",0),"off")==0
117 ){
118 return; /* Don't create table for disabled email */
119 }
120 db_exec_sql(zAlertInit);
121 }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
122 db_multi_exec(
123 "ALTER TABLE repository.pending_alert"
124 " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
125 );
126 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127 }
128
129 /*
130 ** Enable triggers that automatically populate the pending_alert
131 ** table.
@@ -270,10 +287,23 @@
270 @ This is short name used to identifies the repository in the
271 @ Subject: line of email alerts. Traditionally this name is
272 @ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]".
273 @ (Property: "email-subname")</p>
274 @ <hr>
 
 
 
 
 
 
 
 
 
 
 
 
 
275
276 multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
277 "off", count(azSendMethods)/2, azSendMethods);
278 @ <p>How to send email. Requires auxiliary information from the fields
279 @ that follow. Hint: Use the <a href="%R/announce">/announce</a> page
@@ -1391,12 +1421,12 @@
1391 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1392 if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
1393 ssub[nsub] = 0;
1394 zCode = db_text(0,
1395 "INSERT INTO subscriber(semail,suname,"
1396 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1397 "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
1398 "RETURNING hex(subscriberCode);",
1399 /* semail */ zEAddr,
1400 /* suname */ suname,
1401 /* sverified */ needCaptcha==0,
1402 /* sdigest */ PB("di"),
@@ -1641,10 +1671,11 @@
1641 int eErr = 0; /* Type of error */
1642 char *zErr = 0; /* Error message text */
1643 int sid = 0; /* Subscriber ID */
1644 int nName; /* Length of zName in bytes */
1645 char *zHalfCode; /* prefix of subscriberCode */
 
1646
1647 db_begin_transaction();
1648 if( alert_webpages_disabled() ){
1649 db_commit_transaction();
1650 return;
@@ -1665,10 +1696,11 @@
1665 sid = db_int(0,
1666 "SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
1667 " THEN subscriberId ELSE 0 END"
1668 " FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
1669 " LIMIT 1", zName, zName);
 
1670 }
1671 if( sid==0 && isLogin ){
1672 sid = db_int(0, "SELECT subscriberId FROM subscriber"
1673 " WHERE suname=%Q", g.zLogin);
1674 }
@@ -1697,11 +1729,12 @@
1697 blob_init(&update, "UPDATE subscriber SET", -1);
1698 blob_append_sql(&update,
1699 " sdonotcall=%d,"
1700 " sdigest=%d,"
1701 " ssub=%Q,"
1702 " mtime=strftime('%%s','now'),"
 
1703 " smip=%Q",
1704 sdonotcall,
1705 sdigest,
1706 ssub,
1707 g.zIpAddr
@@ -1727,10 +1760,15 @@
1727 if( eErr==0 ){
1728 db_exec_sql(blob_str(&update));
1729 ssub = 0;
1730 }
1731 blob_reset(&update);
 
 
 
 
 
1732 }
1733 if( P("delete")!=0 && cgi_csrf_safe(1) ){
1734 if( !PB("dodelete") ){
1735 eErr = 9;
1736 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1921,10 +1959,59 @@
1921 db_finalize(&q);
1922 style_finish_page();
1923 db_commit_transaction();
1924 return;
1925 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1926
1927 /* This is the message that gets sent to describe how to change
1928 ** or modify a subscription
1929 */
1930 static const char zUnsubMsg[] =
@@ -2111,11 +2198,11 @@
2111 nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
2112 if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
2113 int nNewPending;
2114 db_multi_exec(
2115 "DELETE FROM subscriber"
2116 " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')"
2117 );
2118 nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
2119 " WHERE NOT sverified");
2120 nDel = nPending - nNewPending;
2121 nPending = nNewPending;
@@ -2122,11 +2209,11 @@
2122 nTotal -= nDel;
2123 }
2124 if( nPending>0 ){
2125 @ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
2126 if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
2127 " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')")
2128 ){
2129 style_submenu_element("Purge Pending","subscribers?purge");
2130 }
2131 }else{
2132 @ <h1>%,d(nTotal) Subscribers</h1>
@@ -2142,11 +2229,12 @@
2142 " suname," /* 3 */
2143 " sverified," /* 4 */
2144 " sdigest," /* 5 */
2145 " mtime," /* 6 */
2146 " date(sctime,'unixepoch')," /* 7 */
2147 " (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */
 
2148 " FROM subscriber"
2149 );
2150 if( P("only")!=0 ){
2151 blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
2152 style_submenu_element("Show All","%R/subscribers");
@@ -2153,27 +2241,30 @@
2153 }
2154 blob_append_sql(&sql," ORDER BY mtime DESC");
2155 db_prepare_blob(&q, &sql);
2156 iNow = time(0);
2157 @ <table border='1' class='sortable' \
2158 @ data-init-sort='6' data-column-types='tttttKt'>
2159 @ <thead>
2160 @ <tr>
2161 @ <th>Email
2162 @ <th>Events
2163 @ <th>Digest-Only?
2164 @ <th>User
2165 @ <th>Verified?
2166 @ <th>Last change
 
2167 @ <th>Created
2168 @ </tr>
2169 @ </thead><tbody>
2170 while( db_step(&q)==SQLITE_ROW ){
2171 sqlite3_int64 iMtime = db_column_int64(&q, 6);
2172 double rAge = (iNow - iMtime)/86400.0;
2173 int uid = db_column_int(&q, 8);
2174 const char *zUname = db_column_text(&q, 3);
 
 
2175 @ <tr>
2176 @ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
2177 @ %h(db_column_text(&q,1))</a></td>
2178 @ <td>%h(db_column_text(&q,2))</td>
2179 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
@@ -2182,10 +2273,11 @@
2182 }else{
2183 @ <td>%h(zUname)</td>
2184 }
2185 @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
2186 @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
 
2187 @ <td>%h(db_column_text(&q,7))</td>
2188 @ </tr>
2189 }
2190 @ </tbody></table>
2191 db_finalize(&q);
2192
--- src/alerts.c
+++ src/alerts.c
@@ -64,11 +64,12 @@
64 @ sdonotcall BOOLEAN, -- true for Do Not Call
65 @ sdigest BOOLEAN, -- true for daily digests only
66 @ ssub TEXT, -- baseline subscriptions
67 @ sctime INTDATE, -- When this entry was created. unixtime
68 @ mtime INTDATE, -- Last change. unixtime
69 @ smip TEXT, -- IP address of last change
70 @ lastContact INT -- Last contact. days since 1970
71 @ );
72 @ CREATE INDEX repository.subscriberUname
73 @ ON subscriber(suname) WHERE suname IS NOT NULL;
74 @
75 @ DROP TABLE IF EXISTS repository.pending_alert;
@@ -83,28 +84,33 @@
84 @ sentSep BOOLEAN DEFAULT false, -- individual alert sent
85 @ sentDigest BOOLEAN DEFAULT false, -- digest alert sent
86 @ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent
87 @ ) WITHOUT ROWID;
88 @
89 @ -- Obsolete table. No longer used.
90 @ DROP TABLE IF EXISTS repository.alert_bounce;
 
 
 
 
 
 
 
 
 
91 ;
92
93 /*
94 ** Return true if the email notification tables exist.
95 */
96 int alert_tables_exist(void){
97 return db_table_exists("repository", "subscriber");
98 }
99
100 /*
101 ** Record the fact that user zUser has made contact with the repository.
102 ** This resets the subscription timeout on that user.
103 */
104 void alert_user_contact(const char *zUser){
105 if( db_table_has_column("repository","subscriber","lastContact") ){
106 db_multi_exec(
107 "UPDATE subscriber SET lastContact=now()/86400 WHERE suname=%Q",
108 zUser
109 );
110 }
111 }
112
113 /*
114 ** Make sure the table needed for email notification exist in the repository.
115 **
116 ** If the bOnlyIfEnabled option is true, then tables are only created
@@ -116,16 +122,27 @@
122 && fossil_strcmp(db_get("email-send-method",0),"off")==0
123 ){
124 return; /* Don't create table for disabled email */
125 }
126 db_exec_sql(zAlertInit);
127 return;
 
 
 
 
128 }
129 if( db_table_has_column("repository","subscriber","lastContact") ){
130 return;
131 }
132 db_multi_exec(
133 "DROP TABLE IF EXISTS repository.alert_bounde;\n"
134 "ALTER TABLE repository.subscriber ADD COLUMN lastContact INT;\n"
135 "UPDATE subscriber SET lastContact=mtime/86400;"
136 );
137 if( db_table_has_column("repository","pending_alert","sentMod") ){
138 return;
139 }
140 db_multi_exec(
141 "ALTER TABLE repository.pending_alert"
142 " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
143 );
144 }
145
146 /*
147 ** Enable triggers that automatically populate the pending_alert
148 ** table.
@@ -270,10 +287,23 @@
287 @ This is short name used to identifies the repository in the
288 @ Subject: line of email alerts. Traditionally this name is
289 @ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]".
290 @ (Property: "email-subname")</p>
291 @ <hr>
292
293 entry_attribute("Subscription Renewal Interval In Days", 8,
294 "email-renew-interval", "eri", "", 0);
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
298 @ interaction with the user. This prevents sending notifications
299 @ to abandoned accounts. If a subscription gets close to expiring
300 @ a separate email goes out with the daily digest that prompts the
301 @ subscriber to click on a link to the "/renew" webpage in order to
302 @ extend their subscription.
303 @ (Property: "email-renew-interval")</p>
304 @ <hr>
305
306 multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
307 "off", count(azSendMethods)/2, azSendMethods);
308 @ <p>How to send email. Requires auxiliary information from the fields
309 @ that follow. Hint: Use the <a href="%R/announce">/announce</a> page
@@ -1391,12 +1421,12 @@
1421 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1422 if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
1423 ssub[nsub] = 0;
1424 zCode = db_text(0,
1425 "INSERT INTO subscriber(semail,suname,"
1426 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)"
1427 "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)"
1428 "RETURNING hex(subscriberCode);",
1429 /* semail */ zEAddr,
1430 /* suname */ suname,
1431 /* sverified */ needCaptcha==0,
1432 /* sdigest */ PB("di"),
@@ -1641,10 +1671,11 @@
1671 int eErr = 0; /* Type of error */
1672 char *zErr = 0; /* Error message text */
1673 int sid = 0; /* Subscriber ID */
1674 int nName; /* Length of zName in bytes */
1675 char *zHalfCode; /* prefix of subscriberCode */
1676 int keepAlive = 0; /* True to update the last contact time */
1677
1678 db_begin_transaction();
1679 if( alert_webpages_disabled() ){
1680 db_commit_transaction();
1681 return;
@@ -1665,10 +1696,11 @@
1696 sid = db_int(0,
1697 "SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
1698 " THEN subscriberId ELSE 0 END"
1699 " FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
1700 " LIMIT 1", zName, zName);
1701 if( sid ) keepAlive = 1;
1702 }
1703 if( sid==0 && isLogin ){
1704 sid = db_int(0, "SELECT subscriberId FROM subscriber"
1705 " WHERE suname=%Q", g.zLogin);
1706 }
@@ -1697,11 +1729,12 @@
1729 blob_init(&update, "UPDATE subscriber SET", -1);
1730 blob_append_sql(&update,
1731 " sdonotcall=%d,"
1732 " sdigest=%d,"
1733 " ssub=%Q,"
1734 " mtime=now(),"
1735 " lastContact=now()/86400,"
1736 " smip=%Q",
1737 sdonotcall,
1738 sdigest,
1739 ssub,
1740 g.zIpAddr
@@ -1727,10 +1760,15 @@
1760 if( eErr==0 ){
1761 db_exec_sql(blob_str(&update));
1762 ssub = 0;
1763 }
1764 blob_reset(&update);
1765 }else if( keepAlive ){
1766 db_multi_exec(
1767 "UPDATE subscriber SET lastContact=now()/86400"
1768 " WHERE subscriberId=%d", sid
1769 );
1770 }
1771 if( P("delete")!=0 && cgi_csrf_safe(1) ){
1772 if( !PB("dodelete") ){
1773 eErr = 9;
1774 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1921,10 +1959,59 @@
1959 db_finalize(&q);
1960 style_finish_page();
1961 db_commit_transaction();
1962 return;
1963 }
1964
1965 /*
1966 ** WEBPAGE: renew
1967 **
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.
1972 */
1973 void renewal_page(void){
1974 const char *zName = P("name");
1975 int iInterval = db_get_int("email-renew-interval", 0);
1976 Stmt s;
1977 int rc;
1978
1979 style_header("Subscription Renewal");
1980 if( zName==0 || strlen(zName)<4 ){
1981 @ <p>No subscription specified</p>
1982 style_finish_page();
1983 return;
1984 }
1985
1986 if( !db_table_has_column("repository","subscriber","lastContact")
1987 || iInterval<1
1988 ){
1989 @ <p>This repository does not expire email notification subscriptions.
1990 @ No renewals are necessary.</p>
1991 style_finish_page();
1992 return;
1993 }
1994
1995 db_prepare(&s,
1996 "UPDATE subscriber"
1997 " SET lastContact=now()/86400"
1998 " WHERE subscriberCode=hextoblob(%Q)"
1999 " RETURNING semail, date('now','+%d days');",
2000 zName, iInterval+1
2001 );
2002 rc = db_step(&s);
2003 if( rc==SQLITE_ROW ){
2004 @ <p>The email notification subscription for %h(db_column_text(&s,0))
2005 @ has been extended until %h(db_column_text(&s,1)) UTC.
2006 }else{
2007 @ <p>No such subscriber-id: %h(zName)</p>
2008 }
2009 db_finalize(&s);
2010 style_finish_page();
2011 }
2012
2013
2014 /* This is the message that gets sent to describe how to change
2015 ** or modify a subscription
2016 */
2017 static const char zUnsubMsg[] =
@@ -2111,11 +2198,11 @@
2198 nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
2199 if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
2200 int nNewPending;
2201 db_multi_exec(
2202 "DELETE FROM subscriber"
2203 " WHERE NOT sverified AND mtime<now()-86400"
2204 );
2205 nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
2206 " WHERE NOT sverified");
2207 nDel = nPending - nNewPending;
2208 nPending = nNewPending;
@@ -2122,11 +2209,11 @@
2209 nTotal -= nDel;
2210 }
2211 if( nPending>0 ){
2212 @ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
2213 if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
2214 " WHERE NOT sverified AND mtime<now()-86400")
2215 ){
2216 style_submenu_element("Purge Pending","subscribers?purge");
2217 }
2218 }else{
2219 @ <h1>%,d(nTotal) Subscribers</h1>
@@ -2142,11 +2229,12 @@
2229 " suname," /* 3 */
2230 " sverified," /* 4 */
2231 " sdigest," /* 5 */
2232 " mtime," /* 6 */
2233 " date(sctime,'unixepoch')," /* 7 */
2234 " (SELECT uid FROM user WHERE login=subscriber.suname)," /* 8 */
2235 " coalesce(lastContact,mtime/86400)" /* 9 */
2236 " FROM subscriber"
2237 );
2238 if( P("only")!=0 ){
2239 blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
2240 style_submenu_element("Show All","%R/subscribers");
@@ -2153,27 +2241,30 @@
2241 }
2242 blob_append_sql(&sql," ORDER BY mtime DESC");
2243 db_prepare_blob(&q, &sql);
2244 iNow = time(0);
2245 @ <table border='1' class='sortable' \
2246 @ data-init-sort='6' data-column-types='tttttKKt'>
2247 @ <thead>
2248 @ <tr>
2249 @ <th>Email
2250 @ <th>Events
2251 @ <th>Digest-Only?
2252 @ <th>User
2253 @ <th>Verified?
2254 @ <th>Last change
2255 @ <th>Last contact
2256 @ <th>Created
2257 @ </tr>
2258 @ </thead><tbody>
2259 while( db_step(&q)==SQLITE_ROW ){
2260 sqlite3_int64 iMtime = db_column_int64(&q, 6);
2261 double rAge = (iNow - iMtime)/86400.0;
2262 int uid = db_column_int(&q, 8);
2263 const char *zUname = db_column_text(&q, 3);
2264 sqlite3_int64 iContact = db_column_int64(&q, 9);
2265 double rContact = (iNow/86400) - iContact;
2266 @ <tr>
2267 @ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
2268 @ %h(db_column_text(&q,1))</a></td>
2269 @ <td>%h(db_column_text(&q,2))</td>
2270 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
@@ -2182,10 +2273,11 @@
2273 }else{
2274 @ <td>%h(zUname)</td>
2275 }
2276 @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
2277 @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
2278 @ <td data-sortkey='%010llx(iContact)'>%z(human_readable_age(rContact))</td>
2279 @ <td>%h(db_column_text(&q,7))</td>
2280 @ </tr>
2281 }
2282 @ </tbody></table>
2283 db_finalize(&q);
2284
+4 -1
--- src/info.c
+++ src/info.c
@@ -496,10 +496,11 @@
496496
const char *zHash;
497497
int rid;
498498
Stmt q;
499499
int cnt = 0;
500500
Blob sql;
501
+ char const *zType;
501502
502503
login_check_credentials();
503504
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
504505
rid = name_to_rid_www("name");
505506
if( rid==0 ){
@@ -508,11 +509,13 @@
508509
style_finish_page();
509510
return;
510511
}
511512
zHash = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
512513
style_header("Tags and Properties");
513
- @ <h1>Tags and Properties for Check-In \
514
+ zType = whatis_rid_type(rid);
515
+ if(!zType) zType = "Artifact";
516
+ @ <h1>Tags and Properties for %s(zType) \
514517
@ %z(href("%R/ci/%!S",zHash))%S(zHash)</a></h1>
515518
db_prepare(&q,
516519
"SELECT tag.tagid, tagname, "
517520
" (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
518521
" value, datetime(tagxref.mtime,toLocal()), tagtype,"
519522
--- src/info.c
+++ src/info.c
@@ -496,10 +496,11 @@
496 const char *zHash;
497 int rid;
498 Stmt q;
499 int cnt = 0;
500 Blob sql;
 
501
502 login_check_credentials();
503 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
504 rid = name_to_rid_www("name");
505 if( rid==0 ){
@@ -508,11 +509,13 @@
508 style_finish_page();
509 return;
510 }
511 zHash = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
512 style_header("Tags and Properties");
513 @ <h1>Tags and Properties for Check-In \
 
 
514 @ %z(href("%R/ci/%!S",zHash))%S(zHash)</a></h1>
515 db_prepare(&q,
516 "SELECT tag.tagid, tagname, "
517 " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
518 " value, datetime(tagxref.mtime,toLocal()), tagtype,"
519
--- src/info.c
+++ src/info.c
@@ -496,10 +496,11 @@
496 const char *zHash;
497 int rid;
498 Stmt q;
499 int cnt = 0;
500 Blob sql;
501 char const *zType;
502
503 login_check_credentials();
504 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
505 rid = name_to_rid_www("name");
506 if( rid==0 ){
@@ -508,11 +509,13 @@
509 style_finish_page();
510 return;
511 }
512 zHash = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
513 style_header("Tags and Properties");
514 zType = whatis_rid_type(rid);
515 if(!zType) zType = "Artifact";
516 @ <h1>Tags and Properties for %s(zType) \
517 @ %z(href("%R/ci/%!S",zHash))%S(zHash)</a></h1>
518 db_prepare(&q,
519 "SELECT tag.tagid, tagname, "
520 " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
521 " value, datetime(tagxref.mtime,toLocal()), tagtype,"
522
+13 -9
--- src/login.c
+++ src/login.c
@@ -170,17 +170,21 @@
170170
static void record_login_attempt(
171171
const char *zUsername, /* Name of user logging in */
172172
const char *zIpAddr, /* IP address from which they logged in */
173173
int bSuccess /* True if the attempt was a success */
174174
){
175
- if( !db_get_boolean("access-log", 0) ) return;
176
- create_accesslog_table();
177
- db_multi_exec(
178
- "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
179
- "VALUES(%Q,%Q,%d,julianday('now'));",
180
- zUsername, zIpAddr, bSuccess
181
- );
175
+ if( db_get_boolean("access-log", 0) ){
176
+ create_accesslog_table();
177
+ db_multi_exec(
178
+ "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
179
+ "VALUES(%Q,%Q,%d,julianday('now'));",
180
+ zUsername, zIpAddr, bSuccess
181
+ );
182
+ }
183
+ if( bSuccess ){
184
+ alert_user_contact(zUsername);
185
+ }
182186
}
183187
184188
/*
185189
** Searches for the user ID matching the given name and password.
186190
** On success it returns a positive value. On error it returns 0.
@@ -1714,12 +1718,12 @@
17141718
ssub[nsub] = 0;
17151719
capability_free(pCap);
17161720
/* Also add the user to the subscriber table. */
17171721
zCode = db_text(0,
17181722
"INSERT INTO subscriber(semail,suname,"
1719
- " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1720
- " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
1723
+ " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)"
1724
+ " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)"
17211725
" ON CONFLICT(semail) DO UPDATE"
17221726
" SET suname=excluded.suname"
17231727
" RETURNING hex(subscriberCode);",
17241728
/* semail */ zEAddr,
17251729
/* suname */ zUserID,
17261730
--- src/login.c
+++ src/login.c
@@ -170,17 +170,21 @@
170 static void record_login_attempt(
171 const char *zUsername, /* Name of user logging in */
172 const char *zIpAddr, /* IP address from which they logged in */
173 int bSuccess /* True if the attempt was a success */
174 ){
175 if( !db_get_boolean("access-log", 0) ) return;
176 create_accesslog_table();
177 db_multi_exec(
178 "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
179 "VALUES(%Q,%Q,%d,julianday('now'));",
180 zUsername, zIpAddr, bSuccess
181 );
 
 
 
 
182 }
183
184 /*
185 ** Searches for the user ID matching the given name and password.
186 ** On success it returns a positive value. On error it returns 0.
@@ -1714,12 +1718,12 @@
1714 ssub[nsub] = 0;
1715 capability_free(pCap);
1716 /* Also add the user to the subscriber table. */
1717 zCode = db_text(0,
1718 "INSERT INTO subscriber(semail,suname,"
1719 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1720 " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
1721 " ON CONFLICT(semail) DO UPDATE"
1722 " SET suname=excluded.suname"
1723 " RETURNING hex(subscriberCode);",
1724 /* semail */ zEAddr,
1725 /* suname */ zUserID,
1726
--- src/login.c
+++ src/login.c
@@ -170,17 +170,21 @@
170 static void record_login_attempt(
171 const char *zUsername, /* Name of user logging in */
172 const char *zIpAddr, /* IP address from which they logged in */
173 int bSuccess /* True if the attempt was a success */
174 ){
175 if( db_get_boolean("access-log", 0) ){
176 create_accesslog_table();
177 db_multi_exec(
178 "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
179 "VALUES(%Q,%Q,%d,julianday('now'));",
180 zUsername, zIpAddr, bSuccess
181 );
182 }
183 if( bSuccess ){
184 alert_user_contact(zUsername);
185 }
186 }
187
188 /*
189 ** Searches for the user ID matching the given name and password.
190 ** On success it returns a positive value. On error it returns 0.
@@ -1714,12 +1718,12 @@
1718 ssub[nsub] = 0;
1719 capability_free(pCap);
1720 /* Also add the user to the subscriber table. */
1721 zCode = db_text(0,
1722 "INSERT INTO subscriber(semail,suname,"
1723 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)"
1724 " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)"
1725 " ON CONFLICT(semail) DO UPDATE"
1726 " SET suname=excluded.suname"
1727 " RETURNING hex(subscriberCode);",
1728 /* semail */ zEAddr,
1729 /* suname */ zUserID,
1730
+31
--- src/name.c
+++ src/name.c
@@ -737,10 +737,41 @@
737737
cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath);
738738
rid = 0;
739739
}
740740
return rid;
741741
}
742
+
743
+/*
744
+** Given an RID of a structural artifact, which is assumed to be
745
+** valid, this function returns a brief string (in static memory)
746
+** describing the record type. Returns NULL if rid does not refer to
747
+** an artifact record (as determined by reading the event table). The
748
+** returned string is intended to be used in headers which can refer
749
+** to different artifact types. It is not "definitive," in that it
750
+** does not distinguish between closely-related types like wiki
751
+** creation, edit, and removal.
752
+*/
753
+char const * whatis_rid_type(int rid){
754
+ Stmt q = empty_Stmt;
755
+ char const * zType = 0;
756
+ /* Check for entries on the timeline that reference this object */
757
+ db_prepare(&q,
758
+ "SELECT type FROM event WHERE objid=%d", rid);
759
+ if( db_step(&q)==SQLITE_ROW ){
760
+ switch( db_column_text(&q,0)[0] ){
761
+ case 'c': zType = "Check-in"; break;
762
+ case 'w': zType = "Wiki-edit"; break;
763
+ case 'e': zType = "Technote"; break;
764
+ case 'f': zType = "Forum-post"; break;
765
+ case 't': zType = "Ticket-change"; break;
766
+ case 'g': zType = "Tag-change"; break;
767
+ default: break;
768
+ }
769
+ }
770
+ db_finalize(&q);
771
+ return zType;
772
+}
742773
743774
/*
744775
** Generate a description of artifact "rid"
745776
*/
746777
void whatis_rid(int rid, int verboseFlag){
747778
--- src/name.c
+++ src/name.c
@@ -737,10 +737,41 @@
737 cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath);
738 rid = 0;
739 }
740 return rid;
741 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742
743 /*
744 ** Generate a description of artifact "rid"
745 */
746 void whatis_rid(int rid, int verboseFlag){
747
--- src/name.c
+++ src/name.c
@@ -737,10 +737,41 @@
737 cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath);
738 rid = 0;
739 }
740 return rid;
741 }
742
743 /*
744 ** Given an RID of a structural artifact, which is assumed to be
745 ** valid, this function returns a brief string (in static memory)
746 ** describing the record type. Returns NULL if rid does not refer to
747 ** an artifact record (as determined by reading the event table). The
748 ** returned string is intended to be used in headers which can refer
749 ** to different artifact types. It is not "definitive," in that it
750 ** does not distinguish between closely-related types like wiki
751 ** creation, edit, and removal.
752 */
753 char const * whatis_rid_type(int rid){
754 Stmt q = empty_Stmt;
755 char const * zType = 0;
756 /* Check for entries on the timeline that reference this object */
757 db_prepare(&q,
758 "SELECT type FROM event WHERE objid=%d", rid);
759 if( db_step(&q)==SQLITE_ROW ){
760 switch( db_column_text(&q,0)[0] ){
761 case 'c': zType = "Check-in"; break;
762 case 'w': zType = "Wiki-edit"; break;
763 case 'e': zType = "Technote"; break;
764 case 'f': zType = "Forum-post"; break;
765 case 't': zType = "Ticket-change"; break;
766 case 'g': zType = "Tag-change"; break;
767 default: break;
768 }
769 }
770 db_finalize(&q);
771 return zType;
772 }
773
774 /*
775 ** Generate a description of artifact "rid"
776 */
777 void whatis_rid(int rid, int verboseFlag){
778
+1 -1
--- src/rebuild.c
+++ src/rebuild.c
@@ -397,11 +397,11 @@
397397
" WHERE type='table'"
398398
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
399399
"'config','shun','private','reportfmt',"
400400
"'concealed','accesslog','modreq',"
401401
"'purgeevent','purgeitem','unversioned',"
402
- "'subscriber','pending_alert','alert_bounce','chat')"
402
+ "'subscriber','pending_alert','chat')"
403403
" AND name NOT GLOB 'sqlite_*'"
404404
" AND name NOT GLOB 'fx_*'"
405405
);
406406
while( db_step(&q)==SQLITE_ROW ){
407407
blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
408408
--- src/rebuild.c
+++ src/rebuild.c
@@ -397,11 +397,11 @@
397 " WHERE type='table'"
398 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
399 "'config','shun','private','reportfmt',"
400 "'concealed','accesslog','modreq',"
401 "'purgeevent','purgeitem','unversioned',"
402 "'subscriber','pending_alert','alert_bounce','chat')"
403 " AND name NOT GLOB 'sqlite_*'"
404 " AND name NOT GLOB 'fx_*'"
405 );
406 while( db_step(&q)==SQLITE_ROW ){
407 blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
408
--- src/rebuild.c
+++ src/rebuild.c
@@ -397,11 +397,11 @@
397 " WHERE type='table'"
398 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
399 "'config','shun','private','reportfmt',"
400 "'concealed','accesslog','modreq',"
401 "'purgeevent','purgeitem','unversioned',"
402 "'subscriber','pending_alert','chat')"
403 " AND name NOT GLOB 'sqlite_*'"
404 " AND name NOT GLOB 'fx_*'"
405 );
406 while( db_step(&q)==SQLITE_ROW ){
407 blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
408
+3
--- src/wiki.c
+++ src/wiki.c
@@ -634,10 +634,13 @@
634634
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
635635
}
636636
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
637637
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
638638
manifest_crosslink(nrid, pWiki, MC_NONE);
639
+ if( login_is_individual() ){
640
+ alert_user_contact(login_name());
641
+ }
639642
return nrid;
640643
}
641644
642645
/*
643646
** Output a selection box from which the user can select the
644647
--- src/wiki.c
+++ src/wiki.c
@@ -634,10 +634,13 @@
634 db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
635 }
636 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
637 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
638 manifest_crosslink(nrid, pWiki, MC_NONE);
 
 
 
639 return nrid;
640 }
641
642 /*
643 ** Output a selection box from which the user can select the
644
--- src/wiki.c
+++ src/wiki.c
@@ -634,10 +634,13 @@
634 db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
635 }
636 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
637 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
638 manifest_crosslink(nrid, pWiki, MC_NONE);
639 if( login_is_individual() ){
640 alert_user_contact(login_name());
641 }
642 return nrid;
643 }
644
645 /*
646 ** Output a selection box from which the user can select the
647
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -75,15 +75,19 @@
7575
</ol>
7676
7777
<li><p>
7878
Run the following commands on Linux and verify no major memory leaks
7979
and no run-time errors or warnings (except for the well-known jump on an
80
-uninitialized value that occurs within zlib).
80
+uninitialized value that occurs within zlib).</p>
8181
<ol type="a">
8282
<li> <b>valgrind fossil rebuild</b>
8383
<li> <b>valgrind fossil sync</b>
8484
</ol>
85
+
86
+<p>Achtung: make sure to point valgrind to the proper fossil binary
87
+so that it does not pick up another from the PATH.</p>
88
+
8589
8690
<li><p>
8791
8892
Inspect [http://fossil-scm.org/home/vdiff?from=release&to=trunk&sbs=1|all code changes since the previous release], paying particular
8993
attention to the following details:
9094
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -75,15 +75,19 @@
75 </ol>
76
77 <li><p>
78 Run the following commands on Linux and verify no major memory leaks
79 and no run-time errors or warnings (except for the well-known jump on an
80 uninitialized value that occurs within zlib).
81 <ol type="a">
82 <li> <b>valgrind fossil rebuild</b>
83 <li> <b>valgrind fossil sync</b>
84 </ol>
 
 
 
 
85
86 <li><p>
87
88 Inspect [http://fossil-scm.org/home/vdiff?from=release&to=trunk&sbs=1|all code changes since the previous release], paying particular
89 attention to the following details:
90
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -75,15 +75,19 @@
75 </ol>
76
77 <li><p>
78 Run the following commands on Linux and verify no major memory leaks
79 and no run-time errors or warnings (except for the well-known jump on an
80 uninitialized value that occurs within zlib).</p>
81 <ol type="a">
82 <li> <b>valgrind fossil rebuild</b>
83 <li> <b>valgrind fossil sync</b>
84 </ol>
85
86 <p>Achtung: make sure to point valgrind to the proper fossil binary
87 so that it does not pick up another from the PATH.</p>
88
89
90 <li><p>
91
92 Inspect [http://fossil-scm.org/home/vdiff?from=release&to=trunk&sbs=1|all code changes since the previous release], paying particular
93 attention to the following details:
94

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button