Skip to content

Commit

Permalink
Compile the achievement notification email templates in a safe compar…
Browse files Browse the repository at this point in the history
…tment.

To do so the WeBWorK::SafeTemplate module must be used.  This module
derives from the Mojo::Template module and overrides the _trap method.
The Mojo::Template _wrap method adds "use Mojo::Base -strict" and "no
warnings 'ambiguous'" to the code generated in this method.  When that
is called later it causes an error in the safe compartment because
"require" is trapped. So instead the WeBWorK::SafeTemplate override
method imports strict, warnings, and utf8 which is equivalent to calling
"use Mojo::Base -strict". Calling "no warnings 'ambiguous'" prevents
warnings from a lack of parentheses on a scalar call in the generated
code.  So if this package is used, those warnings need to be prevented
in another way.  That is done when the module is used in
Mojolicious::WeBWorK::Tasks::AchievementNotification using a
$SIG{__WARN__} handler that doesn't do anything if the ambiguous warning
is encountered.
  • Loading branch information
drgrice1 committed Dec 2, 2023
1 parent 47c1082 commit f3a978a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<%= $user->first_name %>
<%= $user->first_name %>,

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

Expand Down
51 changes: 35 additions & 16 deletions lib/Mojolicious/WeBWorK/Tasks/AchievementNotification.pm
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ use Mojo::Base 'Minion::Job', -signatures;

use Email::Stuffer;
use Email::Sender::Transport::SMTP;
use Mojo::Template;
use Mojo::File;

use WeBWorK::Debug qw(debug);
use WeBWorK::CourseEnvironment;
use WeBWorK::DB;
use WeBWorK::Localize;
use WeBWorK::Utils qw(createEmailSenderTransportSMTP);
use WeBWorK::WWSafe;
use WeBWorK::SafeTemplate;

# send student notification that they have earned an achievement
sub run ($job, $mail_data) {
Expand Down Expand Up @@ -62,22 +66,37 @@ sub send_achievement_notification ($job, $ce, $db, $mail_data) {
die "User $mail_data->{recipient} does not have an email address -- skipping\n"
unless ($user_record->email_address =~ /\S/);

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

# what other data might need to be passed to the template?
my $body = $renderer->render_file(
$template,
{
ce => $ce, # holds achievement URLs
achievement => $mail_data->{achievement}, # full db record
setID => $mail_data->{set_id},
nextLevelPoints => $mail_data->{nextLevelPoints},
pointsEarned => $mail_data->{pointsEarned},
user => $user_record,
user_status => $ce->status_abbrev_to_name($user_record->status)
}
);
my $compartment = WeBWorK::WWSafe->new;
$compartment->share_from('main',
[qw(%Encode:: %Mojo::Base:: %Mojo::Exception:: %Mojo::Template:: %WeBWorK::SafeTemplate::)]);

# Since the WeBWorK::SafeTemplate module can not add "no warnings 'ambiguous'", those warnings must be prevented
# with the following $SIG{__WARN__} handler.
local $SIG{__WARN__} = sub {
my $warning = shift;
return if $warning =~ /Warning: Use of "scalar" without parentheses is ambiguous/;
warn $warning;
};

our $template_vars = {
ce => $ce,
user => $user_record,
user_status => $ce->status_abbrev_to_name($user_record->status),
achievement => $mail_data->{achievement},
setID => $mail_data->{set_id},
nextLevelPoints => $mail_data->{nextLevelPoints},
pointsEarned => $mail_data->{pointsEarned}
};

our $template =
Mojo::File->new("$ce->{courseDirs}{achievement_notifications}/$mail_data->{achievement}{email_template}")
->slurp;
$compartment->share(qw($template $template_vars));

my $body = $compartment->reval(
'my $renderer = WeBWorK::SafeTemplate->new(vars => 1); $renderer->render($template, $template_vars);', 1);

die $@ if $@;

my $email =
Email::Stuffer->to($user_record->email_address)->from($from)->subject($mail_data->{subject})->text_body($body)
Expand Down
29 changes: 29 additions & 0 deletions lib/WeBWorK/SafeTemplate.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package WeBWorK::SafeTemplate;
use Mojo::Base 'Mojo::Template';

# The Mojo::Template _wrap method adds "use Mojo::Base -strict" and "no warnings 'ambiguous'" to the code generated in
# this method. When that is called later it causes an error in the safe compartment because "require" is trapped. So
# instead this imports strict, warnings, and utf8 which is equivalent to calling "use Mojo::Base -strict". Calling "no
# warnings 'ambiguous'" prevents warnings from a lack of parentheses on a scalar call in the generated code. So if this
# package is used, those warnings need to be prevented in another way.
sub _wrap {
my ($self, $body, $vars) = @_;

# Variables
my $args = '';
if ($self->vars && (my @vars = grep {/^\w+$/} keys %$vars)) {
$args = 'my (' . join(',', map {"\$$_"} @vars) . ')';
$args .= '= @{shift()}{qw(' . join(' ', @vars) . ')};';
}

# Wrap lines
my $num = () = $body =~ /\n/g;
my $code = $self->_line(1) . "\npackage @{[$self->namespace]};";
$code .= 'BEGIN { strict->import; warnings->import; utf8->import; }';
$code .= "sub { my \$_O = ''; @{[$self->prepend]};{ $args { $body\n";
$code .= $self->_line($num + 1) . "\n;}@{[$self->append]}; } \$_O };";

return $code;
}

1;

0 comments on commit f3a978a

Please sign in to comment.