]>
Commit | Line | Data |
---|---|---|
9f3d18d4 | 1 | #!/usr/bin/perl -T |
fe2a998b FB |
2 | # This Source Code Form is subject to the terms of the Mozilla Public |
3 | # License, v. 2.0. If a copy of the MPL was not distributed with this | |
4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
f6c796ad | 5 | # |
fe2a998b FB |
6 | # This Source Code Form is "Incompatible With Secondary Licenses", as |
7 | # defined by the Mozilla Public License, v. 2.0. | |
f6c796ad | 8 | |
9 | ################################################################################ | |
10 | # Script Initialization | |
11 | ################################################################################ | |
12 | ||
1001cbba | 13 | use 5.10.1; |
f6c796ad | 14 | use strict; |
9f3d18d4 FB |
15 | use warnings; |
16 | ||
415e32d4 | 17 | use lib qw(. lib); |
f6c796ad | 18 | |
7f446ae8 | 19 | use Bugzilla; |
f6c796ad | 20 | use Bugzilla::Constants; |
21 | use Bugzilla::Search; | |
22 | use Bugzilla::User; | |
2b9f5bf8 | 23 | use Bugzilla::Mailer; |
e4fa5195 | 24 | use Bugzilla::Util; |
ca06c579 | 25 | use Bugzilla::Group; |
f6c796ad | 26 | |
27 | # create some handles that we'll need | |
28 | my $template = Bugzilla->template; | |
29 | my $dbh = Bugzilla->dbh; | |
30 | my $sth; | |
31 | ||
2f9f28d0 | 32 | # @seen_schedules is a list of all of the schedules that have already been |
33 | # touched by reset_timer. If reset_timer sees a schedule more than once, it | |
34 | # sets it to NULL so it won't come up again until the next execution of | |
35 | # whine.pl | |
36 | my @seen_schedules = (); | |
37 | ||
f6c796ad | 38 | # These statement handles should live outside of their functions in order to |
39 | # allow the database to keep their SQL compiled. | |
40 | my $sth_run_queries = | |
41 | $dbh->prepare("SELECT " . | |
10272c25 | 42 | "query_name, title, onemailperbug " . |
f6c796ad | 43 | "FROM whine_queries " . |
44 | "WHERE eventid=? " . | |
45 | "ORDER BY sortkey"); | |
46 | my $sth_get_query = | |
47 | $dbh->prepare("SELECT query FROM namedqueries " . | |
48 | "WHERE userid = ? AND name = ?"); | |
49 | ||
50 | # get the event that's scheduled with the lowest run_next value | |
51 | my $sth_next_scheduled_event = $dbh->prepare( | |
52 | "SELECT " . | |
53 | " whine_schedules.eventid, " . | |
54 | " whine_events.owner_userid, " . | |
55 | " whine_events.subject, " . | |
0665a744 | 56 | " whine_events.body, " . |
57 | " whine_events.mailifnobugs " . | |
f6c796ad | 58 | "FROM whine_schedules " . |
59 | "LEFT JOIN whine_events " . | |
60 | " ON whine_events.id = whine_schedules.eventid " . | |
61 | "WHERE run_next <= NOW() " . | |
b9402d3e | 62 | "ORDER BY run_next " . |
63 | $dbh->sql_limit(1) | |
f6c796ad | 64 | ); |
65 | ||
66 | # get all pending schedules matching an eventid | |
67 | my $sth_schedules_by_event = $dbh->prepare( | |
cc73e8e4 | 68 | "SELECT id, mailto_type, mailto " . |
f6c796ad | 69 | "FROM whine_schedules " . |
70 | "WHERE eventid=? AND run_next <= NOW()" | |
71 | ); | |
72 | ||
73 | ||
74 | ################################################################################ | |
75 | # Main Body Execution | |
76 | ################################################################################ | |
77 | ||
78 | # This script needs to check through the database for schedules that have | |
79 | # run_next set to NULL, which means that schedule is new or has been altered. | |
80 | # It then sets it to run immediately if the schedule entry has it running at | |
81 | # an interval like every hour, otherwise to the appropriate day and time. | |
82 | ||
83 | # After that, it looks over each user to see if they have schedules that need | |
84 | # running, then runs those and generates the email messages. | |
85 | ||
eedbd699 | 86 | # Send whines from the address in the 'mailfrom' Parameter so that all |
5d6b815d | 87 | # Bugzilla-originated mail appears to come from a single address. |
eedbd699 | 88 | my $fromaddress = Bugzilla->params->{'mailfrom'}; |
f6c796ad | 89 | |
7a4bbc15 | 90 | # get the current date and time |
91 | my ($now_sec, $now_minute, $now_hour, $now_day, $now_month, $now_year, | |
92 | $now_weekday) = localtime; | |
93 | # Convert year to two digits | |
94 | $now_year = sprintf("%02d", $now_year % 100); | |
95 | # Convert the month to January being "1" instead of January being "0". | |
96 | $now_month++; | |
63dde600 | 97 | |
f6c796ad | 98 | my @daysinmonth = qw(0 31 28 31 30 31 30 31 31 30 31 30 31); |
99 | # Alter February in case of a leap year. This simple way to do it only | |
100 | # applies if you won't be looking at February of next year, which whining | |
101 | # doesn't need to do. | |
102 | if (($now_year % 4 == 0) && | |
103 | (($now_year % 100 != 0) || ($now_year % 400 == 0))) { | |
104 | $daysinmonth[2] = 29; | |
105 | } | |
106 | ||
107 | # run_day can contain either a calendar day (1, 2, 3...), a day of the week | |
108 | # (Mon, Tue, Wed...), a range of days (All, MF), or 'last' for the last day of | |
109 | # the month. | |
110 | # | |
111 | # run_time can contain either an hour (0, 1, 2...) or an interval | |
112 | # (60min, 30min, 15min). | |
113 | # | |
114 | # We go over each uninitialized schedule record and use its settings to | |
115 | # determine what the next time it runs should be | |
116 | my $sched_h = $dbh->prepare("SELECT id, run_day, run_time " . | |
117 | "FROM whine_schedules " . | |
118 | "WHERE run_next IS NULL" ); | |
119 | $sched_h->execute(); | |
120 | while (my ($schedule_id, $day, $time) = $sched_h->fetchrow_array) { | |
121 | # fill in some defaults in case they're blank | |
122 | $day ||= '0'; | |
123 | $time ||= '0'; | |
124 | ||
125 | # If this schedule is supposed to run today, we see if it's supposed to be | |
126 | # run at a particular hour. If so, we set it for that hour, and if not, | |
127 | # it runs at an interval over the course of a day, which means we should | |
128 | # set it to run immediately. | |
129 | if (&check_today($day)) { | |
130 | # Values that are not entirely numeric are intervals, like "30min" | |
131 | if ($time !~ /^\d+$/) { | |
132 | # set it to now | |
133 | $sth = $dbh->prepare( "UPDATE whine_schedules " . | |
134 | "SET run_next=NOW() " . | |
135 | "WHERE id=?"); | |
136 | $sth->execute($schedule_id); | |
137 | } | |
138 | # A time greater than now means it still has to run today | |
139 | elsif ($time >= $now_hour) { | |
140 | # set it to today + number of hours | |
02a04970 MKA |
141 | $sth = $dbh->prepare( |
142 | "UPDATE whine_schedules " . | |
143 | "SET run_next = " . | |
144 | $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'HOUR') . | |
145 | " WHERE id = ?"); | |
f6c796ad | 146 | $sth->execute($time, $schedule_id); |
147 | } | |
148 | # the target time is less than the current time | |
149 | else { # set it for the next applicable day | |
e4fa5195 | 150 | $day = &get_next_date($day); |
02a04970 MKA |
151 | my $run_next = $dbh->sql_date_math('(' |
152 | . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') | |
153 | . ')', '+', '?', 'HOUR'); | |
e4fa5195 | 154 | $sth = $dbh->prepare("UPDATE whine_schedules " . |
02a04970 MKA |
155 | "SET run_next = $run_next |
156 | WHERE id = ?"); | |
e4fa5195 | 157 | $sth->execute($day, $time, $schedule_id); |
f6c796ad | 158 | } |
159 | ||
160 | } | |
161 | # If the schedule is not supposed to run today, we set it to run on the | |
162 | # appropriate date and time | |
163 | else { | |
164 | my $target_date = &get_next_date($day); | |
165 | # If configured for a particular time, set it to that, otherwise | |
166 | # midnight | |
167 | my $target_time = ($time =~ /^\d+$/) ? $time : 0; | |
168 | ||
02a04970 MKA |
169 | my $run_next = $dbh->sql_date_math('(' |
170 | . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') | |
171 | . ')', '+', '?', 'HOUR'); | |
e4fa5195 | 172 | $sth = $dbh->prepare("UPDATE whine_schedules " . |
02a04970 MKA |
173 | "SET run_next = $run_next |
174 | WHERE id = ?"); | |
f6c796ad | 175 | $sth->execute($target_date, $target_time, $schedule_id); |
176 | } | |
177 | } | |
178 | $sched_h->finish(); | |
179 | ||
180 | # get_next_event | |
181 | # | |
182 | # This function will: | |
183 | # 1. Lock whine_schedules | |
184 | # 2. Grab the most overdue pending schedules on the same event that must run | |
185 | # 3. Update those schedules' run_next value | |
186 | # 4. Unlock the table | |
187 | # 5. Return an event hashref | |
188 | # | |
189 | # The event hashref consists of: | |
190 | # eventid - ID of the event | |
191 | # author - user object for the event's creator | |
192 | # users - array of user objects for recipients | |
193 | # subject - Subject line for the email | |
194 | # body - the text inserted above the bug lists | |
0665a744 | 195 | # mailifnobugs - send message even if there are no query or query results |
f6c796ad | 196 | |
197 | sub get_next_event { | |
198 | my $event = {}; | |
199 | ||
200 | # Loop until there's something to return | |
201 | until (scalar keys %{$event}) { | |
202 | ||
bd370f37 | 203 | $dbh->bz_start_transaction(); |
f6c796ad | 204 | |
205 | # Get the event ID for the first pending schedule | |
206 | $sth_next_scheduled_event->execute; | |
207 | my $fetched = $sth_next_scheduled_event->fetch; | |
208 | $sth_next_scheduled_event->finish; | |
209 | return undef unless $fetched; | |
0665a744 | 210 | my ($eventid, $owner_id, $subject, $body, $mailifnobugs) = @{$fetched}; |
f6c796ad | 211 | |
9d4872be | 212 | my $owner = Bugzilla::User->new($owner_id); |
f6c796ad | 213 | |
214 | my $whineatothers = $owner->in_group('bz_canusewhineatothers'); | |
215 | ||
216 | my %user_objects; # Used for keeping track of who has been added | |
217 | ||
218 | # Get all schedules that match that event ID and are pending | |
219 | $sth_schedules_by_event->execute($eventid); | |
220 | ||
221 | # Add the users from those schedules to the list | |
222 | while (my $row = $sth_schedules_by_event->fetch) { | |
cc73e8e4 | 223 | my ($sid, $mailto_type, $mailto) = @{$row}; |
f6c796ad | 224 | |
225 | # Only bother doing any work if this user has whine permission | |
226 | if ($owner->in_group('bz_canusewhines')) { | |
cc73e8e4 | 227 | |
228 | if ($mailto_type == MAILTO_USER) { | |
229 | if (not defined $user_objects{$mailto}) { | |
230 | if ($mailto == $owner_id) { | |
231 | $user_objects{$mailto} = $owner; | |
232 | } | |
233 | elsif ($whineatothers) { | |
234 | $user_objects{$mailto} = Bugzilla::User->new($mailto); | |
235 | } | |
f6c796ad | 236 | } |
cc73e8e4 | 237 | } |
238 | elsif ($mailto_type == MAILTO_GROUP) { | |
239 | my $sth = $dbh->prepare("SELECT name FROM groups " . | |
240 | "WHERE id=?"); | |
241 | $sth->execute($mailto); | |
242 | my $groupname = $sth->fetch->[0]; | |
243 | my $group_id = Bugzilla::Group::ValidateGroupName( | |
244 | $groupname, $owner); | |
245 | if ($group_id) { | |
9d4872be | 246 | my $glist = join(',', |
ca06c579 | 247 | @{Bugzilla::Group->flatten_group_membership( |
0ec82863 | 248 | $group_id)}); |
cc73e8e4 | 249 | $sth = $dbh->prepare("SELECT user_id FROM " . |
250 | "user_group_map " . | |
9d4872be | 251 | "WHERE group_id IN ($glist)"); |
252 | $sth->execute(); | |
cc73e8e4 | 253 | for my $row (@{$sth->fetchall_arrayref}) { |
254 | if (not defined $user_objects{$row->[0]}) { | |
255 | $user_objects{$row->[0]} = | |
256 | Bugzilla::User->new($row->[0]); | |
257 | } | |
258 | } | |
f6c796ad | 259 | } |
260 | } | |
cc73e8e4 | 261 | |
f6c796ad | 262 | } |
263 | ||
264 | reset_timer($sid); | |
265 | } | |
266 | ||
bd370f37 | 267 | $dbh->bz_commit_transaction(); |
f6c796ad | 268 | |
269 | # Only set $event if the user is allowed to do whining | |
270 | if ($owner->in_group('bz_canusewhines')) { | |
271 | my @users = values %user_objects; | |
272 | $event = { | |
273 | 'eventid' => $eventid, | |
274 | 'author' => $owner, | |
275 | 'mailto' => \@users, | |
276 | 'subject' => $subject, | |
277 | 'body' => $body, | |
0665a744 | 278 | 'mailifnobugs' => $mailifnobugs, |
f6c796ad | 279 | }; |
280 | } | |
281 | } | |
282 | return $event; | |
283 | } | |
284 | ||
285 | # Run the queries for each event | |
286 | # | |
287 | # $event: | |
288 | # eventid (the database ID for this event) | |
289 | # author (user object for who created the event) | |
290 | # mailto (array of user objects for mail targets) | |
291 | # subject (subject line for message) | |
292 | # body (text blurb at top of message) | |
0665a744 | 293 | # mailifnobugs (send message even if there are no query or query results) |
f6c796ad | 294 | while (my $event = get_next_event) { |
295 | ||
296 | my $eventid = $event->{'eventid'}; | |
297 | ||
298 | # We loop for each target user because some of the queries will be using | |
299 | # subjective pronouns | |
03352ccf | 300 | $dbh = Bugzilla->switch_to_shadow_db(); |
f6c796ad | 301 | for my $target (@{$event->{'mailto'}}) { |
302 | my $args = { | |
303 | 'subject' => $event->{'subject'}, | |
304 | 'body' => $event->{'body'}, | |
305 | 'eventid' => $event->{'eventid'}, | |
306 | 'author' => $event->{'author'}, | |
307 | 'recipient' => $target, | |
308 | 'from' => $fromaddress, | |
309 | }; | |
310 | ||
311 | # run the queries for this schedule | |
312 | my $queries = run_queries($args); | |
313 | ||
0665a744 | 314 | # If mailifnobugs is false, make sure there is something to output |
315 | if (!$event->{'mailifnobugs'}) { | |
316 | my $there_are_bugs = 0; | |
317 | for my $query (@{$queries}) { | |
318 | $there_are_bugs = 1 if scalar @{$query->{'bugs'}}; | |
319 | } | |
320 | next unless $there_are_bugs; | |
f6c796ad | 321 | } |
f6c796ad | 322 | |
323 | $args->{'queries'} = $queries; | |
324 | ||
325 | mail($args); | |
326 | } | |
03352ccf | 327 | $dbh = Bugzilla->switch_to_main_db(); |
f6c796ad | 328 | } |
329 | ||
330 | ################################################################################ | |
331 | # Functions | |
332 | ################################################################################ | |
333 | ||
334 | # The mail and run_queries functions use an anonymous hash ($args) for their | |
335 | # arguments, which are then passed to the templates. | |
336 | # | |
337 | # When run_queries is run, $args contains the following fields: | |
338 | # - body Message body defined in event | |
339 | # - from Bugzilla system email address | |
340 | # - queries array of hashes containing: | |
341 | # - bugs: array of hashes mapping fieldnames to values for this bug | |
342 | # - title: text title given to this query in the whine event | |
fd004da7 | 343 | # - columnlist: array of fieldnames to display in the mail |
df2bdf81 | 344 | # - name: text name of this query |
f6c796ad | 345 | # - schedule_id integer id of the schedule being run |
346 | # - subject Subject line for the message | |
347 | # - recipient user object for the recipient | |
348 | # - author user object of the person who created the whine event | |
f6c796ad | 349 | sub mail { |
350 | my $args = shift; | |
43b7dc31 | 351 | # Don't send mail to someone whose bugmail notification is disabled. |
948b5806 | 352 | return if $args->{recipient}->email_disabled; |
f6c796ad | 353 | |
948b5806 FB |
354 | $args->{to_user} = $args->{recipient}; |
355 | MessageToMTA(generate_email( | |
356 | $args, | |
f6c796ad | 357 | { |
948b5806 FB |
358 | header => 'whine/header.txt.tmpl', |
359 | text => 'whine/mail.txt.tmpl', | |
360 | html => 'whine/mail.html.tmpl', | |
361 | } | |
362 | )); | |
f6c796ad | 363 | } |
364 | ||
365 | # run_queries runs all of the queries associated with a schedule ID, adding | |
366 | # the results to $args or mailing off the template if a query wants individual | |
367 | # messages for each bug | |
368 | sub run_queries { | |
369 | my $args = shift; | |
370 | ||
371 | my $return_queries = []; | |
372 | ||
373 | $sth_run_queries->execute($args->{'eventid'}); | |
10272c25 | 374 | my @queries = (); |
f6c796ad | 375 | for (@{$sth_run_queries->fetchall_arrayref}) { |
10272c25 | 376 | push(@queries, |
377 | { | |
378 | 'name' => $_->[0], | |
379 | 'title' => $_->[1], | |
380 | 'onemailperbug' => $_->[2], | |
fd004da7 | 381 | 'columnlist' => [], |
10272c25 | 382 | 'bugs' => [], |
383 | } | |
384 | ); | |
f6c796ad | 385 | } |
386 | ||
10272c25 | 387 | foreach my $thisquery (@queries) { |
f6c796ad | 388 | next unless $thisquery->{'name'}; # named query is blank |
389 | ||
390 | my $savedquery = get_query($thisquery->{'name'}, $args->{'author'}); | |
391 | next unless $savedquery; # silently ignore missing queries | |
392 | ||
393 | # Execute the saved query | |
a84cc300 EW |
394 | my @searchfields = ('bug_id', DEFAULT_COLUMN_LIST); |
395 | ||
f6c796ad | 396 | # A new Bugzilla::CGI object needs to be created to allow |
397 | # Bugzilla::Search to execute a saved query. It's exceedingly weird, | |
398 | # but that's how it works. | |
399 | my $searchparams = new Bugzilla::CGI($savedquery); | |
fd004da7 AT |
400 | |
401 | # Use the columnlist for the saved query, if it exists, and make | |
402 | # sure bug_id is always in the list. | |
403 | if (my $columnlist = $searchparams->param('columnlist')) { | |
404 | @searchfields = split(/[\s,]+/, $columnlist); | |
405 | unshift(@searchfields, 'bug_id') unless grep { $_ eq 'bug_id' } @searchfields; | |
406 | } | |
407 | push @{$thisquery->{'columnlist'}}, @searchfields; | |
408 | ||
409 | my @orderstrings = split(/,\s*/, $searchparams->param('order') || ''); | |
f6c796ad | 410 | my $search = new Bugzilla::Search( |
411 | 'fields' => \@searchfields, | |
dbaf1c3a | 412 | 'params' => scalar $searchparams->Vars, |
f6c796ad | 413 | 'user' => $args->{'recipient'}, # the search runs as the recipient |
65b467bd | 414 | 'order' => \@orderstrings |
f6c796ad | 415 | ); |
c9aaffd4 | 416 | # If a query fails for whatever reason, it shouldn't kill the script. |
d8643908 | 417 | my $data = eval { $search->data }; |
c9aaffd4 | 418 | if ($@) { |
acae816c FB |
419 | print STDERR get_text('whine_query_failed', { query_name => $thisquery->{'name'}, |
420 | author => $args->{'author'}, | |
421 | reason => $@ }) . "\n"; | |
c9aaffd4 FB |
422 | next; |
423 | } | |
424 | ||
d8643908 | 425 | foreach my $row (@$data) { |
f6c796ad | 426 | my $bug = {}; |
427 | for my $field (@searchfields) { | |
428 | my $fieldname = $field; | |
429 | $fieldname =~ s/^bugs\.//; # No need for bugs.whatever | |
d8643908 | 430 | $bug->{$fieldname} = shift @$row; |
f6c796ad | 431 | } |
432 | ||
433 | if ($thisquery->{'onemailperbug'}) { | |
434 | $args->{'queries'} = [ | |
435 | { | |
436 | 'name' => $thisquery->{'name'}, | |
437 | 'title' => $thisquery->{'title'}, | |
fd004da7 | 438 | 'columnlist' => $thisquery->{'columnlist'}, |
f6c796ad | 439 | 'bugs' => [ $bug ], |
440 | }, | |
441 | ]; | |
442 | mail($args); | |
443 | delete $args->{'queries'}; | |
444 | } | |
445 | else { # It belongs in one message with any other lists | |
446 | push @{$thisquery->{'bugs'}}, $bug; | |
447 | } | |
448 | } | |
b64f45e6 | 449 | if (!$thisquery->{'onemailperbug'} && @{$thisquery->{'bugs'}}) { |
f6c796ad | 450 | push @{$return_queries}, $thisquery; |
451 | } | |
452 | } | |
453 | ||
454 | return $return_queries; | |
455 | } | |
456 | ||
457 | # get_query gets the namedquery. It's similar to LookupNamedQuery (in | |
458 | # buglist.cgi), but doesn't care if a query name really exists or not, since | |
459 | # individual named queries might go away without the whine_queries that point | |
460 | # to them being removed. | |
461 | sub get_query { | |
462 | my ($name, $user) = @_; | |
463 | my $qname = $name; | |
1c51e402 | 464 | $sth_get_query->execute($user->id, $qname); |
f6c796ad | 465 | my $fetched = $sth_get_query->fetch; |
466 | $sth_get_query->finish; | |
467 | return $fetched ? $fetched->[0] : ''; | |
468 | } | |
469 | ||
470 | # check_today gets a run day from the schedule and sees if it matches today | |
471 | # a run day value can contain any of: | |
472 | # - a three-letter day of the week | |
473 | # - a number for a day of the month | |
474 | # - 'last' for the last day of the month | |
475 | # - 'All' for every day | |
476 | # - 'MF' for every weekday | |
477 | ||
478 | sub check_today { | |
479 | my $run_day = shift; | |
480 | ||
481 | if (($run_day eq 'MF') | |
482 | && ($now_weekday > 0) | |
483 | && ($now_weekday < 6)) { | |
484 | return 1; | |
485 | } | |
486 | elsif ( | |
487 | length($run_day) == 3 && | |
488 | index("SunMonTueWedThuFriSat", $run_day)/3 == $now_weekday) { | |
489 | return 1; | |
490 | } | |
491 | elsif (($run_day eq 'All') | |
492 | || (($run_day eq 'last') && | |
493 | ($now_day == $daysinmonth[$now_month] )) | |
494 | || ($run_day eq $now_day)) { | |
495 | return 1; | |
496 | } | |
497 | return 0; | |
498 | } | |
499 | ||
500 | # reset_timer sets the next time a whine is supposed to run, assuming it just | |
501 | # ran moments ago. Its only parameter is a schedule ID. | |
502 | # | |
503 | # reset_timer does not lock the whine_schedules table. Anything that calls it | |
504 | # should do that itself. | |
505 | sub reset_timer { | |
506 | my $schedule_id = shift; | |
507 | ||
2f9f28d0 | 508 | # Schedules may not be executed more than once for each invocation of |
509 | # whine.pl -- there are legitimate circumstances that can cause this, like | |
510 | # a set of whines that take a very long time to execute, so it's done | |
511 | # quietly. | |
4f5cad99 | 512 | if (grep($_ == $schedule_id, @seen_schedules)) { |
2f9f28d0 | 513 | null_schedule($schedule_id); |
4f5cad99 | 514 | return; |
2f9f28d0 | 515 | } |
516 | push @seen_schedules, $schedule_id; | |
517 | ||
f6c796ad | 518 | $sth = $dbh->prepare( "SELECT run_day, run_time FROM whine_schedules " . |
519 | "WHERE id=?" ); | |
520 | $sth->execute($schedule_id); | |
521 | my ($run_day, $run_time) = $sth->fetchrow_array; | |
522 | ||
4c2d4f95 | 523 | # It may happen that the run_time field is NULL or blank due to |
524 | # a bug in editwhines.cgi when this field was initially 0. | |
525 | $run_time ||= 0; | |
526 | ||
f6c796ad | 527 | my $run_today = 0; |
528 | my $minute_offset = 0; | |
529 | ||
530 | # If the schedule is to run today, and it runs many times per day, | |
531 | # it shall be set to run immediately. | |
532 | $run_today = &check_today($run_day); | |
533 | if (($run_today) && ($run_time !~ /^\d+$/)) { | |
534 | # The default of 60 catches any bad value | |
535 | my $minute_interval = 60; | |
536 | if ($run_time =~ /^(\d+)min$/i) { | |
537 | $minute_interval = $1; | |
538 | } | |
539 | ||
540 | # set the minute offset to the next interval point | |
541 | $minute_offset = $minute_interval - ($now_minute % $minute_interval); | |
542 | } | |
543 | elsif (($run_today) && ($run_time > $now_hour)) { | |
544 | # timed event for later today | |
545 | # (This should only happen if, for example, an 11pm scheduled event | |
546 | # didn't happen until after midnight) | |
547 | $minute_offset = (60 * ($run_time - $now_hour)) - $now_minute; | |
548 | } | |
549 | else { | |
550 | # it's not something that runs later today. | |
551 | $minute_offset = 0; | |
552 | ||
553 | # Set the target time if it's a specific hour | |
554 | my $target_time = ($run_time =~ /^\d+$/) ? $run_time : 0; | |
555 | ||
556 | my $nextdate = &get_next_date($run_day); | |
02a04970 MKA |
557 | my $run_next = $dbh->sql_date_math('(' |
558 | . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') | |
559 | . ')', '+', '?', 'HOUR'); | |
e4fa5195 | 560 | $sth = $dbh->prepare("UPDATE whine_schedules " . |
02a04970 MKA |
561 | "SET run_next = $run_next |
562 | WHERE id = ?"); | |
f6c796ad | 563 | $sth->execute($nextdate, $target_time, $schedule_id); |
564 | return; | |
565 | } | |
566 | ||
f6c796ad | 567 | if ($minute_offset > 0) { |
e4fa5195 | 568 | # Scheduling is done in terms of whole minutes. |
02a04970 MKA |
569 | |
570 | my $next_run = $dbh->selectrow_array( | |
571 | 'SELECT ' . $dbh->sql_date_math('NOW()', '+', '?', 'MINUTE'), | |
572 | undef, $minute_offset); | |
e4fa5195 | 573 | $next_run = format_time($next_run, "%Y-%m-%d %R"); |
574 | ||
f6c796ad | 575 | $sth = $dbh->prepare("UPDATE whine_schedules " . |
e4fa5195 | 576 | "SET run_next = ? WHERE id = ?"); |
577 | $sth->execute($next_run, $schedule_id); | |
f6c796ad | 578 | } else { |
579 | # The minute offset is zero or less, which is not supposed to happen. | |
2f9f28d0 | 580 | # complain to STDERR |
581 | null_schedule($schedule_id); | |
582 | print STDERR "Error: bad minute_offset for schedule ID $schedule_id\n"; | |
f6c796ad | 583 | } |
584 | } | |
585 | ||
2f9f28d0 | 586 | # null_schedule is used to safeguard against infinite loops. Schedules with |
587 | # run_next set to NULL will not be available to get_next_event until they are | |
588 | # rescheduled, which only happens when whine.pl starts. | |
589 | sub null_schedule { | |
590 | my $schedule_id = shift; | |
591 | $sth = $dbh->prepare("UPDATE whine_schedules " . | |
592 | "SET run_next = NULL " . | |
593 | "WHERE id=?"); | |
594 | $sth->execute($schedule_id); | |
595 | } | |
596 | ||
f6c796ad | 597 | # get_next_date determines the difference in days between now and the next |
598 | # time a schedule should run, excluding today | |
599 | # | |
600 | # It takes a run_day argument (see check_today, above, for an explanation), | |
e4fa5195 | 601 | # and returns an integer, representing a number of days. |
f6c796ad | 602 | sub get_next_date { |
603 | my $day = shift; | |
604 | ||
605 | my $add_days = 0; | |
606 | ||
607 | if ($day eq 'All') { | |
608 | $add_days = 1; | |
609 | } | |
610 | elsif ($day eq 'last') { | |
611 | # next_date should contain the last day of this month, or next month | |
612 | # if it's today | |
613 | if ($daysinmonth[$now_month] == $now_day) { | |
614 | my $month = $now_month + 1; | |
615 | $month = 1 if $month > 12; | |
616 | $add_days = $daysinmonth[$month] + 1; | |
617 | } | |
618 | else { | |
619 | $add_days = $daysinmonth[$now_month] - $now_day; | |
620 | } | |
621 | } | |
622 | elsif ($day eq 'MF') { # any day Monday through Friday | |
623 | if ($now_weekday < 5) { # Sun-Thurs | |
624 | $add_days = 1; | |
625 | } | |
626 | elsif ($now_weekday == 5) { # Friday | |
627 | $add_days = 3; | |
628 | } | |
629 | else { # it's 6, Saturday | |
630 | $add_days = 2; | |
631 | } | |
632 | } | |
633 | elsif ($day !~ /^\d+$/) { # A specific day of the week | |
634 | # The default is used if there is a bad value in the database, in | |
635 | # which case we mark it to a less-popular day (Sunday) | |
636 | my $day_num = 0; | |
637 | ||
638 | if (length($day) == 3) { | |
639 | $day_num = (index("SunMonTueWedThuFriSat", $day)/3) or 0; | |
640 | } | |
641 | ||
642 | $add_days = $day_num - $now_weekday; | |
2f9f28d0 | 643 | if ($add_days <= 0) { # it's next week |
f6c796ad | 644 | $add_days += 7; |
645 | } | |
646 | } | |
647 | else { # it's a number, so we set it for that calendar day | |
648 | $add_days = $day - $now_day; | |
649 | # If it's already beyond that day this month, set it to the next one | |
4f5cad99 | 650 | if ($add_days <= 0) { |
f6c796ad | 651 | $add_days += $daysinmonth[$now_month]; |
652 | } | |
653 | } | |
e4fa5195 | 654 | return $add_days; |
f6c796ad | 655 | } |