Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send Email Notification for Achievements #2214

Merged
Merged
10 changes: 10 additions & 0 deletions courses.dist/modelCourse/templates/achievements/default.html.ep
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
$FN

Congratulations, you just earned the "<%= $achievement->{name} %>" achievement!

<%= $achievement->{description} %>

You have <%= $nextLevelPoints - $pointsEarned %> points remaining until your next level-up.

Great job!
--Prof. X
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 3 additions & 2 deletions lib/Mojolicious/WeBWorK.pm
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ sub startup ($app) {

# Setup the Minion job queue.
$app->plugin(Minion => { $ce->{job_queue}{backend} => $ce->{job_queue}{database_dsn} });
$app->minion->add_task(lti_mass_update => 'Mojolicious::WeBWorK::Tasks::LTIMassUpdate');
$app->minion->add_task(send_instructor_email => 'Mojolicious::WeBWorK::Tasks::SendInstructorEmail');
$app->minion->add_task(lti_mass_update => 'Mojolicious::WeBWorK::Tasks::LTIMassUpdate');
$app->minion->add_task(send_instructor_email => 'Mojolicious::WeBWorK::Tasks::SendInstructorEmail');
$app->minion->add_task(send_achievement_email => 'Mojolicious::WeBWorK::Tasks::AchievementNotification');

# Provide the ability to serve data as a file download.
$app->plugin('RenderFile');
Expand Down
114 changes: 114 additions & 0 deletions lib/Mojolicious/WeBWorK/Tasks/AchievementNotification.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
###############################################################################
# WeBWorK Online Homework Delivery System
# Copyright &copy; 2000-2023 The WeBWorK Project, https://github.com/openwebwork
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of either: (a) the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version, or (b) the "Artistic License" which comes with this package.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
# Artistic License for more details.
################################################################################

package Mojolicious::WeBWorK::Tasks::AchievementNotification;
use Mojo::Base 'Minion::Job', -signatures;

use Email::Stuffer;
use Email::Sender::Transport::SMTP;

use WeBWorK::Debug qw(debug);
use WeBWorK::CourseEnvironment;
use WeBWorK::DB;
use WeBWorK::Localize;
use WeBWorK::Utils qw/processEmailMessage createEmailSenderTransportSMTP/;

# send student notification that they have earned an achievement
sub run ($job, $mail_data) {
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $mail_data->{courseName} }); };
return $job->fail("Could not construct course environment for $mail_data->{courseName}.")
unless $ce;

my $db = WeBWorK::DB->new($ce->{dbLayout});
return $job->fail("Could not obtain database connection for $mail_data->{courseName}.")
unless $db;

return $job->fail("Cannot notify student without an achievement.")
unless $mail_data->{achievementID};
$mail_data->{achievement} =
$db->getAchievement($mail_data->{achievementID});
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved
return $job->fail("Could not find achievement $mail_data->{achievementID}.")
unless $mail_data->{achievement};

$job->{language_handle} =
WeBWorK::Localize::getLoc($ce->{language} || 'en');

my $result_message = eval { $job->send_achievement_notification($ce, $db, $mail_data) };
if ($@) {
$job->app->log->error("An error occurred while trying to send email: $@");
return $job->fail(); # fail silently
}
$job->app->log->info("Message sent to $mail_data->{recipient}");
return $job->finish(); # succeed silently
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved
}

sub send_achievement_notification ($job, $ce, $db, $mail_data) {
if ($ce->{mail}{smtpSender} || $ce->{mail}{set_return_path}) {
$mail_data->{from} =
$ce->{mail}{smtpSender} || $ce->{mail}{set_return_path};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that either of $mail{smtpSender} or $mail{set_return_path} are really the right things to use for the from field of this email. In fact the $mail{smtpSender} is specifically documented in site.conf as NOT being the from address for emails and is typically the email address of a system administrator. The $mail{set_return_path} is supposed to only be a return path which is typically a noreply address, and some email servers might reject sending of emails with such an address. The instructor of the course should really be the sender. The problem is in finding that as there may be multiple instructors and such. The instructor email page deals with this by allowing the user to choose the sender. That is not exactly an option in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have a configuration variable for the from address on these messages?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is needed. This could use a course setting for this.

} else {
die "Cannot send system email without one of: mail{set_return_path} or mail{smtpSender}";
}

my $recipient = $mail_data->{recipient};
my $template = $ce->{courseDirs}{achievements} . '/' . $mail_data->{achievement}{email_template};
my $renderer = Mojo::Template->new(vars => 1);

# what other data might need to be passed to the template?
$mail_data->{body} = $renderer->render_file(
$template,
{
ce => $ce, # holds achievement URLs
maketext => sub { maketext($job, @_) },
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved
achievement => $mail_data->{achievement}, # full db record
setID => $mail_data->{set_id},
nextLevelPoints => $mail_data->{nextLevelPoints},
pointsEarned => $mail_data->{pointsEarned},
}
);

my $user_record = $db->getUser($recipient);
unless ($user_record) {
die "Record for user $recipient not found\n";
}
unless ($user_record->email_address =~ /\S/) {
die "User $recipient does not have an email address -- skipping\n";
}

# parse email template similar to how it is done in SendMail.pm
my $msg = processEmailMessage(
$mail_data->{body}, $user_record,
$ce->status_abbrev_to_name($user_record->status),
$mail_data->{merge_data}
);
drgrice1 marked this conversation as resolved.
Show resolved Hide resolved

my $email =
Email::Stuffer->to($user_record->email_address)->from($mail_data->{from})->subject($mail_data->{subject})
->text_body($msg)->header('X-Remote-Host' => $mail_data->{remote_host});
drgrice1 marked this conversation as resolved.
Show resolved Hide resolved

$email->send_or_die({
transport => createEmailSenderTransportSMTP($ce),
$ce->{mail}{set_return_path} ? (from => $ce->{mail}{set_return_path}) : ()
});
debug 'email sent successfully to ' . $user_record->email_address;

return $job->maketext('Message sent to [_1] at [_2].', $recipient, $user_record->email_address) . "\n";
}

sub maketext ($job, @args) {
return &{ $job->{language_handle} }(@args);
}

1;
17 changes: 15 additions & 2 deletions lib/WeBWorK/AchievementEvaluator.pm
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
return '' if $set_id eq $excludedSet;
}

our $set = $db->getMergedSet($user_id, $problem->set_id);
our $set = $db->getMergedSet($user_id, $set_id);
my @achievements = sortAchievements($db->getAchievementsWhere());
my $globalUserAchievement = $db->getGlobalUserAchievement($user_id);

Expand Down Expand Up @@ -245,12 +245,25 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
push(@$cheevoMessage, $c->include('AchievementEvaluator/cheevoMessage', achievement => $achievement));

my $points = $achievement->points;
#just in case points is an ininitialzied variable
#just in case points is an uninitialized variable
$points = 0 unless $points;

$globalUserAchievement->achievement_points($globalUserAchievement->achievement_points + $points);
#this variable is shared and should be considered iffy
$achievementPoints += $points;

# if email_template is defined, send an email to the user
$c->minion->enqueue(
send_achievement_email => [ {
recipient => $user_id,
subject => 'Congratulations on earning a new achievement!',
courseName => $ce->{courseName},
achievementID => $achievement_id,
setID => $set_id,
nextLevelPoints => $nextLevelPoints,
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved
pointsEarned => $achievementPoints,
} ]
) if ($achievement->email_template);
}

#update counter, nfreeze_base64 localData and store
Expand Down
15 changes: 3 additions & 12 deletions lib/WeBWorK/ContentGenerator/Instructor/AchievementEditor.pm
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ WeBWorK::ContentGenerator::Instructor::AchievementEditor - edit an achevement ev
use HTML::Entities;
use File::Copy;

use WeBWorK::Utils qw(not_blank path_is_subdir x);
use WeBWorK::Utils qw(fix_newlines not_blank path_is_subdir x);
drdrew42 marked this conversation as resolved.
Show resolved Hide resolved

use constant ACTION_FORMS => [qw(save save_as)];
use constant ACTION_FORM_TITLES => {
Expand Down Expand Up @@ -204,21 +204,12 @@ sub saveFileChanges ($c, $outputFilePath, $achievementContents = undef) {
return;
}

sub fixAchievementContents ($achievementContents) {
# Handle the problem of line endings.
# Make sure that all of the line endings are of unix type.
# Convert \r\n to \n
$achievementContents =~ s/\r\n/\n/g;
$achievementContents =~ s/\r/\n/g;
return $achievementContents;
}

sub save_handler ($c) {
my $courseName = $c->{courseID};
my $achievementName = $c->{achievementID};

# Grab the achievementContents from the form in order to save it to the source path
$c->stash->{achievementContents} = fixAchievementContents($c->param('achievementContents'));
$c->stash->{achievementContents} = fix_newlines($c->param('achievementContents'));

# Construct the output file path
$c->saveFileChanges($c->{sourceFilePath});
Expand Down Expand Up @@ -249,7 +240,7 @@ sub save_as_handler ($c) {
}

# Grab the achievementContents from the form in order to save it to a new permanent file
$c->stash->{achievementContents} = fixAchievementContents($c->param('achievementContents'));
$c->stash->{achievementContents} = fix_newlines($c->param('achievementContents'));
warn 'achievement contents is empty' unless $c->stash->{achievementContents};

# Rescue the user in case they forgot to end the file name with .at
Expand Down
4 changes: 3 additions & 1 deletion lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ sub import_handler ($c) {
$achievement->achievement_id($achievement_id);

# Fall back for importing an old list without the number or assignment_type fields
if (scalar(@$data) == 9) {
if (scalar(@$data) == 7) {
# Old lists tend to have an extraneous space at the front.
for (my $i = 1; $i <= 7; $i++) {
$$data[$i] =~ s/^\s+//;
drgrice1 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -448,6 +448,7 @@ sub import_handler ($c) {
$achievement->icon($$data[7]);
$achievement->assignment_type('default');
$achievement->number($count + 1);
$achievement->email_template('');
} else {
$achievement->name($$data[1]);
$achievement->number($$data[2]);
Expand All @@ -458,6 +459,7 @@ sub import_handler ($c) {
$achievement->max_counter($$data[7]);
$achievement->test($$data[8]);
$achievement->icon($$data[9]);
$achievement->email_template($$data[10] // '');
}

$achievement->enabled($assign eq "all" ? 1 : 0);
Expand Down
Loading