1 Star 0 Fork 0

datamidplatform/bugzilla

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
whine.pl 21.27 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
#!/usr/bin/perl -T
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
################################################################################
# Script Initialization
################################################################################
use 5.10.1;
use strict;
use warnings;
use lib qw(. lib);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Search;
use Bugzilla::User;
use Bugzilla::Mailer;
use Bugzilla::Util;
use Bugzilla::Group;
# create some handles that we'll need
my $template = Bugzilla->template;
my $dbh = Bugzilla->dbh;
my $sth;
# @seen_schedules is a list of all of the schedules that have already been
# touched by reset_timer. If reset_timer sees a schedule more than once, it
# sets it to NULL so it won't come up again until the next execution of
# whine.pl
my @seen_schedules = ();
# These statement handles should live outside of their functions in order to
# allow the database to keep their SQL compiled.
my $sth_run_queries
= $dbh->prepare("SELECT "
. "query_name, title, onemailperbug "
. "FROM whine_queries "
. "WHERE eventid=? "
. "ORDER BY sortkey");
my $sth_get_query = $dbh->prepare(
"SELECT query FROM namedqueries " . "WHERE userid = ? AND name = ?");
# get the event that's scheduled with the lowest run_next value
my $sth_next_scheduled_event
= $dbh->prepare("SELECT "
. " whine_schedules.eventid, "
. " whine_events.owner_userid, "
. " whine_events.subject, "
. " whine_events.body, "
. " whine_events.mailifnobugs "
. "FROM whine_schedules "
. "LEFT JOIN whine_events "
. " ON whine_events.id = whine_schedules.eventid "
. "WHERE run_next <= NOW() "
. "ORDER BY run_next "
. $dbh->sql_limit(1));
# get all pending schedules matching an eventid
my $sth_schedules_by_event
= $dbh->prepare("SELECT id, mailto_type, mailto "
. "FROM whine_schedules "
. "WHERE eventid=? AND run_next <= NOW()");
################################################################################
# Main Body Execution
################################################################################
# This script needs to check through the database for schedules that have
# run_next set to NULL, which means that schedule is new or has been altered.
# It then sets it to run immediately if the schedule entry has it running at
# an interval like every hour, otherwise to the appropriate day and time.
# After that, it looks over each user to see if they have schedules that need
# running, then runs those and generates the email messages.
# Send whines from the address in the 'mailfrom' Parameter so that all
# Bugzilla-originated mail appears to come from a single address.
my $fromaddress = Bugzilla->params->{'mailfrom'};
# get the current date and time
my (
$now_sec, $now_minute, $now_hour, $now_day,
$now_month, $now_year, $now_weekday
) = localtime;
# Convert year to two digits
$now_year = sprintf("%02d", $now_year % 100);
# Convert the month to January being "1" instead of January being "0".
$now_month++;
my @daysinmonth = qw(0 31 28 31 30 31 30 31 31 30 31 30 31);
# Alter February in case of a leap year. This simple way to do it only
# applies if you won't be looking at February of next year, which whining
# doesn't need to do.
if (($now_year % 4 == 0) && (($now_year % 100 != 0) || ($now_year % 400 == 0)))
{
$daysinmonth[2] = 29;
}
# run_day can contain either a calendar day (1, 2, 3...), a day of the week
# (Mon, Tue, Wed...), a range of days (All, MF), or 'last' for the last day of
# the month.
#
# run_time can contain either an hour (0, 1, 2...) or an interval
# (60min, 30min, 15min).
#
# We go over each uninitialized schedule record and use its settings to
# determine what the next time it runs should be
my $sched_h
= $dbh->prepare("SELECT id, run_day, run_time "
. "FROM whine_schedules "
. "WHERE run_next IS NULL");
$sched_h->execute();
while (my ($schedule_id, $day, $time) = $sched_h->fetchrow_array) {
# fill in some defaults in case they're blank
$day ||= '0';
$time ||= '0';
# If this schedule is supposed to run today, we see if it's supposed to be
# run at a particular hour. If so, we set it for that hour, and if not,
# it runs at an interval over the course of a day, which means we should
# set it to run immediately.
if (&check_today($day)) {
# Values that are not entirely numeric are intervals, like "30min"
if ($time !~ /^\d+$/) {
# set it to now
$sth = $dbh->prepare(
"UPDATE whine_schedules " . "SET run_next=NOW() " . "WHERE id=?");
$sth->execute($schedule_id);
}
# A time greater than now means it still has to run today
elsif ($time >= $now_hour) {
# set it to today + number of hours
$sth
= $dbh->prepare("UPDATE whine_schedules "
. "SET run_next = "
. $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'HOUR')
. " WHERE id = ?");
$sth->execute($time, $schedule_id);
}
# the target time is less than the current time
else { # set it for the next applicable day
$day = &get_next_date($day);
my $run_next
= $dbh->sql_date_math(
'(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
'+', '?', 'HOUR');
$sth = $dbh->prepare(
"UPDATE whine_schedules " . "SET run_next = $run_next
WHERE id = ?"
);
$sth->execute($day, $time, $schedule_id);
}
}
# If the schedule is not supposed to run today, we set it to run on the
# appropriate date and time
else {
my $target_date = &get_next_date($day);
# If configured for a particular time, set it to that, otherwise
# midnight
my $target_time = ($time =~ /^\d+$/) ? $time : 0;
my $run_next
= $dbh->sql_date_math(
'(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
'+', '?', 'HOUR');
$sth = $dbh->prepare(
"UPDATE whine_schedules " . "SET run_next = $run_next
WHERE id = ?"
);
$sth->execute($target_date, $target_time, $schedule_id);
}
}
$sched_h->finish();
# get_next_event
#
# This function will:
# 1. Lock whine_schedules
# 2. Grab the most overdue pending schedules on the same event that must run
# 3. Update those schedules' run_next value
# 4. Unlock the table
# 5. Return an event hashref
#
# The event hashref consists of:
# eventid - ID of the event
# author - user object for the event's creator
# users - array of user objects for recipients
# subject - Subject line for the email
# body - the text inserted above the bug lists
# mailifnobugs - send message even if there are no query or query results
sub get_next_event {
my $event = {};
# Loop until there's something to return
until (scalar keys %{$event}) {
$dbh->bz_start_transaction();
# Get the event ID for the first pending schedule
$sth_next_scheduled_event->execute;
my $fetched = $sth_next_scheduled_event->fetch;
$sth_next_scheduled_event->finish;
return undef unless $fetched;
my ($eventid, $owner_id, $subject, $body, $mailifnobugs) = @{$fetched};
my $owner = Bugzilla::User->new($owner_id);
my $whineatothers = $owner->in_group('bz_canusewhineatothers');
my %user_objects; # Used for keeping track of who has been added
# Get all schedules that match that event ID and are pending
$sth_schedules_by_event->execute($eventid);
# Add the users from those schedules to the list
while (my $row = $sth_schedules_by_event->fetch) {
my ($sid, $mailto_type, $mailto) = @{$row};
# Only bother doing any work if this user has whine permission
if ($owner->in_group('bz_canusewhines')) {
if ($mailto_type == MAILTO_USER) {
if (not defined $user_objects{$mailto}) {
if ($mailto == $owner_id) {
$user_objects{$mailto} = $owner;
}
elsif ($whineatothers) {
$user_objects{$mailto} = Bugzilla::User->new($mailto);
}
}
}
elsif ($mailto_type == MAILTO_GROUP) {
my $sth = $dbh->prepare("SELECT name FROM groups " . "WHERE id=?");
$sth->execute($mailto);
my $groupname = $sth->fetch->[0];
my $group_id = Bugzilla::Group::ValidateGroupName($groupname, $owner);
if ($group_id) {
my $glist = join(',', @{Bugzilla::Group->flatten_group_membership($group_id)});
$sth = $dbh->prepare(
"SELECT user_id FROM " . "user_group_map " . "WHERE group_id IN ($glist)");
$sth->execute();
for my $row (@{$sth->fetchall_arrayref}) {
if (not defined $user_objects{$row->[0]}) {
$user_objects{$row->[0]} = Bugzilla::User->new($row->[0]);
}
}
}
}
}
reset_timer($sid);
}
$dbh->bz_commit_transaction();
# Only set $event if the user is allowed to do whining
if ($owner->in_group('bz_canusewhines')) {
my @users = values %user_objects;
$event = {
'eventid' => $eventid,
'author' => $owner,
'mailto' => \@users,
'subject' => $subject,
'body' => $body,
'mailifnobugs' => $mailifnobugs,
};
}
}
return $event;
}
# Run the queries for each event
#
# $event:
# eventid (the database ID for this event)
# author (user object for who created the event)
# mailto (array of user objects for mail targets)
# subject (subject line for message)
# body (text blurb at top of message)
# mailifnobugs (send message even if there are no query or query results)
while (my $event = get_next_event) {
my $eventid = $event->{'eventid'};
# We loop for each target user because some of the queries will be using
# subjective pronouns
$dbh = Bugzilla->switch_to_shadow_db();
for my $target (@{$event->{'mailto'}}) {
my $args = {
'subject' => $event->{'subject'},
'body' => $event->{'body'},
'eventid' => $event->{'eventid'},
'author' => $event->{'author'},
'recipient' => $target,
'from' => $fromaddress,
};
# run the queries for this schedule
my $queries = run_queries($args);
# If mailifnobugs is false, make sure there is something to output
if (!$event->{'mailifnobugs'}) {
my $there_are_bugs = 0;
for my $query (@{$queries}) {
$there_are_bugs = 1 if scalar @{$query->{'bugs'}};
}
next unless $there_are_bugs;
}
$args->{'queries'} = $queries;
mail($args);
}
$dbh = Bugzilla->switch_to_main_db();
}
################################################################################
# Functions
################################################################################
# The mail and run_queries functions use an anonymous hash ($args) for their
# arguments, which are then passed to the templates.
#
# When run_queries is run, $args contains the following fields:
# - body Message body defined in event
# - from Bugzilla system email address
# - queries array of hashes containing:
# - bugs: array of hashes mapping fieldnames to values for this bug
# - title: text title given to this query in the whine event
# - columnlist: array of fieldnames to display in the mail
# - name: text name of this query
# - schedule_id integer id of the schedule being run
# - subject Subject line for the message
# - recipient user object for the recipient
# - author user object of the person who created the whine event
sub mail {
my $args = shift;
# Don't send mail to someone whose bugmail notification is disabled.
return if $args->{recipient}->email_disabled;
$args->{to_user} = $args->{recipient};
MessageToMTA(generate_email(
$args,
{
header => 'whine/header.txt.tmpl',
text => 'whine/mail.txt.tmpl',
html => 'whine/mail.html.tmpl',
}
));
}
# run_queries runs all of the queries associated with a schedule ID, adding
# the results to $args or mailing off the template if a query wants individual
# messages for each bug
sub run_queries {
my $args = shift;
my $return_queries = [];
$sth_run_queries->execute($args->{'eventid'});
my @queries = ();
for (@{$sth_run_queries->fetchall_arrayref}) {
push(
@queries,
{
'name' => $_->[0],
'title' => $_->[1],
'onemailperbug' => $_->[2],
'columnlist' => [],
'bugs' => [],
}
);
}
foreach my $thisquery (@queries) {
next unless $thisquery->{'name'}; # named query is blank
my $savedquery = get_query($thisquery->{'name'}, $args->{'author'});
next unless $savedquery; # silently ignore missing queries
# Execute the saved query
my @searchfields = ('bug_id', DEFAULT_COLUMN_LIST);
# A new Bugzilla::CGI object needs to be created to allow
# Bugzilla::Search to execute a saved query. It's exceedingly weird,
# but that's how it works.
my $searchparams = new Bugzilla::CGI($savedquery);
# Use the columnlist for the saved query, if it exists, and make
# sure bug_id is always in the list.
if (my $columnlist = $searchparams->param('columnlist')) {
@searchfields = split(/[\s,]+/, $columnlist);
unshift(@searchfields, 'bug_id') unless grep { $_ eq 'bug_id' } @searchfields;
}
push @{$thisquery->{'columnlist'}}, @searchfields;
my @orderstrings = split(/,\s*/, $searchparams->param('order') || '');
my $search = new Bugzilla::Search(
'fields' => \@searchfields,
'params' => scalar $searchparams->Vars,
'user' => $args->{'recipient'}, # the search runs as the recipient
'order' => \@orderstrings
);
# If a query fails for whatever reason, it shouldn't kill the script.
my $data = eval { $search->data };
if ($@) {
print STDERR get_text('whine_query_failed',
{query_name => $thisquery->{'name'}, author => $args->{'author'}, reason => $@})
. "\n";
next;
}
foreach my $row (@$data) {
my $bug = {};
for my $field (@searchfields) {
my $fieldname = $field;
$fieldname =~ s/^bugs\.//; # No need for bugs.whatever
$bug->{$fieldname} = shift @$row;
}
if ($thisquery->{'onemailperbug'}) {
$args->{'queries'} = [
{
'name' => $thisquery->{'name'},
'title' => $thisquery->{'title'},
'columnlist' => $thisquery->{'columnlist'},
'bugs' => [$bug],
},
];
mail($args);
delete $args->{'queries'};
}
else { # It belongs in one message with any other lists
push @{$thisquery->{'bugs'}}, $bug;
}
}
if (!$thisquery->{'onemailperbug'} && @{$thisquery->{'bugs'}}) {
push @{$return_queries}, $thisquery;
}
}
return $return_queries;
}
# get_query gets the namedquery. It's similar to LookupNamedQuery (in
# buglist.cgi), but doesn't care if a query name really exists or not, since
# individual named queries might go away without the whine_queries that point
# to them being removed.
sub get_query {
my ($name, $user) = @_;
my $qname = $name;
$sth_get_query->execute($user->id, $qname);
my $fetched = $sth_get_query->fetch;
$sth_get_query->finish;
return $fetched ? $fetched->[0] : '';
}
# check_today gets a run day from the schedule and sees if it matches today
# a run day value can contain any of:
# - a three-letter day of the week
# - a number for a day of the month
# - 'last' for the last day of the month
# - 'All' for every day
# - 'MF' for every weekday
sub check_today {
my $run_day = shift;
if (($run_day eq 'MF') && ($now_weekday > 0) && ($now_weekday < 6)) {
return 1;
}
elsif (length($run_day) == 3
&& index("SunMonTueWedThuFriSat", $run_day) / 3 == $now_weekday)
{
return 1;
}
elsif (($run_day eq 'All')
|| (($run_day eq 'last') && ($now_day == $daysinmonth[$now_month]))
|| ($run_day eq $now_day))
{
return 1;
}
return 0;
}
# reset_timer sets the next time a whine is supposed to run, assuming it just
# ran moments ago. Its only parameter is a schedule ID.
#
# reset_timer does not lock the whine_schedules table. Anything that calls it
# should do that itself.
sub reset_timer {
my $schedule_id = shift;
# Schedules may not be executed more than once for each invocation of
# whine.pl -- there are legitimate circumstances that can cause this, like
# a set of whines that take a very long time to execute, so it's done
# quietly.
if (grep($_ == $schedule_id, @seen_schedules)) {
null_schedule($schedule_id);
return;
}
push @seen_schedules, $schedule_id;
$sth = $dbh->prepare(
"SELECT run_day, run_time FROM whine_schedules " . "WHERE id=?");
$sth->execute($schedule_id);
my ($run_day, $run_time) = $sth->fetchrow_array;
# It may happen that the run_time field is NULL or blank due to
# a bug in editwhines.cgi when this field was initially 0.
$run_time ||= 0;
my $run_today = 0;
my $minute_offset = 0;
# If the schedule is to run today, and it runs many times per day,
# it shall be set to run immediately.
$run_today = &check_today($run_day);
if (($run_today) && ($run_time !~ /^\d+$/)) {
# The default of 60 catches any bad value
my $minute_interval = 60;
if ($run_time =~ /^(\d+)min$/i) {
$minute_interval = $1;
}
# set the minute offset to the next interval point
$minute_offset = $minute_interval - ($now_minute % $minute_interval);
}
elsif (($run_today) && ($run_time > $now_hour)) {
# timed event for later today
# (This should only happen if, for example, an 11pm scheduled event
# didn't happen until after midnight)
$minute_offset = (60 * ($run_time - $now_hour)) - $now_minute;
}
else {
# it's not something that runs later today.
$minute_offset = 0;
# Set the target time if it's a specific hour
my $target_time = ($run_time =~ /^\d+$/) ? $run_time : 0;
my $nextdate = &get_next_date($run_day);
my $run_next
= $dbh->sql_date_math(
'(' . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY') . ')',
'+', '?', 'HOUR');
$sth = $dbh->prepare(
"UPDATE whine_schedules " . "SET run_next = $run_next
WHERE id = ?"
);
$sth->execute($nextdate, $target_time, $schedule_id);
return;
}
if ($minute_offset > 0) {
# Scheduling is done in terms of whole minutes.
my $next_run
= $dbh->selectrow_array(
'SELECT ' . $dbh->sql_date_math('NOW()', '+', '?', 'MINUTE'),
undef, $minute_offset);
$next_run = format_time($next_run, "%Y-%m-%d %R");
$sth
= $dbh->prepare("UPDATE whine_schedules " . "SET run_next = ? WHERE id = ?");
$sth->execute($next_run, $schedule_id);
}
else {
# The minute offset is zero or less, which is not supposed to happen.
# complain to STDERR
null_schedule($schedule_id);
print STDERR "Error: bad minute_offset for schedule ID $schedule_id\n";
}
}
# null_schedule is used to safeguard against infinite loops. Schedules with
# run_next set to NULL will not be available to get_next_event until they are
# rescheduled, which only happens when whine.pl starts.
sub null_schedule {
my $schedule_id = shift;
$sth = $dbh->prepare(
"UPDATE whine_schedules " . "SET run_next = NULL " . "WHERE id=?");
$sth->execute($schedule_id);
}
# get_next_date determines the difference in days between now and the next
# time a schedule should run, excluding today
#
# It takes a run_day argument (see check_today, above, for an explanation),
# and returns an integer, representing a number of days.
sub get_next_date {
my $day = shift;
my $add_days = 0;
if ($day eq 'All') {
$add_days = 1;
}
elsif ($day eq 'last') {
# next_date should contain the last day of this month, or next month
# if it's today
if ($daysinmonth[$now_month] == $now_day) {
my $month = $now_month + 1;
$month = 1 if $month > 12;
$add_days = $daysinmonth[$month] + 1;
}
else {
$add_days = $daysinmonth[$now_month] - $now_day;
}
}
elsif ($day eq 'MF') { # any day Monday through Friday
if ($now_weekday < 5) { # Sun-Thurs
$add_days = 1;
}
elsif ($now_weekday == 5) { # Friday
$add_days = 3;
}
else { # it's 6, Saturday
$add_days = 2;
}
}
elsif ($day !~ /^\d+$/) { # A specific day of the week
# The default is used if there is a bad value in the database, in
# which case we mark it to a less-popular day (Sunday)
my $day_num = 0;
if (length($day) == 3) {
$day_num = (index("SunMonTueWedThuFriSat", $day) / 3) or 0;
}
$add_days = $day_num - $now_weekday;
if ($add_days <= 0) { # it's next week
$add_days += 7;
}
}
else { # it's a number, so we set it for that calendar day
$add_days = $day - $now_day;
# If it's already beyond that day this month, set it to the next one
if ($add_days <= 0) {
$add_days += $daysinmonth[$now_month];
}
}
return $add_days;
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Perl
1
https://gitee.com/wenchunsz/bugzilla.git
git@gitee.com:wenchunsz/bugzilla.git
wenchunsz
bugzilla
bugzilla
5.0

搜索帮助