5 * This is a command-line script that will send out any email
6 * reminders that are due.
9 * php send_reminders.php
12 * This script should be setup to run periodically on your system.
13 * You could run it once every minute, but every 5-15 minutes should be
16 * To set this up in cron, add a line like the following in your crontab
17 * to run it every 10 minutes:
18 * 1,11,21,31,41,51 * * * * php /some/path/here/send_reminders.php
19 * Of course, change the path to where this script lives. If the
20 * php binary is not in your $PATH, you may also need to provide
21 * the full path to "php".
22 * On Linux, just type crontab -e to edit your crontab.
24 * If you're a Windows user, you'll either need to find a cron clone
25 * for Windows (they're out there) or use the Windows Task Scheduler.
26 * (See docs/WebCalendar-SysAdmin.html for instructions.)
29 * You will need access to the PHP binary (command-line) rather than
30 * the module-based version that is typically installed for use with
31 * a web server.to build as a CGI (rather than an Apache module) for
33 * If running this script from the command line generates PHP
34 * warnings, you can disable error_reporting by adding
35 * "-d error_reporting=0" to the command line:
36 * php -d error_reporting=0 /some/path/here/tools/send_reminders.php
38 *********************************************************************/
40 // How many days in advance can a reminder be sent (max)
41 // this will affect performance, but keep in mind that someone may enter
42 // a reminder to be sent 60 days in advance or they may enter a specific
43 // date for a reminder to be sent that is more than 30 days before the
44 // event's date. If you're only running this once an hour or less often,
45 // then you could certainly change this to look a whole 365 days ahead.
46 $DAYS_IN_ADVANCE = 30;
47 //$DAYS_IN_ADVANCE = 365;
50 // Load include files.
51 // If you have moved this script out of the WebCalendar directory,
52 // which you probably should do since it would be better for security
53 // reasons, you would need to change $includedir to point to the
54 // webcalendar include directory.
55 // We set these twice since config.php unsets these.
56 $includedir = "../includes"; // Set again after config.php
58 include "$includedir/config.php";
60 $basedir = ".."; // points to the base WebCalendar directory relative to
61 // current working directory
62 $includedir = "../includes";
64 include "$includedir/php-dbi.php";
65 include "$includedir/functions.php";
66 include "$includedir/$user_inc";
67 include "$includedir/site_extras.php";
69 $debug = false; // set to true to print debug info...
70 $only_testing = false; // act like we're sending, but don't send -- for debugging
73 // Establish a database connection.
74 $c = dbi_connect ( $db_host, $db_login, $db_password, $db_database );
76 echo "Error connecting to database: " . dbi_error ();
80 load_global_settings ();
82 include "$includedir/translate.php";
87 // Get a list of people who have asked not to receive email
88 $res = dbi_query ( "SELECT cal_login FROM webcal_user_pref " .
89 "WHERE cal_setting = 'EMAIL_REMINDER' " .
90 "AND cal_value = 'N'" );
93 while ( $row = dbi_fetch_row ( $res ) ) {
97 echo "User $user does not want email. <br />\n";
99 dbi_free_result ( $res );
102 // Get a list of the email users in the system.
103 // They must also have an email address. Otherwise, we can't
104 // send them mail, so what's the point?
105 $allusers = user_get_users ();
106 for ( $i = 0; $i < count ( $allusers ); $i++ ) {
107 $names[$allusers[$i]['cal_login']] = $allusers[$i]['cal_fullname'];
108 $emails[$allusers[$i]['cal_login']] = $allusers[$i]['cal_email'];
112 // Get all users language settings.
113 $res = dbi_query ( "SELECT cal_login, cal_value FROM webcal_user_pref " .
114 "WHERE cal_setting = 'LANGUAGE'" );
115 $languages = array ();
117 while ( $row = dbi_fetch_row ( $res ) ) {
119 $user_lang = $row[1];
120 $languages[$user] = $user_lang;
122 echo "Language for $user is \"$user_lang\" <br />\n";
124 dbi_free_result ( $res );
127 // Get all users timezone settings.
128 $res = dbi_query ( "SELECT cal_login, cal_value FROM webcal_user_pref " .
129 "WHERE cal_setting = 'TZ_OFFSET'" );
130 $tzoffset = array ();
132 while ( $row = dbi_fetch_row ( $res ) ) {
134 $user_tzoffset = $row[1];
135 $tzoffset[$user] = $user_tzoffset;
137 echo "TZ OFFSET for $user is \"$user_tzoffset\" <br />\n";
139 dbi_free_result ( $res );
142 $startdate = date ( "Ymd" );
143 $enddate = date ( "Ymd", time() + ( $DAYS_IN_ADVANCE * 24 * 3600 ) );
145 // Now read events all the repeating events (for all users)
146 $repeated_events = query_events ( "", true, "AND (webcal_entry_repeats.cal_end > $startdate OR webcal_entry_repeats.cal_end IS NULL) " );
148 // Read non-repeating events (for all users)
150 echo "Checking for events from date $startdate to date $enddate <br />\n";
151 $events = read_events ( "", $startdate, $enddate );
153 echo "Found " . count ( $events ) . " events in time range. <br />\n";
156 function indent ( $str ) {
157 return " " . str_replace ( "\n", "\n ", $str );
161 // Send a reminder for a single event for a single day to all
162 // participants in the event.
163 // Send to participants who have accepted as well as those who have not yet
164 // approved. But, don't send to users how rejected (cal_status='R').
165 function send_reminder ( $id, $event_date ) {
166 global $names, $emails, $site_extras, $debug, $only_testing,
167 $server_url, $languages, $tzoffset, $application_name;
168 global $EXTRA_TEXT, $EXTRA_MULTILINETEXT, $EXTRA_URL, $EXTRA_DATE,
169 $EXTRA_EMAIL, $EXTRA_USER, $EXTRA_REMINDER, $LANGUAGE, $LOG_REMINDER;
170 global $allow_external_users, $external_reminders;
172 $pri[1] = translate("Low");
173 $pri[2] = translate("Medium");
174 $pri[3] = translate("High");
176 // get participants first...
178 $sql = "SELECT cal_login FROM webcal_entry_user " .
179 "WHERE cal_id = $id AND cal_status IN ('A','W') " .
180 "ORDER BY cal_login";
182 $res = dbi_query ( $sql );
183 $participants = array ();
184 $num_participants = 0;
186 while ( $row = dbi_fetch_row ( $res ) ) {
187 $participants[$num_participants++] = $row[0];
191 // get external participants
192 $ext_participants = array ();
193 $num_ext_participants = 0;
194 if ( ! empty ( $allow_external_users ) && $allow_external_users == "Y" &&
195 ! empty ( $external_reminders ) && $external_reminders == "Y" ) {
196 $sql = "SELECT cal_fullname, cal_email FROM webcal_entry_ext_user " .
197 "WHERE cal_id = $id AND cal_email IS NOT NULL " .
198 "ORDER BY cal_fullname";
199 $res = dbi_query ( $sql );
201 while ( $row = dbi_fetch_row ( $res ) ) {
202 $ext_participants[$num_ext_participants] = $row[0];
203 $ext_participants_email[$num_ext_participants++] = $row[1];
208 if ( ! $num_participants && ! $num_ext_participants ) {
210 echo "No participants found for event id: $id <br />\n";
217 "SELECT cal_create_by, cal_date, cal_time, cal_mod_date, " .
218 "cal_mod_time, cal_duration, cal_priority, cal_type, cal_access, " .
219 "cal_name, cal_description FROM webcal_entry WHERE cal_id = $id" );
221 echo "Db error: could not find event id $id.\n";
226 if ( ! ( $row = dbi_fetch_row ( $res ) ) ) {
227 echo "Error: could not find event id $id in database.\n";
231 // send mail. we send one user at a time so that we can switch
232 // languages between users if needed.
233 $mailusers = array ();
234 $recipients = array ();
235 if ( isset ( $single_user ) && $single_user == "Y" ) {
236 $mailusers[] = $emails[$single_user_login];
237 $recipients[] = $single_user_login;
239 for ( $i = 0; $i < count ( $participants ); $i++ ) {
240 if ( strlen ( $emails[$participants[$i]] ) ) {
241 $mailusers[] = $emails[$participants[$i]];
242 $recipients[] = $participants[$i];
245 echo "No email for user $participants[$i] <br />\n";
248 for ( $i = 0; $i < count ( $ext_participants ); $i++ ) {
249 $mailusers[] = $ext_participants_email[$i];
250 $recipients[] = $ext_participants[$i];
254 echo "Found " . count ( $mailusers ) . " with email addresses <br />\n";
255 for ( $j = 0; $j < count ( $mailusers ); $j++ ) {
256 $recip = $mailusers[$j];
257 $user = $participants[$j];
258 if ( ! empty ( $languages[$user] ) )
259 $userlang = $languages[$user];
261 $userlang = $LANGUAGE; // system default
262 if ( $userlang == "none" )
263 $userlang = "English-US"; // gotta pick something
265 echo "Setting language to \"$userlang\" <br />\n";
266 reset_language ( $userlang );
267 // reset timezone setting for current user
268 if ( empty ( $tzoffset[$user] ) )
269 $GLOBALS["TZ_OFFSET"] = 0;
271 $GLOBALS["TZ_OFFSET"] = $tzoffset[$user];
273 $body = translate("This is a reminder for the event detailed below.") .
276 $create_by = $row[0];
278 $description = $row[10];
280 // add trailing '/' if not found in server_url
281 if ( ! empty ( $server_url ) ) {
282 if ( substr ( $server_url, -1, 1 ) == "/" ) {
283 $body .= $server_url . "view_entry.php?id=" . $id . "\n\n";
285 $body .= $server_url . "/view_entry.php?id=" . $id . "\n\n";
289 $body .= strtoupper ( $name ) . "\n\n";
290 $body .= translate("Description") . ":\n";
291 $body .= indent ( $description ) . "\n";
292 $body .= translate("Date") . ": " . date_to_str ( $event_date ) . "\n";
294 $body .= translate ("Time") . ": " . display_time ( $row[2] ) . "\n";
296 $body .= translate ("Duration") . ": " . $row[5] .
297 " " . translate("minutes") . "\n";
298 if ( ! empty ( $disable_priority_field ) && ! $disable_priority_field )
299 $body .= translate("Priority") . ": " . $pri[$row[6]] . "\n";
300 if ( ! empty ( $disable_access_field ) && ! $disable_access_field )
301 $body .= translate("Access") . ": " .
302 ( $row[8] == "P" ? translate("Public") : translate("Confidential") ) .
304 if ( ! empty ( $single_user_login ) && $single_user_login == false )
305 $body .= translate("Created by") . ": " . $row[0] . "\n";
306 $body .= translate("Updated") . ": " . date_to_str ( $row[3] ) . " " .
307 display_time ( $row[4] ) . "\n";
310 $extras = get_site_extra_fields ( $id );
311 for ( $i = 0; $i < count ( $site_extras ); $i++ ) {
312 $extra_name = $site_extras[$i][0];
313 $extra_descr = $site_extras[$i][1];
314 $extra_type = $site_extras[$i][2];
315 if ( $extras[$extra_name]['cal_name'] != "" ) {
316 $body .= translate ( $extra_descr ) . ": ";
317 if ( $extra_type == $EXTRA_DATE ) {
318 $body .= date_to_str ( $extras[$extra_name]['cal_date'] ) . "\n";
319 } else if ( $extra_type == $EXTRA_MULTILINETEXT ) {
320 $body .= "\n" . indent ( $extras[$extra_name]['cal_data'] ) . "\n";
321 } else if ( $extra_type == $EXTRA_REMINDER ) {
322 $body .= ( $extras[$extra_name]['cal_remind'] > 0 ?
323 translate("Yes") : translate("No") ) . "\n";
325 // default method for $EXTRA_URL, $EXTRA_TEXT, etc...
326 $body .= $extras[$extra_name]['cal_data'] . "\n";
330 if ( ! empty ( $single_user ) && $single_user != "Y" &&
331 ! empty ( $disable_participants_field ) && ! $disable_participants_field ) {
332 $body .= translate("Participants") . ":\n";
333 for ( $i = 0; $i < count ( $participants ); $i++ ) {
334 $body .= " " . $names[$participants[$i]] . "\n";
336 for ( $i = 0; $i < count ( $ext_participants ); $i++ ) {
337 $body .= " " . $ext_participants[$i] . " (" .
338 translate("External User") . ")\n";
342 $subject = translate("Reminder") . ": " . $name;
344 if ( strlen ( $GLOBALS["email_fallback_from"] ) )
345 $extra_hdrs = "From: " . $GLOBALS["email_fallback_from"] . "\r\n" .
346 "X-Mailer: " . translate($application_name);
348 $extra_hdrs = "X-Mailer: " . translate($application_name);
351 echo "Sending mail to $recip (in $userlang)\n";
352 if ( $only_testing ) {
354 echo "<hr /><pre>To: $recip\nSubject: $subject\n$extra_hdrs\n\n$body\n\n</pre>\n";
356 print $recip." ".$subject ;
357 mail ( $recip, $subject, utf8_decode($body), $extra_hdrs );
358 activity_log ( $id, "system", $user, $LOG_REMINDER, "" );
364 // keep track of the fact that we send the reminder, so we don't
366 function log_reminder ( $id, $name, $event_date ) {
367 global $only_testing;
369 if ( ! $only_testing ) {
370 dbi_query ( "DELETE FROM webcal_reminder_log " .
371 "WHERE cal_id = $id AND cal_name = '$name' " .
372 "AND cal_event_date = $event_date" );
373 dbi_query ( "INSERT INTO webcal_reminder_log " .
374 "( cal_id, cal_name, cal_event_date, cal_last_sent ) VALUES ( " .
375 "$id, '" . $name . "', $event_date, " . time() . ")" );
380 // Process an event for a single day. Check to see if it has
381 // a reminder, when it needs to be sent and when the last time it
383 function process_event ( $id, $name, $event_date, $event_time ) {
384 global $site_extras, $debug, $only_testing;
385 global $EXTRA_REMINDER_WITH_OFFSET, $EXTRA_REMINDER_WITH_DATE ;
387 printf ( "Event %d: \"%s\" at %s on %s <br />\n",
388 $id, $name, $event_time, $event_date );
390 // Check to see if this event has any reminders
391 $extras = get_site_extra_fields ( $id );
392 for ( $j = 0; $j < count ( $site_extras ); $j++ ) {
393 $extra_name = $site_extras[$j][0];
394 $extra_type = $site_extras[$j][2];
395 $extra_arg1 = $site_extras[$j][3];
396 $extra_arg2 = $site_extras[$j][4];
400 printf ( " name: %s\n type: %d\n arg1: %s\n arg2: %s\n",
401 $extra_name, $extra_type, $extra_arg1, $extra_arg2 );
402 if ( ! empty ( $extras[$extra_name]['cal_remind'] ) ) {
405 echo " Reminder set for event. <br />\n";
406 // how many minutes before event should we send the reminder?
407 $ev_h = (int) ( $event_time / 10000 );
408 $ev_m = ( $event_time / 100 ) % 100;
409 $ev_year = substr ( $event_date, 0, 4 );
410 $ev_month = substr ( $event_date, 4, 2 );
411 $ev_day = substr ( $event_date, 6, 2 );
412 $event_time = mktime ( $ev_h, $ev_m, 0, $ev_month, $ev_day, $ev_year );
413 if ( ( $extra_arg2 & $EXTRA_REMINDER_WITH_OFFSET ) > 0 ) {
414 $minsbefore = $extras[$extra_name]['cal_data'];
415 $remind_time = $event_time - ( $minsbefore * 60 );
416 } else if ( ( $extra_arg2 & $EXTRA_REMINDER_WITH_DATE ) > 0 ) {
417 $rd = $extras[$extra_name]['cal_date'];
418 $r_year = substr ( $rd, 0, 4 );
419 $r_month = substr ( $rd, 4, 2 );
420 $r_day = substr ( $rd, 6, 2 );
421 $remind_time = mktime ( 0, 0, 0, $r_month, $r_day, $r_year );
423 $minsbefore = $extra_arg1;
424 $remind_time = $event_time - ( $minsbefore * 60 );
427 echo " Mins Before: $minsbefore <br />\n";
429 echo " Event time is: " . date ( "m/d/Y H:i", $event_time ) . "<br />\n";
430 echo " Remind time is: " . date ( "m/d/Y H:i", $remind_time ) . "<br />\n";
432 if ( time() >= $remind_time ) {
433 // It's remind time or later. See if one has already been sent
435 $res = dbi_query ( "SELECT MAX(cal_last_sent) FROM " .
436 "webcal_reminder_log WHERE cal_id = " . $id .
437 " AND cal_event_date = $event_date" .
438 " AND cal_name = '" . $extra_name . "'" );
440 if ( $row = dbi_fetch_row ( $res ) ) {
441 $last_sent = $row[0];
443 dbi_free_result ( $res );
446 echo " Last sent on: " . date ( "m/d/Y H:i", $last_sent ) . "<br />\n";
447 if ( $last_sent < $remind_time ) {
450 echo " SENDING REMINDER! <br />\n";
452 send_reminder ( $id, $event_date );
453 // now update the db...
454 log_reminder ( $id, $extra_name, $event_date );
462 $startdate = time(); // today
463 for ( $d = 0; $d < $DAYS_IN_ADVANCE; $d++ ) {
464 $date = date ( "Ymd", time() + ( $d * 24 * 3600 ) );
465 //echo "Date: $date\n";
466 // Get non-repeating events for this date.
467 // An event will be included one time for each participant.
468 $ev = get_entries ( "", $date );
469 // Keep track of duplicates
470 $completed_ids = array ( );
471 for ( $i = 0; $i < count ( $ev ); $i++ ) {
472 $id = $ev[$i]['cal_id'];
473 if ( ! empty ( $completed_ids[$id] ) )
475 $completed_ids[$id] = 1;
476 process_event ( $id, $ev[$i]['cal_name'], $date, $ev[$i]['cal_time'] );
478 $rep = get_repeating_entries ( "", $date );
479 for ( $i = 0; $i < count ( $rep ); $i++ ) {
480 $id = $rep[$i]['cal_id'];
481 if ( ! empty ( $completed_ids[$id] ) )
483 $completed_ids[$id] = 1;
484 process_event ( $id, $rep[$i]['cal_name'], $date, $rep[$i]['cal_time'] );
489 echo "Done.<br />\n";