Index: src/backoffice.c ================================================================== --- src/backoffice.c +++ src/backoffice.c @@ -262,11 +262,11 @@ getpid()); } backoffice_work(); break; } - if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",1) ){ + if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",0) ){ /* If the no-delay flag is set, exit immediately rather than queuing ** up. Assume that some future request will come along and handle any ** necessary backoffice work. */ db_end_transaction(0); break; Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -1929,16 +1929,12 @@ fd = dup(connection); if( fd!=0 ) nErr++; close(1); fd = dup(connection); if( fd!=1 ) nErr++; - if( 0 && !g.fAnyTrace ){ - close(2); - fd = dup(connection); - if( fd!=2 ) nErr++; - } close(connection); + for(fd=3; close(fd)==0; fd++){} g.nPendingRequest = nchildren+1; g.nRequest = nRequest+1; return nErr; } } Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -3027,11 +3027,11 @@ ** If autosync is enabled setting this to a value greater ** than zero will cause autosync to try no more than this ** number of attempts if there is a sync failure. */ /* -** SETTING: backoffice-nodelay boolean default=on +** SETTING: backoffice-nodelay boolean default=off ** If backoffice-nodelay is true, then the backoffice processing ** will never invoke sleep(). If it has nothing useful to do, ** it simply exits. */ /* Index: src/email.c ================================================================== --- src/email.c +++ src/email.c @@ -77,12 +77,13 @@ @ -- Remaining characters determine the specific event. For example, @ -- 'c4413' means check-in with rid=4413. @ -- @ CREATE TABLE repository.pending_alert( @ eventid TEXT PRIMARY KEY, -- Object that changed -@ sentSep BOOLEAN DEFAULT false, -- individual emails sent -@ sentDigest BOOLEAN DEFAULT false -- digest emails sent +@ sentSep BOOLEAN DEFAULT false, -- individual alert sent +@ sentDigest BOOLEAN DEFAULT false, -- digest alert sent +@ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent @ ) WITHOUT ROWID; @ @ DROP TABLE IF EXISTS repository.email_bounce; @ -- Record bounced emails. If too many bounces are received within @ -- some defined time range, then cancel the subscription. Older @@ -115,10 +116,15 @@ ){ return; /* Don't create table for disabled email */ } db_multi_exec(zEmailInit/*works-like:""*/); email_triggers_enable(); + }else if( !db_table_has_column("repository","pending_alert","sentMod") ){ + db_multi_exec( + "ALTER TABLE repository.pending_alert" + " ADD COLUMN sentMod BOOLEAN DEFAULT false;" + ); } } /* ** Enable triggers that automatically populate the pending_alert @@ -294,18 +300,10 @@ @

This is the email for the human administrator for the system. @ Abuse and trouble reports are send here. @ (Property: "email-admin")

@
- entry_attribute("Inbound email directory", 40, "email-receive-dir", - "erdir", "", 0); - @

Inbound emails can be stored in a directory for analysis as - @ a debugging aid. Put the name of that directory in this entry box. - @ Disable saving of inbound email by making this an empty string. - @ Abuse and trouble reports are send here. - @ (Property: "email-receive-dir")

- @
@

@ db_end_transaction(0); style_footer(); } @@ -757,24 +755,10 @@ fossil_print("%s", blob_str(&all)); } blob_reset(&all); } -/* -** Analyze and act on a received email. -** -** This routine takes ownership of the Blob parameter and is responsible -** for freeing that blob when it is done with it. -** -** This routine acts on all email messages received from the -** "fossil email inbound" command. -*/ -void email_receive(Blob *pMsg){ - /* To Do: Look for bounce messages and possibly disable subscriptions */ - blob_reset(pMsg); -} - /* ** SETTING: email-send-method width=5 default=off ** Determine the method used to send email. Allowed values are ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value ** means no email is ever sent. The "relay" value means emails are sent @@ -806,16 +790,10 @@ /* ** SETTING: email-self width=40 ** This is the email address for the repository. Outbound emails add ** this email address as the "From:" field. */ -/* -** SETTING: email-receive-dir width=40 -** Inbound email messages are saved as separate files in this directory, -** for debugging analysis. Disable saving of inbound emails omitting -** this setting, or making it an empty string. -*/ /* ** SETTING: email-send-relayhost width=40 ** This is the hostname and TCP port to which output email messages ** are sent when email-send-method is "relay". There should be an ** SMTP server configured as a Mail Submission Agent listening on the @@ -822,80 +800,72 @@ ** designated host and port and all times. */ /* -** COMMAND: email +** COMMAND: alerts ** -** Usage: %fossil email SUBCOMMAND ARGS... +** Usage: %fossil alerts SUBCOMMAND ARGS... ** ** Subcommands: ** -** exec Compose and send pending email alerts. +** pending Show all pending alerts. Useful for debugging. +** +** reset Hard reset of all email notification tables +** in the repository. This erases all subscription +** information. ** Use with extreme care ** +** +** send Compose and send pending email alerts. ** Some installations may want to do this via ** a cron-job to make sure alerts are sent ** in a timely manner. ** Options: ** ** --digest Send digests -** --test Resets to standard output -** -** inbound [FILE] Receive an inbound email message. This message -** is analyzed to see if it is a bounce, and if -** necessary, subscribers may be disabled. -** -** reset Hard reset of all email notification tables -** in the repository. This erases all subscription -** information. Use with extreme care. -** -** send TO [OPTIONS] Send a single email message using whatever +** --test Write to standard output +** +** settings [NAME VALUE] With no arguments, list all email settings. +** Or change the value of a single email setting. +** +** status Report on the status of the email alert +** subsystem +** +** subscribers [PATTERN] List all subscribers matching PATTERN. +** +** test-message TO [OPTS] Send a single email message using whatever ** email sending mechanism is currently configured. -** Use this for testing the email configuration. -** Options: +** Use this for testing the email notification +** configuration. Options: ** ** --body FILENAME ** --smtp-trace ** --stdout ** --subject|-S SUBJECT ** -** settings [NAME VALUE] With no arguments, list all email settings. -** Or change the value of a single email setting. -** -** subscribers [PATTERN] List all subscribers matching PATTERN. -** ** unsubscribe EMAIL Remove a single subscriber with the given EMAIL. */ void email_cmd(void){ const char *zCmd; int nCmd; db_find_and_open_repository(0, 0); email_schema(0); zCmd = g.argc>=3 ? g.argv[2] : "x"; nCmd = (int)strlen(zCmd); - if( strncmp(zCmd, "exec", nCmd)==0 ){ - u32 eFlags = 0; - if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST; - if( find_option("test",0,0)!=0 ){ - eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT; - } - verify_all_options(); - email_send_alerts(eFlags); - }else - if( strncmp(zCmd, "inbound", nCmd)==0 ){ - Blob email; - const char *zInboundDir = db_get("email-receive-dir",""); - verify_all_options(); - if( g.argc!=3 && g.argc!=4 ){ - usage("inbound [FILE]"); - } - blob_read_from_file(&email, g.argc==3 ? "-" : g.argv[3], ExtFILE); - if( zInboundDir[0] ){ - char *zFN = file_time_tempname(zInboundDir,".email"); - blob_write_to_file(&email, zFN); - fossil_free(zFN); - } - email_receive(&email); + if( strncmp(zCmd, "pending", nCmd)==0 ){ + Stmt q; + verify_all_options(); + if( g.argc!=3 ) usage("pending"); + db_prepare(&q,"SELECT eventid, sentSep, sentDigest, sentMod" + " FROM pending_alert"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%10s %7s %10s %7s\n", + db_column_text(&q,0), + db_column_int(&q,1) ? "sentSep" : "", + db_column_int(&q,2) ? "sentDigest" : "", + db_column_int(&q,3) ? "sentMod" : ""); + } + db_finalize(&q); }else if( strncmp(zCmd, "reset", nCmd)==0 ){ int c; int bForce = find_option("force","f",0)!=0; verify_all_options(); @@ -923,10 +893,85 @@ ); email_schema(0); } }else if( strncmp(zCmd, "send", nCmd)==0 ){ + u32 eFlags = 0; + if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST; + if( find_option("test",0,0)!=0 ){ + eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT; + } + verify_all_options(); + email_send_alerts(eFlags); + }else + if( strncmp(zCmd, "settings", nCmd)==0 ){ + int isGlobal = find_option("global",0,0)!=0; + int nSetting; + const Setting *pSetting = setting_info(&nSetting); + db_open_config(1, 0); + verify_all_options(); + if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]"); + if( g.argc==5 ){ + const char *zLabel = g.argv[3]; + if( strncmp(zLabel, "email-", 6)!=0 + || (pSetting = db_find_setting(zLabel, 1))==0 ){ + fossil_fatal("not a valid email setting: \"%s\"", zLabel); + } + db_set(pSetting->name, g.argv[4], isGlobal); + g.argc = 3; + } + pSetting = setting_info(&nSetting); + for(; nSetting>0; nSetting--, pSetting++ ){ + if( strncmp(pSetting->name,"email-",6)!=0 ) continue; + print_setting(pSetting); + } + }else + if( strncmp(zCmd, "status", nCmd)==0 ){ + int nSetting, n; + static const char *zFmt = "%-29s %d\n"; + const Setting *pSetting = setting_info(&nSetting); + db_open_config(1, 0); + verify_all_options(); + if( g.argc!=3 ) usage("status"); + pSetting = setting_info(&nSetting); + for(; nSetting>0; nSetting--, pSetting++ ){ + if( strncmp(pSetting->name,"email-",6)!=0 ) continue; + print_setting(pSetting); + } + n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); + fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n); + n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest"); + fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n); + n = db_int(0,"SELECT count(*) FROM subscriber"); + fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n); + n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" + " AND NOT sdonotcall AND length(ssub)>1"); + fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n); + }else + if( strncmp(zCmd, "subscribers", nCmd)==0 ){ + Stmt q; + verify_all_options(); + if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]"); + if( g.argc==4 ){ + char *zPattern = g.argv[3]; + db_prepare(&q, + "SELECT semail FROM subscriber" + " WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'" + " OR semail GLOB '*%q*' or suname GLOB '*%q*'" + " ORDER BY semail", + zPattern, zPattern, zPattern, zPattern); + }else{ + db_prepare(&q, + "SELECT semail FROM subscriber" + " ORDER BY semail"); + } + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s\n", db_column_text(&q, 0)); + } + db_finalize(&q); + }else + if( strncmp(zCmd, "test-message", nCmd)==0 ){ Blob prompt, body, hdr; const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; int i; u32 mFlags = EMAIL_IMMEDIATE_FAIL; const char *zSubject = find_option("subject", "S", 1); @@ -957,62 +1002,19 @@ email_sender_free(pSender); blob_reset(&hdr); blob_reset(&body); blob_reset(&prompt); }else - if( strncmp(zCmd, "settings", nCmd)==0 ){ - int isGlobal = find_option("global",0,0)!=0; - int nSetting; - const Setting *pSetting = setting_info(&nSetting); - db_open_config(1, 0); - verify_all_options(); - if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]"); - if( g.argc==5 ){ - const char *zLabel = g.argv[3]; - if( strncmp(zLabel, "email-", 6)!=0 - || (pSetting = db_find_setting(zLabel, 1))==0 ){ - fossil_fatal("not a valid email setting: \"%s\"", zLabel); - } - db_set(pSetting->name, g.argv[4], isGlobal); - g.argc = 3; - } - pSetting = setting_info(&nSetting); - for(; nSetting>0; nSetting--, pSetting++ ){ - if( strncmp(pSetting->name,"email-",6)!=0 ) continue; - print_setting(pSetting); - } - }else - if( strncmp(zCmd, "subscribers", nCmd)==0 ){ - Stmt q; - verify_all_options(); - if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]"); - if( g.argc==4 ){ - char *zPattern = g.argv[3]; - db_prepare(&q, - "SELECT semail FROM subscriber" - " WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'" - " OR semail GLOB '*%q*' or suname GLOB '*%q*'" - " ORDER BY semail", - zPattern, zPattern, zPattern, zPattern); - }else{ - db_prepare(&q, - "SELECT semail FROM subscriber" - " ORDER BY semail"); - } - while( db_step(&q)==SQLITE_ROW ){ - fossil_print("%s\n", db_column_text(&q, 0)); - } - db_finalize(&q); - }else if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){ verify_all_options(); if( g.argc!=4 ) usage("unsubscribe EMAIL"); db_multi_exec( "DELETE FROM subscriber WHERE semail=%Q", g.argv[3]); }else { - usage("exec|inbound|reset|send|setting|subscribers|unsubscribe"); + usage("pending|reset|send|setting|status|" + "subscribers|test-message|unsubscribe"); } } /* ** Do error checking on a submitted subscription form. Return TRUE @@ -1789,11 +1791,12 @@ /* ** A single event that might appear in an alert is recorded as an ** instance of the following object. */ struct EmailEvent { - int type; /* 'c', 't', 'w', 'f' */ + int type; /* 'c', 'f', 'm', 't', 'w' */ + int needMod; /* Pending moderator approval */ Blob txt; /* Text description to appear in an alert */ EmailEvent *pNext; /* Next in chronological order */ }; #endif @@ -1812,33 +1815,34 @@ /* ** Compute and return a linked list of EmailEvent objects ** corresponding to the current content of the temp.wantalert ** table which should be defined as follows: ** -** CREATE TEMP TABLE wantalert(eventId TEXT); +** CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN); */ -EmailEvent *email_compute_event_text(int *pnEvent){ +EmailEvent *email_compute_event_text(int *pnEvent, int doDigest){ Stmt q; EmailEvent *p; EmailEvent anchor; EmailEvent *pLast; const char *zUrl = db_get("email-url","http://localhost:8080"); db_prepare(&q, "SELECT" - " blob.uuid," /* 0 */ - " datetime(event.mtime)," /* 1 */ + " blob.uuid," /* 0 */ + " datetime(event.mtime)," /* 1 */ " coalesce(ecomment,comment)" " || ' (user: ' || coalesce(euser,user,'?')" " || (SELECT case when length(x)>0 then ' tags: ' || x else '' end" " FROM (SELECT group_concat(substr(tagname,5), ', ') AS x" " FROM tag, tagxref" " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" " AND tagxref.rid=blob.rid AND tagxref.tagtype>0))" - " || ')' as comment," /* 2 */ + " || ')' as comment," /* 2 */ " tagxref.value AS branch," /* 3 */ - " wantalert.eventId" /* 4 */ + " wantalert.eventId," /* 4 */ + " wantalert.needMod" /* 5 */ " FROM temp.wantalert JOIN tag CROSS JOIN event CROSS JOIN blob" " LEFT JOIN tagxref ON tagxref.tagid=tag.tagid" " AND tagxref.tagtype>0" " AND tagxref.rid=blob.rid" " WHERE blob.rid=event.objid" @@ -1853,13 +1857,15 @@ const char *zType = ""; p = fossil_malloc( sizeof(EmailEvent) ); pLast->pNext = p; pLast = p; p->type = db_column_text(&q, 4)[0]; + p->needMod = db_column_int(&q, 5); p->pNext = 0; switch( p->type ){ case 'c': zType = "Check-In"; break; + case 'f': zType = "Forum post"; break; case 't': zType = "Wiki Edit"; break; case 'w': zType = "Ticket Change"; break; } blob_init(&p->txt, 0, 0); blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n", @@ -1867,10 +1873,16 @@ zType, db_column_text(&q,2), zUrl, db_column_text(&q,0) ); + if( p->needMod ){ + blob_appendf(&p->txt, + "** Pending moderator approval (%s/modreq) **\n", + zUrl + ); + } (*pnEvent)++; } db_finalize(&q); return anchor.pNext; } @@ -1905,32 +1917,44 @@ ** command line, generate text for all events named in the ** pending_alert table. ** ** This command is intended for testing and debugging the logic ** that generates email alert text. +** +** Options: +** +** --digest Generate digest alert text +** --needmod Assume all events are pending moderator approval */ void test_alert_cmd(void){ Blob out; int nEvent; + int needMod; + int doDigest; EmailEvent *pEvent, *p; + doDigest = find_option("digest",0,0)!=0; + needMod = find_option("needmod",0,0)!=0; db_find_and_open_repository(0, 0); verify_all_options(); db_begin_transaction(); email_schema(0); - db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT)"); + db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT, needMod BOOLEAN)"); if( g.argc==2 ){ - db_multi_exec("INSERT INTO wantalert SELECT eventid FROM pending_alert"); + db_multi_exec( + "INSERT INTO wantalert(eventId,needMod)" + " SELECT eventid, %d FROM pending_alert", needMod); }else{ int i; for(i=2; ipNext){ blob_append(&out, "\n", 1); blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); } email_free_eventlist(pEvent); @@ -1947,12 +1971,12 @@ ** ** Add one or more events to the pending_alert queue. Use this ** command during testing to force email notifications for specific ** events. ** -** EVENTIDs are text. The first character is 'c', 'w', or 't' -** for check-in, wiki, or ticket. The remaining text is a +** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' +** for check-in, forum, ticket, or wiki. The remaining text is a ** integer that references the EVENT.OBJID value for the event. ** Run /timeline?showid to see these OBJID values. ** ** If the --backoffice option is included, then email_backoffice() is run ** after all alerts have been added. This will cause the alerts to @@ -1984,11 +2008,35 @@ #define SENDALERT_TRACE 0x0008 /* Trace operation for debugging */ #endif /* INTERFACE */ /* -** Send alert emails to all subscribers. +** Send alert emails to subscribers. +** +** This procedure is run by either the backoffice, or in response to the +** "fossil alerts send" command. Details of operation are controlled by +** the flags parameter. +** +** Here is a summary of what happens: +** +** (1) Create a TEMP table wantalert(eventId,needMod) and fill it with +** all the events that we want to send alerts about. The needMod +** flags is set if and only if the event is still awaiting +** moderator approval. Events with the needMod flag are only +** shown to users that have moderator privileges. +** +** (2) Call email_compute_event_text() to compute a list of EmailEvent +** objects that describe all events about which we want to send +** alerts. +** +** (3) Loop over all subscribers. Compose and send one or more email +** messages to each subscriber that describe the events for +** which the subscriber has expressed interest and has +** appropriate privileges. +** +** (4) Update the pending_alerts table to indicate that alerts have been +** sent. */ void email_send_alerts(u32 flags){ EmailEvent *pEvents, *p; int nEvent = 0; Stmt q; @@ -2001,10 +2049,11 @@ EmailSender *pSender = 0; u32 senderFlags = 0; if( g.fSqlTrace ) fossil_trace("-- BEGIN email_send_alerts(%u)\n", flags); db_begin_transaction(); + email_schema(0); if( !email_enabled() ) goto send_alerts_done; zUrl = db_get("email-url",0); if( zUrl==0 ) goto send_alerts_done; zRepoName = db_get("email-subname",0); if( zRepoName==0 ) goto send_alerts_done; @@ -2014,25 +2063,36 @@ senderFlags |= EMAIL_TRACE; } pSender = email_sender_new(zDest, senderFlags); db_multi_exec( "DROP TABLE IF EXISTS temp.wantalert;" - "CREATE TEMP TABLE wantalert(eventId TEXT);" + "CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN, sentMod);" ); if( flags & SENDALERT_DIGEST ){ + /* Unmoderated changes are never sent as part of a digest */ db_multi_exec( - "INSERT INTO wantalert SELECT eventid FROM pending_alert" + "INSERT INTO wantalert(eventId,needMod)" + " SELECT eventid, 0" + " FROM pending_alert" " WHERE sentDigest IS FALSE" + " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2));" ); zDigest = "true"; }else{ + /* Immediate alerts might include events that are subject to + ** moderator approval */ db_multi_exec( - "INSERT INTO wantalert SELECT eventid FROM pending_alert" - " WHERE sentSep IS FALSE" + "INSERT INTO wantalert(eventId,needMod,sentMod)" + " SELECT eventid," + " EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2))," + " sentMod" + " FROM pending_alert" + " WHERE sentSep IS FALSE;" + "DELETE FROM wantalert WHERE needMod AND sentMod;" ); } - pEvents = email_compute_event_text(&nEvent); + pEvents = email_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0); if( nEvent==0 ) goto send_alerts_done; blob_init(&hdr, 0, 0); blob_init(&body, 0, 0); db_prepare(&q, "SELECT" @@ -2051,13 +2111,26 @@ const char *zEmail = db_column_text(&q, 1); const char *zCap = db_column_text(&q, 3); int nHit = 0; for(p=pEvents; p; p=p->pNext){ if( strchr(zSub,p->type)==0 ) continue; - if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){ - /* Setup and admin users can get any notification */ + if( p->needMod ){ + /* For events that require moderator approval, only send an alert + ** if the recipient is a moderator for that type of event */ + char xType = '*'; + switch( p->type ){ + case 'f': xType = '5'; break; + case 't': xType = 'q'; break; + case 'w': xType = 'l'; break; + } + if( strchr(zCap,xType)==0 ) continue; + }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){ + /* Setup and admin users can get any notification that does not + ** require moderation */ }else{ + /* Other users only see the alert if they have sufficient + ** privilege to view the event itself */ char xType = '*'; switch( p->type ){ case 'c': xType = 'o'; break; case 'f': xType = '2'; break; case 't': xType = 'r'; break; @@ -2089,15 +2162,24 @@ blob_reset(&body); db_finalize(&q); email_free_eventlist(pEvents); if( (flags & SENDALERT_PRESERVE)==0 ){ if( flags & SENDALERT_DIGEST ){ - db_multi_exec("UPDATE pending_alert SET sentDigest=true"); + db_multi_exec( + "UPDATE pending_alert SET sentDigest=true" + " WHERE eventid IN (SELECT eventid FROM wantalert);" + "DELETE FROM pending_alert WHERE sentDigest AND sentSep;" + ); }else{ - db_multi_exec("UPDATE pending_alert SET sentSep=true"); + db_multi_exec( + "UPDATE pending_alert SET sentSep=true" + " WHERE eventid IN (SELECT eventid FROM wantalert WHERE NOT needMod);" + "UPDATE pending_alert SET sentMod=true" + " WHERE eventid IN (SELECT eventid FROM wantalert WHERE needMod);" + "DELETE FROM pending_alert WHERE sentDigest AND sentSep;" + ); } - db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep"); } send_alerts_done: email_sender_free(pSender); if( g.fSqlTrace ) fossil_trace("-- END email_send_alerts(%u)\n", flags); db_end_transaction(0); @@ -2131,11 +2213,11 @@ ** A web-form to send an email message to the repository administrator, ** or (with appropriate permissions) to anybody. */ void contact_admin_page(void){ const char *zAdminEmail = db_get("email-admin",0); - unsigned int uSeed; + unsigned int uSeed = 0; const char *zDecoded; char *zCaptcha = 0; login_check_credentials(); if( zAdminEmail==0 || zAdminEmail[0]==0 ){ Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ src/json_timeline.c @@ -35,10 +35,11 @@ that we end up with HTTP clients using 3 different names for the same requests. */ {"branch", json_timeline_branch, 0}, {"checkin", json_timeline_ci, 0}, +{"event", json_timeline_event, 0}, {"ticket", json_timeline_ticket, 0}, {"wiki", json_timeline_wiki, 0}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; @@ -519,10 +520,66 @@ payV = NULL; ok: db_finalize(&q); return payV; } + +/* +** Implementation of /json/timeline/event. +** +*/ +cson_value * json_timeline_event(){ + /* This code is 95% the same as json_timeline_ci(), by the way. */ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_array * list = NULL; + int check = 0; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + if( !g.perm.RdWiki ){ + json_set_err( FSL_JSON_E_DENIED, "Event timeline requires 'j' access."); + return NULL; + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + check = json_timeline_setup_sql( "e", &sql, pay ); + if(check){ + json_set_err(check, "Query initialization failed."); + goto error; + } + +#if 0 + /* only for testing! */ + cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)))); +#endif + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); + blob_reset(&sql); + db_prepare(&q, "SELECT" + /* For events, the name is generally more useful than + the uuid, but the uuid is unambiguous and can be used + with commands like 'artifact'. */ + " substr((SELECT tagname FROM tag AS tn WHERE tn.tagid=json_timeline.tagId AND tagname LIKE 'event-%%'),7) AS name," + " uuid as uuid," + " mtime AS timestamp," + " comment AS comment, " + " user AS user," + " eventType AS eventType" + " FROM json_timeline" + " ORDER BY rowid"); + list = cson_new_array(); + json_stmt_to_array_of_obj(&q, list); + cson_object_set(pay, "timeline", cson_array_value(list)); + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + db_finalize(&q); + blob_reset(&sql); + return payV; +} /* ** Implementation of /json/timeline/wiki. ** */ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1838,41 +1838,43 @@ if( g.fCgiTrace ){ fossil_trace("######## Calling %s #########\n", pCmd->zName); cgi_print_all(1, 1); } #ifdef FOSSIL_ENABLE_TH1_HOOKS - /* - ** The TH1 return codes from the hook will be handled as follows: - ** - ** TH_OK: The xFunc() and the TH1 notification will both be executed. - ** - ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be - ** skipped. If the xFunc() is being hooked, the error message - ** will be emitted. - ** - ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. - ** - ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be - ** skipped. - ** - ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be - ** executed. - */ - int rc; - if( !g.fNoThHook ){ - rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags); - }else{ - rc = TH_OK; - } - if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ - if( rc==TH_OK || rc==TH_RETURN ){ + { + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be + ** skipped. If the xFunc() is being hooked, the error message + ** will be emitted. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + int rc; + if( !g.fNoThHook ){ + rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ #endif - pCmd->xFunc(); + pCmd->xFunc(); #ifdef FOSSIL_ENABLE_TH1_HOOKS - } - if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ - Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags); + } + if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags); + } } } #endif } @@ -2019,10 +2021,15 @@ } g.httpOut = stdout; g.httpIn = stdin; fossil_binary_mode(g.httpOut); fossil_binary_mode(g.httpIn); +#if !defined(_WIN32) + /* Work around a bug in older versions of althttpd by making sure no + ** file descriptors other than 0, 1, and 2 are open. */ + { int i; for(i=3; close(i)==0 || i<6; i++){} } +#endif g.cgiOutput = 1; blob_read_from_file(&config, zFile, ExtFILE); while( blob_line(&config, &line) ){ if( !blob_token(&line, &key) ) continue; if( blob_buffer(&key)[0]=='#' ) continue; Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -1806,10 +1806,11 @@ foreach s [lsort $extra_files] { if {$i > 0} { writeln " \\" writeln -nonewline " " } + set s [file nativename $s] writeln -nonewline "\$(SRCDIR)\\${s}"; incr i } writeln "\n" set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation] writeln -nonewline "OBJ = " Index: src/translate.c ================================================================== --- src/translate.c +++ src/translate.c @@ -203,37 +203,44 @@ fprintf(out,"\n%*s\"%s%s\"",indent+5, "", zOut, zNewline); } } } } + +static void print_source_ref(const char *zSrcFile, FILE *out){ +/* Set source line reference to the original source file. + * This makes compiler show the original file name in the compile error + * messages, instead of referring to the translated file. + * NOTE: This somewhat complicates stepping in debugger, as the resuling + * code would not match the referenced sources. + */ +#ifndef FOSSIL_DEBUG + const char *arg; + if( !*zSrcFile ){ + return; + } + fprintf(out,"#line 1 \""); + for(arg=zSrcFile; *arg; arg++){ + if( *arg!='\\' ){ + fprintf(out,"%c", *arg); + }else{ + fprintf(out,"\\\\"); + } + } + fprintf(out,"\"\n"); +#endif +} int main(int argc, char **argv){ if( argc==2 ){ - char *arg; FILE *in = fopen(argv[1], "r"); if( in==0 ){ fprintf(stderr,"can not open %s\n", argv[1]); exit(1); } zInFile = argv[1]; -#ifndef FOSSIL_DEBUG - /* Set source line reference to the original source file. - * This makes compiler show the original file name in the compile error - * messages, instead of referring to the translated file. - * NOTE: This somewhat complicates stepping in debugger, as the resuling - * code would not match the referenced sources. - */ - printf("#line 1 \""); - for(arg=argv[1]; *arg; arg++){ - if( *arg!='\\' ){ - printf("%c", *arg); - }else{ - printf("\\\\"); - } - } - printf("\"\n"); -#endif + print_source_ref(zInFile, stdout); trans(in, stdout); fclose(in); }else{ trans(stdin, stdout); } Index: src/webmail.c ================================================================== --- src/webmail.c +++ src/webmail.c @@ -708,11 +708,11 @@ @ if( d==2 ){ @ @ }else{ - @ + @ if( d!=1 ){ @ } @ } @@ -732,10 +732,11 @@ while( db_step(&q)==SQLITE_ROW ){ const char *zId = db_column_text(&q,0); const char *zFrom = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zSubject = db_column_text(&q, 4); + if( zSubject==0 || zSubject[0]==0 ) zSubject = "(no subject)"; @ @ @ %h(zFrom) @ %h(zSubject) \ @ %s(zDate) Index: src/winhttp.c ================================================================== --- src/winhttp.c +++ src/winhttp.c @@ -20,10 +20,14 @@ ** server to be run without any user logged on. */ #include "config.h" #ifdef _WIN32 /* This code is for win32 only */ +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0501 +# endif +#include #include #include #include #include "winhttp.h" Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -720,11 +720,11 @@ lappend ignored_test $name } else { protOut "test $name FAILED!" 1 if {$::QUIET} {protOut "RESULT: $RESULT" 1} lappend bad_test $name - if {$::HALT} exit + if {$::HALT} {exit 1} } } } set bad_test {} set ignored_test {} Index: win/Makefile.mingw.mistachkin ================================================================== --- win/Makefile.mingw.mistachkin +++ win/Makefile.mingw.mistachkin @@ -407,10 +407,14 @@ LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 endif else LIB += -lkernel32 -lws2_32 endif + +#### Library required for DNS lookups. +# +LIB += -ldnsapi #### Tcl shell for use in running the fossil test suite. This is only # used for testing. # TCLSH = tclsh @@ -435,10 +439,11 @@ SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ + $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ @@ -461,17 +466,19 @@ $(SRCDIR)/descendants.c \ $(SRCDIR)/diff.c \ $(SRCDIR)/diffcmd.c \ $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ + $(SRCDIR)/email.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ + $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ @@ -530,10 +537,11 @@ $(SRCDIR)/sha1hard.c \ $(SRCDIR)/sha3.c \ $(SRCDIR)/shun.c \ $(SRCDIR)/sitemap.c \ $(SRCDIR)/skins.c \ + $(SRCDIR)/smtp.c \ $(SRCDIR)/sqlcmd.c \ $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ @@ -552,10 +560,11 @@ $(SRCDIR)/user.c \ $(SRCDIR)/utf8.c \ $(SRCDIR)/util.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ + $(SRCDIR)/webmail.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/wysiwyg.c \ @@ -636,10 +645,11 @@ TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ + $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ @@ -662,17 +672,19 @@ $(OBJDIR)/descendants_.c \ $(OBJDIR)/diff_.c \ $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/dispatch_.c \ $(OBJDIR)/doc_.c \ + $(OBJDIR)/email_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ + $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ @@ -731,10 +743,11 @@ $(OBJDIR)/sha1hard_.c \ $(OBJDIR)/sha3_.c \ $(OBJDIR)/shun_.c \ $(OBJDIR)/sitemap_.c \ $(OBJDIR)/skins_.c \ + $(OBJDIR)/smtp_.c \ $(OBJDIR)/sqlcmd_.c \ $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ @@ -753,10 +766,11 @@ $(OBJDIR)/user_.c \ $(OBJDIR)/utf8_.c \ $(OBJDIR)/util_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ + $(OBJDIR)/webmail_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winfile_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/wysiwyg_.c \ @@ -766,10 +780,11 @@ OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ + $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ @@ -792,17 +807,19 @@ $(OBJDIR)/descendants.o \ $(OBJDIR)/diff.o \ $(OBJDIR)/diffcmd.o \ $(OBJDIR)/dispatch.o \ $(OBJDIR)/doc.o \ + $(OBJDIR)/email.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ + $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ @@ -861,10 +878,11 @@ $(OBJDIR)/sha1hard.o \ $(OBJDIR)/sha3.o \ $(OBJDIR)/shun.o \ $(OBJDIR)/sitemap.o \ $(OBJDIR)/skins.o \ + $(OBJDIR)/smtp.o \ $(OBJDIR)/sqlcmd.o \ $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ @@ -883,10 +901,11 @@ $(OBJDIR)/user.o \ $(OBJDIR)/utf8.o \ $(OBJDIR)/util.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ + $(OBJDIR)/webmail.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ @@ -1115,10 +1134,11 @@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ + $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ @@ -1141,17 +1161,19 @@ $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \ $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \ $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ + $(OBJDIR)/email_.c:$(OBJDIR)/email.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ + $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ @@ -1210,10 +1232,11 @@ $(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \ $(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \ $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \ $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \ $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \ + $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \ $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \ $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ @@ -1232,10 +1255,11 @@ $(OBJDIR)/user_.c:$(OBJDIR)/user.h \ $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h \ $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h \ $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h \ + $(OBJDIR)/webmail_.c:$(OBJDIR)/webmail.h \ $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \ @@ -1272,10 +1296,18 @@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers + +$(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ + +$(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c + +$(OBJDIR)/backoffice.h: $(OBJDIR)/headers $(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/bag.c >$@ $(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h @@ -1480,10 +1512,18 @@ $(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c $(OBJDIR)/doc.h: $(OBJDIR)/headers + +$(OBJDIR)/email_.c: $(SRCDIR)/email.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/email.c >$@ + +$(OBJDIR)/email.o: $(OBJDIR)/email_.c $(OBJDIR)/email.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/email.o -c $(OBJDIR)/email_.c + +$(OBJDIR)/email.h: $(OBJDIR)/headers $(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/encode.c >$@ $(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h @@ -1536,10 +1576,18 @@ $(OBJDIR)/foci.o: $(OBJDIR)/foci_.c $(OBJDIR)/foci.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/foci.o -c $(OBJDIR)/foci_.c $(OBJDIR)/foci.h: $(OBJDIR)/headers + +$(OBJDIR)/forum_.c: $(SRCDIR)/forum.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/forum.c >$@ + +$(OBJDIR)/forum.o: $(OBJDIR)/forum_.c $(OBJDIR)/forum.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/forum.o -c $(OBJDIR)/forum_.c + +$(OBJDIR)/forum.h: $(OBJDIR)/headers $(OBJDIR)/fshell_.c: $(SRCDIR)/fshell.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fshell.c >$@ $(OBJDIR)/fshell.o: $(OBJDIR)/fshell_.c $(OBJDIR)/fshell.h $(SRCDIR)/config.h @@ -2032,10 +2080,18 @@ $(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c $(OBJDIR)/skins.h: $(OBJDIR)/headers + +$(OBJDIR)/smtp_.c: $(SRCDIR)/smtp.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/smtp.c >$@ + +$(OBJDIR)/smtp.o: $(OBJDIR)/smtp_.c $(OBJDIR)/smtp.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/smtp.o -c $(OBJDIR)/smtp_.c + +$(OBJDIR)/smtp.h: $(OBJDIR)/headers $(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$@ $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h @@ -2208,10 +2264,18 @@ $(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h: $(OBJDIR)/headers + +$(OBJDIR)/webmail_.c: $(SRCDIR)/webmail.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/webmail.c >$@ + +$(OBJDIR)/webmail.o: $(OBJDIR)/webmail_.c $(OBJDIR)/webmail.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/webmail.o -c $(OBJDIR)/webmail_.c + +$(OBJDIR)/webmail.h: $(OBJDIR)/headers $(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/wiki.c >$@ $(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -513,65 +513,65 @@ wysiwyg_.c \ xfer_.c \ xfersetup_.c \ zip_.c -EXTRA_FILES = $(SRCDIR)\../skins/aht/details.txt \ - $(SRCDIR)\../skins/ardoise/css.txt \ - $(SRCDIR)\../skins/ardoise/details.txt \ - $(SRCDIR)\../skins/ardoise/footer.txt \ - $(SRCDIR)\../skins/ardoise/header.txt \ - $(SRCDIR)\../skins/black_and_white/css.txt \ - $(SRCDIR)\../skins/black_and_white/details.txt \ - $(SRCDIR)\../skins/black_and_white/footer.txt \ - $(SRCDIR)\../skins/black_and_white/header.txt \ - $(SRCDIR)\../skins/blitz/css.txt \ - $(SRCDIR)\../skins/blitz/details.txt \ - $(SRCDIR)\../skins/blitz/footer.txt \ - $(SRCDIR)\../skins/blitz/header.txt \ - $(SRCDIR)\../skins/blitz/ticket.txt \ - $(SRCDIR)\../skins/blitz_no_logo/css.txt \ - $(SRCDIR)\../skins/blitz_no_logo/details.txt \ - $(SRCDIR)\../skins/blitz_no_logo/footer.txt \ - $(SRCDIR)\../skins/blitz_no_logo/header.txt \ - $(SRCDIR)\../skins/blitz_no_logo/ticket.txt \ - $(SRCDIR)\../skins/bootstrap/css.txt \ - $(SRCDIR)\../skins/bootstrap/details.txt \ - $(SRCDIR)\../skins/bootstrap/footer.txt \ - $(SRCDIR)\../skins/bootstrap/header.txt \ - $(SRCDIR)\../skins/default/css.txt \ - $(SRCDIR)\../skins/default/details.txt \ - $(SRCDIR)\../skins/default/footer.txt \ - $(SRCDIR)\../skins/default/header.txt \ - $(SRCDIR)\../skins/eagle/css.txt \ - $(SRCDIR)\../skins/eagle/details.txt \ - $(SRCDIR)\../skins/eagle/footer.txt \ - $(SRCDIR)\../skins/eagle/header.txt \ - $(SRCDIR)\../skins/enhanced1/css.txt \ - $(SRCDIR)\../skins/enhanced1/details.txt \ - $(SRCDIR)\../skins/enhanced1/footer.txt \ - $(SRCDIR)\../skins/enhanced1/header.txt \ - $(SRCDIR)\../skins/khaki/css.txt \ - $(SRCDIR)\../skins/khaki/details.txt \ - $(SRCDIR)\../skins/khaki/footer.txt \ - $(SRCDIR)\../skins/khaki/header.txt \ - $(SRCDIR)\../skins/original/css.txt \ - $(SRCDIR)\../skins/original/details.txt \ - $(SRCDIR)\../skins/original/footer.txt \ - $(SRCDIR)\../skins/original/header.txt \ - $(SRCDIR)\../skins/plain_gray/css.txt \ - $(SRCDIR)\../skins/plain_gray/details.txt \ - $(SRCDIR)\../skins/plain_gray/footer.txt \ - $(SRCDIR)\../skins/plain_gray/header.txt \ - $(SRCDIR)\../skins/rounded1/css.txt \ - $(SRCDIR)\../skins/rounded1/details.txt \ - $(SRCDIR)\../skins/rounded1/footer.txt \ - $(SRCDIR)\../skins/rounded1/header.txt \ - $(SRCDIR)\../skins/xekri/css.txt \ - $(SRCDIR)\../skins/xekri/details.txt \ - $(SRCDIR)\../skins/xekri/footer.txt \ - $(SRCDIR)\../skins/xekri/header.txt \ +EXTRA_FILES = $(SRCDIR)\..\skins\aht\details.txt \ + $(SRCDIR)\..\skins\ardoise\css.txt \ + $(SRCDIR)\..\skins\ardoise\details.txt \ + $(SRCDIR)\..\skins\ardoise\footer.txt \ + $(SRCDIR)\..\skins\ardoise\header.txt \ + $(SRCDIR)\..\skins\black_and_white\css.txt \ + $(SRCDIR)\..\skins\black_and_white\details.txt \ + $(SRCDIR)\..\skins\black_and_white\footer.txt \ + $(SRCDIR)\..\skins\black_and_white\header.txt \ + $(SRCDIR)\..\skins\blitz\css.txt \ + $(SRCDIR)\..\skins\blitz\details.txt \ + $(SRCDIR)\..\skins\blitz\footer.txt \ + $(SRCDIR)\..\skins\blitz\header.txt \ + $(SRCDIR)\..\skins\blitz\ticket.txt \ + $(SRCDIR)\..\skins\blitz_no_logo\css.txt \ + $(SRCDIR)\..\skins\blitz_no_logo\details.txt \ + $(SRCDIR)\..\skins\blitz_no_logo\footer.txt \ + $(SRCDIR)\..\skins\blitz_no_logo\header.txt \ + $(SRCDIR)\..\skins\blitz_no_logo\ticket.txt \ + $(SRCDIR)\..\skins\bootstrap\css.txt \ + $(SRCDIR)\..\skins\bootstrap\details.txt \ + $(SRCDIR)\..\skins\bootstrap\footer.txt \ + $(SRCDIR)\..\skins\bootstrap\header.txt \ + $(SRCDIR)\..\skins\default\css.txt \ + $(SRCDIR)\..\skins\default\details.txt \ + $(SRCDIR)\..\skins\default\footer.txt \ + $(SRCDIR)\..\skins\default\header.txt \ + $(SRCDIR)\..\skins\eagle\css.txt \ + $(SRCDIR)\..\skins\eagle\details.txt \ + $(SRCDIR)\..\skins\eagle\footer.txt \ + $(SRCDIR)\..\skins\eagle\header.txt \ + $(SRCDIR)\..\skins\enhanced1\css.txt \ + $(SRCDIR)\..\skins\enhanced1\details.txt \ + $(SRCDIR)\..\skins\enhanced1\footer.txt \ + $(SRCDIR)\..\skins\enhanced1\header.txt \ + $(SRCDIR)\..\skins\khaki\css.txt \ + $(SRCDIR)\..\skins\khaki\details.txt \ + $(SRCDIR)\..\skins\khaki\footer.txt \ + $(SRCDIR)\..\skins\khaki\header.txt \ + $(SRCDIR)\..\skins\original\css.txt \ + $(SRCDIR)\..\skins\original\details.txt \ + $(SRCDIR)\..\skins\original\footer.txt \ + $(SRCDIR)\..\skins\original\header.txt \ + $(SRCDIR)\..\skins\plain_gray\css.txt \ + $(SRCDIR)\..\skins\plain_gray\details.txt \ + $(SRCDIR)\..\skins\plain_gray\footer.txt \ + $(SRCDIR)\..\skins\plain_gray\header.txt \ + $(SRCDIR)\..\skins\rounded1\css.txt \ + $(SRCDIR)\..\skins\rounded1\details.txt \ + $(SRCDIR)\..\skins\rounded1\footer.txt \ + $(SRCDIR)\..\skins\rounded1\header.txt \ + $(SRCDIR)\..\skins\xekri\css.txt \ + $(SRCDIR)\..\skins\xekri\details.txt \ + $(SRCDIR)\..\skins\xekri\footer.txt \ + $(SRCDIR)\..\skins\xekri\header.txt \ $(SRCDIR)\ci_edit.js \ $(SRCDIR)\diff.tcl \ $(SRCDIR)\forum.js \ $(SRCDIR)\graph.js \ $(SRCDIR)\href.js \